サーフェスシェーダーの記述
サーフェイスシェーダーでのカスタムライティングモデル

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

Surface Shader の例です。以下の例は、組み込みライティングモデルの使用に集中しています。カスタムのライティングモデルの実装方法については、サーフェイスシェーダーライティングの例 にあります。

簡単

非常に基本的なシェーダーとその作成から始めましょう。以下は、表面の色を「白」にするだけのシェーダーです。これは、組み込み 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 つの ライト が設定されたモデルでどのように見えるかです。

テクスチャ

真っ白なオブジェクトはかなりつまらないので、テクスチャを追加しましょう。プロパティー ブロックをこのシェーダーに追加するので、マテリアルでテクスチャセレクタを取得します。その他の変更は以下に太字で記載されています。

  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:functionNameinout 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 つのパラメーターを摂る必要があります。ここで組み込み値ではない入力メンバーを記入できます。

注記:この方法において使われる Custom 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 が、法線の絶対値に設定されます。

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

Final Color モディファイア

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

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

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

Final Color モディファイアによるカスタムフォグ

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

ここに、画面中心からの距離に基づいてフォグの色合いを適用するシェーダーがあります。これは、頂点モディファイアをカスタム頂点データ(fog)と final color モディファイアを組み合わせます。フォワードレンダリングの追加パスで使用されるとき、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"
  }

Linear Fog

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

デカール

一般的に、デカールは、ランタイムにマテリアルに詳細を追加するために使用されます (例えば、弾丸の衝撃)。デカールは、特に deferred レンダリングで効果を発揮するツールです。なぜなら、デカールは GBuffer がライティングをする前に GBuffer を変更し、そのため、効果が残るからです。

通常の場合は、デカールは不透明なオブジェクトの後にレンダリングし、以下のシェーダーラボの「Tags」に示すように、シャドウキャスターにすべきではありません。

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
    }
}
サーフェスシェーダーの記述
サーフェイスシェーダーでのカスタムライティングモデル