이 튜토리얼에서는 Unity 셰이더에서 버텍스 및 프래그먼트 프로그램을 작성하는 방법을 설명합니다. ShaderLab에 대한 기본 소개는 튜토리얼 시작을 참조하십시오. 조명과 상호작용하는 셰이더를 작성하려면 표면 셰이더를 참조하십시오.
셰이더의 일반 구조 중 작은 개요부터 시작합니다.
Shader "MyShaderName"
{
Properties
{
// material properties here
}
SubShader // subshader for graphics hardware A
{
Pass
{
// pass commands ...
}
// more passes if needed
}
// more subshaders if needed
FallBack "VertexLit" // optional fallback
}
마지막에 새롭게 추가된 FallBack “VertexLit” 커맨드에 대해서 설명하겠습니다. Fallback 명령은 셰이더의 마지막에 사용할 수 있으며 현재 셰이더의 SubShaders 를 사용자의 그래픽스 하드웨어에서 실행할 수 없는 경우, 사용해야 하는 셰이더를 지정합니다. 마지막 폴백 셰이더로부터 모든 서브셰이더를 포함하는 것과 동일한 효과를 가지고 있습니다. 예를 들어, 화려한 노멀 맵 셰이더를 작성하려는 경우 오래된 그래픽 카드용으로 매우 기본적인 비노멀맵 서브셰이더를 작성하는 대신 빌트인 VertexLit 셰이더를 폴백할 수 있습니다.
셰이더의 기본 빌딩 블록은 프로퍼티, 서브셰이더 및 패스에 관한 전체 문서를 참조할 수 있지만, 첫 번째 셰이더 튜토리얼에 소개되어 있습니다.
서브셰이더를 빌드하는 빠른 방법은 다른 셰이더에 정의된 패스를 사용하는 방법입니다. UsePass 커맨드만을 사용하여 깔끔한 스타일의 셰이더 코드를 재사용할 수 있습니다. 예를 들어, 다음 명령은 패스를 빌트인 Specular 셰이더로부터의 이름 “FORWARD”와 함께 사용합니다. UsePass “Specular/FORWARD”.
UsePass 가 작동하려면 사용하고자 하는 이름을 패스에 부여해야 합니다. 패스의 Name 명령은 이름을 부여합니다. Name “MyPassName”.
첫 튜토리얼에서 싱글 텍스처 콤바인 명령을 사용하는 패스를 기술했습니다. 이제 패스에서 버텍스 및 프래그먼트 프로그램을 어떻게 사용할 수 있는지 알아보겠습니다.
버텍스 및 프래그먼트 프로그램 (이른바 “프로그래밍 가능한 파이프라인”)을 사용할 때 그래픽스 하드웨어에 하드코딩된 대부분의 기능(“고정 함수 파이프라인”)은 스위치가 꺼집니다. 예를 들어, 버텍스 프로그램을 사용하면 표준 3D 변환, 조명, 텍스처 좌표 생성이 완전히 꺼집니다. 이와 유사하게 프래그먼트 프로그램을 사용하면 SetTexture 명령에 정의된 모든 텍스처 콤바인 모드는 대체되며 SetTexture 명령이 필요하지 않습니다.
버텍스 및 프래그먼트 프로그램 작성은 OpenGL 등 API에 빌드된 고정 함수를 재작성해야 하기 때문에 3D 변환, 조명, 좌표 공간에 대한 깊은 지식이 필요합니다. 하지만 빌트인된 것보다 훨씬 더 많은 것을 할 수 있습니다!
ShaderLab의 셰이더는 보통 Cg/HLSL 프로그래밍 언어로 작성됩니다. Cg 및 DX9 스타일 HLSL은 실제로 동일 언어이므로 Cg 및 HLSL이 통용됩니다. 자세한 내용은 이 페이지를 참조하십시오.
셰이더 코드는 셰이더 텍스트에 “Cg/HLSL 스니핏”을 첨부하여 작성됩니다. 스니핏은 Unity 에디터를 사용하여 낮은 레벨의 셰이더 어셈블리로 컴파일되며 게임 데이터 파일에 포함된 최종 셰이더는 이 낮은 레벨의 어셈블리 또는 플랫폼 특화된 바이트코드를 포함합니다. Project View 에서 셰이더를 선택할 때 인스펙터에는 디버깅 목적에 도움이 되는 컴파일된 셰이더 코드를 표시하는 버튼이 존재합니다. Unity는 Cg 스니핏을 모든 적절한 플랫폼(Direct3D 9, OpenGL, Direct3D 11, OpenGL ES 등)으로 자동으로 컴파일합니다. Cg/HLSL 코드가 에디터로 컴파일되기 때문에 런타임 시점에 스크립트로부터 셰이더를 생성할 수 없습니다.
일반적으로, 스니핏은 패스 블록 내에 위치하며, 다음과 같이 표시됩니다.
Pass {
// ... the usual pass state setup ...
CGPROGRAM
// compilation directives for this snippet, e.g.:
#pragma vertex vert
#pragma fragment frag
// the Cg/HLSL code itself
ENDCG
// ... the rest of pass setup ...
}
다음 예제는 오브젝트 노멀을 컬러로 렌더링하는 완전한 셰이더를 설명합니다.
Shader "Tutorial/Display Normals" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.color = v.normal * 0.5 + 0.5;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4 (i.color, 1);
}
ENDCG
}
}
}
오브젝트에 적용될 때 다음과 같은 이미지가 도출됩니다.
“디스플레이 노멀” 셰이더는 프로퍼티를 포함하지 않으며 Cg/HLSL 코드를 제외하고는 비어있는 싱글 패스로 싱글 서브셰이더를 포함합니다. 코드를 파트별로 분석하겠습니다.
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// ...
ENDCG
전체 스니핏은 CGPROGRAM 과 ENDCG 키워드 사이에 작성됩니다. 시작할 때 컴파일 지시자는 #pragma 구문으로 지정됩니다.
다음 컴파일 명령은 평범한 Cg/HLSL 코드입니다. 빌트인 첨부 파일을 포함하며 시작합니다.
#include "UnityCG.cginc"
UnityCG.cginc 파일은 일반적으로 사용된 선언 및 함수를 포함하여 셰이더는 유사하게 유지될 수 있습니다. 자세한 내용은 셰이더 첨부 파일을 참조하십시오. 이 파일의 appdata_base 구조를 사용합니다. 셰이더에 직접 정의만 가능하며 파일을 포함하지 않을 수 있습니다.
다음으로 버텍스에서 프래그먼트 프로그램에 전달하는 정보인 “vertex to fragment” 구조를 정의합니다(여기서는 v2f 입니다). 포지션 및 컬러 파라미터를 전달합니다. 컬러는 버텍스 프로그램에서 연산되어 프래그먼트 프로그램으로 출력됩니다.
그런 다음 버텍스 프로그램(vert 함수 )을 정의합니다. 여기에서 포지션을 연산하고 컬러로 입력 노멀을 출력합니다. o.color = v.normal * 0.5 + 0.5;
노멀 컴포넌트는 –1..1 범위에 있는 반면 컬러는 01 범위에 있기 때문에 위의 코드에서 노멀을 스케일하고 바이어스합니다. 다음으로 연산된 컬러와 알파 컴포넌트로 1을 출력하는 frag 함수인 프래그먼트 프로그램을 정의합니다.
fixed4 frag (v2f i) : SV_Target
{
return fixed4 (i.color, 1);
}
이제 셰이더를 완료했습니다. 이 간단한 셰이더는 메시 노멀을 시각화하는 데 매우 유용합니다.
셰이더는 광원에 전혀 반응하지 않기 때문에 이 점이 조금 더 흥미롭습니다. 자세한 내용은 표면 셰이더를 참조하십시오.
셰이더에서 프로퍼티를 정의할 때 프로퍼티에 \Color_ 또는 \MainTex_ 같은 이름을 부여합니다. Cg/HLSL에서 사용하려면 이름과 타입을 일치시키는 함수를 정의해야 합니다. 자세한 내용은 셰이더 프로그램의 프로퍼티 페이지를 참조하십시오.
컬러별로 모듈화된 텍스처를 표시하는 완전한 셰이더입니다. 물론, 텍스처 컴바이너 호출에서 쉽게 동일한 작업을 할 수 있지만 여기에서는 Cg에서 프로퍼티를 어떻게 사용하는지 보여주는 것이 핵심입니다:
Shader "Tutorial/Textured Colored" {
Properties {
_Color ("Main Color", Color) = (1,1,1,0.5)
_MainTex ("Texture", 2D) = "white" { }
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 _Color;
sampler2D _MainTex;
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
float4 _MainTex_ST;
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 texcol = tex2D (_MainTex, i.uv);
return texcol * _Color;
}
ENDCG
}
}
}
셰이더 구조는 이전 예제와 동일합니다. 여기에서 \Color_ 및 \MainTex_ 로 명명된 두 프로퍼티를 정의합니다. Cg/HLSL 코드상 상응하는 변수를 정의합니다.
fixed4 _Color;
sampler2D _MainTex;
자세한 내용은 Cg/HLSL에서 셰이더 프로퍼티 액세스를 참조하십시오.
여기에서 버텍스 및 프래그먼트 프로그램은 아무런 멋진 효과를 내지 않습니다. 버텍스 프로그램은 텍스처 스케일 및 오프셋이 정확하게 적용됐는지 확인하기 위해 UnityCG.cginc의 TRANSFORM_TEX 매크로를 사용하고 프래그먼트 프로그램은 단순히 텍스처를 샘플링하여 컬러 프로퍼티로 곱합니다.
몇 가지 간단한 단계로 사용자 셰이더 프로그램을 어떻게 작성할 수 있는지 살펴봤습니다. 여기에 제공한 예제는 매우 간단한 것으로 필요할 경우 복잡한 셰이더 프로그램을 작성할 수 있습니다. 이렇게 하면 Unity를 최대한 활용하고 최적화된 렌더링 결과를 달성하는 데 도움을 줄 수 있습니다.
여기에는 완전한 ShaderLab 레퍼런스 매뉴얼이 있으며 버텍스 및 프래그먼트 셰이더 예제 페이지에서 더 많은 예제를 확인할 수 있습니다. forum.unity3d.com에 셰이더에 대한 포럼을 방문하여 셰이더에 대한 도움을 받을 수도 있습니다. 즐겁게 프로그램을 작성하고 Unity와 ShaderLab의 탁월한 성능을 만끽하시기 바랍니다.