ホーム < ゲームつくろー! < DirectX技術編 < ワールドにオブジェクトを100個作るには?
その13 ワールドにオブジェクトを100個作るには?
今回は少しクラス寄りな話かもしれません。多くのゲームでは同じ形のキャラクタが沢山出現します。限られたスペックとメモリ容量の中で高速にゲームを動かすには、オブジェクトの使いまわしをすることがとても重要なのです。しかし使い回しにはそれなりのトレードオフがあります。ある元オブジェクトを複数のコピーオブジェクトが参照している時、親が変化するとそれがコピーオブジェクトにも影響してしまいます。他に影響しないようにワールドに沢山のオブジェクトを配置するにはどうしたら良いのか?この章ではそれを試行錯誤考えてみます。
@ Direct3Dオブジェクトは判子もしくは着せ替え人形
3Dの描画と言うと、まずオブジェクトをワールドに並べて、ライトを照らし、カメラでワールドを撮って、それをスクリーンに投影する・・・そんなイメージがありますよね。現実の映画などは実際そういう撮り方をします。しかし、実はDirect3Dはちょっと違います!「え!」っとお思いになるでしょうが、本当です。
よ〜く考えてみてください。通常Direct3Dは次のような手順でレンダリングをするはずです。
@ ローカル座標にあるオブジェクトをワールド変換行列で世界に配置する
A 世界に配置されたオブジェクトをビュー変換行列でカメラの前に再配置する
B カメラで切り取られた世界をスクリーンに投影する
C @〜Bを必要なオブジェクトすべてで繰り返す
これです。このC番。オブジェクトは1つ1つ@〜Bの過程を経て、切り絵のようにスクリーンの前に順々に投影されていくのです。ここが現実のカメラと違います。オブジェクトをいっぺんに世界に並べたりはしないわけです。これは、Direct3Dが主にゲームのようなリアルタイムレンダリングで使用されるため、こういうZバッファ方式にせざるを得ないわけです。現実の映画や3Dレンダリングソフトはこうではなくてレイトレーシング方式(光線追従法)を採用してリアリティを重視しています。
この現実との違いが重要です。Direct3Dは1つオブジェクトを行列演算後完全にスクリーンに投影してしまいます。ということは、ローカル座標にあるオブジェクトに対して別の行列を適用すれば、別のオブジェクトとしてスクリーンに投影される事になります。つまり、ローカル座標にあるオブジェクトは雛形、それこそポンポン複写ができる判子のような役目をなします。
この辺りをちょっと図示してみます。
Direct3Dはレンダリング完了まで頂点の参照のみで計算を行い、頂点自体を変更しないので、同じ頂点情報に対してA、B2つのワールド変換行列を適用する事が可能です。テクスチャ、レンダリングステートも独自に適用できます。結果として2つの場所に異なるテクスチャが貼られたオブジェクトが生成されます。メモリを食う巨大な頂点情報をオブジェクトの数だけ持つよりはずっと効率的です。
ただ、これには大元の頂点が不動であるという前提が必要になります。オブジェクトAが頂点を変更してしまうと、次にレンダリングされるオブジェクトBもその影響を受けます。その場合無理せず頂点を2つ用意するか、頂点を初期化するプロセスを置けばOKです。100個のオブジェクトに頂点を与えるよりは、オブジェクトの頂点を再初期化した方がメモリの大幅な節約になるのは明らかですね。もちろん、初期化によるオーバーヘッドとの兼ね合いはあります。
頂点情報が判子なら、異なるワールド変換行列やテクスチャは「着せ替え人形」です。このことから、少なくとも頂点情報と変換行列・テクスチャレンダリングは別のクラスとして定義するか頂点バッファインターフェイスを参照形式にした方が良いでしょうね。
A テクスチャ・レンダリングステートは最低限の再初期化を
レンダリングパイプラインの機能設定を司るレンダリングステートは共通使用されます。よって、あるオブジェクトを描画するためにレンダリングステートを変更した場合、次のオブジェクトの描画に影響を与える事があります。このことから、レンダリングステートは変更して描画した後に元に戻すのが原則です。これはテクスチャステージステートも同様です。ただ、レンダリングステートを頻繁に変更するとそれなりのオーバーヘッドがかかります。多くの場合、レンダリングステートは合成パラメータの変更が主ですから、描画終了後に最低限次の再初期化はしておきたいところです。
// レンダリングステートの再初期化
m_cpD3DDev->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE);
m_cpD3DDev->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE );
m_cpD3DDev->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO );
これでワールドにオブジェクトをボンボン置いていってもある程度は正しく描画されるようになります。
短い章でしたが、これを突き詰めていくと効率の良い描画処理が可能になってきます。ただでさえ負担の重い描画。効率重視でいきましょう。