Skip to content

Instantly share code, notes, and snippets.

@Refsa
Last active August 10, 2023 06:18
Show Gist options
  • Save Refsa/4949519af2160b9b29ea31d115de5dad to your computer and use it in GitHub Desktop.
Save Refsa/4949519af2160b9b29ea31d115de5dad to your computer and use it in GitHub Desktop.
DrawMeshInstancedIndirect with ShaderGraph and URP

First is a passthrough custom function node that loads the hlsl code. image

Second custom function node contains the code to setup procedural instancing. It needs to be implmeneted in a custom function node or else it wont work.
Important bits is the #pragma instancing_options procedural:setup where "procedural:setup" calls a function in the included code in the previous node. image

The output from this goes into position output of vertex stage. It needs to be connected for shader graph to compile it. image

Shader Graph should now work just as normal, just that it uses data passed from ComputeBuffers.

Do note that the example code in Render.cs is sub-optimal and is mostly there as an example of how to use it from the C# side.

You can find a sample unitypackage here

#ifndef SHADER_GRAPH_SUPPORT_H
#define SHADER_GRAPH_SUPPORT_H
// You could also upload the model matrix
struct DrawData {
float3 position;
float4 rotation;
float3 scale;
};
StructuredBuffer<DrawData> _DrawData;
inline float4x4 TRSMatrix(float3 position, float4 rotation, float3 scale)
{
float4x4 m = 0.0;
m[0][0] = (1.0 - 2.0 * (rotation.y * rotation.y + rotation.z * rotation.z)) * scale.x;
m[1][0] = (rotation.x * rotation.y + rotation.z * rotation.w) * scale.x * 2.0;
m[2][0] = (rotation.x * rotation.z - rotation.y * rotation.w) * scale.x * 2.0;
m[3][0] = 0.0;
m[0][1] = (rotation.x * rotation.y - rotation.z * rotation.w) * scale.y * 2.0;
m[1][1] = (1.0 - 2.0 * (rotation.x * rotation.x + rotation.z * rotation.z)) * scale.y;
m[2][1] = (rotation.y * rotation.z + rotation.x * rotation.w) * scale.y * 2.0;
m[3][1] = 0.0;
m[0][2] = (rotation.x * rotation.z + rotation.y * rotation.w) * scale.z * 2.0;
m[1][2] = (rotation.y * rotation.z - rotation.x * rotation.w) * scale.z * 2.0;
m[2][2] = (1.0 - 2.0 * (rotation.x * rotation.x + rotation.y * rotation.y)) * scale.z;
m[3][2] = 0.0;
m[0][3] = position.x;
m[1][3] = position.y;
m[2][3] = position.z;
m[3][3] = 1.0;
return m;
}
inline void SetUnityMatrices(uint instanceID, inout float4x4 objectToWorld, inout float4x4 worldToObject)
{
#if UNITY_ANY_INSTANCING_ENABLED
DrawData drawData = _DrawData[instanceID];
objectToWorld = mul(objectToWorld, TRSMatrix(drawData.position, drawData.rotation, drawData.scale));
float3x3 w2oRotation;
w2oRotation[0] = objectToWorld[1].yzx * objectToWorld[2].zxy - objectToWorld[1].zxy * objectToWorld[2].yzx;
w2oRotation[1] = objectToWorld[0].zxy * objectToWorld[2].yzx - objectToWorld[0].yzx * objectToWorld[2].zxy;
w2oRotation[2] = objectToWorld[0].yzx * objectToWorld[1].zxy - objectToWorld[0].zxy * objectToWorld[1].yzx;
float det = dot(objectToWorld[0].xyz, w2oRotation[0]);
w2oRotation = transpose(w2oRotation);
w2oRotation *= rcp(det);
float3 w2oPosition = mul(w2oRotation, -objectToWorld._14_24_34);
worldToObject._11_21_31_41 = float4(w2oRotation._11_21_31, 0.0f);
worldToObject._12_22_32_42 = float4(w2oRotation._12_22_32, 0.0f);
worldToObject._13_23_33_43 = float4(w2oRotation._13_23_33, 0.0f);
worldToObject._14_24_34_44 = float4(w2oPosition, 1.0f);
#endif
}
void passthroughVec3_float(in float3 In, out float3 Out)
{
Out = In;
}
void setup()
{
#if UNITY_ANY_INSTANCING_ENABLED
SetUnityMatrices(unity_InstanceID, unity_ObjectToWorld, unity_WorldToObject);
#endif
}
#endif
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
class Render : MonoBehaviour
{
struct DrawData
{
public Vector3 Pos;
public Quaternion Rot;
public Vector3 Scale;
}
public Mesh mesh;
public Material material;
List<DrawData> instances;
ComputeBuffer drawDataBuffer;
ComputeBuffer argsBuffer;
uint[] args = new uint[5];
MaterialPropertyBlock mpb;
void Awake()
{
instances = new List<DrawData>();
argsBuffer = new ComputeBuffer(5, sizeof(uint), ComputeBufferType.IndirectArguments);
// Meshes with sub-meshes needs more structure, this assumes a single sub-mesh
args[0] = mesh.GetIndexCount(0);
mpb = new MaterialPropertyBlock();
SpawnExample();
}
void OnDestroy()
{
argsBuffer?.Release();
drawDataBuffer?.Release();
}
void LateUpdate()
{
// Only needs to be called if "instances" changed
PushDrawData();
mpb.SetBuffer("_DrawData", drawDataBuffer);
args[1] = (uint)instances.Count;
argsBuffer.SetData(args);
Graphics.DrawMeshInstancedIndirect(
mesh, 0, material,
new Bounds(Vector3.zero, Vector3.one * 1000f),
argsBuffer, 0,
mpb
);
}
void PushDrawData()
{
if (drawDataBuffer == null || drawDataBuffer.count < instances.Count)
{
drawDataBuffer?.Release();
drawDataBuffer = new ComputeBuffer(instances.Count, Marshal.SizeOf<DrawData>());
}
drawDataBuffer.SetData(instances);
}
void SpawnExample()
{
instances.Clear();
for (int i = 0; i < 4096; i++)
{
instances.Add(new DrawData()
{
Pos = Random.insideUnitSphere * 100f,
Rot = Random.rotation,
Scale = Vector3.one * Random.Range(1f, 10f)
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment