While the documentation examples don’t provide real use cases – since you would usually want to write to graphics buffers from a Compute shader and not from a C# script – they are great as unit tests to validate how one can use Shader Graph with lower level graphics APIs.
First of all, Graphics.Draw__() methods were deprecated and replaced with Graphics.Render__().
This post will detail how the latter can be used with Shader Graph shaders, but it applies to former deprecated methods the same.
Also, since those examples use an Unlit material and only translate positions, I’m using the most straightforward approach to handle it. But I’ll follow up on dealing with Lit materials and per-instance rotation.
Compatibility & Requirements
Render Pipelines
With the exception of RenderMeshInstanced that requires a material that enables GPU Instancing, the following examples will work with both Unlit and Lit Shader Graphs, with Built-in, Universal and High Definition Render Pipelines.
The following examples were tested using:
- Built-in Render Pipeline (Forward, Deferred)
- Universal Render Pipeline (Forward, Deferred, Forward+, Deferred+)
- High-Definition Render Pipeline (Forward Only, Deferred Only, Both)
- Unlit and Lit targets
Frame Debugger
If you look in the Frame Debugger, it will appear like it’s not using Draw Procedural, but a deeper look in RenderDoc will display the same results as a handwritten shader.
Documentation Examples
Renders a mesh with given rendering parameters.
There are no specific requirements for this example. It will work with any Shader Graph shader.
Renders multiple instances of a mesh using GPU instancing.
- This requires GPU Instancing to be enabled on the Material.
- The InstanceID Node will return the unique index of the instance.
Note: Shader Graph shaders do not support GPU Instancing with the Built-in Render Pipeline.
Although this can be forced with mat.enableInstancing = true;, it will only work with an Unlit shader.
As mentioned in this other post, note that ‘GPU Instancing’ is referring to “legacy” GPU Instancing, and not the more modern approach using the GPU Resident Drawer.
Renders multiple instances of a mesh using GPU instancing and rendering command arguments from commandBuffer.
This function only works on platforms that support compute shaders.
See → SystemInfo.supportsIndirectArgumentsBuffer
This requires the following define and include:
#define UNITY_INDIRECT_DRAW_ARGS IndirectDrawIndexedArgs
#include "UnityIndirect.cginc"
These can be put in an HLSL file and referenced from a Custom Function Node.
GetCommandID and GetIndirectInstanceCount can both be accessed with a Custom Function Node.
They both need InitIndirectDrawArgs(0) to be called before. It’s then better to fetch them both from the same function as to call InitIndirectDrawArgs only once.
uniform float4x4 _ObjectToWorld;
This can either be declared in the HLSL file, and accessed with another Custom Function Node, or simply set as a Per Material Matrix 4x4 Property in the Blackboard.
The InstanceID Node will return the unique index of the instance.
The following HLSL file can be used with Custom Function Nodes in a Shader Graph to recreate the example.
#ifndef UNITY_INDIRECT_DRAW_ARGS
#define UNITY_INDIRECT_DRAW_ARGS IndirectDrawIndexedArgs
#include "UnityIndirect.cginc"
#endif
#ifndef RENDER_MESH_INDIRECT_EXAMPLE
#define RENDER_MESH_INDIRECT_EXAMPLE
void CommandID_IndirectInstanceCount_float(out uint CommandID, out uint IndirectInstanceCount)
{
#ifndef SHADERGRAPH_PREVIEW
InitIndirectDrawArgs(0);
CommandID = GetCommandID(0);
IndirectInstanceCount = GetIndirectInstanceCount();
#else
CommandID = 0;
IndirectInstanceCount = 1;
#endif
}
#endif
Using the Custom Function above, the following graph does the same thing as the shader in the documentation example.
Note: a Custom Interpolator allows setting the color in vertex stage, then getting the interpolated value in fragment stage.
Renders multiple instances of a Mesh using GPU instancing and a custom shader.
This example requires no custom HLSL.
The InstanceID Node will return the unique index of the instance.
uniform float4x4 _ObjectToWorld;
uniform float _NumInstances;
These can be set as Per Material Properties in the Blackboard.
The following graph does the same thing as the shader in the documentation example.
Renders non-indexed primitives with GPU instancing and a custom shader.
This example requires custom HLSL to declare and access the StructuredBuffer and uint uniforms.
StructuredBuffer<int> _Triangles;
StructuredBuffer<float3> _Positions;
uniform uint _StartIndex;
uniform uint _BaseVertexIndex;
void Position_float(uint vertexID, out float3 Out)
{
Out = _Positions[_Triangles[vertexID + _StartIndex] + _BaseVertexIndex];
}
Using the Custom Function above, the following graph does the same thing as the shader in the documentation example.
Note: a Predefined Keyword SHADERGRAPH_PREVIEW, with the preview value set to true, allows bypassing the Position fed by the Custom Function Node, as to make the Shader Graph Main Preview work.
Renders primitives with GPU instancing and a custom shader using rendering command arguments from commandBuffer.
This function only works on platforms that support compute shaders.
Requirements are similar to those of RenderMeshIndirect and RenderPrimitives altogether, except we need to use IndirectDrawArgs and not IndirectDrawIndexedArgs.
#ifndef UNITY_INDIRECT_DRAW_ARGS
#define UNITY_INDIRECT_DRAW_ARGS IndirectDrawArgs
#include "UnityIndirect.cginc"
#endif
#ifndef RENDER_PRIMITIVES_INDIRECT_EXAMPLE
#define RENDER_PRIMITIVES_INDIRECT_EXAMPLE
StructuredBuffer<int> _Triangles;
StructuredBuffer<float3> _Positions;
uniform uint _StartIndex;
uniform uint _BaseVertexIndex;
void Position_float(uint vertexID, out float3 Out)
{
Out = _Positions[_Triangles[vertexID + _StartIndex] + _BaseVertexIndex];
}
void CommandID_IndirectInstanceCount_float(out uint CommandID, out uint IndirectInstanceCount)
{
#ifndef SHADERGRAPH_PREVIEW
InitIndirectDrawArgs(0);
CommandID = GetCommandID(0);
IndirectInstanceCount = GetIndirectInstanceCount();
#else
CommandID = 0;
IndirectInstanceCount = 1;
#endif
}
#endif
Using the Custom Function above, the following graph does the same thing as the shader in the documentation example.
Renders indexed primitives with GPU instancing and a custom shader.
Requirements are similar to those of RenderPrimitives.
#ifndef RENDER_PRIMITIVES_INDEXED_EXAMPLE
#define RENDER_PRIMITIVES_INDEXED_EXAMPLE
StructuredBuffer<float3> _Positions;
uniform uint _BaseVertexIndex;
void Position_float(uint vertexID, out float3 Out)
{
Out = _Positions[vertexID + _BaseVertexIndex];
}
#endif
Using the Custom Function above, the following graph does the same thing as the shader in the documentation example.
Renders indexed primitives with GPU instancing and a custom shader with rendering command arguments from commandBuffer.
This function only works on platforms that support compute shaders.
Similar to RenderMeshIndirect and RenderPrimitives altogether.
#ifndef UNITY_INDIRECT_DRAW_ARGS
#define UNITY_INDIRECT_DRAW_ARGS IndirectDrawIndexedArgs
#include "UnityIndirect.cginc"
#endif
#ifndef RENDER_PRIMITIVES_INDEXED_INDIRECT_EXAMPLE
#define RENDER_PRIMITIVES_INDEXED_INDIRECT_EXAMPLE
StructuredBuffer<float3> _Positions;
void Position_float(uint vertexID, out float3 Out)
{
Out = _Positions[vertexID];
}
void CommandID_IndirectInstanceCount_float(out uint CommandID, out uint IndirectInstanceCount)
{
#ifndef SHADERGRAPH_PREVIEW
InitIndirectDrawArgs(0);
CommandID = GetCommandID(0);
IndirectInstanceCount = GetIndirectInstanceCount();
#else
CommandID = 0;
IndirectInstanceCount = 1;
#endif
}
#endif
Using the Custom Function above, the following graph does the same thing as the shader in the documentation example.
I hope this helps and will follow up with more examples.