ホームゲームつくろー!衝突判定編<内積と外積の使い方

基礎の基礎編
その1 内積と外積の使い方

 
 この章では3Dゲームの特に衝突判定に無くてはならない「内積・外積」というベクトルの基本的な演算についてお話します。内積は高校で、外積はたぶん大学で習います。そのきちんとした意味を理解するのは大切ですが、ゲームで使う上では性質を体得する方が近道かと思います。そのためにはイメージが大切です。

 この章ではゲームで使用するベクトルの内積や外積をイメージと一緒に見ていこうと思います。



@ 方向と大きさを表せる「ベクトル」

 この記事をご覧になっている方の多くはきっと高校生以上だと思います(そうでない方は賞賛に値します!立派なプログラマーになれますよ(^-^))。高校の頃には必ず「ベクトル(vector)」を習います。ベクトルは「方向と大きさを表す方法」です。下の図をご覧下さい:

 見た目平面ですが、ゲームでは主に3次元のベクトルを使います。高校の時のベクトルのイメージは2次元です。どちらもベクトルですが、扱いがちょっと異なりますので注意が必要になります。

 上の図を見ると、ベクトルには始点と終点があるように思ってしまいます。しかし気を付けて下さい。ベクトル自身は「方向と大きさを表す記法」であって、位置はそこに含まれません。図にある(x,y,z)というのはあくまでも方向と大きさです。ですから、ベクトルは座標のどの場所にでも置けて、その位置の向きと大きさを示す事ができるんです。

 ベクトルは足し算ができます。方向と大きさを足すというのは、次のような2つのイメージができます:

 左の図は軌跡の足し算だと思うと良いと思います。まずベクトルAだけ進みます。次に進んだ先からベクトルBに方向転換。結果として終点にたどり着きます。実は2つのベクトルを足し算すると、必ず始点と終点を結んだベクトル(赤いベクトル)になります。これはベクトルを何千回足そうと同じになります。

 一方右側は物理の特に力などでお世話になるベクトルの足し算です。AさんとBさんがそれぞれ右図のような方向と大きさで根元を引っ張るとします。すると、根元は赤い矢印の方向と大きさに行こうとします。感覚的にもわかりますよね。2つの力の合成ならまだ良いのですが、これが何十と重なると見た目では分からなくなりますが、ベクトルを足し算するとちゃんと合成結果の方向(赤い矢印)が出てきます。

 ベクトルの足し算は、各成分を足し算するだけです。すなわち、

 A + B = ( Ax + Bx, Ay + By, Az + Bz )

です。

 ベクトルの引き算は足し算に比べるとそのままではイメージし辛いものです。しかし、引き算と言うのは「マイナスなものを足した物」でもあります。ベクトルをマイナス(符号転換)すると同じ大きさで方向がま逆になります。矢印の頭が逆につくわけです。となると、結局は足し算で考えられる事になります:

 A - B = A + (-B)

 引き算で迷ったら、引く側を反対にして足し算にすると、イメージが急に沸く事が多いのでお勧めです。

 足す引くと来ると次は「掛ける」「割る」です。でも、さすがに方向と大きさを掛けるというのはイメージが飛び越えています。実は、ベクトル同士の掛け算(割り算)は定義されていません。ただし、「定数倍」は定義されています。例えばA
 =(2,3)とすると、

 A * 2 = ( 2 * 2, 3 * 2 ) = ( 4, 6 )

となります。このイメージはベクトルの大きさを引き伸ばしたと解釈できます:

 なぜベクトル同士の掛け算は無いのか?本当は解釈があります。ベクトル同士の掛け算は「空間のスケール変換」に当たるんです。詳しい話はしません。

 割り算は逆数にすれば掛け算になりますから同じです。

 この節をまとめると、

・ ベクトルは大きさと方向を表す。基本的に位置は表さない!
・ 足し算はあれこれ動いた結果の始点と終点を結ぶベクトルになる。
・ 掛け算は定数倍のみ定義。方向を変えずに大きさを変える。
・ ベクトルの掛け算は基本的には定義されない。

となります。



A ベクトルの内積

 ベクトルの基本的なおさらいをしましたので、内積の話にうつります。内積というのは英語でinner productと書きますが、dot product(ドット積)とも呼ばれます。3Dゲームの表現で多いのはdot productの方です。D3DXVec3Dot関数などを見たら「内積だな」と思って下さい。

 さて、内積というのは次のような計算式で表される「定数」です:

Dot = v1v2 = x1*x2 + y1*y2 = |v1||v2|cos(θ)

 上の式の「θ」は2つのベクトルが作る角度です。この式はぱっと見では不思議に思えます。各成分を掛けて足し合わせるという計算がなぜか右の「ベクトルの大きさ同士を掛け算して、cosθを掛けている!」というものとイコールになっています。なぜそうなるのか、数学的にはもちろん証明できます。でも、それは数学屋さんが好きなこと。ゲーム屋はこれを別の視点で見るんです。「成分同士掛けて足すというのが内積の計算方法。その意味は右辺だ」。つまり、右辺を捉えるのがゲーム屋として内積をつかむコツです。(ちなみに証明はこちら

 ゲームで内積を扱う時、たぶん十中八九は「どちらかのベクトルが標準化されている」状態です。標準化というのはベクトルの方向は変えずに大きさを「1」にする事を指します。何故標準化なのか?例えば上の図でベクトルv2が標準化されているとしましょう。その大きさは1ですから|v2|=1となります。その状態を式で表したのがこちら:

Dot = v1v2 = x1*x2 + y1*y2 = |v1|cos(θ)

右辺から|V2|が消えました。さて、この状態で次の図をご覧下さい:

 2つのベクトルv1、v2があり、そのなす角度がθです。高校の頃にcosθというのは直角三角形の「底辺/斜辺」と習ったと思います。今斜辺の長さは|v1|です。底辺/|v1| = cosθ。よって、底辺 = |v1|cosθ。これは = Dotとも言えます。上の図で底辺がそうなっているのはこの計算からきています。そして、上の三角形は「直角三角形」です。というのことは、一方を標準化した内積の値というのは「標準化ベクトルv2を含む直線にベクトルv1を真っ直ぐ下ろした(正射影した)時の長さ」であることがわかります。これが、ものすご〜〜〜〜く大切なんです。ただし、気をつけることが1つ。cosθはθが鈍角(90度〜180度)だとマイナスになります。よって、正射影した長さには符号が付きます。

 話をさらに続けます。先ほどv2は標準化されているとしました。ベクトルの節でお話した事を思い出して欲しいのですが、ベクトルには掛け算が定義されていました。ベクトルに定数を掛けると、ベクトルの大きさを伸ばしてくれます。v2のベクトルの大きさは1。そして今Dotの大きさが底辺の長さである事もわかりました。ということは、Dot * v2というのは、「v2の方向で大きさがdotのベクトル」であるわけです:

 これがゲームにおける内積のイメージと言っても過言ではありません。事実、上の図式は衝突判定のあらゆる所で同じような形で出てきます。ちなみに、これは3D座標でもまったく同じ性質を持っています。

 あるベクトルを標準化したベクトルに真っ直ぐ落とした時の長さがDot。それに先に標準化してあるベクトルをさらに掛け算するとその方向が出る。内積のイメージを固めて下さい。

 内積のもう1つの使い方。それは「ベクトルがなす角度が鈍角か鋭角かを見る」です。先ほどの式に含まれるcosθは鈍角だとマイナスになります。つまり、内積の結果の符号を見れば、2つのベクトルの成す角度の鈍角・鋭角がわかります。これはポリゴン内に点が含まれているかどうか、ポリゴンに飛ばした光がポリゴンに当たるか否かなど、多くの場面で使います。



B ベクトルの外積

 内積は高校でもやります。一方外積は高校の学習指導要領には記載されていません。大学でもそれを使う学部に入るか線形代数を選択で取らないとたぶん出て来ません。つまり、内積に比べて外積は触れるチャンスがかなり少ない物なんです。しかし、これがすこぶる便利なんです。そして、3Dゲームは外積を知らずに作るのは多分不可能です!

 まず、外積の計算式を2つ挙げます:

v1×v2= x1*y2-x2*y1 = |v1||v2|sin(θ)
v1×v2= (y1*z2-z1*y2, z1*x2-x1*z2, x1*y2-y1*x2) = (x3, y3, z3) =  v3

 実は、内積と違い外積はベクトルの次元によって結果の次元までもが変わってしまう性質を持っています。理由を知りたい方はこの辺をどうぞ。上は2次元のベクトルの場合で、結果は定数。下は3次元ベクトルの外積の計算で、結果は同様に3次元のベクトルになります。

 内積の時と同様に、ゲーム屋は上の式を「成分同士の云々は計算方法、右辺は意味だ」と捉えます。しかし…2Dの方は何だかよく分かりますが、3Dの方は謎ですね。実は、3Dの外積の結果は「2つのベクトルに垂直な方向を向いた大きさが|v1||v2|sinθのベクトル」になります。超大事な外積の性質です!

 2Dの方のイメージは内積のそれとそっくりです。cosθがsinθになっただけ。図にするとこういう感じです:

 外積は高さになるわけです。これは便利ですよね。2Dゲームで地面からキャラクタがジャンプした時、その高さは外積で一発というわけです。またv2という進行方向に対してv1が左にあるのか右にあるのかも、外積の符号結果をみればわかってしまいます。

 3Dの外積のイメージは次の通りです:

 3次元の空間に2つのベクトルv1、v2(標準化)があると見て下さい。そのなす角度はθとします。この時外積を取るとv3という大きさが|v1|sinθのベクトルが出現します。このベクトルv3は図にありますようにv1にもv2にも垂直なただ1つのベクトルになります。この三つ巴の直角状態。これが超重要です。

 v1とv2のベクトルを含む平面はただ1つです。それに対してベクトルv3は鉛直になるのが分かると思います。平面に鉛直なベクトルの事を「法線ベクトル」と呼びます。このベクトルは衝突判定に置いて驚異的にあらゆる場所で出てきます。

 長さもまた極めて重要で、平面と点との距離を計算する時には外積だけでほぼ終わります。

 ところで、3Dの外積で「2つのベクトルに鉛直なベクトルがただ1つ決まる」と書きましたが、良く考えてみると、上のv3の反対ベクトルも2つのベクトルに垂直ですよね。これはいったいどちらの方向のベクトルが出てくるのでしょうか?実は、これは「左手系」と「右手系」で答えが分かれます。

 左手系というのは、左手の親指をX軸、人差し指をY軸、その両方に鉛直にした中指がZ軸とする直交座標系です。右手系の場合は右手の親指がX、人差し指が指Y、鉛直方向の中指がZ軸になります。上の図で親指はv1、人差し指はv2になります。左手系の場合、外積の結果は上の図のようにスクリーンの向こうかこちらに向かうベクトルになります。一方右手系で同じ指の当て方をすると、外積結果が真反対になるのが分かるはずです。よって、系によって答えが変わってしまいます。DirectXは左手系です。その一方でOpenGLは右手系です。



○ 2Dの外積は左右のチェックに使う

 2Dの外積の結果出てくる値はsin(θ)です。結果の符合を見れば、「×」の左側のベクトルに対して右側のベクトルが左にあるか右にあるかを判定するのに使えます。また、結果が0ならば2つのベクトルは平行であることもわかります。


○ 3Dの外積は三角ポリゴンの向きを計算する

 外積は三角ポリゴンの向きを調べることが出来ます。三角ポリゴンは3つの頂点を持つ平面です。頂点の1つを原点(始点)と考えると、他の2つの頂点までの軌跡はベクトルになります。しかも、その2つの軌跡はポリゴンにぺったりとくっついています。いわゆる「平面上の線」です。ということは、その外積を計算すると、ポリゴンに直行する法線を得ることができます。演算順によって符号は変ってしまいますが、きちっとルールを設ければ、ポリゴンが3D空間でどの方向を向いているかを一意に定めることが出来ます。3Dの外積で最も多用される利用法の1つです。



 ベクトルと内積、外積の話をざっとしてきました。内積・外積のイメージが完全にでてきていないと、3Dのゲーム製作、特に衝突判定をするところでは意味不明と混乱が生じてしまいます。図を何度も見返して、イメージを脳に焼付けて下さい。それが楽しい(?)衝突判定に繋がります(^-^)