ライティングの設定

前回、最後に書いたシェーダーでは青一色で塗りつぶされており、陰影がありませんでした。
これは光の計算を考慮せず、出力結果を単色で指定しているためです。

前回のシェーダーに Lighting On を加えてライティングを有効にします。

Shader "Custom/NewSurfaceShader" {
    SubShader {
        Pass {
            Color (0,0,1,1)
            Lighting On
        }
    }
}

この結果は、黒一色になります。

ライティングの基礎

先に進む前に、3D グラフィックスにおけるライティングの基礎を復習しておきます。

現実世界の人間が見ている視覚情報は、網膜に対する光の刺激から生じているため、
光のない場所では物は見えず、光のある場所では物から反射した光を通じて色を知覚します。

ここで抑えておきたい点は、目の前に赤色の物体が見える場合、
物体自身が持っている情報は「赤い色」ではなく「赤い成分を返す光の反射率」ということです。
3D グラフィックスでも同様に、物体に対しては表示色ではなく、光の反射性質を定義することになります。

ちなみに、近年発表された Vantablack は 99.965% の光を吸収する性質を持っており、
現実世界で限りなく光を反射しない物体はこのような見た目になります。
これは、光の反射性質が未定義であった冒頭のシェーダーの結果に近似します。

光の反射性質の定義について、Direct3D や OpenGL では、

  • Ambient
  • Diffuse
  • Specular
  • Emissive

の各要素を光の三原色で指定してライティング計算に用います。

Ambient は光源ベクトルに依存しない環境光。
Diffuse は光源ベクトルに依存する拡散反射光。
Specular は光源ベクトルとカメラベクトルに依存する鏡面反射光。
Emissive は光源色に依存しない自己発光。

例えば、代表的なライティング処理のランバート反射では、
光源と物体の拡散反射光で色成分の大半が決まり、輝度には各々のベクトルの向きが影響します。

ランバート反射において、平行光源と物体の面を対象とした場合、
拡散反射光の輝度は、光源ベクトル(\(\vec{L}\))と面の法線ベクトル(\(\vec{N}\))の内積なので、

$$ \vec{L} \cdot \vec{N} = |\vec{L}||\vec{N}|\cos \theta $$

になります。

この式の注目点は、光源ベクトルと面の法線ベクトルが正規化されている状態なら、
ベクトルのなす角 \(\theta\) が \(0^{\circ}\) のときが最大になるということです。

光源に対して水平の面が最も明るく、傾くにつれて暗くなるという現象は、直感的にも理解できると思います。

\(0^{\circ}\) の水平状態で輝度は \(1\)。
\(60^{\circ}\) 傾いた状態で輝度は \(0.5\)。
\(90^{\circ}\) 傾いた状態で輝度は \(0\) 。

この輝度を Diffuse の RGB 値にかけることで陰影が生まれ、
光源が面に届かない \(90^{\circ}\) では反射光の結果が \(0\) になることがわかります。

また、\(90^{\circ}\) を超えて光源が裏側にある場合は、内積の値が負になるため、
内積の正負からライティング対象を判断することが可能です。

本来は、光源と物体の距離に応じた光の減衰を考慮するべきですが、
Direct3D や OpenGL では平行光源の減衰計算を省略することが一般的です。

拡散反射光の確認

冒頭のシェーダーの黒一色は、光を反射せずに全て吸収している状態でした。
今回は緑色の Diffuse 値を設定して、拡散反射光の効果を確認します。

Shader "Custom/NewSurfaceShader" {
    SubShader {
        Pass {
            Material {
                Diffuse (0,1,0,1)
            }
            Lighting On
        }
    }
}

プロジェクトに設置されている「Directional Light」の光が反射され、上画面のようになります。
前述のとおり、光源ベクトルと物体の法線ベクトル関係が陰影として表れていることが確認できます。

法線ベクトルの算出

内積に用いた法線ベクトルについて補足しておきます。

面に対して垂直な法線ベクトルは、面を構成する 2 辺のベクトル(\(\vec{a}, \ \vec{b}\))の外積なので、

$$ \vec{a} \times \vec{b} = \begin{pmatrix} \vec{i} & \vec{j} & \vec{k} \\ a_1 & a_2 & a_3 \\ b_1 & b_2 & b_3 \end{pmatrix} \\[20pt] \vec{i} = (1, 0, 0) \quad \vec{j} = (0, 1, 0) \quad \vec{k} = (0, 0, 1) $$

になります。

行列を展開すると、

$$ \begin{eqnarray} \vec{a} \times \vec{b} &=& \begin{pmatrix} a_2 & a_3 \\ b_2 & b_3 \end{pmatrix} \vec{i} - \begin{pmatrix} a_1 & a_3 \\ b_1 & b_3 \end{pmatrix} \vec{j} + \begin{pmatrix} a_1 & a_2 \\ b_1 & b_2 \end{pmatrix} \vec{k} \\[10pt] &=& (a_2 b_3 - a_3 b_2, \ a_3 b_1 - a_1 b_3, \ a_1 b_2 - a_2 b_1) \end{eqnarray} $$

になり、これは \(\vec{a}\) と \(\vec{b}\) が構成する平面に対して垂直なベクトルです。
ベクトルの大きさは平面の面積なので、最終的には正規化して内積に用います。

このように 3D グラフィックスでは、法線には外積のベクトルを、表裏には内積のスカラーを利用することが基本になります。