Flash3D/自分用のパッケージ
Flashで3D表現を行う場合、利用できるAPIはそこそこ低レベルですので、自由な実装ができる反面、やりたいことが簡単にはできないと思います。多くの人はやりたいことがある程度決まっているので、同じことを二度も三度もしなくて済むようによくやることをパッケージ化することでしょう。(もしくは既存のパッケージを選んで使うのでしょう。)私も今そんな作業を行っているのですが、パッケージ作成にあたって最低限必要な実装とこんな実装があると便利そうということを記録してみたいと思います。
最低限の実装
3D表現にあたって必要な項目を挙げてみましょう。
- Stage3DからContext3Dを取得する
- Context3DからVertexBuffer3Dを生成
- VertexBuffer3Dに頂点の情報(座標、色など)を設定
- Context3DからIndexBuffer3Dを生成
- IndexBuffer3Dに頂点のつなぎ方を設定
- Context3DからProgram3Dを生成
- Program3Dにバーテックスシェーダーとフラグメントシェーダーを設定
- Context3Dに設定済みのVertexBuffer3DとProgram3Dをセット
- Context3Dをclear
- Context3Dに設定済みのIndexBuffer3Dを元にdrawTrianglesでポリゴン描画
- Context3Dをpresent
こうしてみるとContext3Dを中心に仕組みができあがっていることがよくわかります。Context3Dに事前にお膳立てをして置き、あとは描画したいポリゴンを指定するだけというわけですね。上記の流れで一枚分の描画ができますが、アニメーションさせる場合には、その時間に応じた分量の複数枚の絵が必要となります。VertexBuffer3Dを更新して、Context3Dを「clear→drawTriangles→present」という流れでアニメーションは表現可能です。
最も簡単な実装はここまででよいのですが、複数のオブジェクトが登場したり、一つのオブジェクトの一部分だけアニメーションしたり、テクスチャをオブジェクトに張りつけたり・・・と、いろいろな拡張が考えられますので、ある程度の融通性を担保したパッケージを設計するには、予め考えておくべきことが多そうです。
エンジンとなる部分
3D表現のパッケージ化はどんなオブジェクトを用意して、どんな機能をオブジェクトに振り分けるのかということを考えて作ることになります。その際に上述した最低限の流れを意識しておくと円滑にパッケージの設計作業が進められると思います。
- Context3Dの各種設定
- Context3Dのclear(画面のクリア)
- Context3Dへの描画指示(drawTriangles)
- Context3Dのpresent(画面の表示)
この繰返しを、例えばEnterFrameイベント毎に実行するオブジェクトがあれば、毎秒決まったフレーム数で画面の描画処理を行ってくれ、アニメーションの再生ができそうです。その場合、描画処理が重いときには、EnterFrameイベントが思った通りに発生しないので、思い通りにアニメーションしません。そんなときにはフレームを間引いてくれるような仕掛けがあるとよさそうです。フレームを間引いて描画遅延に対応する場合、アニメーション再生開始からどのくらいの時間が経過しているかをフレーム数から算出することができなくなりますので、物理演算なども並行して行う場合は時間の経過を記録できるような仕組みが設計に必要になりそうです。また、描画の遅延へのアプローチとしては、不要なオブジェクトの描画を間引くといったことがよくされます。遠すぎる物体や近すぎる物体は表示しなくてもよいケースがありますし、視界の外側にあるものも表示する必要はありません。また、微妙に遠いものは少ないポリゴンで表現してもごまかしがききます。
とにかく、全体の心臓部となる基本的な処理を繰り返すエンジン的なクラスを作っておくとよさそうです。このエンジンにはstartやstopといったメソッドを設けて全体的な流れを制御できるようにしたら便利かもしれません。
Context3Dの操作が多そうなので、プロパティに持たせたりしそうですね。
物体を配置する入れ物
Flashで二次元のベクトルデータを表示するときにはStageになんでもaddChildしておけば表示されましたが、Stage3Dではそうはいかないようです。でも同じ感覚で3Dのオブジェクトも操作したいというのが人情です。というわけで、3DオブジェクトをaddChildできるような舞台となるクラスを作ると便利そうです。となると、オブジェクトの追加、削除、検索ができるメソッドがほしいところです。また、このオブジェクトにMatrix3Dのプロパティを持たせておくと、追加されたオブジェクト全体を移動、回転、拡大縮小するのも簡単になるのではないでしょうか?
3Dの物体
入れ物のクラスを作ったら、そこに配置する3Dオブジェクトを表現するクラスも作れそうです。この3Dオブジェクトのクラスにも、入れ物と同じようにaddChildして子供が増やせるようにすると、子供の部分だけ独立して動かせるようになりそうなのは、2DのFlashと同じですね。3Dオブジェクト同士で入れ子にできるようにしておくと、ルートを3Dの入れ物とする木構造があり、XMLで表現しやすいですね。いろんな3Dモデルのデータは木構造になっていることが多いので、よそのデータとの互換性も生まれやすいはずです。木構造は根っこを辿ればどのオブジェクトからでも他のオブジェクトを探索できますしね。(あんまりやらないか。)やはり各オブジェクトにはMatrix3Dのプロパティを持たせておき、移動、回転、拡大縮小などのどんな座標変換を行ったのかをそのプロパティで表現します。末端のオブジェクトのMatrix3Dプロパティから親を辿りつつ親のプロパティをどんどん合成していき、ルートまで辿りついたら、合成の結果が実際にその末端のオブジェクトの座標位置を示すデータになります。(その辺もFlashのDisplayObjectと一緒になります。)
物体にはその外形を表すデータも必要になります。これは頂点情報の集合と頂点のつなぎ合わせ方の情報が必要になり、VertexBuffer3DとIndexBuffer3DとしてContext3Dに渡されるのがまさしくこの情報となります。このオブジェクト全体の移動・回転・拡大縮小は前述のMatrix3Dのプロパティ次第でどうとでもなるのですが、一部の頂点だけ動かしたいとなると話がややこしくなります。頂点の情報を更新してVertexBuffer3Dを再作成(あるいは更新)することになるのですが、この操作には基本的にGPUを使えませんので多用するとパフォーマンスに響きそうです。
また、三角形の集合として記述される外形をどのように描画するかはProgram3D次第になります。ただProgram3Dは他のオブジェクトと共用できることも多そうなので、オブジェクトごとにProgram3Dを生成するのは処理コストが高そうな気がします。ある程度汎用性の高いProgram3Dは共通して再利用して、例外的に他のProgram3Dも使えるようにしようかなぁ、どうしようかなぁ、とか用途に応じて考えたらよい気がします。
三角形の表面にテクスチャを張り付けることもできます。テクスチャはContext3Dに予め登録しておく必要があり、Program3Dで扱いを決定されます。テクスチャのあるオブジェクトとないオブジェクトを峻別する仕組みが必要になりそうですね。
Context3Dに設定する項目のうち、VertexBuffer3D、IndexBuffer3D、Program3D、Textureに関してはもっと詳細に設計を考えた方がよさそうですね。
カメラに関するオブジェクト
3D表現を実際に画面に表示するためには、2Dに射影する必要があります。3Dのどの部分をどのように切り取って射影するかは一つのMatrix3Dで表現できたと思いますが、このMatrix3Dを表すクラスをカメラになぞらえると、現実をテレビや写真に映し出す機能がよく似ていることから理解しやすいようです。定点をみつめるカメラや、グルグル円軌道に沿って移動しながら撮影するカメラ、マウスの操作に反応して移動するカメラなどいろんなカメラを用意して、ユーザの視線を誘導できたらいいと思います。
カメラの持つMatrix3Dの値は、Context3DにsetProgramConstantsFromMatrixメソッドを通して設定しておき、GPUに座標変換処理を行わせる際に利用できます。各オブジェクトの座標系を表すMatrix3Dのプロパティと掛け合わせて使うことになりそうです。
質量に関するオブジェクト
個人的に物理演算エンジンもこのパッケージに組み込みたいという希望があるため、質量に関するクラスがほしいと思ってます。重力に応じて加速したり、別の質量と衝突したりしてほしいのですが、GPUに処理を投げるのが難しそうなので、パフォーマンスに影響しない程度の簡素なものにしようか悩むところです。
Date: 2011/11/7