[SUBSTANCE DESIGNER] Remaking rush job stone bricks into pretty stone bricks.

edited in General
Not sure if this qualifies as a "tutorial" per say, but as I wait around hoping for a new job opportunity, I needed some way to take my mind off things (stressing out yo). So I decided to resolve an issue with some textures I created for my current personal project, I figured I would show it off as it progresses here, this time I am not going to rush it. But, I figure (hope) someone out there might find the approach used here useful. There might be a better way, if there is one, I would certainly be interested.

PART 1: Bummer.... stoned...

The Problem
My previous set of stone/brick/and tile substances were, in my eyes, substandard, as they were put together in a real hurry. They looked OK enough, specially with post processing and the right lighting setup, but my approach to simulating damage to the bricks were a bit... MEH!!! Luckily detail maps and detail normals did a good job at hiding the bad parts.

So lets have a look first at the original results of my Base Stone Gen graph and inheriting graph results, starting with a screenshot from within unity:

Literally all of the stone bricks, tiles, and things in the above screenshot were generated from only one substance graph, under different settings. The only thing missing, is Parallax. Otherwise I suppose it looks pretty good, as none of these meshes have UVs.
(World Space Triplanar mapping breaks parallax AND normals in a surface shader, because inverted uvs.., so it is disabled, but I have been working on a "Box Mapped" World Space Triplanar Standard Shader which has no inverted UVS, basic parallax worked there, but it is awaiting for steep parallax mapping to be added to the shader later)

But if we take a much closer look at what was generated by the substance graph, we will start to see the all of little problems. I was going to draw a bunch of arrows, but there would be more arrows than render if I did this...
Exhibit A:

And when generating tiles...

Looking closely at the edges, they don't chamber out very well. When tile heights are near zero, the damages begin to invert. Frankly, they look horrible. For bricks, they also clip very sharply at the endpoints of the bricks. Just horrible... pure evil...

Another graph also used a similar approach for generic brick walls... but here the edges of the bricks are just to noisy - they don't look very either.

If you cannot see what I am talking about, look at the edges:


So, I set out to find some references needed to create the kind of stone brick I needed... but as it turns out, there are none in my environment. However, I did take some photos a few months ago when I found more or less what I was looking for.

I found these bricks at Melrose Arch, in Randburg...

These mostly behave like the stones I wanted. Some are flat, some are warped with various levels of "rampyness", some are jutting out at various angles, and most of them have small chunks chipped away. They look perfect. The only thing I would want more, is that some bricks should be a bit more rounder.

The Result:

I needed a way to damage the bricks which would work for bricks of all shapes and sizes, preferably some kind of approach which does not need to be adjusted manually every time I change my tile, size, or resolution values. So I went to work on a fresh graph. After some play, trying out a few different approaches, I ended up with this:

(heights/normals and AO only so far)

Woo... I think I pretty much nailed it this time. Lets take a closer look:

And the same brick from a different angle:

In (what was actually a far simpler graph), I have managed to mostly solved all of the requirements, that sharp clipping is nowhere to be seen either, wooohooo!. They look pretty well worn, some are round, some are flat, some are warped, some are a little chipped, and some are chipped very much, and have a little bit of added detail.

The graph in it's current state, looks like this:

Not a whole lot going on, yet, didn't even need to create any pixel function nodes. There are currently only two paths of things happening:
One Edge path calculates the edge damages using 3 mask gens as a starting point, multiple blends, which are fed into two bevel nodes which are blended together. The blended bevels give us both our edge chipping and brick roundness in one go..

The other route, generates 3 ramp heights with different seed values. This are mixed with moisture noise. The first two are blended using the 3rd. These give us some of our finer bump detail, and also give us all the variation we need in brick surface shape, round, curved, or flat. These are blended with random heights as well to add more variation and to balance out the ramps a little.

Both routes are combined with only a single blend for my final height-map.

I will go over the individual segments of the graph in detail in my follow up post (tomorrow).
(Part 2: Dissecting dinosaurs with spoons...?).

Thanked by 3konman Tuism JamesRay


  • Part 2: Totally not dissecting a dinosaur...

    Before starting any graph, it is a good idea to ensure you are setup for your target environment.
    I figured it would be good to mention these things now because I have royally screwed things up badly before.
    In the case of UNITY, there are two main things which may ruin your results:

    1: Workflow for Metallic:
    - Before Unity 2017.4, the Standard Shader had no "Roughness" variant, just Metallic/Smooth. By default, the Metallic workflows in Substance Designer is Roughness or Specular with no Metal/Smooth templates. You would have had to either invert your roughness output in Designer, or create a custom standard shader to take roughness into account (most people will probably just invert smoothness in shader), or set a corrected output node.

    2014.x and later, now includes a roughness variation of the standard shader by default, so you will not need to worry about that channel at all. There are some things related to the Metal/Rough workflow also included in Unity's CGINC files for the standard shader on the roughness setup. But otherwise the two shaders are virtually identical minus the CGINCLUDE block in the roughness variant which exists just after the properties block. ("StandardRoughness.shader").


    I do not yet know if there are any differences on the PBR Shaders using the HDRP or LWRP on Unity 2018.X.
    (Not yet played with such things).

    As far as I am aware, the specular workflow defaults should output results "as expected" in conjuction with unity's Specular Workflow variation of the standard shader. I have rarely ever worked on anything diffuse/specular workflow since the Unity4.X era...

    2: Normals
    By default, Substance Designer assumes you want all of your normal maps to be generated for DirectX. Unity however, expects you normal maps to be generated for OpenGL (Y is Up. Max Green should be UP).
    (3DSMax Users, this actually kinda applies to you too if you are baking details there, ensure normals are set to bake as Y-Up)

    I unfortunately made the mistake of accidentally batch rendering almost 40 textures with DirectX normals a little while back.
    Re-Rendering the batch would have taken too long, so I had to quickly write an external command line tool to process all of them in one go... flipping the Y axis on all of these Normal Maps... what a pain in the donkey...

    As long as your imported normal maps are Y-Up, everything "should" appear correctly (this also assumes your tangents are pointing the right way too...)

    When you add any default substance designer node which relates to normals or generating normals, that node will generally have a switch allowing you to switch that node between OpenGL and DirectX, as well as nodes which may expect a Normal Map input (such as the Curvature nodes). However, you also need to set the Renderer Viewport's material to match this. Forget to set the intended normal format anywhere, then you normals may not render correctly or your lighting may come out looking like bird poop.

    When making textures for Unity (or any other pipeline which expects Y-UP channel), manually setting these nodes all the time is tedious and simply annoying.
    However, you can set a default format as well globally, and all nodes (as well as the 3D Viewport) will assume the global setting by default, not including the IRay preview which still has to be set manually (as of 2018 Spring release) (don't ask me why...). Setting your default normal format globally is perhaps the easiest way to avoid confusion later, as this also affects Normal Map defaults in the Map Baker.

    Setting Global Normal Defaults:

    So with all that out of the way, my next post will actually get back to the substance graph at hand.

  • edited
    Part 3:

    Starting off the graph we need some kind of pattern or tiles shape for the bricks. As I intend to make this a reusable sub graph later, it needs to work with any tile or brick tile map, or with a custom Pattern Map node. Eventually I will be replacing the starting node with an Input node so that any map input can be used. For now, I have started off with the Tile Random Node. As long as there is at least some space between the bricks, we should be able to generate some damaged edges.

    But for the sake of checking that my outputs are still working, we can also do a test render using some other pattern node.
    Remember, we need space between the tiles, or it simply wont work. See Exhibit A: With the Arc Pavement node

    See it? What is causing two "rows" of arc tiles to break?
    The answer is very simple, there are two reasons:
    1 : The tiles are touching another tile
    2 : There is a Flood Fill node in the graph.

    It is located here:

    The flood fill node struggles when two fill zones are "just barely" touching each other (it would seem), but this is the node needed for all of the other fill nodes to work, it generates boundary the data we need for flood fills. A single brick is pretty simple, and convex. When the corners of two bricks are touching however.... then the flood fill pretty much just vomits all over area it is trying to generate boundaries for.... breaking all of the following flood nodes... If they overlap any further, both touching bricks are pretty much guaranteed to break. Flood fill wants to be fed convex shapes.

    The simplest fix in this particular case, is just add some more space, but at the same time we don't want to much gap (we have nothing to fill the gaps with yet). On the Arc Tile Map, the Decrease Pattern Height slider may turn out to be your best friend. Try to get all the bricks as close to each other as possible, without overlapping. We can deal with all that extra gap some other time.

    When it works, it at least validates that my damages are working on a wildly different tile node type.

    The Nodes: Path of destruction
    .. on totally NOT the path were our flood fill is, is where I started first, chipping away at the edges.
    As mentioned before, I am using 3 different edge masks. But the edge masks all require an input first: Curvature

    Getting this is simple enough.
    - Generate a simple normal from the tiles. (which is never used again)
    - Generate a curvature from the normal. (don't forget about normal format).
    (any of the curvature nodes will work, I went with Curvature Sobal)


    Then, feeding it into my edge masks...
    ... which brings us back to this:

    The little bit of blur applied to the edge wear followed by a multiply on itself, exists to soften the noisyness , and enhance the contrast... just a bit. Otherwise, the masks can be tweaked to find a good balance between destruction and pure ugliness. A reasonably thick and fragmented destruction (not too much) is what we are looking for.

    Everything else, is trial and error. tweaking and previewing, trying different blends, until we get a texture that "looks" like it provides what could become a good basis for our chipped edges. Node that I am also feeding the original tile map as inputs to the Edge Damages mask, and is also blended with the Edge Notch mask. All three blends in this frame are using the Subtract blend mode. These 3 steps could probably just be replaced by a single Pixel Processor node later to perform all 3 blends in one go, but we will leave this as is for now for simplicity sake.

    At the end of the final blend, our output looks currently something like this: (with Arc Tiles)
    Or in our original case, with a Tile Map, like this:
    (Edit, I screen capped the wrong node here, will fix later)

    The edges are way to fragmented, the gap between our bricks has also expanded quite heavily, but we don't care. The pair of Bevel nodes blended together will take care of this in the next frame.


    Both bevels have a distance value of +0.01 (Positive, to bevel from the outside instead of inside the damage areas,). Any corner mode is fine. The main difference are the smoothing values between the two nodes.
    One is set to a large value: 2.0. This is the smoothing which will round off the brick a bit and potentually create a chambfer further inside the brick, depending on the mode used or the smoothing amount. This was mainly added to prevent "perfectly flat" bricks from appearing, this is also kind of why I have the term "relief" in the frame title.

    The other is set to 0.75. This is the one to play around with, this is the chipped edges themselves. Too much smoothing here will fade the edges away, and the effect will be lost. Too little and the edges will become to noisy or fragmented.

    Both bevels are blended together using the Min blend mode, and then multiplied by itself again, for some added steepness on the edges..

    Adding a Height To Normal World Units node, to preview what the normals look like if generated from this point in the graph.

    Looks a bit messy between the bricks, but those are the chips we need in our height map, and is to be expected as we are beveling outside the destruction mask, but this is only half of the graph as it stands now anyhow, we still have a whole other section of nodes which will process the face and groove of the stone bricks separately, where we will also reinforce the groove between the bricks, adding the "cliff" as well.

    I will cover the Ramp Heights and blends in the next follow up post, but for now I go to bed. Good night. It's midnight.

  • Using the flood fill node. I have 3 instances of the Fill To Gradient node.
    They are all slightly overlay blended with moisture noise (with various seeds).

    The 3rd ramp/noise blend is used to blend the other two together, and the noise helps to add some variation to the blend.
    Once blended, the ramps come out looking like this:

    Balancing out the ramps as well as adding some height variation comes from a 4th flood node, Fill To Random Greyscale node. This is also simply overlay blended on top, with some noise as the mask.

    I am simply multiplying the two height routes together to generate my final heightmap for the bricks themselves.
    So nothing fancy is happening here.


    With that it is now time to prepare for the next phase of building my material bases. I'll add some paramaters to the graph, replace the Tile Random node with an input node, and toss it in a folder to be used/reused later for building brick materials.
    Since it will be a sub-graph, disabling "Show In Library" will prevent some errors when attempting to export substance for use externally (eg: Painter/Unity/Maya...).

    Final Height Graph, one input and output.
    Thanked by 1JamesRay
  • Nice work dude !
    Keep it up :D
  • That is awesome! Very nice writeup as well.
    Great job!
  • Agreed, Great write up. Its cool to see how others approach making materials. Keep it up guys would be awesome to see more.
Sign In or Register to comment.