unity-game-engineurp

How reduce draw call in this sence? URP SRP Batcher


Unity2021.3.10f1, URP, SRP Batcher on

I created a 10x10x10 cube matrix in the sence. The cube is a prefab. It 1:1:1 default size, with a SRP Batcher compatible shader.

Now 5000+ Batches in runtime. How can I reduce the batches?

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here


Solution

  • workflow source: https://gist.github.com/andrew-raphael-lukasik/df4a36ff2ad89078258fd653c422a021

    // 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 ) );
            }
        }
    
    }
    

    This solution provides AABB frustum culling - it is not enough to optimize your voxels-like case but a starting point. If you consider these boxes to represent voxels then your next step would be to implement a voxel occlusion culling here (to hide boxes that are inside this shape).