Astro de 3D(13) PerspectiveProjection は放棄<4>
2008/06/17 21:19 - Astro
前々々回提示した平行投影による 3D 表現のコードを透視投影による 3D 表現をおこなうように書き換えます。
変更点は Main.as で3点、Vertex.as で1点。 前々回の解説編で「肝」だと言った部分が対象です。
- 透視投影のソース (ZIP)
なお、今エントリーでは、このソースコードをコンパイルした SWF は提示しません。
理由はこの文章の最後に明らかになります。
Main.as の変更
まずはこれがなくっちゃ始まらない。PerspectiveProjection オブジェクトを生成します。
残りの二つは perspective メソッドと render メソッドにおいて、Vertex クラスのメソッドやプロパティ操作にともなう変更が発生している点です(Vertex.as の変更を参照)。
なお、平行投影のときのコードでは投影とZソートをひとつのメソッドでおこなっていましたが、今回は分離しました。
Vertex.as の変更
変更点を分かりやすくするために、平行投影のコードと併記します。
/** * 投影処理(透視投影) */ public function perspective( mat:Matrix3D, fov:PerspectiveProjection ):void { (1) _projection = mat.transformVector( _local ); (2) _projection.w = fov.focalLength / (fov.focalLength+_projection.z); (3) _projection.project(); }
/** * 投影処理(平行投影) */ public function perspective( mat:Matrix3D ):void { (1) _projection = mat.transformVector( _local ); (2) _projection.w = 1; (3) _projection.project(); }
変更点は2点。
まずはこれがなくっちゃ始まらない。PerspectiveProjection を引数として取り込みます。 この変更が Main.as の perspective メソッドに波及しています。
そして肝心要の(2)の行。
平行投影では強制的に1を代入していた Vector3D.w に対して、透視投影では、以下のような計算結果を代入するようにします。
l / ( l + z )
この計算式は、三次元座標から二次元座標への変換と、頂点に割り当てていた表示オブジェクトのスケール計算を当時におこなう優れものの公式です。 これは Main.as の render メソッドに反映されています。
どっからこういう式が導き出されたのか、というのは「透視投影」をキーワードに検索をかければいろいろ出てくるとは思いますが、とりあえずひとつだけページを示しておきます。
このページの後ろの方「drawTriangles」の説明中に、Astro ASDoc の PerspectiveProjection のページ冒頭にあるのと同じような図があり、そこに以下の記述があります。
T = focalLength / ( focalLength + z );
なお、以前参考書として使用(こことかこことかここ)した「コンピュータゲームの数学」3.8 投影変換(P80~82)には行列式を使ったこの公式の説明があります。
ちなみに「コンピュータゲームの数学」では分母がちょっと違っていて、l / ( l - z ) という式になっていますが、計算的には同じことです。
ところで前々回、Vector3D.project は x、y、z を w でそれぞれ÷るが、私は×る方が馴染みがあると書きました。
今まで三次元表現に関する本やサイトでは、三次元座標の x、y に l / ( l + z ) を「×」れば透視変換された二次元座標になり、その二次元座標を指示する表示オブジェクトの scaleX、scaleY に「×」れば透視に応じたスケールになる、って書いてあったんですよ例外なく。
だが Flash ビルトインでは÷るという。 まぁ÷るんだっていうなら、Vector3D.w が逆数になるように計算すればいいだけなんだけどねー。
しかし、w に ( l + z ) / l の計算結果を代入してもスケール変換の際にうまくいかないのは、一体どういうわけだ? 逆数だからこれで良いはずなんだけどなぁ……
さらに不思議なのは render メソッドにあるとおり、表示オブジェクト scaleX、scaleY に 1/w を×ると期待どおりの結果が出るんですが、それは w の値が ( l + z ) / l でも l / ( l + z ) でも同じ。なぜだ。
まぁここんとこがちょっと疑問ですがとりあえず棚上げ。
次回は 私に PerspectiveProjection 放棄を決心させたポイント、fieldOfView の納得できない挙動について、今回掲示したソースをそのまま使って説明します。