GPU 인스턴싱은 CPU 렌더링에 비해 성능 향상 폭이 매우 큽니다. 빌보드(Billboard) 파티클 렌더링의 기본 렌더링 모드 대신에 파티클 시스템으로 메시(Mesh) 파티클을 렌더링하려는 경우에 사용할 수 있습니다.
파티클 시스템으로 GPU 인스턴싱을 사용하려면 다음을 따르십시오.
파티클 시스템의 렌더러 모드를 메시(Mesh) 로 설정합니다.
GPU 인스턴싱을 지원하는 렌더러 머티리얼에 셰이더를 사용합니다.
GPU 인스턴싱을 지원하는 플랫폼에서 프로젝트를 실행합니다.
파티클 시스템에 대해 GPU 인스턴싱을 활성화하려면 파티클 시스템의 렌더러(Renderer) 모듈에서 GPU 인스턴싱 활성화(Enable GPU Instancing) 체크박스를 선택해야 합니다.
Unity는 GPU 인스턴싱을 지원하는 빌트인 파티클 셰이더를 제공하지만, 기본 파티클 머티리얼에서는 사용되지 않기 때문에 파티클 셰이더가 GPU 인스턴싱을 사용하도록 변경해야 합니다. GPU 인스턴싱을 지원하는 파티클 셰이더는 파티클/표준 표면(Particles/Standard Surface) 이라고 불립니다. 이 파티클 셰이더를 사용하려면 직접 새 머티리얼(Material) 을 생성하고 해당 머티리얼의 셰이더를 파티클/표준 표면(Particles/Standard Surface) 으로 설정해야 합니다. 그런 다음 새로 생성한 머티리얼을 파티클 시스템 렌더러 모듈의 머티리얼 필드에 할당하십시오.
다른 셰이더를 파티클에 사용하려는 경우 해당 셰이더가 ‘#pragma target 4.5’ 이상을 사용해야 합니다. 자세한 내용은 셰이더 컴파일 타겟을 참조하십시오. 이 요구 사항은 Unity의 일반 GPU 인스턴싱보다 높은데, 이는 파티클 시스템이 인스턴싱을 여러 드로우 콜로 나누는 것이 아니라 모든 인스턴스 데이터를 대형 단일 버퍼에 작성하기 때문입니다.
GPU 인스턴싱을 활용하는 커스텀 셰이더를 작성할 수도 있습니다. 자세한 내용은 다음 섹션을 참조하십시오.
다음은 파티클 시스템 GPU 인스턴싱을 사용하는 표면 셰이더의 완전한 활용 예제입니다.
Shader "Instanced/ParticleMeshesSurface" {
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
// And generate the shadow pass with instancing support
#pragma surface surf Standard fullforwardshadows addshadow vertex:vert
// Enable instancing for this shader
#pragma multi_compile_instancing
#pragma instancing_options procedural:vertInstancingSetup
#pragma exclude_renderers gles
#include "UnityStandardParticleInstancing.cginc"
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
fixed4 vertexColor;
};
fixed4 _Color;
half _Glossiness;
half _Metallic;
void vert (inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input, o);
vertInstancingColor(o.vertexColor);
vertInstancingUVs(v.texcoord, o.uv_MainTex);
}
void surf (Input IN, inout SurfaceOutputStandard o) {
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * IN.vertexColor * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
위 예제에는 일반 표면 셰이더와 많은 부분에서 약간의 차이가 있으며, 이는 파티클 인스턴싱에 사용할 수 있도록 하기 위한 것입니다.
먼저, 다음 두 개의 줄을 추가하여 절차적 인스턴싱을 활성화하고 빌트인 버텍스 설정 함수를 지정해야 합니다. 이 함수는 UnityStandardParticleInstancing.cginc에 상주하며 다음과 같은 인스턴스당(Per-instance)(파티클당) 포지션 데이터를 로드합니다.
#pragma instancing_options procedural:vertInstancingSetup
#include "UnityStandardParticleInstancing.cginc"
예제에서 다른 수정 부분은 버텍스 함수로, 인스턴스당(Per-instance) 특성, 즉 파티클 컬러와 텍스처 시트 애니메이션 텍스처 좌표를 적용하는 두 개의 줄이 추가됩니다.
vertInstancingColor(o.vertexColor);
vertInstancingUVs(v.texcoord, o.uv_MainTex);
다음은 파티클 시스템 GPU 인스턴싱을 사용하는 커스텀 셰이더의 완전한 활용 사례입니다. 이 커스텀 셰이더는 스탠다드 파티클 셰이더에 없는 기능인 텍스처 시트 애니메이션의 개별 프레임 간 페이드를 추가로 제공합니다.
Shader "Instanced/ParticleMeshesCustom"
{
Properties
{
_MainTex("Albedo", 2D) = "white" {}
[Toggle(_TSANIM_BLENDING)] _TSAnimBlending("Texture Sheet Animation Blending", Int) = 0
}
SubShader
{
Tags{ "RenderType" = "Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile __ _TSANIM_BLENDING
#pragma multi_compile_instancing
#pragma instancing_options procedural:vertInstancingSetup
#include "UnityCG.cginc"
#include "UnityStandardParticleInstancing.cginc"
struct appdata
{
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
# ifdef _TSANIM_BLENDING
float3 texcoord2AndBlend : TEXCOORD1;
# endif
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 readTexture(sampler2D tex, v2f IN)
{
fixed4 color = tex2D(tex, IN.texcoord);
# ifdef _TSANIM_BLENDING
fixed4 color2 = tex2D(tex, IN.texcoord2AndBlend.xy);
color = lerp(color, color2, IN.texcoord2AndBlend.z);
# endif
return color;
}
v2f vert(appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
o.color = v.color;
o.texcoord = v.texcoord;
vertInstancingColor(o.color);
# ifdef _TSANIM_BLENDING
vertInstancingUVs(v.texcoord, o.texcoord, o.texcoord2AndBlend);
# else
vertInstancingUVs(v.texcoord, o.texcoord);
# endif
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
half4 albedo = readTexture(_MainTex, i);
return i.color * albedo;
}
ENDCG
}
}
}
이 예제에서는 표면 셰이더와 동일한 설정 코드를 사용하여 포지션 데이터를 로드합니다.
#pragma instancing_options procedural:vertInstancingSetup
#include "UnityStandardParticleInstancing.cginc"
버텍스 함수에 대한 수정 부분도 표면 셰이더와 매우 유사합니다.
vertInstancingColor(o.color);
# ifdef _TSANIM_BLENDING
vertInstancingUVs(v.texcoord, o.texcoord, o.texcoord2AndBlend);
# else
vertInstancingUVs(v.texcoord, o.texcoord);
# endif
위의 첫 번째 예제와 비교할 때 유일한 차이점은 텍스처 시트 애니메이션 블렌딩입니다. 다시 말해, 셰이더가 텍스처 시트 애니메이션의 프레임을 하나가 아니라 두 개를 읽고 블렌딩하려면 추가 텍스처 좌표 세트가 필요합니다.
마지막으로 프래그먼트 셰이더가 텍스처를 읽고 최종 컬러를 계산합니다.
위 예제에서는 기본 버텍스 스트림만 파티클 설정에 사용합니다. 여기에는 포지션, 노멀, 컬러, 하나의 UV가 포함되어 있습니다. 하지만 커스텀 버텍스 스트림을 사용하면 다른 데이터(예: 속도, 회전, 크기)를 셰이더에 보낼 수 있습니다.
다음 예제에서 셰이더는 특수 효과를 표시하도록 디자인되어 빠른 파티클은 밝게 나타나고 느린 파티클은 어둡게 나타납니다. 속도 버텍스 스트림을 사용하여 속도에 따라 파티클을 밝게 만드는 코드가 추가되어 있습니다. 또한 이 셰이더는 효과가 텍스처 시트 애니메이션을 사용하지 않는다고 가정하기 때문에 커스텀 스트림 구조체에서 생략됩니다.
완전한 셰이더는 다음과 같습니다.
Shader "Instanced/ParticleMeshesCustomStreams"
{
Properties
{
_MainTex("Albedo", 2D) = "white" {}
}
SubShader
{
Tags{ "RenderType" = "Opaque" }
LOD 100
Pass
{
CGPROGRAM
# pragma exclude_renderers gles
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#pragma instancing_options procedural:vertInstancingSetup
#define UNITY_PARTICLE_INSTANCE_DATA MyParticleInstanceData
#define UNITY_PARTICLE_INSTANCE_DATA_NO_ANIM_FRAME
struct MyParticleInstanceData
{
float3x4 transform;
uint color;
float speed;
};
#include "UnityCG.cginc"
#include "UnityStandardParticleInstancing.cginc"
struct appdata
{
float4 vertex : POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
o.color = v.color;
o.texcoord = v.texcoord;
vertInstancingColor(o.color);
vertInstancingUVs(v.texcoord, o.texcoord);
# if defined(UNITY_PARTICLE_INSTANCING_ENABLED)
UNITY_PARTICLE_INSTANCE_DATA data = unity_ParticleInstanceData[unity_InstanceID];
o.color.rgb += data.speed;
# endif
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
half4 albedo = tex2D(_MainTex, i.texcoord);
return i.color * albedo;
}
ENDCG
}
}
}
이 셰이더는 UnityStandardParticleInstancing.cginc
셰이더 파일을 포함합니다. 여기에는 커스텀 버텍스 스트림이 사용되지 않을 때를 위한 기본 인스턴싱 데이터 레이아웃이 들어 있습니다. 따라서 커스텀 스트림을 사용할 때는 해당 헤더에서 정의된 일부 기본값을 오버라이드해야 합니다. 이러한 오버라이드는 셰이더 파일이 포함하기 전에 이루어져야 합니다. 위 예제에서 설정하는 커스텀 오버라이드는 다음과 같습니다.
먼저, Unity에 ‘MyParticleInstanceData’ 커스텀 구조체를 커스텀 스트림 데이터에 사용하도록 지시하는 줄이 있습니다. 이때 UNITY_PARTICLE_INSTANCE_DATA 매크로가 사용됩니다.
#define UNITY_PARTICLE_INSTANCE_DATA MyParticleInstanceData
그런 다음, 또 다른 줄이 인스턴싱 시스템에 애니메이션 프레임 스트림이 이 셰이더에 필요하지 않다고 알립니다. 이는 이 예제의 효과가 텍스처 시트 애니메이션을 사용하지 않기 때문입니다.
#define UNITY_PARTICLE_INSTANCE_DATA_NO_ANIM_FRAME
세 번째로, 커스텀 스트림 데이터용 구조체가 선언됩니다.
struct MyParticleInstanceData
{
float3x4 transform;
uint color;
float speed;
};
이러한 오버라이드는 모두 UnityStandardParticleInstancing.cginc
셰이더 파일이 포함되기 전에 이루어지므로, 셰이더가 해당 정의에 고유 기본값을 사용하지 않습니다.
구조체를 작성하는 경우 변수는 파티클 시스템 렌더러 모듈의 인스펙터에 나열된 버텍스 스트림과 일치해야 합니다. 즉, 렌더러 모듈 UI에 사용할 스트림을 선택한 후 커스텀 스트림 데이터 구조체의 변수 정의에 추가해야 변수와 버텍스 스트림을 일치시킬 수 있습니다.
첫 번째 항목(포지션)은 필수이므로 제거할 수 없습니다. 다른 항목들은 더하기 및 빼기 버튼으로 자유롭게 추가/제거하여 버텍스 스트림 데이터를 커스터마이즈할 수 있습니다.
뒤에 INSTANCED 가 붙은 리스트 항목에는 인스턴스 데이터가 들어 있기 때문에 파티클 인스턴스 데이터 구조체에 반드시 포함해야 합니다. INSTANCED 단어 바로 뒤에 붙어 있는 숫자(예: INSTANCED0 의 0과, INSTANCED1 의 1)는 첫 “transform” 변수 이후 변수가 구조체에 나타나는 순서를 뜻합니다. 뒤에 붙은 문자(.x, .xy, .xyz 또는 .xyzw)는 변수의 타입을 표시하며, 셰이더 코드에서 float, float2, float3 및 float4 변수 타입에 매핑됩니다.
리스트에 나타나는 다른 버텍스 스트림 데이터는 생략할 수 있지만, 이 경우 파티클 인스턴스 데이터 구조체에서 뒤에 INSTANCED 단어가 붙어 있지 않습니다. 이는 셰이더에서 처리하는 인스턴스 데이터가 아니기 때문입니다. 이러한 데이터는 소스 메시(예: UV), 노멀 및 탄젠트에 속합니다.
예제를 완성하기 위한 마지막 단계는 버텍스 셰이더 내 파티클 컬러에 속도를 적용하는 것입니다.
# if defined(UNITY_PARTICLE_INSTANCING_ENABLED)
UNITY_PARTICLE_INSTANCE_DATA data = unity_ParticleInstanceData[unity_InstanceID];
o.color.rgb += data.speed;
# endif
인스턴싱이 사용되지 않을 때 컴파일할 수 있도록 모든 인스턴싱 코드를 UNITY_PARTICLE_INSTANCING_ENABLED 검사 내에 래핑해야 합니다.
이때 프래그먼트 셰이더에 데이터를 전달하고 싶은 경우 다른 셰이더 데이터와 마찬가지로 데이터를 v2f 구조체에 작성할 수 있습니다.
이 예제에서는 커스텀 버텍스 스트림을 사용하도록 커스텀 셰이더를 수정하는 방법을 설명합니다. 하지만 표면 셰이더에 이와 완전히 동일한 방식을 적용하여 동일한 기능을 구현할 수도 있습니다.