그래픽스 커맨드 버퍼(Graphics Command Buffers)
스파스 텍스처(Sparse Textures)

GPU 인스턴싱(GPU instancing)

개요

GPU 인스턴싱을 사용하면 적은 수의 드로우 콜을 사용하여 동일한 메시의 여러 복제본을 한 번에 그리거나 렌더링할 수 있습니다. 씬에서 반복적으로 나타나는 건물, 나무, 풀 등의 오브젝트를 그릴 때 유용합니다.

GPU 인스턴싱은 각 드로우 콜마다 동일한 메시만 렌더링하지만 각각의 인스턴스에는 다른 파라미터(예: 컬러 또는 스케일)를 포함할 수 있어 변화를 주거나 반복되는 외형을 완화할 수 있습니다.

GPU 인스턴싱을 사용하여 씬마다 사용되는 드로우 콜의 수를 줄일 수 있습니다. 이를 통해 프로젝트의 렌더링 성능이 상당히 개선됩니다.

머티리얼에 인스턴싱을 추가하는 방법

머티리얼에서 GPU 인스턴싱을 활성화하려면 프로젝트 창에서 머티리얼을 선택한 후 인스펙터에서 Enable Instancing 체크박스를 선택합니다.

머티리얼 인스펙터 창의 Enable Instancing 체크박스
머티리얼 인스펙터 창의 Enable Instancing 체크박스

Unity 에서는 머티리얼 셰이더가 GPU 인스턴싱을 지원하는 경우에만 이 체크박스가 표시됩니다. 스탠다드 셰이더, 스탠다드 스페큘러 셰이더, 모든 표면 셰이더가 여기에 해당합니다. 자세한 내용은 스탠다드 셰이더 문서를 참조하십시오.

아래의 스크린샷은 게임 오브젝트가 여러 개 있는 동일한 씬을 보여주고 있습니다. 위의 이미지는 GPU 인스턴싱이 활성화된 상태이고, 아래의 이미지는 그렇지 않습니다. FPS, Batches, Saved by batching 항목의 차이를 살펴보겠습니다.

GPU 인스턴싱이 있는 경우: 동일한 게임 오브젝트 여러 개를 포함한 간단한 씬, GPU 인스턴싱은 활성화됨
GPU 인스턴싱이 있는 경우: 동일한 게임 오브젝트 여러 개를 포함한 간단한 씬, GPU 인스턴싱은 활성화됨
GPU 인스턴싱이 없는 경우: 동일한 게임 오브젝트 여러 개를 포함한 간단한 씬, GPU 인스턴싱은 비활성화됨
GPU 인스턴싱이 없는 경우: 동일한 게임 오브젝트 여러 개를 포함한 간단한 씬, GPU 인스턴싱은 비활성화됨

GPU 인스턴싱을 사용하는 경우 다음과 같은 제한 사항이 적용됩니다.

  • Unity 는 인스턴싱에 사용할 MeshRenderer 컴포넌트와 Graphics.DrawMesh 호출을 자동으로 선택합니다. SkinnedMeshRenderer는 지원되지 않습니다.

  • Unity 는 단일 GPU 인스턴싱 드로우 콜에서 동일한 메시와 머티리얼을 공유하는 게임 오브젝트만 일괄 처리합니다. 인스턴싱 효율성을 개선하려면 메시와 머티리얼을 조금만 사용해야 합니다. 변화를 주려면 셰이더 스크립트를 수정하여 인스턴스별 데이터를 추가해야 합니다. 이에 대한 자세한 내용은 다음 섹션을 참조하십시오.

Graphics.DrawMeshInstancedGraphics.DrawMeshInstancedIndirect 호출을 사용하여 스크립트에서 GPU 인스턴싱을 수행할 수도 있습니다.

GPU 인스턴싱은 다음 플랫폼과 API 에서 사용할 수 있습니다.

  • Windows DirectX 11DirectX 12

  • Windows, macOS, Linux, iOS 및 Android OpenGL Core 4.1+/ES3.0+

  • macOS 및 iOS Metal

  • Windows 및 Android Vulkan

  • PlayStation 4Xbox One

  • WebGL(WebGL 2.0 API 필요)

인스턴스별 데이터를 추가하는 방법

기본적으로 Unity 는 각각의 인스턴스화된 드로우 콜에 다른 트랜스폼이 있는 게임 오브젝트의 인스턴스만 일괄 처리합니다. 인스턴스화된 게임 오브젝트에 더 많은 변화를 주려면 셰이더를 수정하여 머티리얼 컬러 등과 같이 인스턴스별 프로퍼티를 추가해야 합니다.

아래의 예제는 각 인스턴스에 다른 컬러 값을 가지는 인스턴스화된 셰이더를 생성하는 방법에 대해 설명합니다.

Shader "Custom/InstancedColorSurfaceShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }

    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows
        // Use Shader model 3.0 target
        #pragma target 3.0
        sampler2D _MainTex;
        struct Input {
            float2 uv_MainTex;
        };
        half _Glossiness;
        half _Metallic;
        UNITY_INSTANCING_BUFFER_START(Props)
           UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
        UNITY_INSTANCING_BUFFER_END(Props)
        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

When you declare _Color as an instanced property, Unity will gather _Color values from the MaterialPropertyBlock objects set on GameObjects and put them in a single draw call.

MaterialPropertyBlock props = new MaterialPropertyBlock();
MeshRenderer renderer;

foreach (GameObject obj in objects)
{
   float r = Random.Range(0.0f, 1.0f);
   float g = Random.Range(0.0f, 1.0f);
   float b = Random.Range(0.0f, 1.0f);
   props.SetColor("_Color", new Color(r, g, b));

   renderer = obj.GetComponent<MeshRenderer>();
   renderer.SetPropertyBlock(props);
}

Note that in normal cases (where an instancing shader is not used, or _Color is not a per-instance property), draw call batches are broken due to different values in the MaterialPropertyBlock.

이 변경 내용이 반영되려면 GPU 인스턴싱을 활성화해야 합니다. 이렇게 하려면 프로젝트 창에서 셰이더를 선택하고 인스펙터에서 Enable Instancing 체크박스를 선택해야 합니다.

셰이더 인스펙터 창에 표시된 Enable Instancing 체크박스
셰이더 인스펙터 창에 표시된 Enable Instancing 체크박스

버텍스 및 프래그먼트 셰이더에 인스턴싱을 추가하는 방법

다음 예제에서는 단순한 언릿 셰이더를 사용하여 다른 컬러로 인스턴싱합니다.

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
        }
    }
}

셰이더 수정

추가 기능
#pragma multi_compile_instancing 이 옵션을 사용하여 인스턴싱 배리언트를 생성할 수 있습니다. 표면 셰이더에는 필요하지 않습니다.
UNITY_VERTEX_INPUT_INSTANCE_ID 버텍스 셰이더 입출력 구조에서 이 옵션을 사용하여 인스턴스 ID 를 정의할 수 있습니다. 자세한 내용은 SV_InstanceID를 참조하십시오.
UNITY_INSTANCING_BUFFER_START(name) / UNITY_INSTANCING_BUFFER_END(name) 모든 인스턴스별 프로퍼티는 특별히 명명된 상수 버퍼에 정의되어야 합니다. 이 매크로 페어를 사용하여 각 인스턴스에 고유하게 만들 프로퍼티를 래핑해야 합니다.
UNITY_DEFINE_INSTANCED_PROP(float4, _Color) 이 옵션을 사용하여 인스턴스별 셰이더 프로퍼티를 타입이나 이름으로 정의할 수 있습니다. 이 예제에서 _Color 프로퍼티는 고유합니다.
UNITY_SETUP_INSTANCE_ID(v); 이 옵션을 사용하여 인스턴스 ID 가 셰이더 함수에 액세스할 수 있게 만들 수 있습니다. 버텍스 셰이더의 맨 처음에 사용해야 하며 프래그먼트 셰이더에서는 선택 사항입니다.
UNITY_TRANSFER_INSTANCE_ID(v, o); 이 옵션을 사용하여 버텍스 셰이더의 입력 구조에서 출력 구조로 인스턴스 ID 를 복사할 수 있습니다. 이는 프래그먼트 셰이더의 인스턴스별 데이터에 액세스해야 하는 경우에만 필요합니다.
UNITY_ACCESS_INSTANCED_PROP(arrayName, color) Use this to access a per-instance Shader property declared in an instancing constant buffer. It uses an instance ID to index into the instance data array. The arrayName in the macro must match the one in UNITY_INSTANCING_BUFFER_END(name) macro.

참고:

  • 여러 개의 인스턴스별 프로퍼티를 사용하는 경우 MaterialPropertyBlocks에 모든 프로퍼티를 채울 필요가 없습니다.

  • 인스턴스 하나에 프로퍼티가 없는 경우 Unity 는 참조된 머티리얼에서 기본값을 임포트합니다. 머티리얼에 지정된 프로퍼티의 기본값이 없는 경우 Unity 는 이 값을 0 으로 설정합니다. 인스턴스를 비활성화하기 때문에 MaterialPropertyBlock에 비인스턴스 프로퍼티를 두면 안 됩니다. 대신 다른 머티리얼을 만들어야 합니다.

고급 GPU 인스턴싱 팁

배칭 우선 순위

일괄 작업에서 Unity 는 인스턴싱보다 정적 배칭을 우선시합니다. 게임 오브젝트에 정적 배칭이 설정되어 있는 경우 해당 게임 오브젝트의 인스턴싱은 비활성화 됩니다. 이런 경우 인스펙터 창에 정적 배칭을 비활성화하라는 경고 메시지가 표시됩니다. 이는 Player Settings(Edit > Project Settings > Player)에서 Other Settings를 열고 Rendering 섹션 아래에 있는 Static Batching 체크박스를 선택 해제해야 합니다.

Unity 는 동적 배칭보다 인스턴싱을 우선시합니다. Unity 가 메시를 인스턴스할 수 있는 경우 해당 메시에 대한 동적 배칭은 비활성화됩니다.

Graphics.DrawMeshInstanced

일부 요인으로 인해 게임 오브젝트가 자동으로 일괄 인스턴스화되지 않습니다. 이러한 요인으로는 머티리얼의 변경, 뎁스 정렬 등이 있습니다. Graphics.DrawMeshInstanced를 사용하면 일괄 인스턴스화가 가능하여 Unity 가 GPU 인스턴싱을 사용하는 이러한 오브젝트를 드로우할 수 있습니다. 이 함수는 Graphics.DrawMesh와 마찬가지로 불필요한 게임 오브젝트를 생성하지 않고 하나의 프레임을 위한 메시를 드로우합니다.

Graphics.DrawMeshInstancedIndirect

스크립트에 DrawMeshInstancedIndirect를 사용하면 인스턴스 수를 포함한 인스턴싱 드로우 콜의 파라미터를 컴퓨트 버퍼에서 읽어냅니다. 이 함수는 GPU 로부터 모든 인스턴스 데이터를 채우고, CPU 가 드로우할 인스턴스 수를 모를 경우 유용합니다(예를 들어 GPU 컬링 실행 시). 자세한 내용 및 코드 예제는 Graphics.DrawMeshInstancedIndirect에 대한 API 문서를 참조하십시오.

Global Illumination support

Since Unity 2018.1, Global Illumination (GI) rendering is supported by GPU Instancing in the form of light probes, occlusion probes (in Shadowmask mode) and lightmap STs. Standard shaders and surface shaders have GI support automatically enabled.

Dynamic renderers affected by light probes and occlusion probes baked in the scene, and static renderers baked to the same lightmap texture, can be automatically batched together using GPU Instancing by Forward and Deferred render loop.

For Graphics.DrawMeshInstanced, you can enable light probe and occlusion probe rendering by setting the LightProbeUsage argument to CustomProvided and providing a MaterialPropertyBlock with probe data copied in. See API documentation on LightProbes.CalculateInterpolatedLightAndOcclusionProbes for a detailed explanation and code examples.

Global Illumination and GPU Instancing

GPU Instancing supports Global Illumination (GI) rendering in Unity. Each GPU instance can support GI coming from either different Light Probes, one lightmap (but multiple atlas regions in that lightmap), or one Light Probe Proxy Volume component (baked for the space volume containing all the instances). Standard shaders and surface shaders come with this support enabled.

You can use GPU Instancing to automatically batch dynamic Mesh Renderers affected by baked Light Probes (including their occlusion data), or static Mesh Renderers baked to the same lightmap Texture, via a Forward and Deferred render loop. See documentation on the Rendering pipeline for more information.

For Graphics.DrawMeshInstanced, you can enable the rendering of Light Probes (including their occlusion data) by setting the LightProbeUsage argument to CustomProvided and providing a MaterialPropertyBlock with probe data copied in. See API documentation on LightProbes.CalculateInterpolatedLightAndOcclusionProbes for a detailed explanation and code examples.

Alternatively, you can pass an LPPV component reference and LightProbeUsage.UseProxyVolume to Graphics.DrawMeshInstanced. When you do this, all instances sample the volume for the L0 and L1 bands of the Light Probe data. Use MaterialPropertyBlock if you want to supplement L2 data and occlusion data. For more information, see Light Probes: Technical Information.

Shader warming-up

Since Unity 2017.3, you need to warm up shaders to use instancing on OpenGL if you want absolutely smooth rendering when the shader renders for the first time. If you warm up shaders for instancing on a platform that doesn’t require shader warm up, nothing will happen.

See ShaderVariantCollection.WarmUp and Shader.WarmupAllShaders for more information.

#pragma instancing_options

#pragma instancing_options 지시문에서는 다음의 스위치를 사용할 수 있습니다.

스위치 기능
forcemaxcount:batchSize and maxcount:batchSize On most platforms, Unity automatically calculates the instancing data array size by dividing the maximum constant buffer size on the target device with the size of the structure containing all per-instance properties. Generally you don’t need to worry about the batch size. However, on some platforms (Vulkan, Xbox One and Switch), a fixed array size is still required. You can specify the batch size for those platforms by using maxcount option. The option is completely ignored on the other platforms. If you really want to force a batch size for all platforms, use forcemaxcount (for example, when you know you will only issue draws with 256 instanced sprites via DrawMeshInstanced). The default value for the two options is 500.
assumeuniformscaling Use this to instruct Unity to assume that all the instances have uniform scalings (the same scale for all X, Y and Z axes).
nolodfade Use this to prevent Unity from applying GPU Instancing to LOD fade values.
nolightprobe Use this to prevent Unity from applying GPU Instancing to Light Probe values (including their occlusion data). This is useful for performance if you are absolutely sure that there are no GameObjects using both GPU Instancing and Light Probes.
nolightmap Use this to prevent Unity from applying GPU Instancing to Lightmap ST (atlas information) values. This is useful for performance if you are absolutely sure that there are no GameObjects using both GPU Instancing and lightmaps.
procedural:FunctionName Use this to instruct Unity to generate an additional variant for use with Graphics.DrawMeshInstancedIndirect.
At the beginning of the vertex Shader stage, Unity calls the function specified after the colon. To set up the instance data manually, add per-instance data to this function in the same way you would normally add per-instance data to a Shader. Unity also calls this function at the beginning of a fragment Shader if any of the fetched instance properties are included in the fragment Shader.

UnityObjectToClipPos

셰이더 스크립트를 작성할 때는 mul(UNITY_MATRIX_MVP,v.vertex) 대신 항상 UnityObjectToClipPos(v.vertex)를 사용해야 합니다.

인스턴스화된 셰이더에서도 UNITY_MATRIX_MVP를 정상적으로 계속 사용할 수는 있지만 UnityObjectToClipPos는 오브젝트 공간에서 클립 공간으로 버텍스 포지션을 변환하는 가장 효율적인 방법입니다. 또한 Unity 는 프로젝트의 모든 셰이더를 스캔하는 셰이더 업그레이더를 구현하고 mul(UNITY_MATRIX_MVP, v)의 모든 발생 빈도를 UnityObjectToClipPos(v)로 자동으로 대체합니다.

UNITY_MATRIX_MVP(UNITY_MATRIX_MV와 함께)가 사용되는 장소가 여전히 있는 경우 Window > Console 메뉴의 콘솔 창은 성능 경고를 표시합니다.

추가 참고 사항

  • 표면 셰이더에는 #pragma 표면 지시문에서 noinstancing을 지정하지 않는 한 기본적으로 생성된 인스턴싱 배리언트가 있습니다. 스탠다드 셰이더와 스탠다드 스페큘러 셰이더는 이미 수정되어 인스턴싱을 지원할 수 있지만 인스턴스별 프로퍼티는 트랜스폼 이외에는 정의되지 않습니다. Unity 는 표면 셰이더에서 #pragma multi_compile_instancing 사용을 무시합니다.

  • 씬의 게임 오브젝트에서 GPU 인스턴싱을 활성화하지 않으면 Unity 가 인스턴싱 배리언트를 스트리핑합니다. 스트리핑 동작을 오버라이드하려면 Edit > Project Settings > Graphics 메뉴에서 그래픽스 설정을 열고 Shader stripping 섹션으로 이동하여 Instancing Variants를 변경합니다.

  • For Graphics.DrawMeshInstanced, you need to enable GPU Instancing on the Material that the script is passing into this method. However, Graphics.DrawMeshInstancedIndirect does not require you to enable GPU Instancing. The indirect instancing keyword PROCEDURAL_INSTANCING_ON is not affected by stripping.

  • 인스턴스화된 드로우 콜은 프레임 디버거에서 Draw Mesh (instanced)로 표시됩니다.

  • 인스턴스별 프로퍼티를 항상 정의할 필요는 없습니다. 하지만 인스턴스 ID 는 반드시 설정해야 하는데 월드 매트릭스가 올바르게 작동하기 위해 필요하기 때문입니다. 표면 셰이더는 자동으로 인스턴스 ID 를 설정합니다. 커스텀 버텍스와 프래그먼트 셰이더에 대한 인스턴스 ID 를 수동으로 설정해야 합니다. 이렇게 하려면 셰이더 시작 부분에 UNITY_SETUP_INSTANCE_ID를 사용해야 합니다.

  • 포워드 렌더링을 사용하는 경우 Unity 는 다수의 광원에 영향을 받는 오브젝트를 효율적으로 인스턴스화할 수 없습니다. 베이스 패스만 효율적으로 인스턴스화할 수 있으며 추가 패스는 불가능합니다. 조명 패스에 대한 자세한 내용은 포워드 렌더링패스 태그 문서를 참조하십시오.

  • 멀티 패스 셰이더에 두 개 이상의 패스가 있는 경우 첫 번째 패스만 인스턴스화됩니다. 이는 Unity 가 이후 패스를 각 오브젝트와 동시에 렌더링하게 하여 머티리얼을 강제로 변경하기 때문입니다.

  • 위의 예제에서 사용한 모든 셰이더 매크로는 UnityInstancing.cginc에 정의되어 있습니다. 이 파일은 [Unity 설치 폴더]\Editor\Data\CGIncludes에서 찾을 수 있습니다.


  • 2017–10–24 편집 리뷰를 거쳐 페이지 수정됨

  • 인스턴스화 활성화 체크박스 설명, DrawMeshInstancedIndirect, #pragma multi-compile 은 5.6 버전에서 추가됨

  • Shader warm up for GPU instancing added in 2017.3 NewIn20173

  • Global Illumination (GI) support in GPU instancing added in 2018.1 NewIn20181

그래픽스 커맨드 버퍼(Graphics Command Buffers)
스파스 텍스처(Sparse Textures)