Unity 매뉴얼: 표면 셰이더 및 테셀레이션
이 문서에서는 Unity의 표면 셰이더와 테셀레이션에 대해 알아보고, 각 기능을 활용하는 여러 예제를 제공합니다. 테셀레이션은 3D 모델의 표면을 부드럽게 만들어 주는 중요한 기능입니다. 아래 내용에서는 표면 셰이더의 기본 개념과 테셀레이션의 사용법, 적용 예제를 설명하겠습니다.
서론
Unity에서는 표면 셰이더를 사용하여 다양한 그래픽스 API에 맞춰 테셀레이션을 지원합니다. 테셀레이션을 통해 모델의 디테일과 부드러움을 조정할 수 있습니다.
테셀레이션 개요
테셀레이션은 주어진 형태의 메시를 더 작고 세밀한 삼각형으로 나누어 더 많은 디테일을 제공합니다. 이 과정에서 주로 사용되는 함수는 다음과 같습니다: - tessellate: 테셀레이션 팩터를 계산합니다. - vertex: 테셀레이션 후 각 버텍스에 대한 처리를 수행합니다.
현재 테셀레이션 지원의 한계
- 삼각형 도메인만 지원되고 있으며, 사각형 및 등치선 테셀레이션은 지원하지 않습니다.
- GPU 테셀레이션을 사용하지 않을 경우, 셰이더가 자동으로 셰이더 모델 4.6로 컴파일됩니다.
표면 셰이더 기본 예제
아래는 테셀레이션을 사용하지 않고, 변위 매핑을 하는 표면 셰이더의 예제입니다.
Shader "Tessellation Sample" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _DispTex ("Disp Texture", 2D) = "gray" {} _NormalMap ("Normalmap", 2D) = "bump" {} _Displacement ("Displacement", Range(0, 1.0)) = 0.3 _Color ("Color", color) = (1,1,1,0) _SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5) } SubShader { Tags { "RenderType"="Opaque" } LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp nolightmap
#pragma target 4.6
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
예제 설명
위의 셰이더는 변위 맵을 샘플링하여 노멀 방향으로 버텍스를 이동시킵니다. 이 방식으로 자연스러운 표면을 표현할 수 있습니다.
고정 크기 테셀레이션
각 면의 크기가 비슷할 때 고정된 양의 테셀레이션을 적용합니다. 아래 예제는 고정 테셀레이션 팩터를 사용하는 방법입니다.
Shader "Tessellation Sample" { Properties { _Tess ("Tessellation", Range(1,32)) = 4 _MainTex ("Base (RGB)", 2D) = "white" {} _DispTex ("Disp Texture", 2D) = "gray" {} _NormalMap ("Normalmap", 2D) = "bump" {} _Displacement ("Displacement", Range(0, 1.0)) = 0.3 _Color ("Color", color) = (1,1,1,0) _SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5) } SubShader { Tags { "RenderType"="Opaque" } LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessFixed nolightmap
#pragma target 4.6
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _Tess;
float4 tessFixed()
{
return _Tess;
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
거리 기반 테셀레이션
카메라와의 거리에 따라 테셀레이션 레벨을 조절하는 방법도 있습니다. 아래의 코드는 그런 기능을 보여줍니다.
Shader "Tessellation Sample" { Properties { _Tess ("Tessellation", Range(1,32)) = 4 _MainTex ("Base (RGB)", 2D) = "white" {} _DispTex ("Disp Texture", 2D) = "gray" {} _NormalMap ("Normalmap", 2D) = "bump" {} _Displacement ("Displacement", Range(0, 1.0)) = 0.3 _Color ("Color", color) = (1,1,1,0) _SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5) } SubShader { Tags { "RenderType"="Opaque" } LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessDistance nolightmap
#pragma target 4.6
#include "Tessellation.cginc"
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _Tess;
float4 tessDistance (appdata v0, appdata v1, appdata v2) {
float minDist = 10.0;
float maxDist = 25.0;
return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, minDist, maxDist, _Tess);
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
성능 최적화를 위한 테셀레이션
모서리 길이에 기반한 테셀레이션을 사용하면 더욱 효율적으로 디테일을 조절할 수 있습니다. 이 예제에서는 UnityEdgeLengthBasedTess 함수를 설명합니다.
Shader "Tessellation Sample" { Properties { _EdgeLength ("Edge length", Range(2,50)) = 15 _MainTex ("Base (RGB)", 2D) = "white" {} _DispTex ("Disp Texture", 2D) = "gray" {} _NormalMap ("Normalmap", 2D) = "bump" {} _Displacement ("Displacement", Range(0, 1.0)) = 0.3 _Color ("Color", color) = (1,1,1,0) _SpecColor ("Spec color", color) = (0.5,0.5,0.5,0.5) } SubShader { Tags { "RenderType"="Opaque" } LOD 300
CGPROGRAM
#pragma surface surf BlinnPhong addshadow fullforwardshadows vertex:disp tessellate:tessEdge nolightmap
#pragma target 4.6
#include "Tessellation.cginc"
struct appdata {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
float _EdgeLength;
float4 tessEdge (appdata v0, appdata v1, appdata v2)
{
return UnityEdgeLengthBasedTess(v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}
sampler2D _DispTex;
float _Displacement;
void disp (inout appdata v)
{
float d = tex2Dlod(_DispTex, float4(v.texcoord.xy,0,0)).r * _Displacement;
v.vertex.xyz += v.normal * d;
}
struct Input {
float2 uv_MainTex;
};
sampler2D _MainTex;
sampler2D _NormalMap;
fixed4 _Color;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Specular = 0.2;
o.Gloss = 1.0;
o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
}
ENDCG
}
FallBack "Diffuse"
}
퐁 테셀레이션
퐁 테셀레이션은 기하학적 형태의 세부 사항을 매끄럽게 만드는 데 효과적입니다. Unity에서는 퐁 테셀레이션을 간단하게 적용할 수 있습니다.
Shader "Phong Tessellation" { Properties { _EdgeLength ("Edge length", Range(2,50)) = 5 _Phong ("Phong Strengh", Range(0,1)) = 0.5 _MainTex ("Base (RGB)", 2D) = "white" {} _Color ("Color", color) = (1,1,1,0) } SubShader { Tags { "RenderType"="Opaque" } LOD 300
CGPROGRAM
#pragma surface surf Lambert vertex:dispNone tessellate:tessEdge tessphong:_Phong nolightmap
#include "Tessellation.cginc"
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
void dispNone (inout appdata v) { }
float _Phong;
float _EdgeLength;
float4 tessEdge (appdata v0, appdata v1, appdata v2)
{
return UnityEdgeLengthBasedTess(v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}
struct Input {
float2 uv_MainTex;
};
fixed4 _Color;
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
마무리
Unity의 테셀레이션 기능을 활용하면 3D 모델에 더 많은 디테일과 부드러움을 부여할 수 있습니다. 따라서 적절한 테셀레이션 기법을 선택하여 사용하면 완료된 씬의 품질을 크게 향상시킬 수 있습니다.
이 문서에서 제공된 다양한 예제를 통해 필자의 기법을 실습해보세요. Unity에서 아름답고 상세한 게임 환경을 만드는 데 도움이 될 것입니다.