Skip to content

第07回 ベクトル演算とライティング

前回: 第06回 ノイズ / 次回: 第08回 内積とフレネル、外積と法線マップ

今回の授業内容

  • 位置と方向を表すベクトルを区別する
  • 正規化した法線とライト方向を扱う
  • 内積からLambertライティングを実装する
  • 外積から2方向に垂直な方向を求める
  • 反射ベクトルと屈折ベクトルの役割を理解する

ベクトルの基礎

複数の数値をまとめるベクトル

  • ベクトルは、数値の配列 です
  • 2Dなら(x, y)、3Dなら(x, y, z)、4Dなら(x, y, z, w)のように表します
  • CG の世界では、主に位置、方向、色などを表すのに使います
  • 色の RGB 値も、位置座標も、方向も、 シェーダー内ではベクトルとして扱われます
csharp
// C# でのベクトルの例
Vector2 position2D = new Vector2(x, y);
Vector3 position3D = new Vector3(x, y, z);
Vector4 color = new Vector4(r, g, b, a);

// Vector 構造体は
// 配列のようにもアクセスできます
float x = position3D[0]; // x 成分
float y = position3D[1]; // y 成分
float z = position3D[2]; // z 成分

color[0] = 1.0f; // r 成分を 1 にする
color[1] = 0.5f; // g 成分を 0.5 にする
color[2] = 0.0f; // b 成分を 0 にする
color[3] = 1.0f; // a 成分を 1 にする

3D空間でベクトルを使う理由

3DCG は、点、線、面、光などで構成される幾何学の世界です
位置や方向をベクトルで表せば、数学的な演算でそれらを操作できます

例えば加算や減算で、位置を移動させる操作や2点間の方向を求められます
色の情報も RGB を同時に扱うことで計算をまとめられます

さらに後ほど説明する内積や外積を使えば、 与えられた情報から新しい情報を導き出すことができます

ベクトルは、光の向きや面の向きを計算し、立体感や質感を表現できる 3DCG の世界の便利な共通言語です

位置と方向の違い

位置は、空間内のどこにあるかを表します

方向は、どちらへ向いているかを表します

速度は、方向と速さを同時に持つ値です
これらはすべて Vector3 で表せますが、同じ Vector3 でも何を表しているかによって計算の意味が変わってきます

シェーダーでは「このベクトルは位置なのか方向なのか」を意識して使い分ける必要があります

長さを1にそろえる正規化

ベクトルには大きさの情報も含まれていますが、 向きだけを使いたい場合があります

例えば、視線の方向やライトの方向などは、通常向きだけが重要で、大きさは必要ありません

ベクトルの向きだけを使いたい場合は、 ベクトルの大きさ(長さ)を得てからベクトル全体をその長さで割ります
これで大きさが 1 になり、向きだけを表すベクトルが得られます

この操作を正規化、Normalize と呼びます

ベクトルの長さと正規化の式

2次元ベクトル v = (x, y) の長さは $$|v| = \sqrt{x^2 + y^2}$$

3次元ベクトル v = (x, y, z) の長さは $$|v| = \sqrt{x^2 + y^2 + z^2}$$

ベクトルを正規化する式は $$\text{Normalize}(v) = \frac{v}{|v|}$$

csharp
// C# での正規化の例
Vector3 v = new Vector3(x, y, z);

// ベクトルの長さを計算
float length =
    Mathf.Sqrt(
          v.x * v.x +
          v.y * v.y +
          v.z * v.z);

// ベクトルを長さで割って正規化
Vector3 normalized = v / length;

// Unity には正規化のメソッドもあります
normalized = Vector3.Normalize(v);

内積でライティングを計算する

ライト方向

ライトの方向と面の関係
ライティングの例光の方向を向いている面は明るくなる

単純に考えると、 法線とライトの向きが同じなら面は明るく、 直角なら面は暗くなるはずです

ライトの方向は、光源から面に向かうベクトルで表せます

例えば日の光の場合、ライトの方向は太陽から地球に向かうベクトルになります

面に垂直な法線

曲面上の法線ベクトル
面の向きを表す法線ベクトルの例青のベクトルが法線出典: Geek3, Wikimedia Commons, CC0 1.0

法線は、面がどちらを向いているかを表すベクトルです

3D モデルの各面には法線が定義されており、 面の向きや立体感を表現するために使われます

ライティングでは、法線とライトの向きから面の明るさを計算します

法線は面の向きを表す重要な情報であり、 シェーダーの立体表現の基礎になります

2つの向きの近さを求める内積

方向ベクトル同士の向きの一致度を計算できれば、面の向きと光の向きから面の明るさを算出できます

その計算が 内積(Dot Product) です
内積は、2つのベクトルの成分ごとの積の和で計算されます $$ \text{Dot}(A, B) = A.x \cdot B.x + A.y \cdot B.y + A.z \cdot B.z $$

csharp
// C# での内積の例
Vector3 A = new Vector3(aX, aY, aZ);
Vector3 B = new Vector3(bX, bY, bZ);

// 内積を計算
float dot = A.x * B.x +
            A.y * B.y +
            A.z * B.z;

// Unity には内積のメソッドもあります
dot = Vector3.Dot(A, B);

Lambertライティング

Lambert ライティングは拡散反射の基本的なモデルです
光の方向と面の向きの関係 だけで 明るさを計算します

法線とライトの方向ベクトルの内積を計算し、 その値を明るさとして使えば、最小限のライティングが実現できます

単純ですが、例えば月の表面のようなマットな質感の表現には十分効果的です

Shader GraphでLambertライティングを作る

Lambert ライティングの実装例
Lambert ライティングの実装例

  1. Normal Vector Main Light Direction ノードを追加する
  2. Dot Product で法線とライト方向を計算する
  3. 明るくなる面の法線とライトの方向は逆向きなので、結果を Negate ノードで反転する
  4. Saturate で 0 から 1 に収める

外積で垂直な方向を求める

2つのベクトルに垂直なベクトル

外積と平行四辺形の関係
a と b の外積の模式図 a と b の外積は、a と b を辺とする平行四辺形の面に垂直なベクトルになる出典: Acdx, Wikimedia Commons, Public domain

ライティングには面の法線が必要です
ベクトルの 外積(Cross Product) は、 2つの方向ベクトルからその両方に垂直な新しい方向を作る演算です

面の上にある2つの辺の方向が分かれば、 その面に垂直な法線を求められます

外積の式

3次元ベクトル a = (aX, aY, aZ)b = (bX, bY, bZ) の外積は $$ \text{Cross}(a, b) = \begin{pmatrix} aY \cdot bZ - aZ \cdot bY \ aZ \cdot bX - aX \cdot bZ \ aX \cdot bY - aY \cdot bX \end{pmatrix} $$

csharp
// C# での外積の例
Vector3 a = new Vector3(aX, aY, aZ);
Vector3 b = new Vector3(bX, bY, bZ);

// 外積を計算
Vector3 cross = new Vector3(
    a.y * b.z - a.z * b.y,
    a.z * b.x - a.x * b.z,
    a.x * b.y - a.y * b.x);

// Unity には外積のメソッドもあります
cross = Vector3.Cross(a, b);

面の向きや空間の基準へ利用する

  • 面の法線を求める 三角形や四角形の辺の方向から、面に垂直な法線ベクトルを計算できます
  • ビルボードの向きを作る カメラ方向と上方向から、スプライトの横方向を求めれば常にカメラを向くビルボードが作れます
  • エフェクトの広がる方向を決める 進行方向に対して直交する向きを作れば、 軌跡や衝撃波の幅の方向に使うことができます

外積を使うときの注意点

外積ベクトルは、計算の順番が逆になると向きも逆になります a x b と b x a は逆向きのベクトルになります $$\text{Cross}(a, b) = -\text{Cross}(b, a)$$

外積ベクトルは大きさも変わります a と b が平行に近いときは、外積の長さが小さくなります
逆に、a と b が直交しているときは、外積の長さが最大になります $$|\text{Cross}(a, b)| = |a| \cdot |b| \cdot \sin(\theta)$$

外積を法線として使う場合は正規化して長さを 1 にする必要がありますが a と b が平行に近いときは外積の長さが 0 に近くなり正規化できなくなります

反射と屈折の方向

面で跳ね返る反射ベクトル

面、法線、入射方向、反射方向の関係
反射方向は入射方向と法線から求められる画像はAI生成

反射のベクトルを求めることができれば、 金属、鏡、水面などの光や視線を強く反射する表現に近づけることができます

反射方向は、入射方向と法線から求められます $$\text{Reflect}(I, N) = I - 2 \cdot \text{Dot}(I, N) \cdot N$$

Shader Graph では Reflect ノードで反射ベクトルを作れます

透明な物体で曲がる屈折ベクトル

簡易な屈折表現の例
簡易な屈折表現の例屈折を正確に扱うには光線の追跡が必要だが省略した表現でも効果は大きく、ゲームでよく使われる

屈折は、光が透明な物体を通るときに進む方向が変わる現象です

光が空気中から水やガラスに入るとき、 その進む向きが少し曲がります
屈折方向を計算で求めることができればガラスや水の表現に近づけることができます

Shader Graph では Refract ノードで屈折方向のベクトルを作れます

実習:内積でのライティング計算

基本にして奥義な立体表現

実習1:法線を色として表示する

  1. URP Unlit Shader Graph を作成する
  2. Normal Vector ノードを追加する
  3. 出力を Base Color に接続する
  4. Space を Object、World などに切り替える
  5. モデルの向きや形によって色が変わることを確認する

実習2:内積で面の明るさを計算する

  1. Vector3 プロパティ LightDirection を作る
  2. LightDirectionNormalize に接続する
  3. Normal VectorLightDirectionDot Product に接続する
  4. Saturate で 0 から 1 に収める
  5. 好きな色に Multiply して Base Color に接続する

完成条件

  • 法線を色として表示し、面ごとの方向を確認できる
  • 法線とライト方向を正規化して内積を計算できる
  • 内積をSaturateして面の明るさへ利用できる
  • 位置と方向、内積と外積の役割の違いを説明できる

今回のまとめ

  • 法線は面の向きを表す重要な情報です
  • 内積を用いて、法線とライト方向の一致度から面の明るさを求めることができます
  • 外積は、2つの向きから直交する新しい向きを作ります
  • 反射や屈折は、視線や光の向きを法線に応じて変化させる考え方です
  • これらのベクトル演算は、シェーダーで立体感や質感を表現するための基本的なツールになります

使用画像と出典