Coming out a bit late this week, but it's been a busy past few days for me, with a ton work being put into this. Naughty Dog is one of the real time graphics leaders in the quality and quantity of procedural textures they produce. This in part is due to the expertise of Roguelio Olguin, one of their excellent texture artists. He recently released a great walk through of the way he goes about constructing a procedural texture that I couldn't miss.

It was incredibly insightful as to learn how he organizes his workflow, which allowed a few concepts to crystallize in my mind about the way to build procedural texture graphs from start to finish. Before learning from his approach I was a bit all over the place with how I build a substance graph, but now I've got a much stronger feel for the way to go about organizing the many steps of creating PBR textures. The general workflow for this Cobblestone texture goes like this:

1.) Height Pass

You generally always want to create your Height map first. From the Height map you can derive most of the information you'll need to complete your PBR texture, such as Normal, Ambient Occlusion, Base Color, Metallic, and Roughness. Height information is calculated with the lighter areas as the peaks, and the darker areas as the recesses. Complete all of your height detail before you move on to the others passes. For this texture there are no Metallic values in cobblestone, so I will wait until I create another texture with metallic elements to discuss that pass.

1a.) Define the basic shape of your tile. Make sure to include a gradient overlay to give the tile an angle, as no tiles are perfectly flat in relation to the horizon.

1b.) Use the Tile Random Node to place your tile in whatever pattern you like. There is a ton of flexibility here that can yield infinite variations.

1c.) Use variation Noise generators to create a dirt layer and use levels nodes to get the desired height level.

1d.) You can create separate graphs with exposed parameters to splatter around things like woodchips, pebbles, etc.

2.) Normal Pass

The Normal map tells the shader how to reflect light off of your texture without creating memory taxing geometry. You can convert your Height map directly into a normal map in most cases. You can easily adjust the intensity of the normal to get your desired level of detail. Generally it is beneficial to connect your Height output into the normal output pretty early on during the creation of your Height Pass, as you can see how the Normal detail is developing while you work in real time.

3.) Ambient Occlusion

The Ambient Occlusion Map is optional, but in most cases it is extremely nice to have it. It creates dark (occluded) areas in the recesses of your texture, where light would have a hard time getting to. This can really make your texture pop but is not strictly correct when in comes to PBR. I generally enjoy more stylized hand crafted textures, so an Ambient Occlusion pass can really bring some life into the texture. You can easily convert your Height information into an Ambient Occlusion node as well. Again, it's worth it to plug this map into your shader in the early stages to get a feel for it.

4.) Base Color

Aside from the Height information, this is probably the other most import aspect of your shader. The Base Color refers to the color of your texture with NO lighting on it, meaning you need to aim to achieve a static lighting look for the Base Color. For more stylized textures, you can darken the recesses a bit as I've done here, but generally to create a physically accurate material you want all lighting information removed from this pass.

4a.) You can create custom gradients and plug in your height pass to create some very interesting results. Blending various gradient maps together is the general work flow for creating a solid base color pass.

4b.) Use masks from your height pass to isolate the various details of your graph before you input them into your custom gradients. Creating these masks is hugely useful for many aspects of refining the texture. For instance here I created custom masks for the stones, dirt, pebbles, woodchips, etc.

5.) Roughness

Lastly, at least for this texture, you can create the Roughness Pass. This texture tells the shader how glossy or rough the various surfaces of your texture are. For instance, a piece of glass with high glossiness needs to have a dark roughness value, while chalk would need to have a near white value in the roughness pass. Here the masks we created for the Base Color will come in very handy again, as we need to isolate the various different components of our texture to give them different roughness values. For instance, the stone needs to reflect light a lot differently than the wet dirt in the recesses.

5a.) Use various grainy noise generators to break up the way light reflects off your objects. It can create some various intriguing results.

5.b) Use levels nodes to clamp the values of your roughness. Nothing in nature has the value of pure glossiness, or pure roughness, so avoid values like pure black or pure white in the roughness pass.

Here's a picture of the Graph. As you can see it's considerably more organized than my previous efforts. From left to right, top top bottom, you can see the workflow of starting with Height and Normal, then creating the Masks for Base Color and finally finishing up with Roughness at the bottom. I learned from Rogelio that it helps to separate every Noise generator you use early on, so that you can re-use them to save memory and make your substance that much more efficient.

Here are a few more renders of what this procedural texture can do and what it looks like with some fancy rendering. It was a blast to learn from Rogelio and I highly recommend this to anyone interested in creating procedural textures.