표면 셰이더는 DirectX 11/OpenGL Core GPU 테셀레이션을 다음과 같이 일부 지원합니다.
tessellate:FunctionName
모디파이어(modifier)로 나타냅니다. 이 함수는 삼각형 에지와 내부 테셀레이션 팩터를 계산합니다.vertex:FunctionName
)가 테셀레이션 후에 도메인 셰이더에 생성된 각 버텍스에 대해 호출됩니다. 여기서 일반적으로 변위 매핑을 합니다.현재 테셀레이션 지원의 한계:
우선 테셀레이션 사용 없이 변위 매핑을 하는 표면 셰이더에서 시작해 봅시다. 이 셰이더는 단순히 변위 맵에서 오는 양에 기반하여 버텍스의 노멀을 따라 버텍스를 움직입니다.
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 5.0
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"
}
위 셰이더는 상당히 표준에 가깝습니다. 여기서 주목할 점은 다음과 같습니다.
disp
버텍스 모디파이어는 변위 맵을 샘플링하고 해당 노멀을 따라 버텍스를 움직입니다.appdata_full
대신 커스텀 “버텍스 데이터 입력” 구조(appdata
)를 사용합니다. 아직은 필요하지 않지만 테셀레이션에서는 가능한 한 작은 구조를 사용하는 편이 훨씬 효율적입니다.nolightmap
지시자를 추가합니다.다음은 간단한 오브젝트가 이 셰이더를 사용하면 어떻게 보이는지를 나타냅니다.
고정 크기 테셀레이션, 즉 전체 메시에 대해 동일 테셀레이션 레벨을 추가해 봅시다. 모델의 면이 스크린 크기와 대략 같을 경우 적합한 접근방식입니다. 카메라까지의 거리에 기반하여 일부 스크립트에서는 이후 코드에서 테셀레이션 레벨을 변경할 수 있습니다.
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 5.0
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"
}
이 셰이더에서 tessFixed
라는 이름의 테셀레이션 함수는 테셀레이션 팩터를 단일 float4 값으로 반환합니다. 이 값은 삼각형의 각 모서리에 대한 트리 팩터, 그리고 삼각형 내부 용의 팩터 하나로 이루어집니다. 여기서는 단지 머티리얼 프로퍼티에 설정된 상수 값을 반환합니다.
카메라로부터의 거리에 기반하여 테셀레이션 레벨을 변경할 수도 있습니다. 예를 들어, 다음과 같이 두 거리 값을 정의할 수 있습니다. 하나는 테셀레이션이 최대일 때의 거리(예: 10미터), 다른 하나는 테셀레이션 레벨이 점점 감소해 가는 거리(예: 20미터)입니다.
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 5.0
#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"
}
여기서 테셀레이션 함수는 세 개의 파라미터, 즉 테셀레이션 전의 세 삼각형 코너 버텍스 데이터를 받습니다. 이 파라미터를 가지고 이제 버텍스 포지션에 따라 달라지는 테셀레이션 레벨을 계산합니다. 내장형 헬퍼 파일 Tessellation.cginc
을 포함하고 이 파일로부터 UnityDistanceBasedTess
함수를 호출하여 모든 동작이 이루어지도록 합니다. 이 함수는 각 버텍스에서 카메라까지의 거리를 계산하고 최종 테셀레이션 팩터를 유도합니다.
순수 거리 기반 테셀레이션은 삼각형 크기가 상당히 유사한 경우에만 유용합니다. 위 이미지의 경우 작은 삼각형을 가지는 오브젝트는 테셀레이션이 지나치게 된 것을 볼 수 있으며 큰 삼각형을 가지는 오브젝트는 충분히 테셀레이션되지 않은 것을 확인할 수 있습니다.
대신 화면의 삼각형 모서리 길이를 기반으로 테셀레이션 레벨을 계산할 수도 있습니다. 모서리가 길수록 더 큰 테셀레이션 팩터가 적용됩니다.
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 5.0
#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"
}
여기서도 마찬가지로 모든 실제 작업을 처리하기 위해 Tessellation.cginc
에서 UnityEdgeLengthBasedTess
을 호출합니다.
성능 향상을 위해 권장되는 방법은 패치 절두체 컬링을 수행하는 UnityEdgeLengthBasedTessCull
함수를 대신 호출합니다. 이렇게 하면 셰이더 비용이 조금 더 들지만 카메라 뷰 바깥에 있는 메시 부분에 대한 GPU 작업을 상당히 절약할 수 있습니다.
퐁 테셀레이션은 세부 분할된 면의 포지션을 수정하여 그 결과로 얻어진 표면이 메시 노멀을 다소 따르도록 해 줍니다. 이 방식은 폴리곤이 적은 메시를 더 매끄럽게 만드는 데 상당히 효과적인 방법입니다.
Unity의 표면 셰이더는 tessphong:VariableName
컴파일 지시자를 사용하여 퐁 테셀레이션을 자동으로 계산할 수 있습니다. 아래는 예제 셰이더입니다.
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"
}
다음은 일반 셰이더(상단 행)와 퐁 테셀레이션을 사용한 셰이더(하단 행)의 차이를 비교합니다. 변위 매핑이 전혀 없이도 표면이 더 둥글게 변한 것을 볼 수 있습니다.