資料館/なるだけ処理を軽くしよう

Last-modified: 2008-05-14 (水) 21:42:27

なるべく処理を軽くしよう

概要

80:20の法則というものをご存知だろうか。オイラは昔先生に70:30だと教わったのだが、そんな細かい話は土井でもよくて、全体に影響を及ぼしているのはごく一部だ、とかなんとかそんな意味の経験則らしい。
これをプログラムに置き換えた言葉がある。

  • (中略)プログラムの処理にかかる時間の80%はコード全体の20%の部分が占める*1

描写処理を頑張って削る

さてぶっちゃけた話、ゲームの処理時間は大半が描写処理に食われているといえよう。おいらのゲームで図ってみた所、ピーク時には全体の94%が描写処理に当てられていた。ナニそれ。
ソースコードで言えば2~3ファイルに過ぎない描写処理の部分が、全体のスピードを文字通り根こそぎ削ぎ落としている。という事は、この描写処理が改善できれば大幅なスピードアップが期待できるという事になる。

ローポリモデルを使う

最も効果的な方法と思われる。なるべく面数を減らしたい所だ。が、シロートプログラマのオイラが知恵を絞ってもどうにも出来ない部分でもある。桜橘さんの意匠との兼ね合いもある。難しい・・・

遠くのオブジェクトの手を抜く

視野内に居るため描写しなければならないが、細かい所まで見る必要がないほど遠い。
こんな時は、思い切ってなんとか形がわかる程度まで削ったポリゴンを使うのも手だ。これはレースゲームなどで有効だろう。実際に、NINTEND○64のFZEROでこの手法が使われていた覚えがある。自分よりある程度前を走っているマシンは、追い越したりするために位置関係を確認できなければならないが、細部まで見える必要はない、という事だろう。

もう描写そのものの手を抜く

この処理はオイラ自分で試してない。ので見栄えとか効能はまったく知らん。今思いついた。
ファミコンやってて不意に気付いたのだが、オブジェクト全てを毎フレーム描写する必要は果たしてあるのだろうか。
例えばロボットが6体居るなら、偶数フレームに3機、奇数フレームに残りの3機、という風に分散して描写してやれば、ロボットの描写コストは半分になる。ミサイルなんかにも適用してやれば全部半分になる。
無論、描写処理そのものをすっ飛ばすため、点滅したような表示になるはずだ。60FPSで1フレーム消えたからといってそう目立つものではないと思うが、おいらのゲームは30FPSなので少し心配だ。そのうち試すかもしれないし、試さないかもしれない。

プロジェクションを削る

画面の計算を行う範囲を狭めることで高速化を図る。そのまま範囲を狭めると見栄えが悪いので、フォグなどで適宜誤魔化すと良い。こっちも参照。

パーティクルの描写命令を切る

パーティクルが画面内に無い時は、描写命令そのものを呼ばない。これもこちらを参照。

余分なE3Dchkinviewを切る

当たり判定を行わず、かつ描写されないオブジェクトは、E3DChkInViewを行う必要はない。位置や向きを計算するために使うダミーオブジェクトに延々とE3DChkinviewを呼び出している兄貴が居たらすぐに切ろう。俺は問題は出ていない。また

  • 常に視野内にほぼ全てのパーツがあることが確定していて、且つ
  • 当たり判定を行う必要がない。もしくはベクトルの長さなどから自前で判定を行えるブツ

こういった条件に該当する物体についても、E3DChkinviewを毎フレーム呼び出す必要はない。(ハズ)


さらにもう一つ、E3DChkinviewを切っても問題ないものがあった。壁だ。
壁がびゅんびゅん移動する場合は除くが、大抵の場合、マップに対して壁も任意の位置に固定だ。そしてさらに、壁は描写されない。従って、毎フレームE3Dchkinviewを呼ぶ必要はない。


E3DChkinviewを切る場合についてだが、以下の条件下では呼んでやる必要がある

  • 表示はするけど切っちゃうよ、という場合、一番最初の一回
  • 物体同士、とりわけパーツ同士の詳細な当たり判定が必要となった
  • 毎フレームぐりぐりと描写する必要が出てきた

地面との当たり判定が必要な場合、E3DChkinViewを呼ぶ必要はない。地面との判定に必要なのは最新の座標情報二つの線分であり、E3DChkinviewによって更新される情報は一切必要ない。(ハズ)
常に表示させたい物体ではあるが、判定はいらないのでE3DChkinviewは切りたいという場合、該当するオブジェクトを一度視野内に持っていき、一度E3DChkinviewを実行して視野内にあるという事を設定しておかないと描写されないので注意。

小物はビルボードやパーティクルへ移行

細部が見えないような小物で、単純な形状のものはなるべくモデルデータではなく、パーティクルやビルボードに変更しよう。描写に必要な時間は

3Dモデル>ビルボード>パーティクル

の順になり、パーティクルは一番描写速度が速い。パーティクルがビルボードより早い理由は、Zソートを行っていないからだ。バルカンのように単純な丸いボールを大量に書くならば、パーティクルに移行してしまったほうがいい。

フレームスキップを実装する

一番安直且つ強力な逃げ道だ。こちらをクロスリファレンス。

スムージングもOFF

スムージングとは、面の頂点ごとに陰影の平均みたいなそんな、とにかくややこしい計算をして、ローポリモデルの表面を滑らかにしてくれるスグレモノの機能だ。綺麗なモデルが表示できるのだが、計算量も増えてしまうし、表現としてカクッカクのロボなどを作りたいときは逆に不便に感じることもある。
メタセコイア等のオブジェクト設定でOFFに出来るのだが、E3DではメタセコでのON、OFFに関わらずスムージングが有効になる。
これを回避する方法は二つあり

  • メタセコイアのスムージング適用角度を0度にする
  • E3DsetRenderstateでスムージングを切る

となっているが、前者の方法はあまりお勧めしない。後者の方法を使えば、オブジェクト単位でE3D側からスムージングを制御できる。オプションでON、OFFを切り替えるなどの動作も可能だ。ミサイルなどの小物、地形などに適切に設定すると、逆に見栄えがあがるかもしれない。
Renderstateで設定する値は、D3DRS_SHADEMODEとしてe3dhsp3.asで定義されている。わからなかったら9を直接指定してもいい。スムージングを切った場合と適用した場合の見栄えの比較例をおいておくので、参考にして欲しい。
sms.jpgnsms.jpg

80も手をつけよう

ソースコードの全体の80%、速度的にはGPUなどからすれば殆ど素通りに近い部分だが、ココも改善することで僅かながらスピード上昇が見込める。

当たり判定の手を抜く

マップのポリゴン削減、オブジェクト分けがキチンとなされていれば、(少なくともオイラのゲームで分析した結果では)ぶっちゃけさほど重たい処理でも無い気がするが、それでもオブジェクトが増えれば計算量は増える。なので多少精度を落としてもこれを軽くする。


考え自体は簡単で、E3DsetbeforeposとE3Dchkconfgroundを呼ぶ頻度を変えてやれば良い。注意する点は、E3Dsetbeforeposを呼ぶ頻度も同じく落としてやる事。そうしないと前回の判定で壁に当たっていなかった物体が、次の判定までに壁を通り抜ける可能性がある。より美しくするなら、オブジェクトごとに判定するタイミングを分散してやれば良いと思う。
この手抜きの最大の欠点は当たり判定の精度が鈍るということ。当たり判定命令によって返ってくる値の更新頻度も須く鈍る。
802001.jpg
オブジェクトが坂を上ろうとしている。赤が毎フレーム判定、青が判定頻度を落とした例。赤ならスイスイの上っていくが、青は地面に埋まっては戻り、埋まっては戻りを繰り返して上っていく。これが更新頻度が落ちることによって生まれる弊害の顕著な例。従って、マップや壁に沿って一瞬ではなく長時間動くオブジェクト、ぶっちゃけ人間などが壁スリしたり坂を上ったりする処理には使えない。
(坂を下る分にはそれなりに見えるので、くだる時だけやるのは有りかもしれん)
平地であれば問題ないが、全く平面だけで高低差の無い地面ならば当たり判定を行う必要自体がないことに気付いたほうがいい。サッカーゲームとか、地面との判定いらないっしょ。壁があれば。
マップチップのように、正方形のブロックのみでマップが構成されているならば、位置に対応する高さの情報を求めるのにやはり当たり判定の必要はない。


この手法が効力を発揮してくれるのは

  • 壁や地面にぶつかる可能性がほぼ無いもの(全く無いものは判定不要)
  • 地面にめり込んだら潔く消失するもの

上記を程よく満たしてくれるのは、航空機や弾丸といったオブジェクトだ。特にオイラのゲームでは弾の数が多ければ多いほど正義、といった感じなので、当たり判定の頻度を1/4回にしている。一瞬地面にめり込むような感覚はあるが、4フレームならさほど問題は無い。極端に薄い壁は突き抜けてしまうので、スピードの速いものは頻度を上げるなど、処理の改善を行うと良い。

マップデータのオブジェクトは分ける

地面と任意のオブジェクトの当たり判定は、オブジェクトの一つ前の座標と現在の座標を結ぶ線分と、地面データの各面との当たり判定を行う。地面のポリ数が増えるほど、この計算量は増えていく。


しかしたいていの場合において、地面データのポリゴン全てと当たり判定を行う必要は無い。
たとえば、マップを真上から見て、以下のようにグリッドで区切って考えてみる。

12345
678910
1112131415
1617181920
2122232425

プレイヤーがマップの7の位置にいたとしよう。この時、移動速度にもよるが、当たり判定を行う必要があるのは1,2,3,6,7,8,11,12,13の9つのグリッドだけだということがわかると思う。プレイヤーが次の移動で当たる可能性があるマップグリッドに色を付けてみた。
こんな風に、極端に遠い位置にあるポリゴンと、当たり判定を行うこと自体が無駄である。
E3Dの内部では、オブジェクトごとにプレイヤーとの位置を大まかに判定し、詳細な当たり判定を行うかどうかを決めているようだ。
なので、マップのオブジェクトは適宜分けておくと、当たり判定の処理が軽くなる。


*1 フリー百科事典Wikipedia、パレートの法則より