前回の第47回は「3次元空間の回転する正方形を2次元平面に透視投影してテクスチャマッピング」(スクリプト1)した。ところが、テクスチャに遠近法が適用されていないため、分けた三角形の継ぎ目がゆがんでしまった(第47回図3再掲)。これはGraphics.drawTriangles()メソッドに渡す第3引数のuv座標が、2次元で奥行きの座標を含まないからだ。実は、第3引数には奥行きのt軸を加えたuvt座標が渡せる。今回は、このuvt座標により、遠近法を適用したテクスチャマッピングに挑戦したい。
Utils3D.projectVectors()メソッドの仕組み
前回の「3次元空間の回転する正方形を2次元平面に透視投影してテクスチャマッピング」するスクリプト1に手を加えていく。uvt座標のt値は、3次元頂点座標のz座標値から計算式で導ける。けれど、座標をひとつひとつ式で計算しなくてもよい。実は、Utils3D.projectVectors()という優れものの静的メソッドがある。
このメソッドを使えば、3次元空間の頂点座標が納められたVectorオブジェクトから、Graphics.drawTriangles()メソッドに渡す第3引数のt座標値をまとめて求めてくれる。それだけではない。第1引数にする2次元平面に透視投影された座標値のVectorオブジェクトも自動的につくってくれるのだ。
つまり、前回のスクリプト1の3次元空間座標を2次元平面に透視投影する関数(xGetVertices2D())がそっくり要らなくなる。今までの苦労はどうなる、という声が聞こえてきそうだ。しかし、透視投影の考え方を知っておくことは大切だ。それに、Utils3D.projectVectors()メソッドのありがたみも実感できよう。
静的メソッドUtils3D.projectVectors()の仕組みが少し変わっている。というのは、メソッドがやることはふたつある。第1に、Graphics.drawTriangles()メソッドの第1引数に渡す、透視投影した2次元座標がエレメントに納められたVectorオブジェクトをつくる。そして第2は、Graphics.drawTriangles()メソッドの第3引数にするuvt座標のt値を計算することだ。
そこでメソッドから値を返そうとすると、戻り値はひとつしか定められない。Graphics.drawTriangles()メソッドの設計者は、大胆にも戻り値はなし(void)とした。その代わり、第1引数と第3引数にするVectorオブジェクトを引数として渡す。すると、Utils3D.projectVectors()メソッドはふたつの引数のVectorオブジェクトを直接書替えてしまうのだ。
Utils3D.projectVectors()メソッドは、つぎのように4つの引数をとる。第1引数が透視投影の変換を行うMatrix3Dオブジェクト、第2引数は変換される3次元座標値が納められたNumberベース型のVectorオブジェクトだ。そして、第3引数が透視投影した2次元座標を納めるNumberベース型のVectorオブジェクトだ。エレメントとなる座標値はすべてメソッドが計算するので、なんとこのVectorオブジェクトは空っぽで構わない。第4引数にはuvt座標のNumberベース型のVectorオブジェクトを渡す。t座標はメソッドが書替えるので、仮のエレメント値として0を入れておけばよい。
Utils3D.projectVectors()メソッドを使う
前回のスクリプト1にUtils3D.projectVectors()メソッドを組込もう。まずは引数に渡す変数の初期設定に手を入れる。第1に、3次元頂点座標を納めるVectorインスタンス(vertices)は、Utils3D.projectVectors()メソッドの第2引数に合わせて、ベース型をVector3DからNumber型に変える。第2に、uv座標のVectorインスタンス(uvData)にはt値のエレメントを加えた(変数名もuvtDataに変更)。前述のとおり、t値はすべて0で構わない。
つぎに、Utils3D.projectVectors()メソッドを使う流れだ。DisplayObject.enterFrameイベントのリスナー関数(xRotate())からは、前述のとおり3次元空間座標を2次元平面に透視投影する関数(xGetVertices2D())はもはや呼出さない。替わりに、空のNumberベース型Vectorオブジェクト(vertices2D)を座標変換の関数(xTransform())に引数として渡す。そして、このVectorオブジェクトは関数の中で、Utils3D.projectVectors()メソッドの第3引数となる。したがって、メソッドにより透視投影された2次元座標が、Vectorエレメントとして納められるのだ。
もうひとつ、Matrix3Dオブジェクトを変数(worldMatrix3D)として宣言した。これは前回のスクリプト1とは考え方が変わったためだ。前回のスクリプトでは、回転の変換を3次元空間座標に加えてVectorオブジェクト(Vector3Dベース型)の変数(vertices)に納めた。しかし、今回3次元空間の頂点座標は動かさない。変換のMatrix3Dオブジェクトに回転を加えて、その結果のオブジェクトを変数(worldMatrix3D)に残す。つまり、座標ではなく変換行列で座標変換を管理するのだ。
これでスクリプトにUtils3D.projectVectors()メソッドが組込めた。フレームアクション全体は、以下のスクリプト1のとおりだ(図1上段)。しかし、まだタイトルに「暫定」のふた文字がある。理由は[ムービープレビュー]を試せばわかる。回る正方形にパースペクティブがかかっていない(図1中下段)。これは、Utils3D.projectVectors()メソッドの第1引数として渡したMatrix3Dオブジェクトに透視投影が加えられていないためだ。
Matrix3Dオブジェクトに透視投影の変換を加える
Utils3D.projectVectors()メソッドの第1引数として渡したMatrix3Dオブジェクト(worldMatrix3D)には、Matrix3D.prependRotation()メソッドで回転は加えた。しかし、透視投影は行っていなかった。実際、スクリプト1をよく見直してみると、焦点距離の数値(nFocalLength)がどこにも使われていない。
それでは、Matrix3Dオブジェクトにどうやって透視投影を加えればよいか。今回はPerspectiveProjection.toMatrix3D()メソッドを使うことにしよう。タイムラインに置いたDisplayObjectインスタンスには、そのタイムラインのPerspectiveProjectionオブジェクトにより遠近法が適用された(第33回「遠近法の投影」の「PerspectiveProjectionクラスで遠近法を操作する」参照)。PerspectiveProjection.toMatrix3D()メソッドは、PerspectiveProjectionオブジェクトがもつ遠近法の設定をMatrix3Dオブジェクトにして返してくれる。
ただし、PerspectiveProjection.toMatrix3D()メソッドから得たMatrix3Dオブジェクトの遠近法の変換は、いわばカメラのレンズを決めただけだ。3次元空間座標を2次元平面に透視投影するには、カメラと座標空間との距離を定めなければならない。これは焦点距離を基本にするとよい。
具体的には、前掲スクリプト1に透視投影のMatrix3Dオブジェクト(viewMatrix3D)をつぎのように加える。タイムラインのPerspectiveProjectionオブジェクト(myPerspective)からMatrix3DオブジェクトをPerspectiveProjection.toMatrix3D()メソッドにより得たうえで、奥行きを焦点距離の分平行移動する。この移動の変換は、Matrix3D.prependTranslation()メソッドにより透視投影の前に加える。カメラからの距離を先に決めなければ、透視投影できないからだ。
そして、Utils3D.projectVectors()メソッドの第1引数に渡すMatrix3Dオブジェクト(worldMatrix3D)には、この透視投影のMatrix3Dオブジェクト(viewMatrix3D)をMatrix3D.append()メソッドで乗じる。ただし、もととなる3次元空間座標の変換結果は、そのままMatrix3Dオブジェクトとして残しておかなければならない。つまり、このオブジェクトを直接2次元平面に透視投影してはだめだ。そのため、Matrix3D.clone()メソッドで複製したオブジェクト(myMatrix3D)に変換を加える。
2回にわたって取組んだお題がようやく整った。これでマウスポインタの水平座標に応じて水平に回した正方形の3次元空間の頂点座標を、2次元平面に透視投影したうえでテクスチャがマッピングされる(図2)。でき上がったフレームアクション全体を、スクリプト2に掲げる。
今回書いたスクリプトで、3次元空間の頂点座標はひとつの平面上でなければいけない、というつくりにはしてない。そこで次回は、立方体の頂点座標をテクスチャでくるんでみよう。
今回解説した次のサンプルファイルがダウンロードできます。