frustum culling, multiple materials are being segregated into batches with their own AABB:
workflow:
| // scr* https://gist.github.com/andrew-raphael-lukasik/df4a36ff2ad89078258fd653c422a021 | |
| using System.Collections.Generic; | |
| using UnityEngine; | |
| public class GpuInstancingForGameObjects : MonoBehaviour | |
| { | |
| [SerializeField] Camera _camera = null; | |
| [SerializeField] MeshRenderer[] _meshRenderers = new MeshRenderer[0]; | |
| /// <summary> | |
| /// Prefer "true" ☑ as "false" ☐ require updates every frame. | |
| /// It is a good idea to keep lists of still and moving mesh renderers in a separate components. | |
| /// </summary> | |
| public bool meshesAreStill = true; | |
| Dictionary<(Mesh mesh,Material material),(List<Transform> transforms,Bounds aabb)> _sources = new Dictionary<(Mesh,Material),(List<Transform>,Bounds)>(); | |
| Dictionary<(Mesh mesh,Material material),(Matrix4x4[] matrices,Bounds aabb)> _batches = new Dictionary<(Mesh,Material),(Matrix4x4[],Bounds)>(); | |
| Dictionary<int,Stack<Matrix4x4[]>> _freeMatrices = new Dictionary<int,Stack<Matrix4x4[]>>(); | |
| Plane[] _frustum = new Plane[6]; | |
| void Start () | |
| { | |
| Initialize(); | |
| UpdateMatrices(); | |
| if( _camera==null ) _camera = Camera.main; | |
| if( _camera==null ) | |
| { | |
| Debug.LogError( "no camera, can't continue" , this ); | |
| enabled = false; | |
| } | |
| } | |
| void Update () | |
| { | |
| if( !meshesAreStill ) UpdateMatrices(); | |
| GeometryUtility.CalculateFrustumPlanes( _camera , _frustum ); | |
| foreach( var batch in _batches ) | |
| { | |
| var meshMaterialPair = batch.Key; | |
| var matricesAabbPair = batch.Value; | |
| var aabb = matricesAabbPair.aabb; | |
| if( GeometryUtility.TestPlanesAABB(_frustum,aabb) ) | |
| { | |
| Graphics.DrawMeshInstanced( | |
| mesh: meshMaterialPair.mesh , | |
| submeshIndex: 0 , | |
| material: meshMaterialPair.material , | |
| matrices: matricesAabbPair.matrices | |
| ); | |
| } | |
| } | |
| } | |
| #if UNITY_EDITOR | |
| // void OnDrawGizmosSelected () | |
| void OnDrawGizmos () | |
| { | |
| Initialize(); | |
| Gizmos.color = Color.yellow; | |
| foreach( var source in _sources ) | |
| { | |
| var transformsAabbPair = source.Value; | |
| var aabb = transformsAabbPair.aabb; | |
| Gizmos.DrawWireCube( aabb.center , aabb.size ); | |
| if( Application.isPlaying && !GeometryUtility.TestPlanesAABB(_frustum,aabb) ) | |
| UnityEditor.Handles.Label( aabb.center , "(out of camera view)" ); | |
| } | |
| } | |
| #endif | |
| void Initialize () | |
| { | |
| _sources.Clear(); | |
| foreach( var meshRenderer in _meshRenderers ) | |
| { | |
| if( meshRenderer==null ) continue; | |
| var meshFilter = meshRenderer.GetComponent<MeshFilter>(); | |
| if( meshFilter==null ) continue; | |
| var mesh = meshFilter.sharedMesh; | |
| if( mesh==null ) continue; | |
| foreach( var material in meshRenderer.sharedMaterials ) | |
| { | |
| if( !material.enableInstancing && Application.isPlaying ) | |
| { | |
| Debug.LogWarning($"\"{material.name}\" material won't be rendered as it's <b>GPU Instancing</b> is not enabled",meshRenderer); | |
| continue; | |
| } | |
| if( material==null ) continue; | |
| var aabb = meshRenderer.bounds; | |
| var meshMaterialPair = ( mesh , material ); | |
| if( _sources.ContainsKey( meshMaterialPair ) ) | |
| { | |
| var transforms = _sources[meshMaterialPair].transforms; | |
| transforms.Add( meshRenderer.transform ); | |
| var newAabb = _sources[meshMaterialPair].aabb; | |
| newAabb.Encapsulate( aabb ); | |
| _sources[meshMaterialPair] = ( transforms , newAabb ); | |
| } | |
| else | |
| { | |
| _sources.Add( meshMaterialPair , ( new List<Transform>(){ meshRenderer.transform } , aabb ) ); | |
| } | |
| } | |
| if( Application.isPlaying ) | |
| meshRenderer.enabled = false; | |
| } | |
| } | |
| void UpdateMatrices () | |
| { | |
| foreach( var batch in _batches ) | |
| { | |
| var matricesAabbPair = batch.Value; | |
| var matrices = matricesAabbPair.matrices; | |
| if( _freeMatrices.ContainsKey( matrices.Length ) ) | |
| { | |
| _freeMatrices[matrices.Length].Push( matrices ); | |
| } | |
| else | |
| { | |
| var stack = new Stack<Matrix4x4[]>(); | |
| stack.Push( matrices ); | |
| _freeMatrices.Add( matrices.Length , stack ); | |
| } | |
| } | |
| _batches.Clear(); | |
| foreach( var source in _sources ) | |
| { | |
| var meshMaterialPair = source.Key; | |
| var transformsAabbPair = source.Value; | |
| var transforms = transformsAabbPair.transforms; | |
| int numTransforms = transforms.Count; | |
| Matrix4x4[] matrices = null; | |
| if( _freeMatrices.ContainsKey(numTransforms) && _freeMatrices[numTransforms].Count!=0 ) | |
| { | |
| matrices = _freeMatrices[numTransforms].Pop(); | |
| } | |
| else matrices = new Matrix4x4[ numTransforms ]; | |
| for( int i=0 ; i<numTransforms ; i++ ) | |
| matrices[i] = transforms[i].localToWorldMatrix; | |
| _batches.Add( meshMaterialPair , ( matrices , transformsAabbPair.aabb ) ); | |
| } | |
| } | |
| } |
Awsome~ Thank you!