Version: 2018.4
言語: 日本語
サーフェスシェーダーライティングの例
頂点シェーダーとフラグメントシェーダーの記述

DX11/OpenGL コアのテッセレーションを伴うサーフェスシェーダー

Surface Shader は DirectX 11/OpenGL コア の GPU テッセレーションを一部サポートします。つまり、

  • テッセレーションは tessellate:FunctionName モディファイアにより示されます。この関数は三角形の辺と内部のテッセレーション係数を計算します。
  • テッセレーションが使用されている場合、“vertex modifier” (vertex:FunctionName) がテッセレーションの 後に、ドメインシェーダーの生成された各頂点に対して実行されます。通常は、ディスプレースメントマッピングです。
  • サーフェスシェーダーは任意で フォンテッセレーション を計算し、ディスプレースマッピングなしでも、モデルの表面を滑らかにします。

現時点でのテッセレーションのサポートに関する制限

  • 三角形ドメインのみをサポートしており、四角形や等値線テッセレーションはサポートしていません。
  • テッセレーションを使用すると、シェーダーはシェーダーモデル 4.6 ターゲット に自動的にコンパイルされ、古いグラフィックスターゲットでの実行をサポートしなくなります。

GPU テッセレーションなし、頂点モディファイアにディスプレースメントあり

次の例は、テッセレーションを使用せずに、いくらかのディスプレースメントマッピングを行うサーフェスシェーダーを示しています。ディスプレースメントマップに基づく数量によって頂点を法線に沿って移動させます。

    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) が使用されています。これはここではまだ必要ありませんが、テッセレーションが可能な限り小さい構造体を使用するのに効果的です。
  • 上の頂点データは 2 番目の UV 座標を持たないため、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 テッセレーション関数は、1 つの float4 値として 4 つのテッセレーション係数を返します。三角形の各辺の 3 つの係数、三角形の内側の 1 つの係数です。

この例では、Material プロパティで設定される定数値を返します。


距離にもとづいたテッセレーション

テッセレーションのレベルは、カメラからの距離に基づいて変更することもできます。例えば、2 つの距離の値を定義することができます。

  • テッセレーションが最大であるときの距離 (例えば、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 関数を呼び出します。この関数は、各頂点のカメラからの距離を計算し、最後のテッセレーション係数を導き出します。


辺の長さにもとづいたテッセレーション

純粋に距離ベースのテッセレーションは、三角形のサイズがかなり近い場合にのみ有効です。上の画像では、小さな三角形を持つゲームオブジェクトはテッセレーションがあまりにも多く、三角形の大きなゲームオブジェクトは十分テッセレーションされていません。

これを改善する方法の 1 つは、画面上の三角形の辺の長さに基づいてテッセレーションのレベルを計算することです。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 の作業を大幅に節約できます。


フォンテッセレーション

フォンテッセレーション (Phong Tessellation) は細分化した面の位置を修正して、結果として表面がメッシュの法線にある程度沿うようにします。低ポリゴンのメッシュを滑らかにするのにかなり効果的な方法です。

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"
        }

下の例は、通常のシェーダー (上の段) とフォンテッセレーションを使用するシェーダー (下の段) の比較です。ディスプレースメントマッピングを一切行わない場合でも、表面はより滑らかになります。


  • 2018–03–20 編集レビュー を行って修正されたページ
  • Tessellation for Metal は 2018.1 で追加
サーフェスシェーダーライティングの例
頂点シェーダーとフラグメントシェーダーの記述