Version: 2023.2
言語: 日本語
サーフェスシェーダーとレンダリングパス
サーフェスシェーダーでのカスタムライティングモデル

サーフェスシェーダーの例

このページの サーフェスシェーダー の例では、ビルトインのライティングモデルの使用方法を説明しています。例えば、カスタムライティングモデルを実装する方法の例については、サーフェスシェーダーライティングの例 を参照してください。

ビルトインレンダーパイプラインでは、サーフェスシェーダーは、ライティングと相互作用するシェーダーを効率的に記述する方法です。

レンダーパイプラインの互換性

機能名 ビルトインレンダーパイプライン ユニバーサルレンダーパイプライン (URP) HD レンダーパイプライン (HDRP) カスタム SRP
サーフェスシェーダー 不可

URP で Shader オブジェクトを効率的に作成する方法については、Shader Graph を参照してください。
不可

HDRP で Shader オブジェクトを効率的に作成する方法については、Shader Graph を参照してください。
なし

シンプルなシェーダーの例

非常に基本的なシェーダーとその作成から始めましょう。以下は、表面の色を “白” にするだけのシェーダーです。これは、ビルトインのディフューズライト Lambert (デフューズ) ライティングモデルを使用します。

  Shader "Example/Diffuse Simple" {
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input {
            float4 color : COLOR;
        };
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = 1;
        }
        ENDCG
      }
      Fallback "Diffuse"
    }

以下は、2 つの ライト が設定されたモデルでどのように見えるかです。

テクスチャ

真っ白なオブジェクトはかなりつまらないので、テクスチャを追加しましょう。Property ブロックをこのシェーダーに追加して、マテリアルでテクスチャセレクターを取得します。

  Shader "Example/Diffuse Texture" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input {
            float2 uv_MainTex;
        };
        sampler2D _MainTex;
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

法線マップ

法線マップを追加しましょう。

  Shader "Example/Diffuse Bump" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _BumpMap ("Bumpmap", 2D) = "bump" {}
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input {
          float2 uv_MainTex;
          float2 uv_BumpMap;
        };
        sampler2D _MainTex;
        sampler2D _BumpMap;
        void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
          o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

リムライト

今度は、リムライトを追加して、オブジェクトの縁をハイライトします。表面の法線とビュー方向間の角度に基づいて、エミッシブライトを追加します。そのため、ビルトインの viewDir サーフェスシェーダー変数を使用します。

  Shader "Example/Rim" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _BumpMap ("Bumpmap", 2D) = "bump" {}
        _RimColor ("Rim Color", Color) = (0.26,0.19,0.16,0.0)
        _RimPower ("Rim Power", Range(0.5,8.0)) = 3.0
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float3 viewDir;
        };
        sampler2D _MainTex;
        sampler2D _BumpMap;
        float4 _RimColor;
        float _RimPower;
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
            o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
            half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));
            o.Emission = _RimColor.rgb * pow (rim, _RimPower);
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

ディテールテクスチャ

異なる効果を与えるため、ベーステクスチャと結合する細部テクスチャを追加しましょう。細部テクスチャは、同じ UV を使用しますが、通常、マテリアルでのタイリングが異なるため、異なる入力 UV 座標を使用する必要があります。

  Shader "Example/Detail" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _BumpMap ("Bumpmap", 2D) = "bump" {}
        _Detail ("Detail", 2D) = "gray" {}
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float2 uv_Detail;
        };
        sampler2D _MainTex;
        sampler2D _BumpMap;
        sampler2D _Detail;
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
            o.Albedo *= tex2D (_Detail, IN.uv_Detail).rgb * 2;
            o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

チェッカーテクスチャを使用してもあまり実用的な意味はありませんが、この例では何が起こるかを示すためにチェッカーテクスチャを使用します。

スクリーンスペースのディテールテクスチャ

スクリーンスペースでの細部テクスチャを兵士の頭部モデルに施しても実際的にはあまり意味はありませんが、ここでは、ビルトインの screenPos 入力がどのように使用されるかを説明するために使用します。

  Shader "Example/ScreenPos" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _Detail ("Detail", 2D) = "gray" {}
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input {
            float2 uv_MainTex;
            float4 screenPos;
        };
        sampler2D _MainTex;
        sampler2D _Detail;
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
            float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
            screenUV *= float2(8,6);
            o.Albedo *= tex2D (_Detail, screenUV).rgb * 2;
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

短くするために、上記シェーダーから法線マップを削除しました。

キューブマップの反射

以下はビルトインの worldRefl 入力を使用してキューブマップによる反射を行うシェーダーです。ビルトインの反射/ディフューズシェーダーを使うのと非常に似ています。

  Shader "Example/WorldRefl" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _Cube ("Cubemap", CUBE) = "" {}
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input {
            float2 uv_MainTex;
            float3 worldRefl;
        };
        sampler2D _MainTex;
        samplerCUBE _Cube;
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * 0.5;
            o.Emission = texCUBE (_Cube, IN.worldRefl).rgb;
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

Emission として反射色を割り当てるので、ぴかぴかに輝いた兵士が得られます。

法線マップに影響される反射を行たい場合、わずかに関与させる必要があります。INTERNAL_DATAInput 構造体に追加し、WorldReflectionVector 関数を使って、法線出力記述後にピクセルごとの反射ベクトルを計算する必要があります。

  Shader "Example/WorldRefl Normalmap" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _BumpMap ("Bumpmap", 2D) = "bump" {}
        _Cube ("Cubemap", CUBE) = "" {}
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float3 worldRefl;
            INTERNAL_DATA
        };
        sampler2D _MainTex;
        sampler2D _BumpMap;
        samplerCUBE _Cube;
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * 0.5;
            o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
            o.Emission = texCUBE (_Cube, WorldReflectionVector (IN, o.Normal)).rgb;
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

これは、法線マッピングされた輝いている兵士です。

ワールド空間の位置によるスライス

以下は、ほとんど水平に近い輪でピクセルを破棄することで、ゲームオブジェクトを “スライス” するシェーダーです。これは、ピクセルのワールド位置に基づいて、clip() Cg/HLSL 関数を使用して行います。ビルトインの worldPos サーフェスシェーダー変数を使用します。

  Shader "Example/Slices" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _BumpMap ("Bumpmap", 2D) = "bump" {}
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        Cull Off
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float3 worldPos;
        };
        sampler2D _MainTex;
        sampler2D _BumpMap;
        void surf (Input IN, inout SurfaceOutput o) {
            clip (frac((IN.worldPos.y+IN.worldPos.z*0.1) * 5) - 0.5);
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
            o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

頂点モディファイアによる法線の押し出し

“頂点モディファイア” 関数を使用することができます。“頂点モディファイア” は入ってくる頂点データを頂点シェーダーで修正します。これは、手続き型アニメーションや法線に沿った押し出しなどに使用できます。サーフェスシェーダーのコンパイルディレクティブ vertex:functionName は、inout appdata_full パラメーターを取る関数とともにそのために使われます。

以下は、マテリアルで指定した量だけ法線に沿って頂点を動かすシェーダーです。

  Shader "Example/Normal Extrusion" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _Amount ("Extrusion Amount", Range(-1,1)) = 0.5
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert vertex:vert
        struct Input {
            float2 uv_MainTex;
        };
        float _Amount;
        void vert (inout appdata_full v) {
            v.vertex.xyz += v.normal * _Amount;
        }
        sampler2D _MainTex;
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

法線に沿って頂点を動かすことで、兵士が太った外見になります。

頂点ごとに計算されるカスタムデータ

頂点モディファイア関数を使用すると、頂点シェーダー内のカスタムのデータを計算でき、これが、ピクセルごとにサーフェスシェーダー関数に渡されます。同じコンパイルディレクティブ vertex:functionName が使用されますが、この関数は、inout appdata_fullout Input の 2 つのパラメーターをとる必要があります。ここでビルトインの値ではないInput メンバーを記入できます。

ノート: この方法で使うカスタムのInput メンバーは ‘uv’ で始まる名前にしてはいけません。正常に動作しなくなります。

下の例では、カスタムの float3 customColor メンバーを定義しますが、これは、頂点関数で計算されます。

  Shader "Example/Custom Vertex Data" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert vertex:vert
        struct Input {
            float2 uv_MainTex;
            float3 customColor;
        };
        void vert (inout appdata_full v, out Input o) {
            UNITY_INITIALIZE_OUTPUT(Input,o);
            o.customColor = abs(v.normal);
        }
        sampler2D _MainTex;
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
            o.Albedo *= IN.customColor;
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

この例では、customColor が、法線の絶対値に設定されます。

より実用的な使用方は、ビルトインの Input 変数によって与えられない頂点ごとのデータを算出すること、あるいは、シェーダーの演算を最適化することです。例えば、ピクセルごとにサーフェスシェーダーでリムライティングを計算する代わりに、オブジェクトの頂点でそれを行うことが可能です。

最終色のモディファイア

シェーダーで計算された最終的な色を変更する “最終色のモディファイア” 関数を使用することが可能です。サーフェスシェーダーのコンパイルディレクティブ finalcolor:functionNameInput IN, SurfaceOutput o, inout fixed4 color パラメーターをとる関数とともに使用されます。

ここでは、最終的な色に濃淡を適用する簡単なシェーダーです。これはサーフェスのアルベド色に単に薄い色合いを適用することとは異なります。この色合いは、ライトマップ、ライトプローブや類似した追加のソースなど外部からもらって来たどんな色にでも作用します。

  Shader "Example/Tint Final Color" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _ColorTint ("Tint", Color) = (1.0, 0.6, 0.6, 1.0)
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert finalcolor:mycolor
        struct Input {
            float2 uv_MainTex;
        };
        fixed4 _ColorTint;
        void mycolor (Input IN, SurfaceOutput o, inout fixed4 color)
        {
            color *= _ColorTint;
        }
        sampler2D _MainTex;
        void surf (Input IN, inout SurfaceOutput o) {
             o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

最終色のモディファイアによるカスタムのフォグ

最終的な色のモディファイアを使った一般的な例 (上記参照) では、フォワードレンダリングで完全なカスタムフォグが実行されます。フォグは最終的に計算されたピクセルシェーダーの色に影響を与える必要があり、それはまさに finalcolor モディファイアが行っていることです。

ここに、画面中心からの距離に基づいてフォグの色合いを適用するシェーダーがあります。これは、頂点モディファイアをカスタム頂点データ (fog) と最終色モディファイアを組み合わせます。フォワードレンダリングの追加パスで使用されるとき、フォグは、黒色にフェードする必要があります。この例ではそれを処理し、UNITY_PASS_FORWARDADD の確認を行います。

  Shader "Example/Fog via Final Color" {
      Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _FogColor ("Fog Color", Color) = (0.3, 0.4, 0.7, 1.0)
      }
      SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert finalcolor:mycolor vertex:myvert
        struct Input {
            float2 uv_MainTex;
            half fog;
        };
        void myvert (inout appdata_full v, out Input data)
        {
            UNITY_INITIALIZE_OUTPUT(Input,data);
            float4 hpos = UnityObjectToClipPos(v.vertex);
          hpos.xy/=hpos.w;
            data.fog = min (1, dot (hpos.xy, hpos.xy)*0.5);
        }
        fixed4 _FogColor;
        void mycolor (Input IN, SurfaceOutput o, inout fixed4 color)
        {
            fixed3 fogColor = _FogColor.rgb;
            #ifdef UNITY_PASS_FORWARDADD
            fogColor = 0;
            #endif
            color.rgb = lerp (color.rgb, fogColor, IN.fog);
        }
        sampler2D _MainTex;
        void surf (Input IN, inout SurfaceOutput o) {
             o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
      } 
      Fallback "Diffuse"
    }

リニアフォグ

Shader "Example/Linear Fog" {
  Properties {
    _MainTex ("Base (RGB)", 2D) = "white" {}
  }
  SubShader {
    Tags { "RenderType"="Opaque" }
    LOD 200
    
    CGPROGRAM
    #pragma surface surf Lambert finalcolor:mycolor vertex:myvert
    #pragma multi_compile_fog

    sampler2D _MainTex;
    uniform half4 unity_FogStart;
    uniform half4 unity_FogEnd;

    struct Input {
      float2 uv_MainTex;
      half fog;
    };

    void myvert (inout appdata_full v, out Input data) {
      UNITY_INITIALIZE_OUTPUT(Input,data);
      float pos = length(UnityObjectToViewPos(v.vertex).xyz);
      float diff = unity_FogEnd.x - unity_FogStart.x;
      float invDiff = 1.0f / diff;
      data.fog = clamp ((unity_FogEnd.x - pos) * invDiff, 0.0, 1.0);
    }
    void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) {
      #ifdef UNITY_PASS_FORWARDADD
        UNITY_APPLY_FOG_COLOR(IN.fog, color, float4(0,0,0,0));
      #else
        UNITY_APPLY_FOG_COLOR(IN.fog, color, unity_FogColor);
      #endif
    }

    void surf (Input IN, inout SurfaceOutput o) {
      half4 c = tex2D (_MainTex, IN.uv_MainTex);
      o.Albedo = c.rgb;
      o.Alpha = c.a;
    }
    ENDCG
  } 
  FallBack "Diffuse"
}

デカール

一般的に、デカールは、ランタイムにマテリアルに詳細を追加するために使用されます (例えば、弾丸の跡など)。デカールは、特に古いレンダリングで効果を発揮するツールです。なぜなら、デカールは GBuffer がライティングをする前に GBuffer を変更するため、パフォーマンスを節約できます。

通常の場合は、デカールは不透明なオブジェクトの後にレンダリングされるべきであり、以下のシェーダーラボの “Tags” に示すように、影の投影 (Shadow Casting) をすべきではありません。

Shader "Example/Decal" {
  Properties {
    _MainTex ("Base (RGB)", 2D) = "white" {}
  }
  SubShader {
    Tags { "RenderType"="Opaque" "Queue"="Geometry+1" "ForceNoShadowCasting"="True" }
    LOD 200
    Offset -1, -1
    
    CGPROGRAM
    #pragma surface surf Lambert decal:blend
    
    sampler2D _MainTex;
    
    struct Input {
      float2 uv_MainTex;
    };
    
    void surf (Input IN, inout SurfaceOutput o) {
        half4 c = tex2D (_MainTex, IN.uv_MainTex);
        o.Albedo = c.rgb;
        o.Alpha = c.a;
      }
    ENDCG
    }
}
サーフェスシェーダーとレンダリングパス
サーフェスシェーダーでのカスタムライティングモデル