Unityで頂点単位の単一平行光源による拡散反射のシェーディングを実装する

はじめに

前回はプロパティでシェーダに渡す値を指定できるようになりました。

前回までのシェーダは陰がありませんでした。 今回は頂点単位のシェーディングに手を出してみます。

陰のありなし

Unityのバージョン: 2018.2.6f1

影と陰

物体の影にはシャドーイングとシェーディングがあります。 日本語でそれぞれを影と陰のように別の漢字を当てることもあります。 物体の影が床や地面に落ちるのがシャドーイングで、 物体の表面に色の陰影がつくのがシェーディングです。

シェーディングとシャドーイング

今回実装するのはシェーディングになります。

光源の種類

光が物体を照らすときは、光を発する光源があります。 この光源は光の向きに応じて平行光源と点光源に分けられます。

平行光源と点光源

今回は平行光源を扱います。 これは太陽のような無限に遠いところにある光源から発せられる光が 平行になるのをモデル化したものです。

拡散反射

物体の表面で光がどのように反射し、それによりどのような色になるかは 光の情報と物体の表面の情報で決まります。 物体の表面での反射には拡散反射と環境反射、鏡面反射があります。

今回実装するのは拡散反射(diffuse reflection)です。 ザラザラした表面やつや消しの塗料などの反射の仕方になります。 物体の表面にあたった光はすべての方向に拡散されて反射されるものとします。

拡散反射

拡散反射では、光があたった面の色は面の法線と面の色、そして光の向きと色で決めます。 面の法線の方向と光の向きのなす角をθ\thetaとすると、拡散反射による面の色は 次のようになります。

拡散反射による面の色 = 光の色 * 面の色 * cosθ\cos\theta

拡散反射では、あたった光はすべての方向に均等に拡散するので、 どの方向から見ても明るさは同じになります。

拡散反射はどこから見ても同じ色

シェーダの実装

今回はプロパティでライトへの方向を渡してやることにします。 ライトの方向とは逆方向のベクトルを与えることになります。

Shader "MyShader/VertexDiffuse"
{
  Properties
  {
    _LightDirection ("Light Direction", Vector) = (1.0, 1.0, -1.0, 1.0)
    _Ldiffuse ("Ldiffuse", Vector) = (1.0, 1.0, 1.0, 1.0)
    _Kdiffuse ("Kdiffuse", Vector) = (1.0, 1.0, 1.0, 1.0)
  }
  SubShader
  {
    Pass
    {
      CGPROGRAM

      ...

      uniform float3 _LightDirection;
      uniform float3 _Ldiffuse;
      uniform float3 _Kdiffuse;

      ...

      ENDCG
    }
  }
}

ライトの方向とライトの強さ、そして物体の反射の強さをそれぞれVectorで定義しました。 ライトの強さと反射の強さによって、それぞれライトの色と物体の色が決まります。

次に頂点シェーダの入力と出力、そしてフラグメントシェーダの入力について。

struct appdata
{
  float4 vertex : POSITION;
  float3 normal : Normal;
};

struct v2f
{
  float4 vertex : SV_POSITION;
  float3 color : COLOR;
};

頂点シェーダの入力にNORMALセマンティクスを追加して法線情報を手に入れます。 頂点シェーダからフラグメントシェーダへCOLORセマンティクスで色情報を受け渡します。

void vert(in appdata v, out v2f o){
  o.vertex = UnityObjectToClipPos(v.vertex);
  float3 normal = UnityObjectToWorldNormal(v.normal);
  float3 l = normalize(_LightDirection);
  o.color = _Ldiffuse * _Kdiffuse * max(dot(normal, l), 0);
}

最初に頂点の座標変換を行っています。 これについては以前解説しました。

その後、法線とライトへの方向ベクトルを計算しています。 計算はワールドスペースで行っています。 最初に法線ベクトルをモデル座標系からワールド座標系に直しています。 ライトへの方向ベクトルは正規化をしています。

dotでライトベクトルと法線ベクトルの内積をとっています。 2つの単位ベクトルの内積は、それらのなす角の余弦に等しいことを利用しています。 内積は負の数になる場合があるのでmaxで0より小さい時0を返すようにします。

この内積の値に光の色と面の色をかけ合わせて色を計算しています。

float4 frag (v2f i) : SV_Target
{
  return float4(i.color, 1);
}

フラグメントシェーダは頂点シェーダで出力した色をそのまま表示します。

実行結果

シェーダから作ったマテリアルをSphereのMeshRendererに渡しました。

スクリーンショット

スクリーンショット

スクリーンショット

注意深く観察すると、頂点ごとに色が補間されているのがわかりますね。

おわりに

今回は拡散反射のモデルを実装しました。

ソースコードはこちらのリポジトリにおいてあります。

  • Unity
  • Shader