For the past 2.5 years, I have been living in Krakow, Poland. Krakow is a very old city with numerous historical medieval buildings. Being surrounded by real-life references to medieval architecture, I had the idea to create a series of medieval materials for sale. Thus, these Wicker Weave materials are part of a much larger project.
This was my first experience selling something I made myself, and it was a bit scary. I place great importance on the quality of the content I sell.
My goals are as follows: all materials should be customizable, user-friendly, and highly detailed. I created them in 4k resolution to ensure a high level of detail. Since these materials are intended for use in Substance Painter, I didn’t add much dirt or imperfections to minimize repetition.
In this article, I will share the process of creating one of the wicker weave materials, explaining how I made it customizable and how I set up dependencies between different parameters for rendering in my portfolio.
For the technical breakdown, I selected a straw weave material.
The pack containing other weave materials is available for purchase on my marketplace.
This material starts with the default “Weave generator” node. I smoothed the edges of individual straws because I wanted the straw edges to be more readable on a normal map.
To achieve this, I plugged generator masks into the “Non-uniform blur” node and then combined them with the weave height using a “Blend” node in Lighten mode.
In real life, straws are made of hollow dried plant stems. During processing and weaving, they break and form a layered structure. I replicated these layers using the “Directional scratches” node, “Slope blur” to make the layers wide and flat, and “Directional blur” to reduce artifacts.
I could have used “Anisotropic noise” as a base node for this, but it gives pretty even stripes and I wanted more randomly sized stripes for layering.
In the reference I used, each straw had a small amount of dents and chipping.
I replicated this with a “Tile generator” plugged into a “Histogram scan” to reduce the number of spots and increase their contrast.
Then, I plugged it into a “Directional warp” with the previously made layering texture as a mask to make the spots follow the straw’s layered structure.
The last pattern that was added to the height map was scratching in the weave deepenings. I created them using “Clouds 1” noise blended with the “Weave generator” height using Add/Sub mode to achieve an effect as if some dirt was stuck in the deepenings.
I used two “Multi-directional warps” with 0 and 90 degrees warp rotation to transform this blurry dirt into scratches. The “Directional scratches” node was used as a mask for these warps.
Then, I blended these two warps using the “Weave generator” mask to match the weave flow.
The layering, dents, and scratches were blended with the main weave shape using Add/Sub and Subtract blending modes with very small intensity values (around 0.005 for scratches and dents).
The textures I used for layering, scratches, and dents are also used for the roughness and base color maps.
To finish the height map, I blended in “Grunge scratches dirty” noise with an intensity of 0.005 to add slightly visible damage to the straws.
To start building the base color map, I needed a grayscale mask for each straw because I wanted each straw to have a slightly different color.
The “Weave generator” doesn’t provide an option to extract individual horizontal and vertical straws, so I had to create this mask using a “Tile generator” and connect it with the “Weave generator’s” tiling and shape through parametrization.
I brought the tiling values to “Value processor” nodes and then connected them to custom inputs in the “Tile” and “Weave generators” (as shown in the screenshot below). So whenever the tile is changed in the “Value processors,” it also changes in all generators.
The switches you see are used to switch between uniform and non-uniform tiling.
I blended the resulting grayscale mask of individual straws with a layering mask and plugged it into a custom-made coloring node for further colorization.
Because this material was meant to be customizable, I had to make it easy to control, including the base color. In Substance Designer, the base color is usually created using “Gradient maps” with multiple colors.
Unfortunately, the “Gradient map” node cannot be parameterized. Although you can adjust colors using the “HSL” node or by blending multiple colors with multiple masks, the “HSL” node is highly sensitive and doesn’t provide control over each individual color.
Additionally, layering colors on top of each other with multiple masks would result in a large number of different parameters.
So, I needed a way to control the base color with as few sliders and color pickers as possible.
After some research, I found a YouTube video featuring Ben Wilson’s talk on the topic “Exploring Color in Substance Designer.” For his material series, he created a custom coloring node that is also available on his marketplace.
However, I couldn’t use his node in my material for sale, so I had to write my own.
From the video (and by examining Ben’s custom node), I got the idea of how to create a custom coloring node. Unlike Ben’s node, mine turned out to be rather simplistic.
I also added a few parameters that are suitable for my materials. The main idea behind the “Coloring” node is to pick a desired color and separate it into Hue, Saturation, and Lightness to control these parameters individually.
The control is implemented by a black-and-white gradient with a specified number of samples. Essentially, it’s the same gradient ramp you usually see in the “Gradient map” node.
In the “Coloring” node, this gradient has been parameterized and can be controlled. By changing the offset and influence of the black-and-white gradient, the Hue, Saturation, and Lightness values are “stretched” away from the picked color according to the given black-and-white image input.
After receiving a nice base color, I successively layered generic directional noise, dirt, and dust.
I blended the “Curvature” node with color in overlay mode to add more contrast and highlight the edges of the straw layers.
To polish the base color, I blended the “Grunge concrete” node to add more fine damage details, added a subtle layer of overall dirt, and slightly sharpened the whole texture.
For the roughness map, I usually use the same masks as for the base color. For this material, I used the grunge “Scratches_dirty” as a starting point. I blended it with an inverted mask of straw layering and made the edges shinier with an inverted “Curvature” node.
After this, I added a “Histogram Range” node to tweak values and parameterize roughness value and range. In the completed material, I could control roughness with 2 sliders plus a slider for dirt roughness.
After this, I blended in an almost white color for dirt and dust. I also blended in add/sub mode an inverted grunge node that I previously used for fine details in the base color.
All used noises I transformed with directional warp by the straw mask, so each straw has a unique noise pattern.
Because I wanted to have a full set of materials that I can use for texturing all kinds of baskets and wicker props, I needed materials to cover the round surfaces and edges of the props as well.
For circular weaving, I had to start with a “Tile generator” and make a simple alternating weaving with it.
Afterward, I used the “Cartesian to Polar” node to make the “Tile generator” result circular. I masked the very center of the weave with a “Shape – Disc” node – I wanted supporting straws crossing to be visible there.
To make supporting straws, I used 3 “Splatter circular” nodes: the first was a black and white base, the second had the same settings as the previous one except for Luminance random – I used it to control how many straws stack on each other.
In the third Splatter, I used a different input image for the straw shape because, with this node, I wanted to blend in straw damage and stratification.
To blend together supporting straws and base weaving, I used “Height Blend”.
The base color for circular weaving was made in the same way as for regular straw weaving, except that I had to use “Tile generator” and “Splatter circular” to make a grayscale color mask for each weave direction.
One of my main goals was to make materials flexible and customizable. At the same time, I wanted to reduce the number of parameters to a minimum.
You probably have had a situation when changing one parameter drastically impacted other parameters, changing the whole material as a result.
This could be annoying if you spent some time tweaking small details and colors and then decided to increase the tile or upscale some noise.
For flexibility and customization, I wanted to connect a bunch of parameters (like normal intensity, details scale, thread amount, etc.) to tiling. So, whenever I decide to change the tiling or weave pattern, all other details change accordingly.
Besides, in my materials, I often have masks made with “Tile generators” – so I usually want to have one slider to control all tiling values in all generators and noises in my graph.
Further, I’ll describe 3 examples of using a bit of math to make materials customizable.
In this straw weave material I described previously, I used “tile x” as the main parameter. I connected all other parameters to it. You probably noticed that when you change tiling to higher values, your material’s normal strength also changes and becomes too strong or too weak.
It happens because the “Normal” node uses a fixed intensity value that won’t fit with the new level of detailing. I had the same issue in my graph. To fix it with parametrization, I had to check what are the good-looking normal map intensity values for the lowest, the highest, and medium weave tiling values.
In my case, they were 1, 24, and 12. After figuring that out, I created an empty function inside the normal intensity. There, I wrote a linear dependency of normal intensity from weave tiling.
I wanted the normal map intensity to decrease while increasing the tile. For this, I remapped possible tiling values from 1-24 to 0-1 with the “[a,b] to [0,1]” node function and plugged the result into the X input of the linear interpolation node (or “lerp”).
I used the other 2 inputs of the “lerp” node to set up values for normal map intensity, so that when the weave tiling is 1, the normal intensity is 6, and when tiling is 24, the intensity is 0.5. As a result, I didn’t have to manually tweak this parameter every time I changed the tiling.
Then I added a “Multiply” node – I multiplied the result received after “Lerp” with a “Get float” node. Inside “Get float”, I set a pre-made “Normal strength” parameter.
So if the result of the tile-normal dependency doesn’t seem good enough, I can always tweak this parameter by multiplying it by values from 0 to 2, decreasing normal intensity, or increasing it accordingly.
Sometimes you need to connect one tiling slider to both X and Y parameters (Y, which is not equal to X). I had a similar situation with one of the weave materials. I wanted my material to stretch or compress on the Y-axis.
That’s why instead of making 2 sliders for X and Y, I made one slider for “Tiling” (from 4 to 12), and the other for “Weave vertical compression” (from 1 to 10). I wanted to use the Y value further to control all other parameters.
For Y tiling, I used the following formula: tile X multiplied by 4 (constant) plus the vertical compression value (ranging from 1 to 10). Then I converted it to an even value.
These calculations were made inside the “Value processor” node that I plugged into all other nodes that require Y tiling for further calculations.
For example, in the same material, I wanted to increase/decrease the amount and scale of sticking-out threads corresponding to Y-weave tiling. Here, I ran into an issue while checking what values looked good for different tiling.
While the weave tile is 16 (the lowest value), I wanted to have 500 sticking-out threads (that were made with the “Scratch generator”). When Y=168 (maximum Y tiling), there should be 25,000 sticking-out threads, and when Y=72 (medium Y tiling value), there should be 8,000 sticking-out threads. 8k is not the arithmetic mean between 500 and 25k, so I needed to build a non-linear dependency.
Imagine this like building a nonlinear curve with 3 points and bending it afterward in the desired direction. I made this bending with easing nodes and by adding 2 additional “lerps”.
First of all, as usual, I remapped possible Y tiling values from 16-168 to 0-1 with the “[a,b] to [0,1]” node function and plugged the result into the “Ease in sine” node and then into the X input of the “lerp”.
The other 2 inputs of the “lerp” node I used for building a nonlinear dependency – I plugged another 2 lerps there that used 500 and 25k values as extreme values and 15k to control the medium point (I picked this value to get 8k as a result with 72 of Y tiling).
This case was a tough one. I did a lot of picking and tweaking to get an acceptable amount of threads on every other tiling value.
Usually, I use one “lerp” with extreme values and some easing nodes to build a nonlinear dependency.
For portfolio renders, I use Marmoset Toolbag 4. This program is simple, has a user-friendly interface, and allows setting scenes really fast. More importantly, it can produce a very good render result.
I like to present my tileable materials on something that looks appropriate for them. For the weave materials presentation, I made some baskets and boxes in Blender.
I also used a plane mesh. For the shader setup, I used the UE template with tessellation, height, ambient occlusion, and cavity maps to get the best-looking result. Before importing meshes, I added support loops and one step of subdivision to them.
In Marmoset, I subdivided meshes even more until the surface looked really neat, without polygon stretches and artifacts. In Marmoset 3, setting up the displacement was done inside the shader, in Marmoset 4 they separated subdivisions and Height map – now to subdivide the mesh, you need to go to the mesh settings.
I set up Height map scale, tile, and texture offset, turned off mipmaps in texture settings, and checked all maps to have the correct color space.
I picked a suitable sky HDRI and added 2 lights inside the sky window. By clicking somewhere on the sky texture, you can create a directional light that shares color and light intensity with the background sky.
You can increase/decrease its brightness separately afterward. In light source settings, I set up its diameter, spot angle, spot sharpness, intensity, and temperature. Sometimes I use a black-and-white mask of branches and leaves as a gel texture to make the light look like it is partially covered with some vegetation.
I used 2 light sources except for the sky itself. One light source is the main one – it gives most of the light, dark sharp shadows, and quite bright reflections. Sky and the second light source are used for ambient lighting and diffused reflections.
I like to demonstrate the roughness and relief of the material. So, I tweak the light source rotation and camera angle until it gives me nice reflections and shadows in the shot.
As for render settings, I used ray tracing with 4 bounces, 8 rays per pixel and 2048 samples in the viewport (which is probably an overkill).
I turned off denoise as it makes shots blurrier. I set the quality of shadows to “mega” with cascades, tweaked reflections intensity and ambient occlusion.
I usually set up 3-4 cameras. One of them I use for flying around the scene, and the other 2 or 3 I use as bookmarks – I don’t move them after setting the shot angle. I like to have one close-up shot and one overview shot.
In camera settings, I changed the depth of field (it’s around 50mm), turned on the ray-traced depth of field, and set up the focus distance (Marmoset 4 has a focus picker – you can click with it on the mesh where you want to have the camera focused).
In post-processing, I rarely change anything. But, if I do, it’s usually subtle changes in exposure, contrast, and sometimes saturation.
I don’t use sharpen and noise because I like to sharpen render shots in Photoshop – in my opinion, it has a more accurate sharpening algorithm. In viewport settings, I usually turn off anti-aliasing and upscaling and use High DPI viewport resolution.
I rendered shots in a resolution of about 5k to 3k pixels from all cameras simultaneously (you need to add cameras there beforehand, by default there is only one “Main camera”).
I used 2048 samples per pixel with denoise turned off. After rendering shots in Marmoset, I did a bit of Photoshop editing.
I hope you found this breakdown informative and helpful. Thank you for taking the time to read this article. If you’re interested, you can find the full pack of these Wicker Weave materials available for purchase on my marketplace.