개요

SRP Batcher는 RenderState 호출을 배칭하여 렌더콜 API 호출 수를 줄여줍니다.

그러나 DrawCalls 호출 수를 줄이지 않기 때문에 뷰에 Renderer 숫자가 많으면 여전히 CPU 부하 발생 위험이 있습니다.

 

GPU Instancing은 렌더링에 필요한 API 정보를 GPU 버퍼에 저장하여 반복 렌더링 명령을 하는 방식으로 CPU에서 발생하는 렌더콜 숫자를 줄일 수 있습니다.

 

이번 포스팅은 GPU Instancing과 SRP Batcher 성능 비교 정리 자료입니다.

 

테스트 환경

메쉬 3종, Material 2 종으로 구성된 씬입니다. (URP Lit 쉐이더 기반)

Mesh & Material 세팅 조합은 별도로 섞지 않아 총 3개의 Instancing이 나오는 환경입니다.

Batches는 에디터 Stat에 약 2300개가 나오게 세팅했습니다. (막 복사해서 정확한 숫자 모름)

 

URP 환경에서 SRP Batcher를 지원하는 쉐이더는 GPU Instancing이 비활성화됩니다.

그래서 스크립트에서 Graphics.DrawMeshInstanced 함수를 활용해 GPU Instancing이 작동되도록 했습니다.

스크립트는 포스팅 맨 아래 첨부합니다.

 

결과 정리

좌 : GPU Instancing / 우 : SRP Batcher
좌 : GPU Instancing / 우 : SRP Batcher

  SRP Batcher GPU Instancing
프로파일러 - CPU 메인쓰레드 11.84  ms 4.28 ms
프로파일러 - CPU 렌더쓰레드 10.69  ms 2.39 ms
프로팡리러 - CPU 렌더콜 총합 17.10 ms 5.26 ms
RenderDoc - Draw calls 2292 20
RenderDoc - API calls 3558 157
RenderDoc - GPU Buffer + texture load 22.19 MB 24.19 MB

 

 

정리

GPU Instancing이 압도적으로 좋은 성능이 나왔습니다.

하지만 이런 GPU 드로우 방식은 다양한 종류의 메쉬 & Material 지원이 제한적입니다.

추후 GPU Instancing 외에 InDirectDraw등 다른 GPU 드로우 방식을 테스트 해볼 예정입니다.

 

 

 

GPU Instancing 드로우 스크립트

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GPUInstancingManager : MonoBehaviour
{
    private class InstanceData
    {
        public MeshRenderer meshRenderer;
        public Mesh mesh;
        public Material material;
        public Matrix4x4[] matrices;
        public int meshCount = 0;

        public InstanceData(MeshRenderer meshRenderer, Mesh mesh, Material material)
        {
            this.meshRenderer = meshRenderer;
            this.mesh = mesh;   
            this.material = material;
        }
    };

    private struct MatrixData
    {
        public List<Matrix4x4> matrices;
        public MatrixData(List<Matrix4x4> matrices)
        {
            this.matrices = matrices;
        }
    };

    private List<InstanceData> _instanceDatas;

    void Start()
    {
        _instanceDatas = InitMeshInstanced();
        Debug.Log("Instance Count : " + _instanceDatas.Count);
    }

    // Update is called once per frame
    void Update()
    {
        for(int i=0;i< _instanceDatas.Count;i++) 
        {
            InstanceData instanceData = _instanceDatas[i];
            Graphics.DrawMeshInstanced(instanceData.mesh, 0, instanceData.material, instanceData.matrices, instanceData.meshCount);
        }
    }

    private List<InstanceData> InitMeshInstanced()
    {
        List<InstanceData> instanceDatas = new List<InstanceData>();
        List<MatrixData> matrixDatas = new List<MatrixData>();
        MeshRenderer[] meshRenderers = transform.GetComponentsInChildren<MeshRenderer>();

        for(int i=0; i< meshRenderers.Length; i++)
        {
            MeshRenderer renderer = meshRenderers[i];
            if (!renderer) continue;
            Material material = renderer.sharedMaterial;
            if (!material) continue;
            MeshFilter meshFilter = renderer.GetComponent<MeshFilter>();
            if(!meshFilter) continue;
            Mesh mesh = meshFilter.sharedMesh;
            if (!mesh) continue;
            Matrix4x4 matrix = renderer.transform.localToWorldMatrix;

            bool isExistsData = false;
            for(int j=0;j< instanceDatas.Count;j++)
            {
                InstanceData instanceData = instanceDatas[j];
                if(instanceData.mesh == mesh && instanceData.material == material) 
                {
                    isExistsData = true;
                    matrixDatas[j].matrices.Add(matrix);
                    break;
                }
            }

            if(!isExistsData)
            {
                material.enableInstancing = true;
                instanceDatas.Add(new InstanceData(renderer, mesh, material));
                List<Matrix4x4> matrices = new List<Matrix4x4>();
                matrices.Add(matrix);
                matrixDatas.Add(new MatrixData(matrices));
            }
            Destroy(renderer.gameObject);
        }

        for(int i=0;i< instanceDatas.Count;i++)
        {
            instanceDatas[i].matrices = matrixDatas[i].matrices.ToArray();
            instanceDatas[i].meshCount = matrixDatas[i].matrices.Count;
        }

        return instanceDatas;
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,