前回少し触れた、面の向きということに注目してみましょう。空間上の面の向きを指すのに、法線ベクトル(normal vector)という言い方をします。この法線ベクトルは面に対して垂直なベクトルであることは想像がつくでしょうし、その求め方は前回触れた通り、外積から計算ができます。では、何に使えるのでしょうか。
面の向きが解るのなら、面の裏表も解りそうなものですね。もちろん、そう言う風に使います。
大抵の3Dエンジンでは、裏向きのポリゴンは描画されません。無駄に終わる場合が多いからです。ある物体を描画するさいに、裏向きのポリゴンを描いても、それより手前にある表向きのポリゴンに上描きされてしまうことが多いから最初から描かないのです。裏向きのポリゴンが見えるのは、物体の中に顔(視点)を突っ込んだ時だけです。そんなレアケースの為に描画を行うのは馬鹿馬鹿しいですよね。
では、実際どうやって裏表の判定を行うのでしょうか。
簡単ですね、法線と視点との角度を考えればいいのです。面の表面から視点へと伸びる視線ベクトルを考えて、法線と視線ベクトルの成す角度が-90°から90°の範囲内にあれば見えています。0°近辺なら真正面といった感じでしょうか。逆に90°を超えたり-90°より小さかったらもう裏向いています。
でも、なんだかややこしそうです。何度から何度まで、とかそういった場合分けが事を複雑にしているみたいです。ここは cosθを用いて、シンプルに書きましょう。
「視線ベクトルvと法線ベクトルnの成す角をθとして、cosθ>0の時見える」
嘘じゃないですよ。本当です。更に言えば、こうなります。
v・n ------ > 0 の時見える |v||n|
どうですか。内積の式を使っています。もしvもnも単位ベクトル(向き情報のみ)だったら、下のように書けます。
v・n > 0 の時見える
シンプルですね。これ以上無いほどシンプルになってしまいました。
(1999/01/23 Add)この項に関して葛目氏よりご指摘がありました。上記の判定が正確でありうるのはあくまでも画面の中心一点のみで、視野角の影響で投影面の他の部分(ほとんどか)に関しては判定の誤りを起こします。以下に正確な説明があります。ここは信じないで進んでください(笑)
しかし、面の法線の計算を毎回行うのもめんどくさいですね。計算時間も馬鹿にならないですし。ここでは、描画するオブジェクトの形が変化しないこととして、ずるする方法を考えます。
初めに、全部のポリゴンの法線を求めておきます。ただし、オブジェクトは回転したり移動したりしてしまいます。なので、オブジェクトの回転にあわせて法線も回転させてやれば、望む法線の向きが得られます。この場合、移動させてはいけません。あくまでも法線は向き情報のみに絞っておきたいからです。
で、ここで問題が発生します。一体どれだけ回転させればいいの?階層構造だったら?4x4行列で移動行列も含んじゃってるよ?カメラ行列も含んでるし?等々……。
だめそうです。ここは、考え方を変えましょう。法線ベクトルを回転させるのではなく、視線ベクトルを回転させます。そう、オブジェクトが回転してきたのと反対の方向に回転させて、オブジェクトが移動回転する前の法線と比べてやればいいのです。
ここで登場するのが、逆行列(Inverse Matrix)という考え方です。
|A|*|B| = |B|*|A| = |I| |I|は単位行列
となる時、|B|は|A|の逆行列であるといい、|A|は|B|の逆行列といいます。
この逆行列、面白いことに、全て逆さまでものを行います。例えば、「オブジェクト座標から回転して移動してカメラ行列でカメラ座標に持ってくる」行列の逆行列は、「カメラ座標からワールド座標に戻し、移動を戻し、回転を戻して、変換する前のオブジェクト座標系へ戻す」行列なわけです。
これを利用しない手はありません。ある行列から逆行列を求める正確な計算の仕方はここでは長くなりますので省きます。興味のある方はGraphics Gems IVやその他の文献を当たって頂くとして、ここでは、前掲の OTMMATX.DOC(Voltaire/OTM)で紹介されている方法を説明し、この項の後半部では図形変換の行列でのみ有効な逆行列の作成方法を説明します。
正確な逆行列を用意しなくても、逆回転させる方法があります。行と列を対角で反転させた行列を用いる方法です。
| a b c | | d e f | | g h i |
の代わりに、
| a d g | | b e h | | c f i |
を用いて変換を行うと、驚くなかれ、逆行列で回転させたかのように逆回転するのです。不思議です。でも、ちゃんとそうなります。ここで4x4ではなく3x3を用いたのは、移動を無視するためです。単位ベクトルとの角度判定なら回転だけでいいですから。
本当に不思議ですね。理論的な考察はとても私には無理ですので、OTMMATX.DOCに感謝しておくだけに止めておきましょう。(1998/07/31追補:この項の最後に考察があります)
ともあれ、これを用いて視線ベクトルを変換すると、オブジェクト座標での視線の向きが手に入ることになります。これと、変換前の面法線とを内積判定すると、その面が裏か表か判断できることになります。面法線ベクトルをオブジェクト内の全ての面に対して回転させることに対して、これなら視線ベクトルを一度だけ回転させればすむので、お得感も充分ですね。さらに言えば、頂点すら回転させること無く裏面をはじくことができます。
もちろん、カメラ座標まで持ってくる行列の逆を取るので、視線ベクトルはz軸に対して真っ直ぐのシンプルなベクトルになります。
こういった裏面消去に関して、少しだけ問題があります。実は視線はz軸に対して平行ではないんですね。遠くのものは小さく見えるということは、遠くを見る視線はちょっと広い範囲を見ている。つまり視線が広がっているということです。なので、見えると思ってる面が消えてしまったりします。
そこで、角度による裏表判定をあきらめて、距離による判定へと話を移してみましょう。逆行列でオブジェクト座標系へベクトルを持っていく考え方自体は同じですので、上記のことは押さえておいて下さい。
今あなたの視点がありますよね。それを逆行列でオブジェクト座標系へと持っていきます。当然逆移動も必要です。そうすると、オブジェクト座標原点に対して視点の位置が決まるはずです。原点からどれだけ離れているかも計算できます。
あるポリゴンについて注目します。そのポリゴンの原点からの距離を考えてみましょう。あなたの視点の原点からの距離と比べます。どちらが離れてるでしょうか?ポリゴンの方が離れていたら、あなたはオブジェクトに顔を突っ込んでいます。そんなポリゴン見えません。もちろん描く必要がありません。なんとなくイメージがつかめたでしょうか。
実際には、単なる距離ではなく、「法線方向にそった距離」を計算します。少しイメージしにくいかもしれませんが、あなたの足元がオブジェクト原点だとして、少し離れた床上に落ちている雑誌の距離は0となります。雑誌の表面(法線)が天井向き(y方向)なので、足元との法線方向にそった距離は0なんです。この距離と、視点の「法線方向にそった」距離を比べるんです。
そうすると、すごく遠く離れた(様に見える)こちら向きの面も、眼前にある(様に見える)向こう向きの面も、きっちりと判定が出来るんです。顔を突っ込む喩えはちょっと不適当だったかもしれませんね。
あとは面の法線方向にそった原点からの距離と、視点の法線方向にそった原点からの距離をなるべく簡単に計算できればもう問題はないですね。まず、ポリゴンの距離から計算してみましょう。
polydist = n・v1
おしまいです。ここで、nは面の法線ベクトル、v1は面を構成するある頂点の座標です。びっくりしますね。こう書き換えてみましょう。
n・vertex1 = |n||v|cosθ
ここで、法線ベクトルnが単位ベクトルだとしたら、
polydist = |v|cosθ
頂点の座標ということは、原点からのベクトルと考え、その長さ(=原点からの距離)が|v|で、それにcosθを掛ける。ここでθは法線ベクトルと頂点ベクトルのなす角……。素晴らしいことが起こりました。まさに「法線方向にそった原点からの面の距離」が算出されてしまっています。
次に視点の距離ですが、もう何も言うことはないでしょう。以下の式です。
eyedist = n・eye
で、
eyedist > polydist
なら、その面は見えています!
ところで、前節でさりげなく「逆移動」なんてことを言っていましたが、前掲の逆行列を作らず逆回転させる方法ではうまくいかないですよね。ここは、少なくとも移動も考慮した逆行列をつくらねばなりません。基本的には前掲の方法の拡張になります。
最初に、元になる4x4変換行列を書いてみましょう。
| a b c 0 | | d e f 0 | | g h i 0 | | tx ty tz 1 |
3x3行列部は、前掲と同じ考え方、対角で反転させる方法を用います。
| a d g 0 | | b e h 0 | | c f i 0 | | ?x ?y ?z 1 |
これで、回転は逆になりました。移動はどうすれば逆に出来るのでしょうか?答えは言われてみれば簡単です。「平行移動して回転して」ここまできた移動量なんですから、「逆回転して負の方向に平行移動」すればいいんです。
?x = -( a*tx + d*ty + g*tz ) ?y = -( b*tx + e*ty + h*tz ) ?z = -( c*tx + f*ty + i*tz )
これで、図形変換用逆変換4x4行列が見事に手に入りました。一般の行列の逆行列を計算するのには使えませんが、問題ないですよね?
この行列で、カメラ座標での視点をオブジェクト座標へ逆変換することができます。カメラ座標での視点?もちろん(0,0,0)ですよね。(注:現在までのエンジンのシンプルな透視投影での話です。視野角錘(frustum)とかが絡んで、投影面をカメラ座標原点に置く場合、視点は-z方向にずれることになるでしょう。)
この項は、誤りに気づいて加筆修正したため、かなり長くなってしまいました。それだけに、結構興味深い内容になったのではないでしょうか。
変更修正にともない、次項(平行光源)に付属のソースおよびバイナリも変更しておきました。是非ご覧下さい。
アップ後BBSにて、shi3z氏より転置による逆行列についてのご示唆をいただきました。大変感謝しております。以下、BBSよりの一部抜粋です。
逆行列についてですが、正規直交座標系行列の場合、転置するだけで逆行列になります。(近似ではなくて正確な逆行列です)
この理由はきわめて簡単で、そもそも行列とはベクトルを縦にしたものをいくつか横にならべただけのものだからです。逆行列とは、逆ベクトルの行列のことですから、普通は転置すれば良いはずなのですが、正規でないと矛盾がでるため、仕方なく一般逆行列を複雑にもとめる必要があるにすぎなかったと記憶しています。
正規直交座標系行列という用語があるわけではないのですが、これは正規な(すべての軸ベクトルの長さが1の)、直交した座標系の基底行列という意味あいで用いました。
私はご指摘のとおり、近似に近いラッキーケースだと信じ込んでしまっていました。ここで大変有用なご示唆として、逆行列が転置のみで成り立つ条件「正規ベクトル」「直交座標系」という2つのヒントを頂きました。ここまでヒントをもらっておいてやってみなきゃバチがあたりそうです。やってみましょう!
では初めに3x3行列を書いてみます。抜粋にもあったように、ベクトルを横に並べたイメージを重視して、以下のようにしました。
| Ax Bx Cx | | Ay By Cy | | Az Bz Cz |
次に「あるベクトルにこの行列を掛けて転置行列を掛けると元に戻る」を数式にしてみましょう。
| Vx | | Vx | | Ax Bx Cx | | Ax Ay Az | | Vy | = | Vy | * | Ay By Cy | * | Bx By Bz | | Vz | | Vz | | Az Bz Cz | | Cx Cy Cz |
こういうことが起こっているのですね。では、書き下して、
Vx = (VxAx+VyBx+VzCx)Ax + (VxAy+VyBy+VzCy)Ay + (VxAz+VyBz+VzCz)Az Vy = (VxAx+VyBx+VzCx)Bx + (VxAy+VyBy+VzCy)By + (VxAz+VyBz+VzCz)Bz Vz = (VxAx+VyBx+VzCx)Cx + (VxAy+VyBy+VzCy)Cy + (VxAz+VyBz+VzCz)Cz
ちょっとめげそうになりますが、Vx、Vy、Vzで括れそうです。やってみます。
Vx - Vx(AxAx+AyAy+AzAz) = Vy(AxBx+AyBy+AzBz) + Vz(AxCx+AyCy+AzCz) Vy - Vy(BxBx+ByBy+BzBz) = Vx(BxAx+ByAy+BzAz) + Vz(BxCx+ByCy+BzCz) Vz - Vz(CxCx+CyCy+CzCz) = Vx(CxAx+CyAy+CzAz) + Vy(CxBx+CyBy+CzBz)
あれ、なんだか、良く見知った形になってきたと思いませんか?ベクトルの形で書き直すと、
Vx(1-|A||A|) = Vy(A・B) + Vz(A・C) Vy(1-|B||B|) = Vx(B・A) + Vz(B・C) Vz(1-|C||C|) = Vx(C・A) + Vy(C・B)
さぁ、ここでこいつが成り立つ条件を考えてみましょう。などともったいぶらなくてももうわかりますね。一番簡単なケースは、「全てのベクトルの長さの二乗が1」で「任意の2つのベクトルの内積が0」の場合でしょう。左辺も右辺も0になります。
そうです。「全てのベクトルの長さの二乗が1」は「正規の」ですし、「任意の2つのベクトルの内積が0」は「直交している」ということです。頂いた2つのヒントは、ヒントどころか転置で逆行列を表現出来るケースの条件、つまり答えそのものだったんですね。
さて、ここで3D行列の場合上記の条件をクリアしているのかを検証しなければ、片手落ちというものです。上記の条件を確かめる為に、x軸回転行列に再登場していただきましょう。
| 1 0 0 | | 0 cosθ sinθ | | 0 -sinθ cosθ |
こうでしたね。まず、最初の条件「正規の」を考えます。長さが1であれば問題無かったでした。計算してみるまでもなく確かに長さは1ですね。また数値での確認以前に、x軸回転なのでy-z平面で半径1の円を描くであろうことは容易に想像がつきますよね。
もう一つの条件「直交している」ですが、これも内積を計算すれば答えは常に0ですね。最初のベクトルが絡むと問答無用ですし残りの2つ同士でも奇麗に0になります。というか、そうでないと元々回転できないですね。
これは他の軸の回転であっても同じことですし、回転行列をいくら掛け合わせても同じことです。半径1の球にのるのは想像つきますし、結果のベクトルが直交しているのもあたりまえです。
では、他の変換についてはどうでしょうか。結果からいうと残念ながら無理です。スケール変換は直交こそするもののサイズが1でないですし、平行移動やせん断は直交もしないです。ただし、平行移動が逆移動で回避できたようにスケール変換も正規化で回避できそうです。やってみたことがないのでわからないですが。
3D回転行列の逆行列は転置によって正確に求めることが確認できました。そしてまた異様に長い項になりました。こうして助言を得てこの項が充実していくことに少なからず興奮しています。ここでshi3z氏に再度感謝の意を表して最後にしたいと思います。ありがとうございました。(何故演説調に?)
devilman/team-UJI 1998/07/26(last modified 1998/07/31)