このページで学ぶ内容:ゲームをすそ野の広いモバイル端末向けに最適化して、潜在的プレイヤーへのリーチ範囲を最大限に広げる方法についての、Michelle Martin 氏(MetalPop Games 社のソフトウェアエンジニア)からのヒント。
モバイル戦略ゲーム『Galactic Colonies』を開発していた MetalPop Games は、フレームレートが落ちたりデバイスが過熱することなく、プレイヤーがローエンドデバイスで巨大都市を構築できるようにするという課題に取り組んでいました。優美なビジュアルと安定したパフォーマンスのバランスをどのように取ったのかをご覧ください。
最新のモバイルデバイスは高い処理能力を備えていますが、大規模で美しいゲーム環境を安定したフレームレートで実行することはいまだに困難です。より古いモバイルデバイス上で、大規模な 3D 環境で安定して 60 fps を達成するのは難題です。
開発者としては、ハイエンドのスマートフォンのみをターゲットとし、大多数のプレイヤーがゲームを滑らかに実行するのに十分なハードウェアを持っていると見なして開発を進めることもできます。ただし、いまだに数多くの古いデバイスが使用されている現状において、これは潜在的なプレイヤーを大量に締め出す結果となります。古いデバイスのユーザーはすべて、できれば排除したくない潜在的な顧客です。
当社のゲーム『Galactic Colonies』では、プレイヤーが異星人の住む星をコロニーとして、数多くの建物が建ち並ぶ大規模なコロニーを構築します。小さなコロニーの建物の数は 1 ダース程度ですが、大きなコロニーでは優に数百を超えます。
パイプラインの構築を開始したときの目標リストは次のとおりです。
- 多数の建物が建ち並ぶ大きなマップがほしい
- 安価で古いモバイル端末でも早い実行速度で動作するようにしたい
- 光と陰影を美しく見せたい
- 簡単で保守性に優れた制作パイプラインがほしい
ゲームの巧妙なライティングは、3D モデルの見栄えを良くする鍵になります。Unity なら、手間なく巧みなライティングができます。ゲーム面を作って、ダイナミックライトを配置すれば、すぐ見栄え良く使えます。パフォーマンスを注視するなら、全ライトをベイクしてから、ポストプロセッシングスタックを通じて SSAO をいくつか追加し、その他の装飾を散りばめましょう。そうしたらもう、出荷するだけです!
モバイルゲームでは、ライティングの設定時にテクニックを駆使したり、何らかの回避策を講じたりする必要があります。例えば、ハイエンドデバイスを対象としている場合を除けば、ポストプロセッシングエフェクトには一切触れない方が良いでしょう。同様に、大きなシーンでダイナミックライトをふんだんに使ってしまうと、フレームレートが格段に低下してしまいます。
リアルタイムのライティングは、デスクトップ PC でも負荷が高くなりうるものです。モバイル端末ではリソースがさらに限られているので、こういった魅力的な華々しい機能をすべて盛り込む余裕は必ずしもありません。
必要以上にシーンへ妙にこだわったライトを数多く設定して、ユーザーのスマートフォンのバッテリーを無駄に消耗させることは避けたいものです。
ハードウェアを限界まで酷使し続けると、スマートフォンが過熱し、その結果、端末保護のために処理能力がダウンします。これを回避するには、リアルタイムで影を描写する必要のないライトをすべてベイクするという方法があります。
(静的)シーンのハイライトと影を事前に計算しておいて、その情報をライトマップに保存するというのが、ライトベイキングのプロセスです。その後、どこでモデルを明るく(または暗く)するのかをレンダラーに伝え、ライトがあるような錯覚を作り出します。
この方法でレンダリングを高速化できます。負荷が高く時間もかかるライト計算がすべてオフラインで完了していて、レンダラー(シェーダー)は実行時にテクスチャーに格納された結果を見るだけで済むからです。
これには代償があって、ライトマップテクスチャーを余分に積み込む必要があります。これによりビルドのサイズが大きくなり、実行時に余分なテクスチャーメモリが必要になります。また、メッシュにライトマップ UV が必要になり、若干サイズが大きくなるので、ある程度スペースのロスが生じます。しかし全体的に見れば、圧倒的な高速化を実現できます。
しかし、私たちのゲームの場合、これは選択肢には入りませんでした。私たちのゲームの世界は、プレイヤーによってリアルタイムで構築されるものだからです。常にプレーヤーにより新しいエリアが発見され、新しい建物が追加され、既存の建物が改築されることから、どうしても効率的なライトベイキングは不可能です。プレイヤーによって常に変化する動的世界を相手にしている場合は、単に「Bake」ボタンを押しただけでは意味がありません。
そのため、私たちは高度にモジュール化したシーンのライトベイキングにおいて、さまざまな問題に直面しました。
Unity ではライトベイキングデータが、シーンデータに直接関連付けられるかたちで保存されます。これは、各ゲームステージが独立していてシーンが構築済みで、動的オブジェクトの数が限られている場合であれば問題になりません。ライティングを事前にベイクしておけば、それで十分です。
ところがゲームステージを動的に作成する場合、これではうまくいかないのは自明です。都市建築ゲームの世界は、創られていないのが前提です。世界の大部分はプレイヤーが何を、どこに建築するかによって動的に、その場で組み立てられていきます。これは通常、プレイヤーが何かを建築しようとするたびにプレハブをインスタンス化することで実現されます。
この問題の唯一の解決策は、関連するすべてのライトベイキングデータをシーンではなく、プレハブ内に格納することです。残念ながら、使用するライトマップのデータ、その座標、スケールをプレハブにコピーする簡単な方法はありません。
ライトベイクしたプレハブを処理するための安定したパイプラインを実現するには、個々のシーンごと(事実上複数のシーン)にプレハブを作成して、必要になったときにメインゲームにロードするのが、最善のアプローチにです。各モジュラーがライトベイクされ、必要になったらゲームに読み込まれます。
Unity でのライトベイキングの仕組みをよく見ると、実際にはライトベイクされたメッシュに他のテクスチャーが適用され、メッシュの明度が若干調整される(彩色されることもあります)だけのレンダリングであることがわかります。必要なのはライトマップテクスチャーと UV 座標だけです。そしてそのどちらも、ライトベイキングプロセスで Unity により作成されます。
ライトベイキングのプロセスで、Unity は新しい UV 座標(ライトマップテクスチャーの場所を示すもの)と、個々のメッシュのオフセットとスケールをワンセットで作成します。ライトを再ベイクするたびに、座標は変化します。
この問題の解決策を練るうえで、UV チャンネルの仕組みと、それを最大限に活用する方法についての知識が役に立ちます。
各メッシュには複数の UV 座標セット(Unity では UV チャンネルと呼びます)を持つことができます。それぞれのテクスチャー(拡散、鏡面、バンプなど)がすべて画像内の同じ場所に情報を格納するので、たいていの場合 UV セットは 1 つで十分です。
しかしオブジェクトがライトマップのようなテクスチャーを共有していて、1 つの大きなテクスチャー内の限定的な場所に関する情報を参照する必要がある場合は、この共有テクスチャーで使用する別の UV 座標セットを追加する以外の方法がないこともあります。
複数の UV 座標を使うことの欠点は、メモリの消費量が増えることです。UV セットを 1 つではなく 2 つ使うと、メッシュの 1 つ 1 つの頂点で UV 座標が 2 倍になります。すべての頂点に 2 つの浮動小数点数が格納され、レンダリング時にそれらが GPU にアップロードされます。
Unity では、通常のライトベイキング機能を使用して座標とライトマップが生成されます。エンジンによってライトマップの UV 座標がモデルの 2 番目の UV チャンネルに書き込まれます。重要なのは、この場合は 1 番目の UV 座標セットが使えないことに注意することです。なぜなら、モデルをアンラップする必要があるからです。
各面に同じテクスチャを使用したボックスを想像してください。ボックスの個々の側面はすべて同じテクスチャを再利用するため、同じ UV 座標を持ちます。しかし、箱の各面にはそれぞれの光の当たり方と影のつき方があるため、これはライトマップされたオブジェクトでは機能しません。各面について、それぞれのライティングデータに基づくライトマップ内の独自のスペースが必要なのです。そこで、新しい UV セットが必要になります。
新しいライトベイクしたプレハブの設定に必要なのは、行方不明にならないようにテクスチャーとその座標を保存して、それをプレハブにコピーすることだけです。
ライトベイキングが完了したら、シーン内のすべてのメッシュを対象にしたスクリプトを実行し、オフセットとスケールの値が適用された状態で UV 座標をメッシュの UV2 チャンネルに書き込みます。
メッシュを更新するためのコードは、次のように比較的単純です(以下に例を示します)。
もう少し詳しく言うと:これはメッシュのコピーに対して行われ、オリジナルに対しては行われません。これは、ベイク処理中にメッシュをさらに最適化するためです。
コピーが自動的に生成されてプレハブに保存され、新しいマテリアルにはカスタムシェーダーと新しく作成されたライトマップが割り当てられます。これでオリジナルのメッシュには触れないまま、ライトベイクしたプレハブはすぐに使用できる状態になります。
これでワークフローがとてもシンプルになります。グラフィックスのスタイルや見た目を更新するには、該当のシーンを開いて納得いくまで変更を加えたらコードを実行して、自動化されたベイクとコピーのプロセスを開始します。このプロセスが終了すると、ゲームでは更新されたプレハブとメッシュが、更新されたライティングで使用されるようになります。
実際のライトマップテクスチャは、カスタムシェーダーにより追加されます。カスタムシェーダーは、レンダリング中にライトマップを 2 番目のライトテクスチャとしてモデルに適用します。シェーダーはとてもシンプルで短く、カラーとライトマップを適用するだけでなく、低負荷のフェイクスペキュラー/グロスエフェクトの計算を行います。
シェーダーコードを次に示します。上記の画像は、このシェーダーを使用したマテリアル設定の画像です。
このケースでは、すべてのプレハブが設定されている 4 つの異なるシーンを使います。私たちのゲームには熱帯、氷原、砂漠など、さまざまな生物群系があり、それに従ってシーンを分割しています。
特定のシーンで使われるすべてのプレハブで 1 つのライトマップを共有します。つまり、マテリアルを 1 つだけ共有しているプレハブに、テクスチャーが 1 つだけ加わることになります。その結果、すべてのモデルの静的レンダリングと、ほぼ世界全体のバッチレンダリングを、たった 1 つのドローコールで実行できるようになりました。
すべてのタイルや建物が設定されているライトベイキングシーンには、ローカライズしたハイライトを生成するために光源を追加しています。いずれにしてもベイクダウンすることになるので、セットアップシーンには必要と思った分だけいくつでもライトを配置しても構いません。
ベイクプロセスは、必要なすべてのステップを示してくれるカスタム UI ダイアログで進めます。これにより、以下のことが確認できます。
- 正しいマテリアルがすべてのメッシュに割り当てられている
- プロセス中にベイクする必要がないものがすべて非表示になっている
- メッシュが結合/ベイクされている
- UVがコピーされ、プレハブが作成されている
- すべてに正しく名前が付けられ、バージョン管理システムの必要なファイルがチェックアウトされている
適切に命名されたプレハブがメッシュから作成されるので、ゲームコードでそれらを直接ロードして使うことができます。このプロセス中にメタファイルも変更されるので、プレハブのメッシュへの参照が失われることもありません。
このワークフローのおかげで、好きなだけ建物を調整し、好きなようにライティングして、スクリプトですべてのことを処理することができます。
メインシーンに戻ってゲームを実行すると、何ら問題なく動作します。手作業で何か行ったり、他の部分を更新したりする必要はありません。
ライティングを 100% 事前にベイクしたシーンの欠点の 1 つは、動的オブジェクトやモーションを使用しにくくなることです。影を投影する要素はすべて、リアルタイムでライトや影を計算する必要があります。言うまでもなく、これは何としても避けたいものです。
しかし、動くオブジェクトがまったくないと、3D 環境は静的で活気のない世界に見えてしまいます。
見栄えするビジュアルを高速レンダリングで実現することが最優先なので、ある程度の制限は仕方ありません。生き生きとした動きのあるスペースコロニーや都市という印象を生み出すためには、大量のオブジェクトをすべて実際に動き回らせる必要はありません。また、動かすものの大部分を影がなくてもよい、または少なくとも影がなくても気にならないものにします。
私たちは、まずすべての建物のブロックを 2 つのプレハブに分けました。1 つは静的プレハブで、頂点の大部分とメッシュの複雑なビットがすべて含まれます。もう 1 つが動的プレハブで、含まれる頂点はできるだけ少なくなるようにします。
プレハブの動的な部分は、アニメーション化されたビットで、静的な部分の上に配置されます。これらはライトベイクされません。私たちは、非常に高速で負荷が軽いフェイクライティングシェーダーを使って、オブジェクトが動的にライティングされているかのような錯覚を作り出しました。
また、オブジェクトには影を持たせないか、動的ビットの一部としてフェイクシャドウを作成します。私たちのケースではサーフェスの大部分がフラットなので、これが大きな障害になることはありませんでした。
動的ビットに影はありませんが、ないことを知ったうえでよく見てみれば、かろうじて気付く程度です。動的プレハブのライティングもフェイクです。リアルタイムのライティングはまったく使われていません。
最初に私たちは負荷を軽くするための近道として、光源(太陽)の位置をフェイクライティングシェーダーにハードコーディングしました。これはシェーダーがルックアップして世界から動的に入力する必要がある、一種の less 変数です。
動的な値を使うより定数を使った方が、当然処理は早くなります。これにより基本的なライティング、メッシュの明るい面と暗い面が得られました。
もう少し光沢を加えるため、私たちは動的オブジェクトと静的オブジェクトの両方のシェーダーにフェイクのスペキュラー/グロス計算を追加しました。スペキュラー反射はメタリックな見た目を演出する効果がありますが、表面のカーブも表現されます。
スペキュラーハイライトは反射の一種なので、カメラと光源の相対的な角度を正しく計算する必要があります。カメラが移動したり回転したりすると、スペキュラーは変化します。計算を行うすべてのシェーダーで、シーン内のカメラの位置とすべての光源へのアクセスが必要になります。
ただし、このゲームでは、スペキュラに使用する光源は太陽の 1 つだけです。私たちの場合、太陽は決して動かないので、指向性ライトと見なすことができます。使うライトを 1 つだけにして、ポジションと入射角が固定されていると想定したことで、シェーダーを大幅に簡略化できました。
加えて『Galactic Colonies』のカメラが、多くの都市建築ゲームと同じく見下ろし視点でシーンを映すタイプのものであることも功を奏しました。カメラは若干傾けたりズームインやズームアウトしたりできますが、上向き軸を中心に回転することはできません。
全体的に見れば、カメラは常に環境を上から見下ろしている状態です。スペキュラーの見た目を低負荷で偽装するため、私たちはカメラが完全に固定されていて、カメラとライトの角度が常に一定であるということにしました。
この方法で、改めて固定値をシェーダーにハードコーディングして、低負荷のスペキュラー/グロスエフェクトを実現できました。
スペキュラーに固定角を使うのは、もちろん技術的には正しくありませんが、実際にはカメラの角度がほぼ固定の場合と見分けはつきません。
プレイヤーにはあいからわずシーンは問題なく見えるわけで、リアルタイムライティングではそれだけが重要なのです。
リアルタイム ビデオ ゲームでの環境の照明は、物理的に正しくシミュレートすることよりも、視覚的に正しく見えるようにすることが常に重要でした。
ほとんどすべてのメッシュで 1 つのマテリアルを(ライトマップと頂点に由来する多くのディテールと合わせて)共有していたので、私たちはシェーダーにスペキュラーの値をいつどこに、どのくらいの強度で適用すればよいのかを伝えるためにスペキュラーテクスチャーマップを追加しました。テクスチャーには 1 番目の UV チャンネルを使ってアクセスするので、座標セットを追加する必要はありません。また、それほど詳細がないので解像度が非常に低く、ほとんどと言っていいほどスペースを使いません。
頂点数が少ない小さな動的ビットの一部については、Unity の自動ダイナミックバッチング機能を利用して、レンダリングをさらにスピードアップできました。
ベイクしたこれらの影は、どれも新しい問題を引き起こす可能性を秘めています。特に比較的モジュール化の度合いが高い建造物に使う場合です。ある時私たちは、プレイヤーが建築できる倉庫の前に、保管されている商品の種類を表示させようとしました。
これが問題を引き起こしました。ライトベイクしたオブジェクトにライトベイクしたオブジェクトを重ねたからです。いわば、ライトベイクの例外が発生したのです!
別の負荷軽減テクニックを使ってこの問題に取り組むことにしました。
- 追加オブジェクトが追加される予定であったサーフェスはフラットにし、特定のグレーカラーを使用してベースの建物と調和させる必要があった
- そのトレードオフとして、より小さなフラットサーフェス上のオブジェクトをベイクし、それをわずかなオフセットのみでその領域の上に配置できた
- ライト、ハイライト、色付きの光と陰影がすべてタイルにベイクされた
この方法でプレハブを構築してベイキングしたことで、ドローコール数を驚くほど少ない水準に抑えつつ、数百もの建造物がある巨大なマップを実現できました。私たちのゲームでは、世界のほぼ全部がたった 1 つのマテリアルでレンダリングされ、ゲームの世界よりも UI の方がたくさんのドローコールを使うほどです。Unity がレンダリングしなければならないマテリアルが少なければ少ないほど、ゲームのパフォーマンスは向上します。
これにより、パーティクル、天候効果、その他の装飾的な要素を追加する余地が生まれます。
この方法なら、比較的古いデバイスを使っているプレイヤーでも、安定した 60fps を維持したまま数百もの建造物が立ち並ぶ巨大な都市を作ることができます。