유니티 GPU Instancing에 대해서 #동적 객체 최적화 방법

Instancing이란?

Instancing은 주로 '복제' 또는 '반복 생성'의 개념을 뜻합니다. 특정 객체, 데이터, 또는 구조의 여러 인스턴스를 생성하고 관리하는 과정을 포함됩니다.

 

유니티에서 Instancing이라는 개념은 GPU Instancing과 Instantiate라는 2가지 의미로 사용되곤 합니다.

 

이 포스팅에서는 GPU Instancing에 대해서만 말해보고자 합니다.


GPU Instancing이란?

GPU Instancing은 그래픽 처리 단계에서 사용되는 "최적화 기법"입니다. GPU Instancing을 사용하면 '동일한 메시'여러 복제본을 한 번에 그리거나 렌더링할 수 있습니다.

 

좀비 게임을 예로 들어보겠습니다. 좀비 게임을 할 때 좀비가 한 두마리만 생성되는 경우가 있을까요? 좀비 게임할 때는 수 많은 좀비가 떼로 몰려오는 경우가 많습니다. 이때 좀비들이 똑같은 객체라고 가정하겠습니다(A좀비도, B좀비도 Zombie라는 에셋을 사용한다는 뜻). 

 

좀비가 많이 몰려오면 몰려올수록 Draw Call이 커질 것입니다. 정적인 객체가 아니므로 Static Batching 기법도 사용하지 못하겠죠. 이럴 때 사용할 수 있는 것이 GPU Instancing라는 뜻입니다. 똑같은 메쉬와 똑같은 메테리얼을 사용하고 있는 대상에 GPU Instancing를 사용하면, 추가 Draw Call없이 여러 마리의 좀비를 렌더링할 수 있습니다.

 

Draw Call이란? 🔽

 

정적배칭이란? (이론)

# 정적배칭을 사용하는 최적화 방법 정적이란 말 그대로 움직이지 않는 것을 뜻하며, 움직이지 않는 정적 오브젝트에 활용하는 기법이다. 동일한 재질을 공유하는 오브젝트들을 일괄 처리해서

wlsdn629.tistory.com

 

 

GPU Instancing은 GPU 메모리 Buffer에 메쉬 정보를 저장하고 있다가, 동일한 메시를 가진 객체가 생성이 되면 저장된 정보를 사용하여, 해당 객체를 복사해서 렌더링하므로 추가 Draw Call을 발생하지 않습니다.

 

좀 더 쉽게 생각해보자면 그림판에 별을 하나 마우스로 그린 다음 똑같은 별을 마우스로 그리는 것이 아닌 Ctrl+C, Ctrl+V 하는 느낌?

 

GPU Batching이라고 부르지 않는 이유는 객체를 하나로 묶는 것이 아니라, 복제하여 사용한다는 의미가 강하고 GPU에서 복제 및 반복 생성한다는 의미에서 GPU Instancing이라고 불립니다.

 

Remind! 🔽

Instancing은 주로 '복제' 또는 '반복 생성'의 개념을 뜻 함


GPU Instancing 조건

GPU Instancing을 사용하기 위해서는 쉐이더에서 GPU Instancing기능을 지원해줘야 합니다.

GPU 메모리 Buffer에 저장되는 정보는 MVP 행렬 정보이기 때문에 쉐이더에서 따로 작업해줘야 합니다.

 

간단한 쉐이더 예시 🔽

더보기

Unlit 셰이더를 사용하여 Vertex및 Fragment 쉐이더에 다른 Color로 인스턴싱하는 방법

Shader "SimplestInstancedShader"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID // necessary only if you want to access instanced properties in fragment Shader.
            };

            UNITY_INSTANCING_BUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_BUFFER_END(Props)
           
            v2f vert(appdata v)
            {
                v2f o;

                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o); // necessary only if you want to access instanced properties in the fragment Shader.

                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
           
            fixed4 frag(v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i); // necessary only if any instanced properties are going to be accessed in the fragment Shader.
                return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            }
            ENDCG
        }
    }
}

GPU Instancing 정보에 Material Property를 추가하여 런타임 도중에 값을 변경할 수 있는 방법 🔽

더보기

 기존에 Properties에서 사용되던 _Color 변수를 삭제하고 UNITY_INSTANCING_BUFFER_START 메크로 내부에 새로 선언.

Shader "SimplestInstancedShader"
{
    Properties
    {
        // 
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID // necessary only if you want to access instanced properties in fragment Shader.
            };

            UNITY_INSTANCING_BUFFER_START(Props)
                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
                
                #define _Color UNITY_ACCESS_INSTANCED_PROP(Props, _Color)
            UNITY_INSTANCING_BUFFER_END(Props)
           
            v2f vert(appdata v)
            {
                v2f o;

                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o); // necessary only if you want to access instanced properties in the fragment Shader.

                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
           
            fixed4 frag(v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i); // necessary only if any instanced properties are going to be accessed in the fragment Shader.
                return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            }
            ENDCG
        }
    }
}

 

MaterialPropertyBlock를 사용하여 Color를 바꾸는 방법 🔽

[SerializeField] private Renderer renderer;
[SerializeField] private Color color;

public void ChangeColor()
{
    MaterialPropertyBlock properties = new MaterialPropertyBlock();
    properties.SetColor("_Color", color);
    renderer.SetPropertyBlock(properties);
}

 

SkinnedMeshRenderer는 지원되지 않습니다.

 

동일한 메시와 머티리얼을 공유하는 게임 오브젝트만 일괄 처리합니다. Instancing 효율성을 개선하려면 메시와 머티리얼을 조금만 사용해야 합니다.

 

배칭에서는 GPU Instancing보다 Static Batching을 우선시합니다. 게임 오브젝트에 Static Batching이 설정되어 있는 경우, Renderer가 인스턴싱 셰이더를 사용하더라도 해당 게임 오브젝트의 인스턴싱은 비활성화됩니다

 

GPU 메모리 Buffer에 저장할 수 있는 인스턴스 당 Buffer 용량이 제한되어 있기 때문에 무한하게 사용할 수 없습니다.