그래픽스 커맨드 버퍼(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_CBUFFER_START(Props)
           UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
        UNITY_INSTANCING_CBUFFER_END
        void surf (Input IN, inout SurfaceOutputStandard o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(_Color);
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

_Color를 인스턴스화된 프로퍼티로 선언하는 경우 Unity 는 메시와 머티리얼을 공유하는 모든 _Color 게임 오브젝트를 하나의 드로우 콜에 포함합니다.

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

For these changes to take effect, you must enable GPU Instancing. To do this, select your Shader in the Project window, and in the Inspector, tick the Enable Instancing checkbox.

The Enable Instancing checkbox as shown in the Shader Inspector window
The Enable Instancing checkbox as shown in the Shader Inspector window

Adding instancing to vertex and fragment Shaders

The following example takes a simple unlit Shader and makes it capable of instancing with different colors:

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_CBUFFER_START(MyProperties)
                UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_CBUFFER_END

            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(_Color);
            }
            ENDCG
        }
    }
}

Shader modifications

덧셈 기능
#pragma multi_compile_instancing Use this to instruct Unity to generate instancing variants. It is not necessary for surface Shaders.
UNITY_VERTEX_INPUT_INSTANCE_ID Use this in the vertex Shader input/output structure to define an instance ID. See SV_InstanceID for more information.
UNITY_INSTANCING_CBUFFER_START(name) / UNITY_INSTANCING_CBUFFER_END 모든 인스턴스별 프로퍼티는 특별히 명명된 상수 버퍼에 정의되어야 합니다. 이 매크로 페어를 사용하여 각 인스턴스에 고유하게 만들 프로퍼티를 래핑해야 합니다.
UNITY_DEFINE_INSTANCED_PROP(float4, _Color) Use this to define a per-instance Shader property with a type and a name. In this example, the _Color property is unique.
UNITY_SETUP_INSTANCE_ID(v); Use this to make the instance ID accessible to Shader functions. It must be used at the very beginning of a vertex Shader, and is optional for fragment Shaders.
UNITY_TRANSFER_INSTANCE_ID(v, o); Use this to copy the instance ID from the input structure to the output structure in the vertex Shader. This is only necessary if you need to access per-instance data in the fragment Shader.
UNITY_ACCESS_INSTANCED_PROP(color) 이 옵션을 사용하여 인스턴스별 Shader 프로퍼티에 액세스할 수 있습니다. 인스턴스 ID 를 사용하여 인스턴스 데이터 배열을 인덱싱합니다.

참고:

  • When using multiple per-instance properties, you don’t need to fill all of them in MaterialPropertyBlocks.

  • If one instance lacks the property, Unity takes the default value from the referenced Material. If the material does not have a default value for the specified property, Unity sets the value to 0. Do not put non-instanced properties in the MaterialPropertyBlock, because this disables instancing. Instead, create different Materials for them.

Advanced GPU instancing tips

Batching priority

When batching, Unity prioritizes Static batching over instancing. If you mark one of your GameObjects for static batching, and Unity successfully batches it, Unity disables instancing on that GameObject, even if its Renderer uses an instancing Shader. When this happens, the Inspector window displays a warning message suggesting that you disable Static Batching. To do this, open the Player Settings (Edit > Project Settings > Player), open Other Settings, and under the Rendering section, untick the Static Batching checkbox.

Unity prioritizes instancing over dynamic batching. If Unity can instance a Mesh, it disables dynamic batching for that Mesh.

Graphics.DrawMeshInstanced

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

셰이더 스크립트에서 지정한 maxcount(디폴트 500)를 초과하는 인스턴스 배칭은 지정해서는 안 됩니다. OpenGL 또는 Metal에서 그래픽스 툴을 사용하는 경우 Unity 는 maxcount를 4 로 나눈 뒤, 그 값을 지정할 수 있는 최대 배칭 수로 설정합니다. 인스턴스의 개수를 무작위로 설정할 때 추천되는 방법은 미리 할당된 500 의 크기를 가지는 배열 풀을 유지하고(필요한 경우 MaterialPropertyBlocks도 포함) 이 배열을 최대한 많이 재사용하는 것입니다. 오브젝트 풀링에 대한 자세한 내용은 자동 메모리 관리 문서를 참조하십시오.

Graphics.DrawMeshInstancedIndirect

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

pragma instancing_options

The #pragma instancing_options directive can use the following switches:

Switch 기능
maxcount: batchSize 한 번의 인스턴스 드로우 콜에서 그릴 최대 인스턴스 수를 지정하려면 이 옵션을 사용해야 합니다. 기본적으로 이 값은 500 입니다. OpenGL 또는 Metal 을 사용하는 경우 Unity 는 maxcount를 4 로 나눈 값을 최대 배치 수로 사용합니다. 그리려는 인스턴스의 양에 맞게 이 값을 가능한 작게 만들어야 합니다. 예를 들어, 최대 1,000 개의 인스턴스를 그리려면 셰이더 스크립트에 maxcount: 1000을 추가합니다. 값이 클수록 셰이더 컴파일 시간이 길어지고 GPU 성능이 저하될 수 있습니다.
force_same_maxcount_for_gl Use this to force Unity to stop dividing the maxcount by 4 on graphics tools from OpenGL or Metal.
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).
lodfade Use this to make LOD fade values instanceable. This is useful for SpeedTree and other LOD techniques that use the LOD fading feature.
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

When writing shader scripts, always use UnityObjectToClipPos(v.vertex) instead of mul(UNITY_MATRIX_MVP,v.vertex).

While you can continue to use UNITY_MATRIX_MVP as normal in instanced Shaders, UnityObjectToClipPos is the most efficient way to transform vertex positions from object space into clip space. Unity also implements a Shader upgrader that scans all your Shaders in the project, and automatically replaces any occurrence of mul(UNITY_MATRIX_MVP, v) with UnityObjectToClipPos(v).

The console window (menu: Window > Console) displays performance warnings if there are still places where UNITY_MATRIX_MVP (along with UNITY_MATRIX_MV) is used.

Further notes

  • Surface Shaders have instancing variants generated by default, unless you specify noinstancing in the #pragma surface directive. Standard and StandardSpecular Shaders are already modified to have instancing support, but with no per-instance properties defined other than the transforms. Unity ignores uses of #pragma multi_compile_instancing in a surface Shader.

  • Unity strips instancing variants if GPU Instancing is not enabled on any GameObject in the Scene. To override the stripping behaviour, open the Graphics Settings (menu: Edit > Project Settings > Graphics), navigate to the Shader stripping section and change the Instancing Variants.

  • For Graphics.DrawMeshInstanced, you need to enable GPU Instancing on the Material that is being passed 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.

  • Instanced draw calls appear in the Frame Debugger as Draw Mesh (instanced).

  • You don’t always need to define per-instance properties. However, setting up an instance ID is mandatory, because world matrices need it to function correctly. Surface shaders automatically set up an instance ID. You must set up the instance ID for Custom Vertex and Fragment shaders manually. To do this, use UNITY_SETUP_INSTANCE_ID at the beginning of the Shader.

  • When using forward rendering, Unity cannot efficiently instance objects that are affected by multiple lights. Only the base pass can make effective use of instancing, not the added passes. For more information about lighting passes, see documentation on Forward Rendering and Pass Tags

  • Objects that use lightmaps, or are affected by different light or Reflection Probes, can’t be instanced.

  • If you have more than two passes for multi-pass Shaders, only the first passes can be instanced. This is because Unity forces the later passes to be rendered together for each object, forcing Material changes.

  • All the Shader macros used in the above examples are defined in UnityInstancing.cginc. Find this file in the following directory: [Unity installation folder]\Editor\Data\CGIncludes.


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

  • Enable instancing checkbox guidance, DrawMeshInstancedIndirect, #pragma multi-compile added in 5.6

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