For those who don’t know him yet, meet Mark Foreman, Senior Environment Artist at CD PROJEKT RED. Mark is an absolute Substance Designer expert, as you’ll see in the following tutorial. Here is just an excerpt from the epic tutorial he wrote, describing step by step, in extreme detail, how he made his MDL Malachite Material. You can download the material on Substance Share, and find the full tutorial on Substance Academy.
Mark Foreman: In this tutorial, I’m going to guide you through the creation of my Malachite with Chrysocolla Materialize competition entry. I’m going to cover the graph and MDL in full and then finally go into some detail on how I set up my final renders.
Before I even put my first node down, I had to decide whether I was going to try and recreate the reference image as precisely as possible, producing an isolated piece of malachite on a white background, or if I’d use the reference as inspiration to create a piece that recreated what I saw as the essential features in the reference, but filled in the blanks around the reference to become what was more of a traditional material. Ultimately I felt it made more sense to embrace the strengths of Substance Designer and translate as much of what I could see in the reference image into a procedural tiling material.
Having settled on an approach, my next port of call was to expand my reference library and to learn a little more about malachite. I started by gathering some extra reference images of other cut malachite samples. As well as the apparent malachite from the cut face of the reference, there is also the chrysocolla mineral coating on the outside of the malachite. So as well as malachite images I also gathered some other references of uncut stones showing off the chrysocolla coating on the exterior.
With a small collection of references gathered it was time to begin the creation of my material.
A Word on Optimization
But before we begin properly, I thought it would be useful to share a couple of tips that can help keep your graph running a bit faster.
Node Reuse: Try to reuse as many nodes as possible rather than adding duplicates, as each duplicate node must be processed separately by the renderer. For this tutorial, I wanted to keep as many of the utilized nodes visible in each illustration as possible. So I chose to forgo this simple optimization for the sake of clarity. It also reduced the number of strands criss-crossing around the graph as they find their way to the shared nodes. As you can see here, the version of my graph that I submitted for the Materialize Contest reused a number of the generic input noises and gradients.
Node Output Size: Another useful trick for speeding up your graph is to make use of the Output Size of individual nodes to reduce their scale relative to the rest of the graph. This is especially useful when you are creating inputs for a Tile Random node or Splatter node which take an input pattern. Often the resulting individual instances of the scattered input pattern will be smaller than the overall resolution of the graph. Therefore, you can reduce the scale of the input nodes to gain a few ms of render time. Another situation where this trick is useful is when using an input noise that may be blurred for any reason, such as the slope input to a Slope Blur Grayscale node. Reducing the resolution of the input noise can sometimes even negate the need for a blur.
You can see here that the resulting difference between the two outputs is extremely subtle. However, the first method using node scale clocks in at roughly 50ms, while the second approach takes nearly 80ms. The difference may be small, but it can add up to quite a processing time saving over a graph containing hundreds of nodes.
With some research done it’s time to get stuck in to building the material. First I’ll create a new Substance graph. I do this, and name it malachite_with_chrysocolla_tutorial. I choose the Physically Based (metallic/Roughness) template to give me some starting output nodes. For the size mode, I choose Relative to Parent and set the width and height to 2048. Working in 2048×2048 keeps everything running smoothly with quick updates to changes in the graph. As I plan on outputting the final material in 4096, I will however periodically check my adjustments in 4096×4096 to make sure I like what I see at that resolution. Finally, I set the format to 16 bits per channel.
In nearly every material I create, I always begin by building up the Heightmap. For me, this is the most important part of creating a material in Substance Designer, as nearly every other output map relies quite heavily on the information set up in the Heightmap. While working on my Heightmap, I keep my outputs simple. The very first node I create is a Safe Transform Grayscale; I’ll feed my Heightmap output into this node before splitting off to my outputs later. Having the output passed through a simple node like the Safe Transform Grayscale provides a useful separation between the output nodes and my WIP graph. I attach the output of this Safe Transform Grayscale to the Height output. Then I add a Normal node; for this piece I ended up with a relatively high Intensity of 30 in my Normal as I wanted to maximize the appearance of depth from the cut surface down to the valleys in the Chrysocolla. Usually, I finish around the 10-20 range, but in this piece, I found I was dealing with a lot of subtle noises over a fairly extreme Heightmap, so the extra boost to the Normal proved necessary.
I hook this Normal node output up to the Normal output node. From the Normal node, I also create a Curvature Smooth node and attach it to the Base Color output. The Curvature Smooth will help accentuate any edges or crevices I’m creating in my Heightmap. It also helps a lot to show how strong my noises are as I blend them in. For the Roughness output, I add a Uniform Color node set to slightly lighter than middle gray. I also add another Uniform Color node set to a black and attach it to the Metalness output node. For this piece, I’m going to use Ambient Occlusion separate from my diffuse, so I’ll create an Ambient Occlusion node and attach this to a new output node set to ambient occlusion in its usage. It’s important to label it accordingly. Keeping the outputs simple at this stage is going to help me stay focused on getting the precise details I want in my Heightmap before I move on to adding other information in the BaseColor and Roughness passes.
Now, with my outputs set up, I’m ready to start working on the graph. I begin by building or growing the stone as organically as possible. In the reference, I’ve seen that the surface of a piece of unbroken malachite is often made up of some bubbly nodules, so these are what I’ll strive to recreate. The first step is to get the large shapes down. These are going to provide the peaks I will eventually cut away to reveal the malachite and the valleys where the uncut chrysocolla surface will remain.
For the base of my malachite, I use a Shape node with the Thorn Pattern. I chose this shape because the nonlinear gradient from the base to the point of the thorn means the output from the Tile Sampler will have a good contrast between the peaks and the valleys. This will prove super-useful for pulling some masks out for the rest of the graph, such as for creating the Normal map used by the anisotropy in the MDL. I started with two variants of this thorn shape, one slightly squashed and bent around a Shape node using the Bell Pattern with a Directional Warp node to produce some variation in my Malachite Surface. On the other thorn, I add some Gaussian Noise in a Blend node set to Multiply; this helps to break up the uniformity of the shape.
When working on this material for the contest, the result I was getting once these thorn shapes were scattered didn’t feature the satisfying combination of peaks as seen in the Mattershots reference Image, where two peaks are very close together. I chose to manually create this combination by combining two of my thorn shapes by hand.
I use a Transform 2D node to offset one Thorn from the other, and a Levels node to slightly drop the height of this new peak down slightly. Finally, I combine these two in a Blend node set to Max (Lighten).
These three base thorn shapes are then used as pattern inputs of a Tile Sampler node. I set up the Tile Sampler to scatter the shapes pretty evenly across the canvas with a little bit of clustering and some slight scale variation.
The last step here is to frame up the nodes we’ve made to keep the graph organized.
Now, having completed a very basic pass defining the shape and location of each nodule, I move on to refining the base shapes to create the final surface shape Heightmap upon which the rest of the graph will be built.
The first thing to do is smooth out the valleys between the nodule peaks; I want to make sure they are not too sharp, as that might impact the organic appearance.
To do this, I’ll make a mask with which I can isolate the valleys, and then blur them to soften them. The first step is to create a mask that will isolate the valleys of the Heightmap output from the Malachite Base. I do this by taking the output of my Malachite Base and putting it through a Highpass Grayscale node to remove some of the height variations. I then Invert the output and pass it through a Levels node where I clamp the Level in Low to isolate the valleys; I also bring the Levels In Mid down to make the falloff to black less harsh. This mask is then fed into a Non Uniform Blur node in the Blur Map input with my Malachite Base in the Grayscale Input.
Next, I add a tiny amount of noise with a couple of Gaussian Noise nodes set to different scales. Each is combined with the Malachite Base using a Blend node set to Copy and a low opacity. This will prevent the malachite rings that I’ll add later from being perfectly concentric, helping them look more organic. I copy the noiseless output from the Non-Uniform Blur back over this noisy version with a Blend node set to Copy in order to control more precisely the strength of these combined noise passes.
At this point, the graph diverges between the sections used for the cut malachite surface, and what will become the outer chrysocolla layer. So I’ll frame up the nodes added to the Malachite Shape before moving on to the Chrysocolla which I’m going to focus on first.
After the divergence, I’ll begin making adjustments to the Heightmap that will be the beginnings of my Chrysocolla Shape. I’ll start by creating the more pillowy appearance of the raw malachite, over which the chrysocolla will have grown.
First I’ll increase the volume of the malachite nodules – I want a more bulbous surface for my malachite, as opposed to the spiky surface the raw Thorns output will currently provide.
I start by blurring the output of my Malachite Shape in a Blur HQ Grayscale node, I then use a Slope Blur Grayscale node with the output of the Blur HQ Grayscale in both inputs to inflate the thorn shapes. Finally, I add in a small amount of Plasma node noise, softened with a Blur HQ grayscale, this is combined with the Chrysocolla shape using a Blend node set to Copy with a low opacity. This plasma noise is going to add some medium surface variation before adding finer detail in the following stages.
With the main shapes taken care of, it’s now time to start refining and detailing the surface of my Chrysocolla Heightmap. Be sure to frame this section up before moving to the next.
The very first layer of detail I blend in is some noise made by combing a Clouds 1 node with a Clouds 2 node 50/50 using a Blend node set to Copy. I like to do this as it mixes up the detail frequency nicely and also reduces contrast in the noise somewhat as a convenient byproduct. I then very slightly soften this in a Blur HQ Grayscale node to remove the highest frequency detail before adding this over the previous Malachite Shape Heightmap with a Blend node set to screen and a very low opacity.
Next, to get a gritty feeling to the surface, I blend over a Fractal Sum Base node. I use a Blend node set to Copy, again with a very low opacity. I follow this by a BnW Spots 3 node which I add using a Blend node set to soft light to add some medium-scale information to the grittiness.
Now I want to focus on giving the surface a rocky appearance. So in the next pass, I’ll add some medium to large shapes that will make the surface look more like it has been dug out of the ground.