GPU 인스턴싱을 사용하면 적은 수의 드로우 콜을 사용하여 동일한 메시의 여러 복제본을 한 번에 그리거나 렌더링할 수 있습니다. 씬에서 반복적으로 나타나는 건물, 나무, 풀 등의 오브젝트를 그릴 때 유용합니다.
GPU 인스턴싱은 각 드로우 콜마다 동일한 메시만 렌더링하지만 각각의 인스턴스에는 다른 파라미터(예: 컬러 또는 스케일)를 포함할 수 있어 변화를 주거나 반복되는 외형을 완화할 수 있습니다.
GPU 인스턴싱을 사용하여 씬마다 사용되는 드로우 콜의 수를 줄일 수 있습니다. 이를 통해 프로젝트의 렌더링 성능이 상당히 개선됩니다.
머티리얼에서 GPU 인스턴싱을 활성화하려면 프로젝트 창에서 머티리얼을 선택한 후 인스펙터에서 Enable Instancing 체크박스를 선택합니다.
Unity에서는 머티리얼 셰이더가 GPU 인스턴싱을 지원하는 경우에만 이 체크박스가 표시됩니다. 스탠다드 셰이더, 스탠다드 스페큘러 셰이더, 모든 표면 셰이더가 여기에 해당합니다. 자세한 내용은 스탠다드 셰이더 문서를 참조하십시오.
아래의 스크린샷은 게임 오브젝트가 여러 개 있는 동일한 씬을 보여주고 있습니다. 위의 이미지는 GPU 인스턴싱이 활성화된 상태이고, 아래의 이미지는 그렇지 않습니다. FPS, Batches, Saved by batching 항목의 차이를 살펴보겠습니다.
GPU 인스턴싱을 사용하는 경우 다음과 같은 제한 사항이 적용됩니다.
Unity는 인스턴싱에 사용할 MeshRenderer 컴포넌트와 Graphics.DrawMesh
호출을 자동으로 선택합니다. SkinnedMeshRenderer는 지원되지 않습니다.
Unity는 단일 GPU 인스턴싱 드로우 콜에서 동일한 메시와 머티리얼을 공유하는 게임 오브젝트만 일괄 처리합니다. 인스턴싱 효율성을 개선하려면 메시와 머티리얼을 조금만 사용해야 합니다. 변화를 주려면 셰이더 스크립트를 수정하여 인스턴스별 데이터를 추가해야 합니다. 이에 대한 자세한 내용은 다음 섹션을 참조하십시오.
Graphics.DrawMeshInstanced와 Graphics.DrawMeshInstancedIndirect 호출을 사용하여 스크립트에서 GPU 인스턴싱을 수행할 수도 있습니다.
GPU 인스턴싱은 다음 플랫폼과 API에서 사용할 수 있습니다.
Windows DirectX 11 및 DirectX 12
Windows, macOS, Linux, iOS 및 Android OpenGL Core 4.1+/ES3.0+
macOS 및 iOS Metal
Windows 및 Android Vulkan
PlayStation 4 및 Xbox 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"
}
_Color
를 인스턴스화된 프로퍼티로 선언하면 Unity는 게임 오브젝트에 설정된 MaterialPropertyBlock 오브젝트에서 _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);
}
(인스턴스화 셰이더가 사용되지 않거나 _Color
가 인스턴스당 프로퍼티가 아닌) 일반적인 경우에는 MaterialPropertyBlock의 값이 다르기 때문에 드로우 콜 배치가 손상됩니다.
이 변경 내용이 반영되려면 GPU 인스턴싱을 활성화해야 합니다. 이렇게 하려면 프로젝트 창에서 셰이더를 선택하고 인스펙터에서 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) |
인스턴싱 상수 버퍼에서 선언된 인스턴스당 셰이더 프로퍼티에 액세스하는 데 사용합니다. 인스턴스 ID를 사용하여 인스턴스 데이터 배열에 인덱싱합니다. 매크로의 arrayName 은 UNITY_INSTANCING_BUFFER_END(name) 매크로의 이름과 일치해야 합니다. |
참고:
여러 개의 인스턴스별 프로퍼티를 사용하는 경우 MaterialPropertyBlocks
에 모든 프로퍼티를 채울 필요가 없습니다.
인스턴스 하나에 프로퍼티가 없는 경우 Unity는 참조된 머티리얼에서 기본값을 임포트합니다. 머티리얼에 지정된 프로퍼티의 기본값이 없는 경우 Unity는 이 값을 0으로 설정합니다. 인스턴스를 비활성화하기 때문에 MaterialPropertyBlock
에 비인스턴스 프로퍼티를 두면 안 됩니다. 대신 다른 머티리얼을 만들어야 합니다.
일괄 작업에서 Unity는 인스턴싱보다 정적 배칭을 우선시합니다. 게임 오브젝트에 정적 배칭이 설정되어 있는 경우 해당 게임 오브젝트의 인스턴싱은 비활성화 됩니다. 이런 경우 인스펙터 창에 정적 배칭을 비활성화하라는 경고 메시지가 표시됩니다. 이는 Player Settings(Edit > Project Settings > Player)에서 Other Settings__를 열고 Rendering__ 섹션 아래에 있는 Static Batching 체크박스를 선택 해제해야 합니다.
Unity는 동적 배칭보다 인스턴싱을 우선시합니다. Unity가 메시를 인스턴스할 수 있는 경우 해당 메시에 대한 동적 배칭은 비활성화됩니다.
일부 요인으로 인해 게임 오브젝트가 자동으로 일괄 인스턴스화되지 않습니다. 이러한 요인으로는 머티리얼의 변경, 뎁스 정렬 등이 있습니다. Graphics.DrawMeshInstanced를 사용하면 일괄 인스턴스화가 가능하여 Unity가 GPU 인스턴싱을 사용하는 이러한 오브젝트를 드로우할 수 있습니다. 이 함수는 Graphics.DrawMesh와 마찬가지로 불필요한 게임 오브젝트를 생성하지 않고 하나의 프레임을 위한 메시를 드로우합니다.
스크립트에 DrawMeshInstancedIndirect
를 사용하면 인스턴스 수를 포함한 인스턴싱 드로우 콜의 파라미터를 컴퓨트 버퍼에서 읽어냅니다. 이 함수는 GPU로부터 모든 인스턴스 데이터를 채우고, CPU가 드로우할 인스턴스 수를 모를 경우 유용합니다(예를 들어 GPU 컬링 실행 시). 자세한 내용 및 코드 예제는 Graphics.DrawMeshInstancedIndirect에 대한 API 문서를 참조하십시오.
Unity 2017.3부터는 셰이더가 처음 렌더될 때 완전히 부드러운 렌더링을 원할 경우 OpenGL에 인스턴스화를 사용하려면 셰이더를 웜업해야 합니다. 셰이더 웜업이 필요하지 않은 플랫폼에서 셰이더를 인스턴스화하기 위해 웜업하면 아무 작업도 수행되지 않습니다.
자세한 내용은 ShaderVariantCollection.WarmUp과 Shader.WarmupAllShaders를 참조하십시오.
#pragma instancing_options
지시문에서는 다음의 스위치를 사용할 수 있습니다.
스위치 | 기능 |
---|---|
forcemaxcount:batchSize 와 maxcount:batchSize
|
대부분의 플랫폼에서 Unity는 타겟 디바이스의 최대 상수 버퍼 크기를 모든 인스턴스당 프로퍼티가 포함된 구조의 크기로 나눠서 인스턴싱 데이터 배열 크기를 자동으로 계산합니다. 배치 크기는 일반적으로 신경쓰지 않아도 됩니다. 하지만 일부 플랫폼(Vulkan, Xbox One, Switch)에서는 고정된 배열 크기가 계속 필요합니다. maxcount 옵션을 사용하여 이런 플랫폼의 배치 크기를 지정할 수 있습니다. 다른 플랫폼에서는 이 옵션이 완전히 무시됩니다. (예를 들어 DrawMeshInstanced를 통해 256개의 인스턴스화 스프라이트가 있는 드로우만 제공할 것임을 아는 경우) 모든 플랫폼에 배치 크기를 강제로 적용하려면 forcemaxcount 를 사용하십시오. 두 옵션의 기본값은 500입니다. |
assumeuniformscaling |
이 스위치를 사용하면 Unity가 모든 인스턴스는 동일한 스케일링을 가진 것으로 간주합니다(모든 X, Y 및 Z축에 대한 동일한 스케일). |
lodfade |
이 스위치를 사용하여 LOD 페이드 값이 인스턴스화되도록 만들 수 있습니다. 이는 LOD 페이딩 기능을 사용하는 SpeedTree 및 기타 LOD 기술에 유용합니다. |
procedural:FunctionName |
이 스위치를 사용하면 Unity가 Graphics.DrawMeshInstancedIndirect와 함께 사용할 추가 배리언트를 생성합니다. 버텍스 셰이더 시작 단계에서 Unity가 콜론 다음에 지정되는 함수를 호출합니다. 인스턴스 데이터를 수동으로 설정하려면 일반적으로 인스턴스당 데이터를 셰이더에 추가하는 방식과 동일한 방식으로 인스턴스당 데이터를 이 함수에 추가합니다. 또한 가져온 인스턴스 속성 중 프래그먼트 셰이더에 포함된 속성이 있을 경우 Unity는 프래그먼트 셰이더 시작 단계에서 이 함수를 호출합니다. |
셰이더 스크립트를 작성할 때는 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__를 변경합니다.
Graphics.DrawMeshInstanced
의 경우 이 메서드로 패스되는 머티리얼에서 GPU 인스턴싱을 활성화해야 합니다. 하지만 Graphics.DrawMeshInstancedIndirect
에는 GPU 인스턴싱 활성화가 필요하지 않습니다. 간접 인스턴싱 키워드인 PROCEDURAL_INSTANCING_ON
은 스트리핑에 영향을 받지 않습니다.
인스턴스화된 드로우 콜은 프레임 디버거에서 Draw Mesh (instanced)로 표시됩니다.
인스턴스별 프로퍼티를 항상 정의할 필요는 없습니다. 하지만 인스턴스 ID는 반드시 설정해야 하는데 월드 매트릭스가 올바르게 작동하기 위해 필요하기 때문입니다. 표면 셰이더는 자동으로 인스턴스 ID를 설정합니다. 커스텀 버텍스와 프래그먼트 셰이더에 대한 인스턴스 ID를 수동으로 설정해야 합니다. 이렇게 하려면 셰이더 시작 부분에 UNITY_SETUP_INSTANCE_ID
를 사용해야 합니다.
포워드 렌더링을 사용하는 경우 Unity는 다수의 광원에 영향을 받는 오브젝트를 효율적으로 인스턴스화할 수 없습니다. 베이스 패스만 효율적으로 인스턴스화할 수 있으며 추가 패스는 불가능합니다. 조명 패스에 대한 자세한 내용은 포워드 렌더링 및 패스 태그 문서를 참조하십시오.
라이트맵을 사용하거나 다른 라이트 또는 반사 프로브에 영향을 받는 오브젝트는 인스턴스화할 수 없습니다.
멀티 패스 셰이더에 두 개 이상의 패스가 있는 경우 첫 번째 패스만 인스턴스화됩니다. 이는 Unity가 이후 패스를 각 오브젝트와 동시에 렌더링하게 하여 머티리얼을 강제로 변경하기 때문입니다.
위의 예제에서 사용한 모든 셰이더 매크로는 UnityInstancing.cginc에 정의되어 있습니다. 이 파일은 [Unity 설치 폴더]\Editor\Data\CGIncludes에서 찾을 수 있습니다.