Skip to content

Instantly share code, notes, and snippets.

@ArieLeo
Forked from Refsa/Usage.cs
Created November 7, 2021 09:38
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ArieLeo/d7e6bc5485caa9ba99cd3a59d0f53404 to your computer and use it in GitHub Desktop.
Save ArieLeo/d7e6bc5485caa9ba99cd3a59d0f53404 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)
});
}
}
}
@jakobkarlstrand
Copy link

For three days I've been searching for exactly this. Thank you so much!!

@KILLJOY-T3000
Copy link

Thanks! Is the same working for HDRP too? Or is it more complicated?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment