Surface Shaders はDirectX 11 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 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
頂点モディファイアは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 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
は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 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"
}
ここでテッセレーション関数は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 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"
}
ここではまた,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マッピングなしでも表面がより丸くなることが判ります。 the surface becomes more round.