본문 바로가기
유니티 엔진

Unity 5.6 Shader Instancing : 유니티 5.6 셰이더 인스턴싱

by 대마왕J 2017. 4. 17.

유니티 5.5에서 인스턴트 셰이더를 생성할 수 있는 메뉴가 생기더니, 5.6 에서는 아예 인스턴싱 기능이 셰이더 기본 기능으로 들어가 버렸네요. 


뭔지 한 번 알아봅시다. 

https://docs.unity3d.com/Manual/GPUInstancing.html


GPU instancing 
GPU 인스턴싱

Introduction

You can use GPU instancing to draw many identical objects with only a few draw calls. There are some restrictions that you need to bear in mind:
당신은 여러 개의 오브젝트들을 단지 몇 개의 드로우콜로 그려버릴 수 있는 GPU 인스턴싱을 사용할 수 있습니다. 그리고 유념해야 하는 몇 가지 내용이 있지요. 

  • Your identical objects need to share the same Mesh and the same Material. You can, however, add per-instance data. See Adding per-instance data below for more information.
    당신의 동일 오브젝트들은 같은 메쉬와 같은 메터리얼을 공유할 필요가 있습니다. 뭐 그럼에도, 당신은 추가적인 개별 인스턴싱 데이터를 가질 수 있다. 아래에 있는 애드 퍼 인스턴스 데이터 정보를 확인해 보세요
  • The MeshRenderer component and Graphics.DrawMesh API are supported.
    메쉬렌더 컴포넌트와 그래픽스.드로우메쉬 API가 지원됩니다. 
  • GPU instancing is available on the following platforms:
    GPU 인스턴싱은 아래 기기에서 지원됩니다. 

    • Windows: DX11 and DX12 with SM 4.0 and above / OpenGL 4.1 and above
    • OS X and Linux: OpenGL 4.1 and above
    • Mobile: OpenGL ES 3.0 and above / Metal
    • PlayStation 4
    • Xbox One
      (주: 오픈지엘ES 3.0 이상을 지원하는군요. 모바일에서도 쓸만할듯)

Adding instancing to your objects

Standard Surface Shader that supports instancing is available in the Unity Editor. Add one to your project by selecting Shader > Standard Surface Shader (Instanced).
셰이더 만들때 셰이더 인스턴스로 만드세요 (주: 5.6 에서는 그냥 통합되었습니다) 

Adding the Standard Instanced Shader
Adding the Standard Instanced Shader

Apply this Shader to your GameObject’s Material. In your Material’s Inspector window, click the Shader drop-down, roll over the Instanced field, and choose your instanced Shader from the list:
방금 만든 셰이더를 찾아서 당신의 오브젝트의 메터리얼로 적용하세요. (찾는 법 설명은 생략) 

Assigning the Standard Instanced Shader to a Material
Assigning the Standard Instanced Shader to a Material

Adding per-instance data

Even though the instanced GameObjects are sharing the same Mesh and Material, you can set Shader properties on a per-object basis using the MaterialPropertyBlock API. In the example below, each GameObject is assigned a random color value using the _Color property:
인스턴싱된 오브젝트들이 같은 메쉬와 메터리얼을 가지고 있지만,  당신은 MaterialPropertyBlock API를 이용하는 것을 기초로 하여 개체별로 셰이더 속성을 설정할 수 있습니다. 아래 예제처럼, 각 오브젝트는 _Color 프로퍼티에 의해 랜덤한 칼라가 적용됩니다. [각주:1]

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

Adding instancing to your own shaders

The following example takes a simple unlit Shader and makes it capable of 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 //인스턴싱이 안되는 GPU를 위한 멀티 컴파일옵션이네요 
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                UNITY_INSTANCE_ID //인스턴스 아이디를 위한 구조체예요. 버텍스용. 
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                UNITY_INSTANCE_ID //인스턴스 아이디를 위한 구조체예요. 픽셀용. 
            };

            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); //버텍스에서 인스턴스 ID를 만들고요 
                UNITY_TRANSFER_INSTANCE_ID (v, o); //그걸 픽셀로 넘겨요 (픽셀로 넘길 필요가 없음 안해도 돼요)

                o.vertex = UnityObjectToClipPos (v.vertex); // 오브젝트 -> 클립 공간으로 넘기는 더 효율적인 함수입니다. 일반도 사용 가능. 
                return o;
            }
           
            fixed4 frag (v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID (i); //인스턴스 ID를 받아오고요 
                return UNITY_ACCESS_INSTANCED_PROP (_Color);
            }
            ENDCG
        }
    }
}

Added code

Addition Function
#pragma multi_compile_instancing

multi_compile_instancing generates a Shader with two variants: one with built-in keyword INSTANCING_ON defined (allowing instancing), the other with nothing defined. This allows the Shader to fall back to a non-instanced version if instancing isn’t supported on the GPU.

일단 이것으로 두 개의 셰이더가 만들어 집니다 : 하나는 인스턴싱_온 이라는 빌트인 키워드가 정의된(인스턴싱이 되는) 셰이더이고, 또 하나는 정의되지 않은 셰이더입니다. 이것은 GPU에서 인스턴싱을 지원하지 않을때 돌아갈 셰이더를 지정해 주는 겁니다.  

UNITY_INSTANCE_ID

This is used in the vertex Shader input/output structure to define an instance ID. See SV_InstanceID for more information.

인스턴스 ID를 정의해 주기 위해서 버텍스 셰이더의 Input / Output 구조로 사용됩니다. SV_InstancedID를 참고하세요

UNITY_INSTANCING_CBUFFER_START(name)
 / UNITY_INSTANCING_CBUFFER_END

Every per-instance property must be defined in a specially named constant buffer. Use this pair of macros to wrap the properties you want to be made unique to each instance.

모든 인스턴스 프로퍼티스는 스페셜 네임드된 콘스탄트 버퍼에 정의되어야만 합니다.
이 두 메크로로 각 인스턴스별로 독립적으로 만들고 싶은 프로퍼티스를 감싸세요. (사이에 넣으세요)

UNITY_DEFINE_INSTANCED_PROP(float4, color)

This defines a per-instance Shader property with a type and a name. In this example, the _color property is unique.

이것은 인스턴스별 셰이더 프로퍼티를 타입과 이름 형식으로 정의합니다. 예를 들어, _Color 프로퍼티스는 유니크합니다.

UNITY_SETUP_INSTANCE_ID(v);

This makes 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.

셰이더 펑션에 접근가능한 인스턴스 ID를 만듭니다. 이것은 버텍스 셰이더에서 매우 초반에 사용되어야만 하고, 프레그먼트 셰이더에서는 옵션입니다.

 

UNITY_TRANSFER_INSTANCE_ID(v, o);

This copies 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.

 

이것은 버텍스 셰이더에서 인풋 스트럭쳐에서 인스턴스 ID를 아웃풋 스트럭쳐에 카피해 줍니다. 이것은 각 인스턴스 데이터가 프레그먼트 셰이더에서 접근 해야 할 때에만 사용합니다. [각주:2]

UNITY_ACCESS_INSTANCED_PROP(color)

This accesses a per-instance Shader property. It uses an instance ID to index into the instance data array.

이것은 각 인스턴스된 셰이더 프로퍼티스에 접근합니다. 이것은 인스턴스 데이터 배열 안에 인덱스된 인스턴스 ID 에 접근합니다.

Note: As long as Material properties are instanced, Renderers can always be rendered instanced, even if you put different instanced properties into different Renderers. Normal (non-instanced) properties cannot be batched, so do not put them in the MaterialPropertyBlock. Instead, create different Materials for them.

참고: 메터리얼의 프로퍼티스가 복제된 만큼, 렌더러는 언제나 인스턴스로 렌더링 될 수 있습니다. 심지어 당신이 다른 렌더러에 있는 다른 인스턴스 프로퍼티를 넣더라도 말입니다. 인스턴스되지 않은 일반 프로퍼티스는 배치될 수 없고, 그래서 그들을 MaterialPropertyBlock. 에 넣지 마십시오. 대신, 그들을 위해서 다른 메터리얼을 만드십시오.

 

A note regarding UnityObjectToClipPos

UnityObjectToClipPos 에 대한 주의

UnityObjectToClipPos(v.vertex) is always preferred where mul(UNITY_MATRIX_MVP,v.vertex) would otherwise be used. While you can continue to use UNITY_MATRIX_MVPas normal in instanced Shaders, UnityObjectToClipPos is the most efficient way of transforming vertex positions from object space into clip space.

UnityObjectToClipPos(v.vertex)가 언제나 우선 사용이 되고  mul(UNITY_MATRIX_MVP,v.vertex)  가 그 대신 사용됩니다.  당신이 인스턴트 셰이더에서 UNITY_MATRIX_MVP  를 기본으로 계속 사용할 수 있지만, UnityObjectToClipPos    가 오브젝트 스페이스에서 클립 스페이스로 버텍스 포지션을 변환시키는 보다 효율적인 방법입니다.     

In instanced Shaders, UNITY_MATRIX_MVP (among other built-in matrices) is transparently modified to include an extra matrix multiply. Specifically, it is expanded to mul(UNITY_MATRIX_VP, unity_ObjectToWorld)unity_ObjectToWorld is expanded to unity_ObjectToWorldArray[unity_InstanceID]).

인스턴스 셰이더에서, UNITY_MATRIX_MVP   (다른 기본 내장 메트릭스들 중에서) 는  추가적인 행렬 곱셈을 포함하도록 확실히 변경됩니다. 구체적으로, 이것은  mul(UNITY_MATRIX_VP, unity_ObjectToWorld). 에서  unity_ObjectToWorld 는 unity_ObjectToWorldArray[unity_InstanceID]).   으로 확장된다. [각주:3]

UnityObjectToClipPos is optimized to perform two matrix-vector multiplications simultaneously, and is therefore more efficient than performing the
multiplication manually, because the Shader compiler does not automatically perform this optimization.

 UnityObjectToClipPos  은 두 개의 메트릭스 - 벡터 곱셈을 동시에 실행하도록 최적화 되어 있기 때문에, 셰이더 컴파일러가 자동적으로 이 최적화를 행하지 않는 수작업 곱하는 것보다 더 효율적이다.

Modifying multi-pass Shaders to work with instancing
인스턴싱을 멀티 패스 셰이더에서 작업하기

For vertex and fragment Shaders, Unity needs to change the way vertex transformations are calculated in multi-pass scenarios (for example, in the ForwardAdd pass) to avoid z-fighting artifacts against the base/first passes due to floating point errors in matrix calculation. To do this, add #pragma force_concat_matrix to the Shader.
버텍스 & 프레그먼트 셰이더에서, 유니티는 멀티 패스 시나리오에서 버텍스 변환 계산 방법을 바꾸는 것이 필요합니다. (예를 들어, 포워드 애드 패스에서 말입니다) [각주:4]
메크릭스 연산에서 플로팅 포인트의 에러로 인해 발생하는 z 파이팅 아티펙트를 피하기 위해서 말이죠.
그래서, 이렇게 하려면 #pragma force_concat_matrix   를 셰이더에 추가해야 합니다.[각주:5]

Specifically, the vertex transformation in the ForwardAdd pass is calculated by multiplying the M (model) matrix with the VP (view and projection) matrix instead of using a CPU-precomputed MVP matrix.

구체적으로, 포워드 애드 패스에서 버텍스 변환은 VP(뷰 / 투영) 과 M(모델) 메트릭스를 곱하는 대신 미리 계산된 MVP 행렬을 이용하여 계산됩니다.

This is not necessary for surface Shaders, because the correct calculation is automatically substituted.

이것은 서피스 셰이더에서는 필요없습니다, 왜냐면 정확한 계산이 자동적으로 치환되기 때문이지요

Batching priority
배치 우선 순위

Static batching takes priority over instancing. If a GameObject is marked for static batching and is successfully batched, instancing is disabled even if its Renderer uses an instancing Shader. When this happens, a warning box appears in the Inspector suggesting that the Static Batching flag be unchecked in the Player Settings.

스테틱(정적) 배치는 인스턴싱보다 우선됩니다. 게임 오브젝트가 스테틱 배칭이 체크되어 성공적으로 배칭이 되었다면, 인스턴싱은 렌더러가 인스턴싱 셰이더를 사용한다 하더라도 비활성화 됩니다. (!!!) 이런 일이 일어나면, 인스펙터에 '플레이어 셋팅에서 스테틱 배칭을 체크 꺼라' 라고 권장해주는 경고 박스가 나타나게 됩니다. [각주:6]

Instancing takes priority over dynamic batching. If Meshes can be instanced, dynamic batching is disabled.
인스턴싱은 다이나믹 배칭보다 우선됩니다. 만약 메쉬가 인스턴스화 되면, 다이나믹 배칭은 꺼집니다.

Further notes
미래에 할거  

  • Instanced draw calls appear in the Frame Debugger as Draw Mesh (instanced).
  • 프레임 디버거에 인스턴싱된 드로우콜이 나타나게 됩니다. Draw Mesh(instanced) 로.
  • When writing or modifying your own Shaders, don’t forget to instance shadows, too. For a surface Shader, use the addshadow option to force the generation of an instanced shadow pass.
  • You don’t have to define per-instance properties, but setting up an instance ID is mandatory, because world matrices need it to work correctly.

스스로의 셰이더를 만들거나 수정할 때, 인스턴스 그림자를 잊지 마세요, 서피스 셰이더에서, addshadow 옵션으로 인스턴스 그림자 패스를 만들 수 있습니다.[각주:7]

 

  • When using forward rendering, objects affected by multiple lights can’t be instanced efficiently. Only the base pass can make effective use of instancing, not the add passes.
  • 포워드 렌더링을 사용할때, 오브젝트에 여러 개의 라이팅이 적용되면 효과적으로 인스턴싱 될 수 없습니다. [각주:8]  베이스 패스[각주:9]만이 효과적으로 인스턴싱되고, 애드 패스는 안됩니다. [각주:10] 

 

  • Objects that use lightmaps, or are affected by different light or reflection probes, can’t be instanced.
  • 오브젝트가 라이트맵을 사용하거나, 다른 라이트나 리플렉션 프로브를 받을 때에는 인스턴스 될 수 없습니다. [각주:11]

 

  • 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.
  • 투 패스 이상의 멀티 패스 셰이더를 사용할 때, 첫 번째 패스만 인스턴싱 될 수 있습니다. 이것은 유니티가 나중 패스에 대해서는 각 오브젝트별로 강제로 함께 렌더링 하기 때문입니다. [각주:12]
  • D3D constant buffers have a maximum size of 64KB. For OpenGL, it’s usually 16KB. You will reach this limit if you try to define too many per-instance properties. The Shaders may fail to compile or, even worse, the Shader compiler might crash. To work around this, you have to balance between the size of the batch and the size of per-instance properties.
  • D3D의 상수 버퍼의 최대 사이즈는 64KB 입니다. 오픈지엘에서는 보통 16KB 입니다. 만일 당신이 너무 많은 인스턴스 프로퍼티스를 정의한다면 이 한계가 닥칠 것입니다. 셰이더는 컴파일을 실패하거나, 더 최악으로는 셰이더 컴파일러가 크러쉬 날겁니다. 이 문제를 해결하려면, 배칭의 사이즈와 개별 인스턴스 프로퍼티스의 사이즈의 밸런스를 조절해야 합니다. [각주:13]
  • Defining UNITY_MAX_INSTANCE_COUNT with an integer before including any .cginc file allows you to limit the maximum number of instances an instanced draw call can draw. This allows for more properties per instance in the instance constant buffer. You can achieve the same result when using a surface Shader with #pragma instancing_options maxcount:number. The default value of this max instance count is 500. For OpenGL, the actual value is one quarter of the value you specify, so 125 by default.
      UNITY_MAX_INSTANCE_COUNT 로 어떠한 .cginc파일을 인클루드 하기 전에 정수를 정의해서 인스턴스 드로우 콜이 그리는 인스턴스 갯수를 제한할 수 있습니다.  이것은 인스턴스 정수 버퍼에서 더 많은 프로퍼티스가 가능하게 해 줍니다.[각주:14] 당신은 서피스 셰이더에서도 #pragma instancing_options maxcount:number.  로 같은 결과를 낼 수 있습니다.   기본값으로 지정되어 있는 최대 인스턴스 갯수는 500개입니다. 오픈지엘에서는, 실제 값이 정의된 값에서 1/4 이기 때문에, 125개가 기본입니다. [각주:15]
  • All the Shader macros used in the above example are defined in UnityInstancing.cginc. Find this file in [Unity folder]\Editor\Data\CGIncludes.
  • 위 예제에서 사용된 모든 셰이더 메크로는 UnityInstancing.cginc에 정의되어 있다. 유니티 폴더에서 \Editor\Data\CGIncludes.  를 찾아보세요.


  1. 값이 모두 다른데도 인스턴팅이 되어서 드로우콜이 줄어든다는 말입니다. [본문으로]
  2. 픽셀셰이더에서 인스턴스 데이터를 사용해야 할 경우에, 픽셀셰이더에 ID를 넘겨줍니다 v 와 o 는 각각 입력, 출력을 나타냅니다. [본문으로]
  3. 오브젝트투클립포스 함수를 쓰지 않으면, 월드 변환때 어레이로 확장되는군요 흐음. [본문으로]
  4. 유니티는 포워드 렌더링 상태에서 조명이 여러 개 일때 추가적인 렌더링 패스를 작성해야 합니다. [본문으로]
  5. 와 쌩 귀찮네 [본문으로]
  6. 그냥 간편히 스테틱 배칭이 돌아가면 작동 안되요 라는 거군요 [본문으로]
  7. addpass는 원래 서피스셰이더에서 그림자 패스까지 영향을 끼치도록 만들어 주는 기능 [본문으로]
  8. 포워드 렌더링때는 라이트 갯수와 종류에 따라 셰이더가 마구 분화되거든요 [본문으로]
  9. 포워드에서의 첫 번째 조명의 퍼 픽셀 패스 [본문으로]
  10. 포워드에서는 라이팅 하나만 된다는 거군요. 그럼 디퍼드에서는 몇 개라도 잘 되겠네? [본문으로]
  11. 뭐야 이거 등신같네 [본문으로]
  12. 버텍스라이트 4개를 한 큐에 렌더링 하는거 말하는 것인듯? [본문으로]
  13. 그냥 많이 만들지 말란 말이잖아 [본문으로]
  14. 인스턴스가 제한되니까 프로퍼티스가 늘어나겠죠. 이것이 밸런스 조절. [본문으로]
  15. 다이렉트 X의 정수 버퍼가 64K 인데 오픈지엘은 16K 라서 1/4 이라는 것 같습니다. [본문으로]
반응형

댓글