Skip to content

Instantly share code, notes, and snippets.

@Cyanilux
Last active January 27, 2024 07:55
Show Gist options
  • Save Cyanilux/e7afdc5c65094bfd0827467f8e4c3c54 to your computer and use it in GitHub Desktop.
Save Cyanilux/e7afdc5c65094bfd0827467f8e4c3c54 to your computer and use it in GitHub Desktop.
Experiments with DrawMeshInstanced and DrawMeshInstancedIndirect for drawing grass (over terrain)
// https://twitter.com/Cyanilux/status/1396848736022802435
// Requires a specific shader to read the _PerInstanceData buffer at SV_InstanceID
// I use a shader made in Shader Graph, See : https://gist.github.com/Cyanilux/4046e7bf3725b8f64761bf6cf54a16eb
// Also note, there's no frustum culling involved in this example. Typically a compute shader is used for this.
using UnityEngine;
public class DrawGrass : MonoBehaviour {
public int count; // If DrawMeshInstanced is used, this is limited to a max of 1023. No limit for DrawMeshInstancedIndirect
public float range; // (terrain width)
public Terrain terrain;
public Mesh mesh;
public Material material;
private Matrix4x4[] matrices;
private MaterialPropertyBlock MPB;
private UnityEngine.Rendering.ShadowCastingMode shadowCasting = UnityEngine.Rendering.ShadowCastingMode.Off;
private bool receiveShadows = true;
private Bounds bounds;
private ComputeBuffer instancesBuffer;
private ComputeBuffer argsBuffer;
private struct InstanceData {
public Matrix4x4 matrix;
public static int Size() {
return sizeof(float) * 4 * 4;
}
}
public void OnEnable() {
MPB = new MaterialPropertyBlock();
/*
If using DrawMeshInstanced,
matrices = new Matrix4x4[count];
float range = 5f;
for (int i = 0; i < count; i++) {
float height = Random.Range(0.5f, 1.2f);
Vector3 position = new Vector3(Random.Range(-range, range), height * 0.5f, Random.Range(-range, range));
Quaternion rotation = Quaternion.Euler(0, Random.Range(0f, 360f), 0);
Vector3 scale = new Vector3(1, height, 1);
matrices[i] = Matrix4x4.TRS(position, rotation, scale);
}
*/
// If using DrawMeshInstancedIndirect,
bounds = new Bounds(transform.position, new Vector3(101, 1, 101));
InitializeBuffers();
}
private void InitializeBuffers() {
// Args
uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
args[0] = (uint)mesh.GetIndexCount(0);
args[1] = (uint)count;
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);
// Instances
InstanceData[] instances = new InstanceData[count];
for (int i = 0; i < count; i++) {
InstanceData data = new InstanceData();
//Vector3 position = new Vector3(Random.Range(-range, range), 0, Random.Range(-range, range));
Vector3 position = new Vector3(Random.Range(0, range), 0, Random.Range(0, range));
float height = Random.Range(0.3f, 0.9f);
float y = terrain.SampleHeight(position);
//position.y = (height - 1) * 0.5f;
position.y = y + (height) * 0.5f;
Quaternion rotation = Quaternion.Euler(0, Random.Range(0f, 360f), 0);
Vector3 scale = new Vector3(1, height, 1);
data.matrix = Matrix4x4.TRS(position, rotation, scale);
instances[i] = data;
}
instancesBuffer = new ComputeBuffer(count, InstanceData.Size());
instancesBuffer.SetData(instances);
material.SetBuffer("_PerInstanceData", instancesBuffer);
}
public void Update() {
//Graphics.DrawMeshInstanced(mesh, 0, material, matrices, count, MPB, shadowCasting, receiveShadows);
//bounds = new Bounds(transform.position, new Vector3(101, 1, 101));
Graphics.DrawMeshInstancedIndirect(mesh, 0, material, bounds, argsBuffer, 0, MPB, shadowCasting, receiveShadows);
}
private void OnDisable() {
if (instancesBuffer != null) {
instancesBuffer.Release();
instancesBuffer = null;
}
if (argsBuffer != null) {
argsBuffer.Release();
argsBuffer = null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment