I’m evaluating LWRP/Shader Graph for a project that would rely heavily on DrawMeshInstancedIndirect. I’m unclear on how/if it would work with Shader Graph. Its not apparent how I’d access something like unity_InstanceID from a Shader Graph. I guess I don’t really know how you’d read from a StructuredBuffer inside a ShaderGraph either! I don’t see an option for it in the Blackboard section.
Maybe doing instanced rendering that relies on a StructuredBuffer for each instances info would be more of a job for Visual Effect Graph?
I’m just trying to wrap my head around how this would work with LWRP. Any insight appreciated!
Ya I am having the similar issue as well. At the moment I had to flatten the instances if I want to use shader graph rather than copy-and-pasting the whole big chunk of shader code from github. It will be great if there is a way to “hack” around through some custom functions.
Thanks for clarifying that. So in the meantime, is Visual Effect Graph a viable option?
To get more specific, I have been working on a lot of projects visualizing point cloud data and I use DrawMeshInstancedIndirect to draw meshes (usually cubes) at each point contained in a packed buffer for each frame.
I haven’t really dug into VEG yet, but the examples I’ve seen seem to suggest that StructuredBuffers containing custom structs are supported, but that’s just my intuition. Can you confirm if drawing hundred of thousands of cubes from such a buffer in one draw call would be possible using VEG?
It’s possible to do something along these lines using Texture2D and Texture3D as your buffers. Check out Keijiro’s stuff using the VFX Graph on Github.
I got instanced indirect working with the shader graph.
I made a subnode graph that injects the setup function that reads a structured buffer and sets the Object2World and WorldToObject matrices for each instance.
This was done using 2 CustomCode nodes. One with an external file asset with the setup function and the other to add the #pragma to call it.
Sure. Here is a link to a package. It is for HD SRP and needs 7.x version to work because of the change to Asset reference instead of a relative path for include files.
The setup function is for use with Vegetation Studio but just replace that to fit the data/names in your structured buffer.
Is there any specific reason why you used “2” custom function nodes? I tried to access “unity_InstanceID” with separate setup function as you did, but it spits out “undeclared identifier : unity_InstanceID” error. The only difference is I made them in one custom function node.
It seems that your code is working for modifying the pre-existing variables, and it’s working in the “setup” function. Is there any way to access the unity_InstanceID not only in the “setup” function but also in the custom function?
I got DrawMeshInstancedIndirect working with this trick! It fills me with joy that it’s finally possible : ) Thanks LennartJohansen!
This however makes me worry about the shadow culling? Does anyone have any idea if it’s possible in HDRP?
I’m a little confused about how to get this to work. I don’t suppose anyone has a sample project they could post that doesn’t depend on a paid asset like Vegetation Studio?
Hi,
You create script that fills structured buffer with “IndirectShaderData” data (struct with 2 x matrix4x4) and set that structured buffer to the material with SetBuffer() (you can do it only once so you don’t do it every frame)
The material needs Lit Graph with Lennarts nodes and on Update you call Graphics.DrawMeshInstancedIndirect() and it should work. You won’t be needing any meshes ect. just material as you give the mesh, data buffer and argument buffer for DrawMeshInstancedIndirect.
Hey ViCox, I have a very simple script that creates a bunch of quads. However, I’m having trouble with the InversePositionMatrix. It seems to rotate my quads in a weird way. I’m not familiar with this part of shaders and I’m not sure what I’m doing wrong. Any thoughts?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class MeshInstance : MonoBehaviour {
public int population;
public float range;
public Material material;
private ComputeBuffer meshPropertiesBuffer;
private ComputeBuffer argsBuffer;
private Mesh mesh;
private Bounds bounds;
// Mesh Properties struct to be read from the GPU.
// Size() is a convenience function which returns the stride of the struct.
private struct MeshProperties {
public Matrix4x4 PositionMatrix;
public Matrix4x4 InversePositionMatrix;
//public float ControlData;
public static int Size() {
return
sizeof(float) * 4 * 4 + // matrix;
sizeof(float) * 4 * 4; // inverse matrix;
}
}
private void Setup() {
Mesh mesh = CreateQuad();
this.mesh = mesh;
// Boundary surrounding the meshes we will be drawing. Used for occlusion.
bounds = new Bounds(transform.position, Vector3.one * (range + 1));
InitializeBuffers();
}
private void InitializeBuffers() {
// Argument buffer used by DrawMeshInstancedIndirect.
uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
// Arguments for drawing mesh.
// 0 == number of triangle indices, 1 == population, others are only relevant if drawing submeshes.
args[0] = (uint)mesh.GetIndexCount(0);
args[1] = (uint)population;
args[2] = (uint)mesh.GetIndexStart(0);
args[3] = (uint)mesh.GetBaseVertex(0);
argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
argsBuffer.SetData(args);
// Initialize buffer with the given population.
MeshProperties[] properties = new MeshProperties[population];
for (int i = 0; i < population; i++) {
MeshProperties props = new MeshProperties();
Vector3 position = Vector3.one * i;
Quaternion rotation = Quaternion.identity; // Quaternion.Euler(Random.Range(-180, 180), Random.Range(-180, 180), Random.Range(-180, 180));
Vector3 scale = Vector3.one;
props.PositionMatrix = Matrix4x4.TRS(position, rotation, scale);
props.InversePositionMatrix = Matrix4x4.TRS(position, rotation, scale).inverse;
properties[i] = props;
}
meshPropertiesBuffer = new ComputeBuffer(population, MeshProperties.Size());
meshPropertiesBuffer.SetData(properties);
material.SetBuffer("VisibleShaderDataBuffer", meshPropertiesBuffer);
}
private Mesh CreateQuad(float width = 1f, float height = 1f) {
// Create a quad mesh.
var mesh = new Mesh();
float w = width * .5f;
float h = height * .5f;
var vertices = new Vector3[4] {
new Vector3(-w, -h, 0),
new Vector3(w, -h, 0),
new Vector3(-w, h, 0),
new Vector3(w, h, 0)
};
var tris = new int[6] {
// lower left tri.
0, 2, 1,
// lower right tri
2, 3, 1
};
var normals = new Vector3[4] {
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
};
var uv = new Vector2[4] {
new Vector2(0, 0),
new Vector2(1, 0),
new Vector2(0, 1),
new Vector2(1, 1),
};
mesh.vertices = vertices;
mesh.triangles = tris;
mesh.normals = normals;
mesh.uv = uv;
return mesh;
}
private void Start() {
Setup();
}
private void Update() {
Graphics.DrawMeshInstancedIndirect(mesh, 0, material, bounds, argsBuffer);
}
private void OnDisable() {
// Release gracefully.
if (meshPropertiesBuffer != null) {
meshPropertiesBuffer.Release();
}
meshPropertiesBuffer = null;
if (argsBuffer != null) {
argsBuffer.Release();
}
argsBuffer = null;
}
}