표면 셰이더는 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 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"
}
위 셰이더는 표준에 가깝습니다.
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 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"
}
위 예제에서 tessFixed
테셀레이션 함수는 네 개의 테셀레이션 팩터(삼각형 모서리 각각에 대한 팩터 3개, 삼각형 내부에 대한 팩터 1개)를 단일 float4 값으로 반환합니다.
예제에서는 머티리얼 프로퍼티에 설정된 상수 값을 반환합니다.
카메라로부터의 거리에 기반하여 테셀레이션 레벨을 변경할 수도 있습니다. 예를 들어 다음과 같이 두 개의 값을 정의할 수 있습니다.
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"
}
여기에서 테셀레이션 함수는 테셀레이션 전 세 개의 삼각형 꼭짓점에 대한 버텍스 데이터를 세 개의 파라미터로 받습니다.
Unity는 이를 통해 버텍스 포지션에 따라 달라지는 테셀레이션 레벨을 계산합니다.
예제에는 빌트인 헬퍼 파일 Tessellation.cginc가 포함되어 있고 이 파일에서 UnityDistanceBasedTess
함수를 호출하여 모든 작업을 수행합니다. 이 함수는 각 버텍스에서 카메라까지의 거리를 계산하고 최종 테셀레이션 팩터를 파생합니다.
순수 거리 기반 테셀레이션은 삼각형 크기가 상당히 유사한 경우에만 효과적입니다. 위 이미지에서 작은 삼각형이 포함된 게임 오브젝트에는 테셀레이션이 지나치게 적용되었고 큰 삼각형이 포함된 게임 오브젝트에는 테셀레이션이 충분히 적용되지 않았습니다.
이런 문제를 해결하기 위한 한 가지 방법은 화면상의 삼각형 모서리 길이에 기반하여 테셀레이션 레벨을 계산하는 것입니다. Unity는 더 큰 테셀레이션 팩터를 더 긴 모서리에 적용합니다.
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"
}
이 예제에서는 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"
}
다음은 일반 셰이더(상단 행)와 퐁 테셀레이션을 사용한 셰이더(하단 행)의 차이를 비교합니다. 변위 매핑이 없어도 표면이 더 둥글게 변한 것을 볼 수 있습니다.