Making a Procedural Grid Shader in Unity

I don’t think I am exaggerating too much when I say that a versatile grid shader is at the core of a level designer’s toolkit. While blocking out a new scene, the grid provides a proper sense of both scale and distance, and helps delineate between surfaces. If the grid also makes your blockout look a little prettier, then that’s even better!

In this tutorial we’ll be using Unity’s Shader Graph to create a multipurpose world-space grid for both the Universal and High Definition Render Pipelines. With a range of features and being entirely procedural, the shader will be highly customizable and look good at any scale.

In the following sections we’ll be going through the shader creation process step-by-step. If you’re already confident you know your way around Shader Graph and you just want to see my implementation, here is what my graph looks like in version 2021.2.6f1.

You can download both the URP and HDRP versions of the shader (they’re virtually identical) from this resource page, or from the project’s GitHub repository. Feel free to use them in your own projects however you like!

If you’re an Unreal Engine developer and you’ve landed on this page by mistake, I have also created a version of this shader for UE4/5. The Creating a Procedural Grid Material in Unreal Engine tutorial is similar (but not identical) to this one.

Working with the shader

Before we jump into the creation process, I think it’s best to give you a brief rundown of how the finished shader works so you can decide for yourself which features are worth including.

Grid

  • Line color (color)
    The color of the shader’s grid lines. White (1,1,1) by default.
  • Primary grid size (cm) (float)
    The length of a single grid cell in centimeters. By default this is set to 50, which means grid lines will be spaced at half-meter intervals.

    Side note: As we’ve already covered here on techarthub, Unity units are usually measured in meters. Inside our shader I am dividing parameters marked with (cm) by 100 to make the conversion. This is entirely optional, I just prefer cm.
  • Primary grid line width (float)
    The thickness of the primary grid lines. The strength of the two lines that intersect the origin will be double this setting. I found a value of 0.02 looks best in my demo scene.
  • Use secondary grid (boolean)
    This boolean will toggle an additional set of grid lines. By default this is set to true, because subdivisions are sexy.
  • Secondary grid size (cm) (float)
    The size of the secondary grid’s cells, in centimeters. I have set this to 25 by default to create a 4×4 grid within each primary grid cell.
  • Secondary grid line width (float)
    Set to 0.005 by default, a quarter of the primary grid’s line width.
  • Secondary grid intensity (float)
    This parameter lets you set the intensity of the secondary grid lines, to help with visual clarity. A value of 1.0 makes the secondary grid as visible as the primary grid, and a value of 0 will fade it out completely.
  • Secondary grid fade length (float)
    To avoid the grid lines appearing to crowd each other when viewed at longer distances, the shader will start to fade the secondary grid out as the camera moves further from its surface.

    This setting is the distance from the surface to the camera at which the secondary grid will be completely faded out. By default it’s set to 8 meters, but you’ll need to tweak this setting depending on the fineness of your secondary grid lines.
  • Secondary grid fade offset (float)
    Use this setting to offset the distance at which your secondary grid starts to fade.
Fading out the secondary grid based on camera distance.

Checker

  • Wall color (color)
    The color of the shader when applied to vertical surfaces.
  • Floor color (Color)
    The color of the shader when applied to horizontal surfaces.
  • Checker size (cm) (float)
    The size in centimeters of one iteration of the checker pattern. I tend to keep this setting either the same or double the size of the primary grid.
  • Checker contrast (float)
    This will control the contrast of the checker pattern, with a value of 1 showing no visible pattern. Increasing/decreasing this value will change the pattern’s contrast in different ways.
  • Project transition contrast (float)
    This value will dictate how much the material should blend colors between horizontal and vertical surfaces. The higher value, the harsher the transition. By default I have set this to 0 which means on sloped surfaces the colors will be evenly blended together.
Three materials with different ‘projection transition contrast’ settings applied to spheres.
In this example I’m using values (from left to right) of 0, 1, and 32.

Building the shader

To get started you’ll need to create a few new assets in a project using the render pipeline of your choice. The process is very similar whether you’re using the URP or HDRP (or even other node-based shader editors, like Amplify)

You’re going to need the following:

  • A Shader Graph called S_ProceduralGrid
    URP: Shader / Universal Render Pipeline / Lit Shader Graph
    HDRP: Shader / HD Render Pipeline / Lit Shader Graph
  • Two shader Sub Graphs called SG_GridLines and SG_Checker respectively.
  • A Material called M_ProceduralGrid from which you can assign your new shader.

If you’ve never used Sub Graphs before, don’t worry. Think of them as reusable functions that you can call from within your shader, so you can reduce the amount of clutter in your graph. They’re super useful, and make what we’re doing here much more easily extended. If you want more of a rundown on Sub Graphs, check out this video created by Wilmer Lin.

SG_GridLines

We’ll start by creating our Sub Graphs before pulling it all together. This first one is the simplest of the two, and responsible for drawing a series of world space grid lines at a custom interval.

Click for a larger view.

It’s not large, but it does some important math for us. The section highlighted in red generates our grid in three dimensions by dividing the world position of each pixel by our arbitrary grid size input. It then multiplies the remainder by itself, which gets us something that looks like this.

This gets us almost all of the way there, but there is one small problem. At specific world space positions where a mesh’s face is lying parallel to a grid line, we get some very strange behaviour. This is because Unity is trying to draw multiple things on top of each other, and doesn’t know which one has priority.

Not great.

To solve this, we have to be more explicit about what we want, which is what the rest of the Sub Graph does. Using two masks based on the vertex normal (highlighted in green), we blend three separate projections (R+B, G+B, R+G) together to get the result we need.

This creates a grid that will work even when applied to more complex objects.

SG_Checker

The second Sub Graph is a little more technical, but follows the same basic principles.

Click for a larger view.

We start off by once again dividing our arbitrary grid size and grabbing the remainder, but in this instance we’re dividing our grid size by 2 and subtracting it from that remainder to make our checker pattern.

The rest of the graph is a variant of our previous Sub Graph, where we split out three different projections, one each for X, Y, and Z, and then recombine them using our mesh’s vertex normal information.

We are also now modifying those vertex normal masks by a Projection transition contrast value.

Finally, we add some color and contrast control to our checker pattern’s output.

This is the result.

Bringing it all together

Now we’ve got all our components, it’s time to drop them into our S_ProceduralGrid graph. Luckily, the heavy lifting has now all been done.

Click for a larger view.

This graph has three sections to it. Highlighted in red we have the primary grid, which calls the SG_GridLines Sub Graph and feeds some parameters into it.

We then feed the output of that section into the output of another SG_GridLines graph (green), which has some additional logic for fading the lines in and out based on the camera’s distance from the surface. This will make up our secondary grid.

Finally we drop a SG_Checker node into the graph, hook up some variables, and lerp the colors together.

Performance considerations

This procedural grid shader should work at any scale and with any mesh, static or skeletal. It uses very few texture samplers, and will prove to be a flexible and efficient tool to help you with your level blockouts.

If you’d like to make the shader even more performant, you might consider using an unlit shader graph as your base instead. This will significantly reduce its complexity.

Thanks for reading, I hope you’ve found this tutorial useful. I’m keen to create more like this in the near future, so if you have any requests, comments, or questions please reach out. Until next time!

Frequently Asked Questions

Is it possible to move or rotate the grid in object space instead?

Absolutely! If you want to rotate and/or move your grid in object space you’ll need to add a few small modifications to your Sub Graphs.

For both SG_GridLines and SG_Checker, change the settings of the starting Position from Absolute World to Object. If you’d also like the grid to keep its spacing with the rest of the world make sure you multiply those coordinates by the object’s scale.

How can I fix the weird results I get with smoothed vertex normals?

If you’re using smoothed vertex normals you may notice some flickering or strange blending in the checker pattern as it tries to interpret the direction of the grid.

Unsmoothed (left) vs. smoothed vertex normals, and how the shader interprets that information.

One easy fix for this is to generate our own vertex normals. You can do this by adding a few additional nodes to the blending section of your SG_Checker Sub Graph. In this example I am also using a boolean parameter to switch between the mesh’s default vertex normal information and a set of generated hard-edged ones.

Nick Mower

I am a technical artist from Adelaide, Australia. I created techarthub to share my knowledge and love for this industry. I hope you feel it too!