Surface Shader は DirectX 11 / OpenGL Core GPU テッセレーションを一部サポートします。具体的には以下の通りです。
tessellate:FunctionName
修飾子により指定されます。この関数は三角形の辺とテッセレーション要素の内部を計算します。vertex:FunctionName
) がテッセレーションの 後に、ドメインシェーダーの中のおのおの生成された頂点に対して、実行されます。通常は Displacement マッピングです。現時点でのテッセレーションのサポートに関する制約
初めに、テッセレーション なし で Displacement マッピングを行うサーフェイスシェーダーを見ていきます。頂点を法線に沿って、Displacement マップから与えられた量に基づき移動します。
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
頂点モディファイアは Displacment マップをサンプリングし、頂点を法線に沿って移動します。appdata
)を、デフォルトの appdata_full
の代わりに、使用します。これはまだ必要ありませんが、テッセレーションでできるかぎり小さい構造を使用するのに効果的です。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
は 4 つのテッセレーション要素をひとつの float4 の値として戻します。三角形の辺ごとに 3 つの要素、そして 1 つの要素で三角形の内部です。次に、マテリアルプロパティーに設定する定数値を戻します。
カメラからの距離にもとづいてテッセレーションのレベルを変更することができます。例えば、テッセレーションが最大になる距離(例えば 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 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"
}
ここでテッセレーション関数は 3 つの引数を持ちます。テッセレーション前の、3 つの三角形の角の頂点データです。これは、頂点の位置に依存するとここで仮定したテッセレーションレベルを計算するのに必要です。内蔵のヘルパーファイル 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 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"
}
ここではまた、UnityEdgeLengthBasedTess
関数を Tessellation.cginc
ファイルから呼び出すだけで必要な処理を行わせます。
パフォーマンス上の理由から、UnityEdgeLengthBasedTessCull
を呼ぶことが推奨され、Patch Frustum カリングが行われます。これによりシェーダーがより高価になりますが、カメラレビュー外にあるメッシュ部分の GPU 処理を軽減します。
Phong Tessellation は、結果となる表面がメッシュの法線にある程度沿うように、再分割( subdivide )された面の位置を修正します。ローポリのメッシュについてスムージングするのにかなり効果的な方法です。
Unity のサーフェイスシェーダーは、tessphong:VariableName
ディレクティブを使用することで、Phong テッセレーションを自動計算します。次にシェーダーでの例を示します。
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"
}
次に通常のシェーダー(上列)と Phong テッセレーションを使用したシェーダー(下列)の比較です。Displacement マッピングなしでも 表面はより丸くなることがわかります。