3Dゲームファンのためのグラフィックス講座
西川善司の3Dゲームファンのための「GRAVITY DAZE」グラフィックス講座(前編)
(2013/2/12 00:00)
前述したように、「GRAVITY DAZE」は、多層構造の都市を舞台としたオープンワールド型のゲームであり、プレーヤーはここを縦横無尽に動き回ることができる。
建造物、大道具オブジェクト、小道具オブジェクトはパーツ化されていて効率よくデザインされてはいるが、モデル数で数えて3,000~8,000オブジェクトはある。ポリゴン数にすると前述したように約300万に達する。これは到底携帯ゲーム機でレンダリングできるレベルのジオメトリ量ではない。しかし、これをレンダリングしなければならないのだ。この過酷な命題に対して開発チームが立案したのが「高速な動的カリング」(Culling)の開発であった。
グラフィックス用語における「カリング」とは、たとえレンダリングしても最終的に表示されなかったり、切り捨てられたりするレンダリング要素を排除する仕組みのことを言う。「そうしたカリングの仕組みは、GPUの中にも存在するのでは?」と思う人もいるだろう。確かにGPUには頂点パイプライン段階でカリングを行なう早期カリング(Early Culling)の仕組みを持つものがある。また、カリングとは若干意味が異なるが、そのピクセルを描き込むべきか否かを判断するための「深度テスト」がピクセルパイプラインの最終段階に存在する。
ここで取り上げる「GRAVITY DAZE」が実装したカリングとは、そうしたGPU側のハードウェア機構によるものではなく、同作のグラフィックスエンジン側で実装したソフトウェアアプローチのカリングメソッドになる。
しかし、「そもそも論」として、画面に表示されないポリゴンは、GPU側で描画されず破棄されるはずなのに、どうしてわざわざソフトウェアレベルでカリング処理が必要なのか。ここを不思議に思う人もいるだろう。
結果的に破棄されるにしても、その「描画するか/しないか」の判断処理系に、膨大な量のポリゴンが流し込まれたら、GPUはこれにかかりっきりになってしまう。だからGPUに流し込む前に、「描画無用」とわかりきっているポリゴン(オブジェクト)はGPUに流す前に排除してしまおうというわけなのだ。
横川氏:PS VitaのGPUコアであるPowerVR系は、一般的なGPUとは異なり、頂点パイプラインが可変負荷に、ピクセルパイプラインが固定負荷となる傾向が強いアーキテクチャになっています。このため、この事前カリングが特に効いてくるんですよ。
PowerVR系の内部レンダリングパイプラインは「TILE BASED DEFERRED RENDERING(TBDR)」と呼ばれるアーキテクチャが採用されている。詳細はこのTBDRを解説した
筆者連載記事を参照して欲しいが、簡単に解説すると、TBDRでは描画対象となるフレームを適当なブロック(TILE)に分けて、入力されたポリゴンの前後関係の判定(深度テスト)を各ブロックごとの頂点パイプラインの段階で行なってしまうというアーキテクチャになっている。後段のピクセルパイプラインでは、深度テストをパスした確実に描画されるピクセルのピクセルシェーダしか駆動されない。
だから、PowerVR系アーキテクチャでは「頂点パイプラインが可変負荷」で「ピクセルパイプラインが固定負荷」となるのだ。逆にいうと、可変負荷である頂点パイプラインの負荷を低減させることが、PowerVR系での、PS Vitaでのパフォーマンスアップに繋がるわけだ。
2段構えのカリング(2)~1段目:視錐台カリング
「GRAVITY DAZE」では、グラフィックスエンジン側のカリング処理系を2段構えで実装している。
1段目は「視錐台カリング」だ。これはシーン内のオブジェクトのうち、視界に入っていないものを排除するものになる。視界に入っていないものをGPUに入れ込んだところでレンダリングされるわけがないので、これを事前に排除するわけだ。
このアルゴリズムにはスウェーデンのチャルマース工科大学のUlf Assarsson、Tomas Mollerらの「Optimized View Frustum Culling Algorithms for Bounding Boxes」(http://jesper.kalliope.org/blog/library/vfcullbox.pdf)を採用している。
3Dオブジェクトをちょうど覆う大きさの直方体「AABB」(Axis Aligned Bounding Box)が視錐台の外にあったら、その3Dオブジェクトを描画対象から除外するというのがこのアルゴリズムの基本メカニズムだ。
視錐台は描画開始平面(Near Plane)と描画限界平面(Far Plane)の2つの四辺形平面と、この2つを閉じるような4つの四辺形平面、合計6つの平面からなる。
この視錐台を構成する6つの平面のそれぞれから、判定対象の3DオブジェクトのAABB上の8頂点(直方体は8頂点で構成されるため)のうち、最も遠い2頂点が交叉しているかどうかの判定を行なうことで、3Dオブジェクトが視錐台の「外」にあるのか、「内」にあるのか、あるいはまたいで(交叉して)いるのかを判定する。つまり、AABB上の2頂点と視錐台の6平面の交叉判定を行なうわけだ。
また、この視錐台カリングを効率よく行なうために、追加の工夫も行なっている。1つ目は、複数の3Dオブジェクトを親子構造でひとまとめにしてグループ管理する工夫だ。この仕組みにより、まずは親AABBでの視錐台カリング判定を行ない、これで親AABBが「交叉」と判定された場合のみオブジェクト単位の視錐台カリング判定を行なうようにしている。
2つ目の工夫は、前フレームの状態情報を利用した最適化だ。各オブジェクトのAABBが、前フレームの視錐台カリング判定において、視錐台のどの平面で「外」と判定されたのかを記録しておき、現在フレームでの視錐台カリング判定では、その平面から判定するのだ。前フレームで「外」としてカリングされたものは次の現在フレームでも「外」の可能性が高い。視錐台カリングでは、1平面だけでも「外」判定がなされた場合は、そのAABBが「外」判定となるのが自明なので、他平面に対する判定を行なわなくてよくなる。すなわち、この最適化は、視錐台カリングの判定回数の削減に繋がるのだ。
2段構えのカリング(3)~2段目:オクルージョンカリング
2段構えのカリング処理系のうち、後段を務めるのが「オクルージョンカリング」(Occlusion Culling)になる。“オクルージョン”カリングとは、他者に遮蔽(オクルージョン)されたことで視点からは見えないオブジェクトを排除する仕組みのことだ。
宮前氏に「今回実装したオクルージョンカリングがなければ『GRAVITY DAZE』の30fps維持は無理だったかもしれない」と言わしめるほど効果の大きかったものになる。
「他者に遮蔽されているかどうか」の調査を行なうには、結局、シーン内に登場するオブジェクトを視点から描き、その結果で得られる深度バッファの内容が必要になる。いわゆる「深度バッファの先出し」処理がオクルージョンカリングには必要になるのだ。この「深度バッファの先出し」を最終描画に使用する3Dオブジェクトで行なうと負荷が高い。
宮前氏:各3Dオブジェクトに対応する、オクルージョンカリング専用の低ポリゴンモデルを別途用意しており、これで「深度バッファの先出し」を行なっています。影生成用の低ポリゴンモデルとは別のモデルで、影生成用モデルよりもさらに低ポリゴンになっています。
一般にPowerVR系は「深度バッファがない」と言われるが、標準レンダリングパイプラインでまとまった形での深度バッファがないというだけで、実際にはTBDRを実践する際に、タイルごとに局所的な深度バッファは確保されている。そしてPowerVR系にもこの局所的な深度バッファをまとまった深度バッファとして出力する機能は、ちゃんと備わっている。
「GRAVITY DAZE」における、このオクルージョンカリングのために用いる先だし深度バッファの解像度は256×384テクセルという縦長の長方領域となっている。これは「深度バッファの先出し」をプレーヤー(カメラ)視界からと、平行光源(太陽光)視界からの計2回、同一バッファに対して行なうためだ。
これについても補足説明が必要だろう。
まずは、なぜ平行光源位置からも「深度バッファの先出し」を行なうのか?
「GRAVITY DAZE」では、影生成にデプスシャドウ技法を活用している。「GRAVITY DAZE」の影生成についての詳細は後編で触れるつもりだが、デプスシャドウ技法では、影生成のために平行光源の位置を仮想視点として深度バッファレンダリングを行なう。これが「シャドウマップ」と呼ばれるもので、光源位置から遮蔽物までの距離分布をテクスチャ化したものに相当する。実際の最終レンダリング時では、このシャドウマップを参照しつつ、レンダリング対象ピクセルが「影か、否か」を判定して描画を行なっていく。
つまり、デプスシャドウ技法においては、シャドウマップ生成が処理の要と言えるわけだ。シャドウマップは平行光源位置から見下ろした視界で生成される関係上、広範囲のオブジェクト達がレンダリングされる。つまり、この「広範囲のオブジェクト達のレンダリング」においてもオクルージョンカリングが実践できればパフォーマンス向上に繋がる。
「GRAVITY DAZE」では、デプスシャドウ技法による影生成負荷の低減のために、平行光源位置からのオクルージョンカリングも実践するのだ。だから、このために「平行光源位置からの深度バッファの先出し」が必要なのである。
横川氏:先ほど述べたようにPowerVR系では、頂点負荷の低減がパフォーマンスアップに直結するんです。シャドウマップ生成はピクセルシェーダ負荷のない、言い換えればほとんど頂点負荷だけの処理系です。ここの無駄を排除できればパフォーマンスが上がるはずだと目論んだわけです。
続いて、なぜ「深度バッファの先出し」目的のレンダーターゲットを256×384テクセルという長方領域としたのかについて。
実は、この長方レンダーターゲットのうち、192×128テクセルをプレーヤー視界オクルージョンカリングのために用いられ、256×256テクセルを影生成用オクルージョンカリングのために用いている。1つのレンダーターゲットを複数目的で用いるために、長方レンダーターゲットとしているのだ。
では、なぜそのような特異な方法を採択しているのか。それぞれ個別にレンダーターゲットを確保すればいいではないかと思われるが、PowerVR系の特性でそうもいかなかったのである。
横川氏:PowerVR系はレンダーターゲットの切り替えのコストが高くて、切り替えるたびにCPUとGPUへのパフォーマンスインパクトが大きいという特性があります。逆に言うとパフォーマンスを稼ぐためには、なるべくレンダーターゲットの切り替えを減らす努力が有効なんです。そこで、我々の実装では、ViewPortを工夫して単一レンダーターゲットに対して複数目的の描画をする事で、レンダーターゲットを減らしています。これも、一種のハックといえるかも知れませんね(笑)。
先出しした深度バッファを用いて、今度は、実際の遮蔽判定を行なうフェーズになるわけだが、ここでは描画する3DオブジェクトのAABBをその先出しした深度バッファに対して「可視テスト」を行なう事で実践する。
「可視テスト」とは、PowerVR系に搭載されたGPUオクルージョンクエリの機能のこと。オクルージョンクエリとは、実際にレンダーターゲットへの出力としての“描画”はしないが、そのオブジェクトを描画するとしたら何ピクセル描き込めるのかを、調べてくれる機能のことだ。なお、オクルージョンクエリの実行に際しては、実際にレンダーターゲットへの描き出しは行なわれないが、グラフィックスレンダリングパイプラインは駆動されることになる。
先出しした深度バッファに対して直方体としてのAABBの可視テストを実践し、まったく描き込めないという結果が帰ってくれば、その3Dモデルは描画せずに排除するという判断を行なうわけだ。前述したように、この「3DオブジェクトのAABB可視テスト」は、影描画用シャドウマップ生成負荷低減のために用意した、平行光源位置から先出しした深度バッファに対しても行なわれる。
つまり、「3DオブジェクトのAABB可視テスト」は先出しした2つの深度バッファに対して行なわれると言うことだ。どちらか一方でも可視テストをパスしていれば、カリングされずに、本番レンダリングの際にはGPUに流し込まれることになる。
なお、視錐台カリングの時にも用いた以前の状態情報を利用した最適化処置は、このオクルージョンカリングにも盛り込まれている。
まず、直前まで可視と判定されていた3Dモデルは引き続き可視の可能性が高いので、このオクルージョンカリングには5フレームに1回しか回さず、「可視」として本番レンダリング時にGPUに流し込むようにしている。この推測が間違えていたとしてもその時だけGPUの頂点負荷が割り増しになるだけで描画に矛盾が出ることはない。判定ミスよりも判定OKの方の頻度を優先させた採択というわけだ。
一方、直前まで遮蔽されていた3Dモデルは、いつ「可視」として判定されるかわからず、「不可視→可視」の瞬間を逃すと唐突に3Dモデルが出現するポッピング現象に繋がってしまうので、毎フレームオクルージョンカリングを行なうようにしている。