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

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

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

放出として反射色を割り当てるので、非常に輝いた兵士が得られます。

法線マップに影響される反射を行たい場合、わずかに関与させる必要があります。INTERNAL_DATA を入力構造に追加し、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 modifier 関数を使用することができます。これは、手続き型アニメーションや法線に沿った押し出しなどに使用できます。このため、サーフェイスシェーダーコンパイルディレクティブ 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_full out 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 が、法線の絶対値に設定されます。

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

最終的なカラー修飾子(Color Modifier)

シェーダーで計算される最終的な色を変更する “final color modifier” 関数を使用することが可能です。サーフェスシェーダーのコンパイルディレクティブ finalcolor:functionName は、Input 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 Modifier によるカスタムフォグ

最終的なカラー変更のための一般的な使用例(上記参照)は、フォワードレンダリングの完全なカスタムFogが実装可能になったことです。Fog は、正確に finalcolor 修飾子が何を行うか最終的に計算されるピクセルのシェーダーカラーに影響を与える必要があります。

これは、画面中心からの距離に基づいてフォグの色合いを適用するシェーダーです。これは、最終的な color 修飾子とカスタム頂点データ(fog)による頂点修飾子の両方を組み合わせます。Forward Rendering の Additive パスで使用されるとき、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 = mul (UNITY_MATRIX_MVP, 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(mul (UNITY_MATRIX_MV, 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
  } 
サーフェスシェーダーの記述
サーフェイスシェーダーでのカスタムライティングモデル