ぷろみん

プログラミング的な内容を扱ってます

3Dプログラマのための数学

概要

画像付きで詳細に解説しようとしたらモチベーションが足らなかったので下書きをブログで。
Qiitaとかだと数学記号使えるので恰好良く書ける反面書くの面倒。

内積

3Dプログラミングで一番出てくるやつ。
何故大人気かというと三角関数の中で一番高速で計算できるから。

内積

float DotProduct(const float3& a, const float3& b) {
    return a.x * b.x + a.y * b.y + a.z * b.z;
}

のように足し算と掛け算のみで計算できるので速い。
ちなみに引き算は問題ないが割り算は他の演算と比べるとちょっと遅い。
それでもcos関数とかsqrt関数と比べるとマシ。
一般的な認識はそんな所だと思うけど、最近のGPUやCPUは進化しているのでnvidiaの出している情報を見ると、この関数は1命令でいける、これは4命令かかるとかが書いてあったりするので決まった環境での最適化はもうかなり特化した人じゃないと難しそうです。
まー、今はモバイルも強いので上記の一般常識に従う方向でも良いでしょう。

話がそれました。
内積

DotProduct(a, b) = aベクトルの長さ * bベクトルの長さ * cos(aベクトルとbベクトルの作る角度);

とも表すことができるので、aベクトルとbベクトルが単位ベクトル(長さが1のベクトル)の場合aとbベクトルのcosが取得できます。
またaベクトルのbベクトル成分が欲しい場合はaベクトルとbの単位ベクトルの内積をとってやればaの長さ * cosとなり取得できます。
この操作は射影(Projection)とも呼ばれaベクトルからbベクトルに垂線を引いた時の長さに相当します。
3Dでこの情報が欲しい場合の例としては法線ベクトルや接線ベクトル成分を取得したい場合等があります。

長さを求めるのは大変

長さを求めるためにはsqrt関数を使わないといけません。これは他の演算と比べると重いので使いたくありません。
なので長さの2乗を使います。長さの2乗は

return x * x + y * y + z * z;

で取得できるので高速です。
長さの2乗を計算する関数が用意されていない環境ではDotProduct(a, a)のように内積で代用したりします。

ベクトルは長さをもっている

ベクトルaを考えるとき、aベクトルは

aベクトルの長さ * aベクトルの単位ベクトル

と考えることもできます。

ここから一切の特殊な演算を使わずにaベクトルのbベクトル成分ベクトルを求めることができます。

aベクトル * DotProduct(a, b) / DotProduct(a, a)

上記演算の式を整理すると

aの単位ベクトル * bの長さ * cos

となり、aとbの長さを求めていないのにaとbの長さが必要な演算ができてしまうのです。
単位ベクトルを求めるためにはaベクトル / aベクトルの長さを計算する必要がありますからね。

外積

外積は計算上は

return float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);

これはやはり引き算と掛け算のみなので高速です。
しかし、演算結果にベクトルを含んでしまうので扱いにくいです。
意味としては

aの長さ * bの長さ * sin(aとbの作る角) * aとbが作る面の法線単位ベクトル

で表されます。 sinとcosは相反するほぼ同じ意味を持つので、扱いにくい外積よりも内積を使うというわけですね。

最後に

はい、「3Dプログラマのための数学」なんて言っておきながら足し算と引き算と掛け算しかしませんでした。

数学関数を高速動作させるやべーやつも研究されていますが、内積で解決できる問題は内積で解決するのが一番速いです。 github.com