AnyPortrait > 스크립트 > 커스텀 쉐이더
AnyPortrait는 일반적으로 사용되는 다양한 쉐이더들을 제공하지만, 프로젝트에 따라서는 특수한 효과가 더해진 커스텀 쉐이더를 작성할 필요도 있을 것입니다.
AnyPortrait의 약간의 규칙만 준수한다면 커스텀 쉐이더를 쉽게 작성하여 멋진 렌더링 결과를 만들 수 있습니다.
이 페이지에서는 AnyPortrait의 쉐이더 기본형이 어떻게 구성되었는지 설명합니다.
이 설명을 바탕으로 커스텀 쉐이더를 작성하여 적용해보시길 바랍니다.
작성한 쉐이더를 적용하고자 할 때, 재질 라이브러리를 이용하거나 메시의 속성을 변경하면 됩니다.
쉐이더를 변경하는 방법에 대해서는 다음의 메뉴얼을 확인해주세요.
- 재질 라이브러리
- 메시의 쉐이더(Shader) 설정
안내
이 페이지에서는 쉐이더 작성 규칙에 대해서는 다루지 않습니다.
관련된 내용은 유니티 문서나 포럼 및 관련 사이트를 참고해주세요!
AnyPortrait는 1개의 쉐이더를 사용하는 것이 아닌, 여러개의 쉐이더들 중 상황에 맞는 쉐이더를 사용합니다.
쉐이더가 파생되는 3가지 요인에 따라 총 17개의 쉐이더들이 하나의 세트를 이루어서 적용됩니다. (관련 페이지)
해당 요인들은 다음과 같습니다.
1. 색상 공간 (Color Space)
프로젝트의 색상 공간에 따라 쉐이더가 결정됩니다.
Gamma Space를 기준으로 작성되며, Linear Space의 경우엔 색상 연산식이 변경됩니다.
2. 블렌딩 (Blending)
메시는 4 종류의 블렌딩 중 하나의 방식으로 렌더링됩니다.
기본값인 Alpha Blend를 기준으로 작성되며, Additive, Soft Additive, Multiplicative에서는 블렌딩 속성과 알파값 연산과 관련된 코드가 조금 수정됩니다.
3. 클리핑 (Clipping)
클리핑되는 메시는 마스크 텍스쳐 정보를 받아서 알파 연산을 추가로 수행합니다.
마스크를 생성하는 쉐이더 1종도 포함되지만, 이는 고정된 역할을 수행하는 기능성 쉐이더입니다.
위 요인들이 조합되어 총 17개의 쉐이더들이 캐릭터에 적용됩니다.
하지만 커스텀 쉐이더를 작성하고자 할 때, 굳이 모든 쉐이더를 작성할 필요는 없습니다.
다음의 사항을 체크하여 여러분의 프로젝트의 상황에 맞는 쉐이더만 작성하면 되겠습니다.
- 프로젝트의 색상 공간에 따라서 Gamma나 Linear 중 한가지 타입의 쉐이더만 작성하세요.
- 블렌딩 옵션을 사용하지 않는다면 기본 방식인 Alpha Blend 쉐이더만 작성하면 됩니다.
- 클리핑 메시가 없다면 클리핑 쉐이더를 작성하지 않아도 됩니다.
- 클리핑 마스크를 만드는 Alpha Mask 쉐이더는 고정된 역할만 수행하므로 커스텀 쉐이더로 제작하지 않아도 됩니다.
이 페이지에서는 기본값인 "Gamma Space"에서의 "Alpha Blend" 쉐이더를 작성하는 규칙과 방법에 대해서 먼저 설명합니다.
그리고 쉐이더를 결정하는 요인에 따라서 수정해야하는 부분을 각각 설명합니다.
AnyPortrait 패키지에 내장된 쉐이더 코드들을 확인해보시는 것도 도움이 될 것입니다.
쉐이더 에셋들은 기본적으로 "Assets/AnyPortrait/Assets/Shaders"에 위치합니다.
제공되는 쉐이더를 복제하여 필요한 부분을 수정하면 편리할 것입니다.
쉐이더 그래프로 커스텀 쉐이더를 제작하고자 하는 경우, 이 페이지에서 설명하는 규칙을 쉐이더 그래프의 형식으로 작성하면 됩니다.
자세한 내용은 관련 페이지에서 볼 수 있습니다.
AnyPortrait의 기본 쉐이더는 유니티의 기본 Transparent 쉐이더와 크게 다르지 않습니다.
다음의 코드는 "Gamma Space"의 "Alpha Blend"의 기본 쉐이더이며, 이를 바탕으로 커스텀 코드를 추가하면 되겠습니다.
안내
AnyPortrait는 Surface Shader와 Fragment Shader 및 쉐이더 그래프 (Shader Graph)를 모두 지원합니다.
이 페이지에서는 Fragment Shader의 코드를 기반으로 설명을 하며, 여기서 설명하는 규칙을 상황에 맞게 적절히 적용하면 되겠습니다.
Shader "Sample Shader/Gamma Space - Normal - AlphaBlend"
{
Properties
{
_Color ("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
_MainTex ("Main Texture (RGBA)", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
LOD 200
Pass
{
Tags { "LightMode" = "ForwardBase" }
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
return col;
}
ENDCG
}
Pass
{
Tags { "LightMode" = "ShadowCaster" }
ZWrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
V2F_SHADOW_CASTER;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
v2f vert(appdata_base v)
{
v2f o;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
float4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.a *= _Color.a;
if(col.a < 0.05f)
{
discard;
}
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
}
쉐이더 코드의 주요 부분을 살펴봅시다.
1. 프로퍼티
_Color ("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
_MainTex ("Main Texture (RGBA)", 2D) = "white" {}
유니티의 기본 Transparent 쉐이더에서 사용되는 프로퍼티이자 AnyPortrait에서 사용하는 프로퍼티입니다.
클리핑 되는 메시가 아닌 일반적인 경우, 이 프로퍼티들만 사용합니다.
따라서 커스텀 쉐이더에서는 이 두개의 프로퍼티들과 관련된 모든 코드들을 그대로 작성해야합니다.
또한 AnyPortrait의 시스템이 이 프로퍼티들을 제어하므로, 다른 스크립트로 이 프로퍼티들을 수정해서는 안됩니다.
2. 기본 패스
Pass
{
Tags { "LightMode" = "ForwardBase" }
ZWrite Off
...
: 기본 렌더링 패스의 코드입니다.
위와 같이 패스를 명시한 것은, 그림자 생성을 위한 패스가 별도로 존재하기 때문입니다.
만약 그림자 패스를 작성하지 않는다면 "Tags { "LightMode" = "ForwardBase" }" 부분을 생략해도 됩니다.
Z Write를 하지 않으므로, 이 값을 Off로 설정합니다.
3. 블렌딩
Blend SrcAlpha OneMinusSrcAlpha
: "Alpha Blend" 쉐이더의 블렌딩 코드입니다.
4. 색상 연산
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
return col;
: AnyPortrait의 색상 연산식은 "2X Multiply" 입니다.
따라서 RGB에는 추가로 2를 곱해야 합니다.
이 코드는 블렌딩에 따라 바뀝니다.
5. 그림자 패스
Pass
{
Tags { "LightMode" = "ShadowCaster" }
ZWrite On
...
}
: "Alpha Blend" 메시의 경우, 옵션에 따라선 그림자를 생성할 수 있어야 합니다.
따라서 그림자를 생성하는 "ShadowCaster" 패스를 작성해야합니다.
이 패스는 그림자를 생성하는 역할만 하므로, 위 예제대로 작성하면 됩니다.
만약 Surface Shader 방식으로 작성한다면 이 패스를 작성하지 않아도 됩니다.
위의 예제는 Gamma Space에서의 쉐이더 코드이며, 프로젝트의 색상 공간이 만약 Linear Space라면 "색상 연산" 부분을 수정해야합니다.
위의 기본 코드에서 기본 패스 ("ForwardBase")의 frag 함수를 다음과 같이 수정해줍시다.
...
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 4.595f;
col.rgb = pow(col.rgb, 2.2f);
col.a *= _Color.a;
return col;
}
...
Linear Space에서, Gamma Space에 비해 색상에 2.2 제곱을 한 값이 적용되면 원본 이미지 색상에 가깝게 보여집니다.
그래서 위와 같이 "2X Multiply" 연산 규칙임에도 "2"가 아닌 "4.595"를 곱해주고, 최종 RGB도 "2.2" 제곱을 해줍니다.
이 변경점은 블렌딩 종류와 무관하게 적용되어야 합니다.
메시의 옵션을 변경하면 기본 블렌딩인 "Alpha Blend" 외에도, "Additive", "Soft Additive", "Multiplicative" 방식으로 렌더링을 할 수 있습니다.
블렌딩 방식에 따라 쉐이더를 각각 작성해야 하는데, 위의 "Alpha Blend"의 코드를 조금 수정하여 편하게 작성할 수 있습니다.
"Alpha Blend" 외의 블렌딩 옵션들은 주로 "시각 효과"에 대한 것이므로, 그림자를 생성하는 패스를 작성하지 않는 것이 좋습니다.
3종류의 블렌딩에 대해서는 쉐이더 내의 "Blend"와 "frag 함수"의 일부 코드를 수정하면 됩니다.
또한 Alpha Blend와 다르게 그림자 생성 패스를 생략하므로, "LightMode" 지정 코드를 삭제하면 됩니다.
다음의 예제는 3가지 블렌딩에 대한 코드를 모두 포함하므로, 주석을 확인하여 필요한 코드를 취합하면 되겠습니다.
...
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
// --- 블렌딩 종류에 따라 선택하여 작성 ---
Blend One One // Additive
Blend OneMinusDstColor One // Soft Additive
Blend DstColor SrcColor // Multiplicative
LOD 200
Pass
{
// --- 패스 지정 코드 삭제 ---
// Tags { "LightMode" = "ForwardBase" } : 삭제
ZWrite Off
CGPROGRAM
...
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
// --- Additive 또는 Soft Additive ---
col.rgb *= col.a;
col.a = 1.0f;
// ---------------------------------
// --- Multiplicative ---
col.rgb = col.rgb * (col.a) + float4(0.5f, 0.5f, 0.5f, 1.0f) * (1.0f - col.a);
col.a = 1.0f;
// ----------------------
return col;
}
ENDCG
}
// 그림자 생성 패스 코드 삭제
}
}
1. 블렌딩 옵션
Blend One One // Additive
Blend OneMinusDstColor One // Soft Additive
Blend DstColor SrcColor // Multiplicative
: 메시의 블렌딩 옵션에 따라 위 코드 중 하나를 선택하면 됩니다.
(쉐이더 그래프를 이용하는 경우 Soft Additive가 지원되지 않을 수 있습니다.)
2. 멀티 패스 코드 삭제
// Tags { "LightMode" = "ForwardBase" } : 삭제
: 그림자 생성 패스가 없어지므로, 멀티 패스와 관련된 코드는 삭제해도 됩니다.
그림자 생성 패스 코드도 삭제합니다.
3. 색상 연산식 수정
(1) Additive 또는 Soft Additive인 경우
col.rgb *= col.a;
col.a = 1.0f;
(2) Multiplicative인 경우
col.rgb = col.rgb * (col.a) + float4(0.5f, 0.5f, 0.5f, 1.0f) * (1.0f - col.a);
col.a = 1.0f;
: 블렌딩에 맞게 최종적인 색상을 변경하고, Alpha의 값을 1로 변경합니다.
Additive와 Soft Additive의 색상 연산식은 서로 동일합니다.
클리핑되는 메시는 별도의 쉐이더에 의해서 렌더링이 됩니다.
클리핑용 쉐이더에는 클리핑 마스크를 생성하는 메시의 렌더링 결과를 텍스쳐 형식으로 받아서 일부가 렌더링되지 않도록 만드는 연산이 추가됩니다.
클리핑용 프로퍼티와 클리핑 연산을 기본 쉐이더에 추가하면 됩니다.
또한, 클리핑 쉐이더는 Alpha Blend에서도 그림자를 생성할 필요가 없으므로 단일 패스로만 작성하시면 됩니다.
마스크 메시의 그림자 안에 포함되기 때문입니다.
다음의 예제는 Gamma Space에서의 Alpha Blend인 경우의 쉐이더이며, 위의 설명에 맞게 수정할 수 있습니다.
Shader "Sample Shader/Gamma Space - Clipped - AlphaBlend"
{
Properties
{
_Color ("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
_MainTex ("Main Texture (RGBA)", 2D) = "white" {}
// --- 클리핑 프로퍼티 ---
_MaskTex ("Mask Texture (A)", 2D) = "white" {}
_MaskScreenSpaceOffset ("Mask Screen Space Offset (XY_Scale)", Vector) = (0, 0, 0, 1)
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
// --- 클리핑 연산을 위한 Screen Position ---
float4 screenPos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
// --- 클리핑용 프로퍼티 변수 ---
sampler2D _MaskTex;
float4 _MaskScreenSpaceOffset;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// --- 클리핑용 Screen Position 계산 ---
o.screenPos = ComputeScreenPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
// --- 클리핑 마스크에 의한 알파 채널 연산 ---
float2 screenUV = i.screenPos.xy / max(i.screenPos.w, 0.0001f);
screenUV -= float2(0.5f, 0.5f);
screenUV.x *= _MaskScreenSpaceOffset.z;
screenUV.y *= _MaskScreenSpaceOffset.w;
screenUV.x += _MaskScreenSpaceOffset.x * _MaskScreenSpaceOffset.z;
screenUV.y += _MaskScreenSpaceOffset.y * _MaskScreenSpaceOffset.w;
screenUV += float2(0.5f, 0.5f);
col.a *= tex2D(_MaskTex, screenUV).r;
return col;
}
ENDCG
}
}
}
1. 프로퍼티
_MaskTex ("Mask Texture (A)", 2D) = "white" {}
_MaskScreenSpaceOffset ("Mask Screen Space Offset (XY_Scale)", Vector) = (0, 0, 0, 1)
: 클리핑 마스크 처리를 위해서 마스크 텍스쳐를 프로퍼티에 추가합니다.
위와 같이 프로퍼티를 추가하면 AnyPortrait에서 자동으로 클리핑 렌더링을 처리합니다.
_MaskScreenSpaceOffset은 클리핑 마스크를 화면 좌표계(Screen Space)를 기준으로 적용하기 위한 프로퍼티입니다.
sampler2D _MaskTex;
float4 _MaskScreenSpaceOffset;
: 추가된 프로퍼티에 맞게 변수들도 추가해야합니다.
2. Screen Space Position 시맨틱
float4 screenPos : TEXCOORD1;
: 클리핑 마스크는 화면 좌표계(Screen Space)에서 처리됩니다.
따라서 버텍스의 화면 좌표계에서의 위치를 버텍스 쉐이더에서 계산해서 프래그먼트 쉐이더로 전달해야합니다.
o.screenPos = ComputeScreenPos(o.vertex);
: 버텍스 쉐이더에서 화면 좌표계에서의 위치를 계산하는 코드를 추가합니다.
3. 마스크에 의한 알파 채널값 계산
float2 screenUV = i.screenPos.xy / max(i.screenPos.w, 0.0001f);
screenUV -= float2(0.5f, 0.5f);
screenUV.x *= _MaskScreenSpaceOffset.z;
screenUV.y *= _MaskScreenSpaceOffset.w;
screenUV.x += _MaskScreenSpaceOffset.x * _MaskScreenSpaceOffset.z;
screenUV.y += _MaskScreenSpaceOffset.y * _MaskScreenSpaceOffset.w;
screenUV += float2(0.5f, 0.5f);
col.a *= tex2D(_MaskTex, screenUV).r;
: 클리핑 마스크를 계산하여 알파채널에 적용하는 식입니다.
AnyPortrait 시스템에서 사용하는 고유의 연산식이므로 그대로 작성해주세요.
키워드를 이용하여 하나의 쉐이더 코드를 여러 버전으로 컴파일하도록 만들어서 활용할 수 있습니다.
AnyPortrait v1.5.1에 추가된 기능을 이용하여 쉐이더의 키워드를 이용할 수 있습니다.
키워드에 대해선 다음의 유니티 공식 문서를 관련된 정보를 보실 수 있습니다.
- 유니티 메뉴얼
다음은 "SPECIAL_COLOR"라는 키워드에 따라 색상 연산이 바뀌는 쉐이더 예제입니다.
Shader "Shader Example/Gamma Space - Normal - AlphaBlend (Keyword)"
{
Properties
{
_Color ("2X Color (RGBA Mul)", Color) = (0.5, 0.5, 0.5, 1.0)
_MainTex ("Main Texture (RGBA)", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" "PreviewType" = "Plane" }
Blend SrcAlpha OneMinusSrcAlpha
LOD 200
Pass
{
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile __ SPECIAL_COLOR
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb *= _Color.rgb * 2.0f;
col.a *= _Color.a;
#ifdef SPECIAL_COLOR
col.rgb *= fixed3(0.0f, 1.0f, 0.0f);
#endif
return col;
}
ENDCG
}
}
}
1. 키워드 선언
#pragma multi_compile __ SPECIAL_COLOR
: #pragma multi_compile 구문을 이용하여 키워드를 선언합니다.
이 예제에서는 "SPECIAL_COLOR"라는 키워드를 새로 선언했습니다.
유니티에서 쉐이더 배리언트를 생성하는 "multi_compile"와 "shader_feature" 지시문을 이용하거나, 동적 브랜치를 생성하는 "dynamic_branch" 지시문을 이용하여 키워드를 활용할 수 있습니다.
다만 AnyPortrait는 재질을 동적으로 생성하기 때문에 에셋 기반으로 쉐이더 배리언트를 컴파일하는 "shader_feature"는 이용할 수 없습니다.
2. 코드 분기 작성
#ifdef SPECIAL_COLOR
col.rgb *= fixed3(0.0f, 1.0f, 0.0f);
#endif
: 키워드에 따라 다르게 동작하도록 작성합니다.
"multi_compile"를 사용했다면 "#ifdef, #else, #elif, #endif" 지시문을 이용하고, "dynamic_branch"을 사용했다면 "if ( )" 문을 이용합니다.
이 예제에서는 "SPECIAL_COLOR" 키워드가 활성화된 경우 녹색 채널의 색상만 렌더링되도록 작성했습니다.
키워드를 사용하기 위해서는 재질 라이브러리에서 설정을 해야합니다.
자세한 내용은 관련 페이지에서 확인하실 수 있습니다.
(1) 재질 라이브러리를 엽니다.
(2) "SPECIAL_COLOR"라는 이름의 프로퍼티 옵션을 추가하고 "Keyword" 타입으로 설정한 뒤 "Enable"로 설정합니다.
Bake를 하고 실행하면, 위와 같이 키워드 활성화 여부에 따라 렌더링 결과가 달라지는 것을 볼 수 있습니다.