Skip to content

Instantly share code, notes, and snippets.

@reveriejake
Last active November 12, 2020 03:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save reveriejake/b668119ecdccf8bcb267f2ea303b830d to your computer and use it in GitHub Desktop.
Save reveriejake/b668119ecdccf8bcb267f2ea303b830d to your computer and use it in GitHub Desktop.
Simplified 'prefab' that allows you to draw thousands of objects to the screen. Many thanks to Mike from toqoz.fyi for his help in solving some issues I had. This code is Inspired by his blog from https://toqoz.fyi/thousands-of-meshes.html
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Rendering;
namespace PicoGames.Prototype
{
[System.Serializable]
public class InstancePrefab
{
public GameObject prefab;
private Mesh mesh;
private SubMeshInstance[] subMeshInstanceArr;
private Bounds bounds;
private ComputeBuffer propertyBuffer;
private List<MeshProperty> properties = new List<MeshProperty>();
private int instanceCount { get { return properties.Count; } }
private int cachedInstanceCount = -1;
public void BoundsGizmo()
{
Gizmos.DrawWireCube(bounds.center, bounds.size);
}
public void Init()
{
if (subMeshInstanceArr != null)
return;
bounds = new Bounds(Vector3.zero, new Vector3(500, 500, 500));
mesh = prefab.GetComponent<MeshFilter>().sharedMesh;
Renderer renderer = prefab.GetComponent<MeshRenderer>();
Material[] materials = new Material[renderer.sharedMaterials.Length];
subMeshInstanceArr = new SubMeshInstance[materials.Length];
//Debug.Log($"SubmeshCount: {mesh.subMeshCount}, MaterialCount: {materials.Length}");
for (int i = 0; i < materials.Length; i++)
{
materials[i] = new Material(prefab.GetComponent<Renderer>().sharedMaterials[i]);
subMeshInstanceArr[i] = new SubMeshInstance(mesh.GetIndexCount(i), mesh.GetIndexStart(i), mesh.GetBaseVertex(i), materials[i]);
}
}
private void UpdateBuffers()
{
if (cachedInstanceCount == instanceCount)
return;
if (propertyBuffer != null)
propertyBuffer.Release();
propertyBuffer = new ComputeBuffer(instanceCount, MeshProperty.Size());
propertyBuffer.SetData(properties);
for (int i = 0; i < subMeshInstanceArr.Length; i++)
{
subMeshInstanceArr[i].material.SetBuffer("_Properties", propertyBuffer);
subMeshInstanceArr[i].UpdateArgs(instanceCount);
}
cachedInstanceCount = instanceCount;
}
public void Draw()
{
if (prefab == null || instanceCount == 0)
return;
Init();
UpdateBuffers();
for (int i = 0; i < subMeshInstanceArr.Length; i++)
{
Graphics.DrawMeshInstancedIndirect(mesh, i, subMeshInstanceArr[i].material, bounds, subMeshInstanceArr[i].argsBuffer);
}
}
public void AddInstance(Vector3 pos, Quaternion rot, Vector3 sca)
{
if (prefab == null)
return;
var property = new MeshProperty();
property.matrix = Matrix4x4.TRS(pos, rot, sca);
property.color = Color.white;
properties.Add(property);
}
public void ClearInstances()
{
if (prefab == null)
return;
properties.Clear();
}
public void ForceUpdateBuffers()
{
cachedInstanceCount = -1;
}
public void Release()
{
if (propertyBuffer != null)
propertyBuffer.Release();
propertyBuffer = null;
if (subMeshInstanceArr != null)
{
for (int i = 0; i < subMeshInstanceArr.Length; i++)
subMeshInstanceArr[i].Release();
}
}
private struct MeshProperty
{
public Matrix4x4 matrix;
public Vector4 color;
public static int Size()
{
return
sizeof(float) * 4 * 4 + // Matrix
sizeof(float) * 4; // Colors
}
}
private class SubMeshInstance
{
public Material material;
public ComputeBuffer argsBuffer;
private uint[] args;
public SubMeshInstance(uint instVertCount, uint startVertLocation, uint instStartLocation, Material material)
{
this.material = material;
args = new uint[5] { 0, 0, 0, 0, 0 };
args[0] = instVertCount; // Vert Count per Instance
args[1] = 0; // Instance Count
args[2] = startVertLocation; // Start Vertex Location
args[3] = instStartLocation; // Start Instance Location
argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
argsBuffer.SetData(args);
}
public void UpdateArgs(int instanceCount)
{
args[1] = (uint)instanceCount;
argsBuffer.SetData(args);
}
public void Release()
{
if (argsBuffer != null)
argsBuffer.Release();
argsBuffer = null;
}
}
}
}
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using UnityEditor;
namespace PicoGames.Prototype
{
public class PITestCase : MonoBehaviour
{
public int count = 5000;
public float radius = 10;
public InstancePrefab[] prefab;
private void OnValidate()
{
if(EditorApplication.isPlaying)
{
for (int i = 0; i < prefab.Length; i++)
prefab[i].ClearInstances();
InitDummyData();
}
}
private void Start()
{
Random.InitState(12345);
InitDummyData();
}
private void InitDummyData()
{
for (int i = 0; i < count; i++)
{
int pIndex = Random.Range(0, prefab.Length);
prefab[pIndex].AddInstance(Random.insideUnitSphere * radius, Random.rotation, Vector3.one);
prefab[pIndex].ForceUpdateBuffers();
}
}
private void Update()
{
for (int i = 0; i < prefab.Length; i++)
{
prefab[i].Draw();
}
}
private void OnDisable()
{
for (int i = 0; i < prefab.Length; i++)
{
prefab[i].Release();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment