ケース スタディ - Mixed Realityで銀河を作成する
Microsoft HoloLens の発売前に、Microsoft では、経験豊富な社内チームが作成するアプリとして、どのようなアプリが見てみたいかを開発者コミュニティに尋ねました。 5000 以上のアイデアを公開し、24 時間の Twitter 投票を実施した結果、選ばれたのは Galaxy Explorer でした。
プロジェクトのアート リードである Andy Zibits と、チームのグラフィックス エンジニアである Karim Luccin は、アートとエンジニアリングの間で連携をとりながら、Galaxy Explorer で表現される正確かつインタラクティブな銀河がどのように作成されたのかについて語っています。
技術概要
私たちのチーム (2 名のエンジニア、3 名の開発者、4 名のアーティスト、1 名のプロデューサー、1 名のテスターで編成) は、天の川銀河の広大さと美しさを観察して学習できる完成版のアプリを、6 週間かけて作成しました。
私たちは HoloLens の機能をフルに活用して 3D オブジェクトを実際の生活空間に直接レンダリングしたいと考えました。リアルな見た目の銀河を作成し、ユーザーが個々の星をそれぞれの軌道上で、近くに拡大して見られるようにしたいと考えたのです。
開発の第 1 週目に、私たちは天の川銀河の表現に関するいくつかの目標を設定しました。それらを達成するには、奥行き、動き、ボリューム感を表現し、たくさんの星によって銀河の形が作り出されるようにする必要がありました。
何十億もの星で構成された動く銀河を作成するうえで問題となったのは、更新を必要とする要素の数がフレームごとにあまりに多すぎて、HoloLens で CPU を使ってアニメーション化するのは困難だということでした。 これを解決するには、アートとサイエンスを複雑に組み合わせる必要がありました。
バックグラウンド処理
ユーザーが 1 つ 1 つの星を観察できるようにするために、私たちはまず、一度にレンダリングできるパーティクルの数を把握する必要がありました。
パーティクルのレンダリング
現在の一般的な CPU は、シリアル タスクを処理し、(搭載しているコアの数に応じて) 一度に最大数個の並列タスクを処理するのには適していますが、数千の操作を並列処理する場合には、GPU の方がはるかに効果的です。 ただし、通常は CPU と同じメモリを共有しないので、CPU <>と GPU の間でデータを交換すると、すぐにボトルネックになる可能性があります。 そこで私たちは、GPU 上に銀河を作り、完全に GPU 上だけで処理することにししました。
私たちは何千ものポイント パーティクルを使用して、さまざまなパターンでストレス テストを行いました。 そうすることで、HoloLens 上に銀河を作成し、うまくいく方法とうまくいかない方法を確認したのです。
星の位置の作成
星を初期値の位置で生成する C# コードは、チーム メンバーの 1 人が既に記述していました。 星は楕円上に配置され、その位置は curveOffset、ellipseSize、elevation によって記述できます。curveOffset は楕円に対する星の角度、ellipseSize は X および Z 軸で表した楕円の寸法、elevation は銀河内での星の適切な高度です。 これを基に、それぞれの星の属性で初期化されるバッファー (Unity のComputeBuffer) を作成し、それを GPU に送って、エクスペリエンスの最後まで GPU 上で処理を行うことができます。 このバッファーを描画するには、 Unity のDrawProcedural を使用します。これにより、銀河を表す実際のメッシュがなくても、任意のポイント セットに対してシェーダー (GPU 上のコード) を実行できるようになります。
CPU:
GraphicsDrawProcedural(MeshTopology.Points, starCount, 1);
GPU:
v2g vert (uint index : SV_VertexID)
{
// _Stars is the buffer we created that contains the initial state of the system
StarDescriptor star = _Stars[index];
…
}
私たちは、何千ものパーティクルを含んだ生の円形パターンから作業を開始しました。 その結果、多数のパーティクルを管理しながら、高パフォーマンスな速度で処理を実行できることが証明できました。ただし、私たちは銀河の全体的な形状にはまだ満足していませんでした。 形状を改善するために、私たちはさまざまなパターンとパーティクル システムで回転を試しました。 最初のうちは、パーティクルの数とパフォーマンスに一貫性が保たれていたのでうまくいきそうに思えたのですが、中心付近に行くと形状が崩れ、星が外側に向かって放出されて、リアルなものになりませんでした。 そのため、放出方法を変えて時間を操作できるようにし、パーティクルの動きをリアルにして、銀河の中心に向かって渦を巻き続けるようにする必要がありました。
これらのように、私たちはさまざまなパターンとパーティクル システムで回転を試しました。
私たちのチームは、宇宙の仕組みについて研究し、銀河に特化したカスタム パーティクル システムを作成して、楕円上のパーティクルを "密度波理論" に基づいて移動させるようにしました。この理論は、銀河の腕を高密度の領域としながらも、絶えず流動するなかで交通渋滞のような現象が生じるということを論じたものです。 腕は一見安定しているように見えますが、実際には、星はそれぞれの楕円に沿って移動していくうちに、腕を出入りしながら移動することになります。 私たちのシステムでは、CPU 上にパーティクルが存在しないため、私たちはカードを生成し、それらの向きをすべて GPU 上で処理して、システム全体の動作が初期状態 + 時間で決まるようにしました。 次の画像は、処理の進行を示したものです。
GPU レンダリングを使用したパーティクル システムの進行
十分な楕円が追加され、回転するように設定されると、銀河に "腕" が形成され始め、星の動きがそれらの腕で収束するようになりました。 それぞれの楕円パスに沿って移動する星の間隔にはランダム性が適用され、それぞれの星の位置に多少のランダム性が加えられました。 これにより、星の動きの分散と腕の形状がはるかに自然な印象になりました。 最後に、私たちは中心からの距離に基づいて色を操作する機能を追加しました。
星の動きの作成
全体的な星の動きをアニメーション化するために、私たちはフレームごとに一定の角度を追加し、星を一定の視線速度で楕円に沿って移動させる必要がありました。 これが、curveOffset を使用した主な理由です。 これは厳密には正しい動きではありません。星は楕円の長い弧でより速く移動するからです。ただし、全体としての動きは良い感じでした。
星は長い円弧でより速く移動し、端の方では遅くなります。
この場合、各星は curveOffset、ellipseSize、elevation、Age によってすべて記述されます。Age は、シーンが読み込まれた後に経過した合計時間の累積です。
float3 ComputeStarPosition(StarDescriptor star)
{
float curveOffset = star.curveOffset + Age;
// this will be coded as a “sincos” on the hardware which will compute both sides
float x = cos(curveOffset) * star.xRadii;
float z = sin(curveOffset) * star.zRadii;
return float3(x, star.elevation, z);
}
これにより、アプリケーションの起動時に何万もの星を 1 回で生成し、単一化された星のセットを、設定された曲線に沿ってアニメーション化できるようになりました。 すべてが GPU 上にあるので、CPU にコストを生じさせることなく、すべての星を並列処理でアニメーション化できます。
白い四角形を描画した場合の見た目はこのようになります。
各四角形がカメラの方を向くようにするために、私たちはジオメトリ シェーダーを使用して、各星の位置を画面上の 2D の四角形に変換し、それに星のテクスチャを含めるようにしました。
四角形ではなく、ひし形を使用した場合。
オーバードロー (ピクセルが処理される回数) をできるだけ減らしたいので、四角形を回転させて、重なりを減らすようにしました。
クラウドの追加
パーティクルにボリューム感をもたせる方法にはさまざまなものがあります (ボリューム内でのレイ マーチングや、可能な限り多くのパーティクルを描画してクラウドをシミュレートする方法など)。 リアルタイムのレイ マーチングは高コストで作成も難しいので、私たちはまず、ゲームで森をレンダリングする方法 (多数の木の 2D 画像をカメラの方に向かせる方法) を使って、インポスター システムを作成してみました。 ゲームでこれを行う場合は、周囲を回転するカメラからツリーのテクスチャをレンダリングし、それらすべての画像を保存して、各ビルボード カードの実行時にビューの方向に一致する画像を選択します。 画像がホログラムの場合は、この方法もうまくいきません。 左目と右目の視差があるため、はるかに高い解像度が必要になります。そうでないと、見た目が平らになったり反復的になったりします。
2 回目の試みとして、私たちはパーティクルの数を可能な限り増やしてみました。 この場合、パーティクルを追加的に描画し、それらにぼかしを適用してからシーンに追加することで、最も良いビジュアルが得られました。 このアプローチでは、1 回にどれだけのパーティクルを描画できるかや、60fps を維持しながらどれだけの画面領域をカバーできるかによって、問題が生じてくることがわかりました。 結果の画像をぼかすことでクラウドの見た目を表現するのは、非常に高コストな操作になりやすいことがわかったのです。
テクスチャを使用しない場合、不透明度を 2% にすると、クラウドの見た目はこのようになります。
追加によって数を増やすということは、複数の四角形が重なって、同じピクセルを繰り返しシェーディングする必要が生じることを意味します。 銀河の中心では、同じピクセルで何百もの四角形が互いに重なり合うため、全画面で表示すると大きなコストがかかる結果になりました。
クラウドを全画面で実行し、それらにぼかしをかける方法ではうまくいかないことがわかったため、私たちはハードウェアに処理をまかせることにしました。
元となるコンテキストを用意する
ゲームでテクスチャを使用する場合、テクスチャのサイズが使用先の領域と一致することはほとんどありませんが、さまざまな種類のテクスチャ フィルタリングを使用することで、テクスチャのピクセルから必要な色をグラフィック カードに補間させることができます (テクスチャ フィルタリング)。 私たちが注目したフィルタリングは、4 つの直近値を使用して任意のピクセルの値を計算する、バイリニア フィルタリングです。
このプロパティを使用すると、テクスチャを 2 倍の大きさで領域内に描画するたびに、結果がぼやけてしまうことがわかります。
全画面にレンダリングして、他の処理に使えるはずの貴重なミリ秒を失う代わりに、小さなバージョンの画面にレンダリングを行うのです。 その後、このテクスチャをコピーし、それを 2 倍に拡大する操作を複数回行うことで、プロセス内のコンテンツをぼかしながら、全画面へと戻していきます。
3 回拡大してフル解像度に戻します。
これにより、元のコストよりもはるかに少ないコストでクラウド部分を描画できます。 完全な解像度でクラウドを追加するのではなく、1/64 のピクセルだけを描画し、そのテクスチャをフル解像度まで引き引き伸ばすだけです。
左は 1/8 から 1 回の拡大でフル解像度にしたもの。右は 2 の累乗で 3 回拡大したもの。
1/64 のサイズから 1 回でフル サイズにしようとした場合、見た目に大きな違いが出ていることに注目してください。これは、グラフィック カードで使用されるピクセルの設定を 4 ピクセルにしたままで、より大きな領域をシェーディングし、成果物を表示しているためです。
後で、フル解像度の星をそれよりも小さなカードを使って追加すれば、銀河全体を描画することができます。
形状の処理方法がほぼ決まったところで、私たちはクラウドのレイヤーを追加し、一時的なドットを Photoshop で仕上げたものへと交換して、さらにいくつかの色を追加しました。 その結果、アート チームとエンジニアリング チームの両方が納得できる天の川銀河を描画できるようになり、CPU への負荷を生じることなく、奥行き、ボリューム、動きについての目標を達成することができました。
最終的な 3D の天の川銀河。
さらなる模索のために
私たちは、Galaxy Explorer アプリのコードをオープンソース化しました。開発者は GitHub からコードを入手し、さらなる開発に利用できます。
Galaxy Explorer の開発プロセスについてさらに詳しく知りたい場合は、 Microsoft HoloLens の YouTube チャネルで過去のプロジェクト アップデートを確認してください。
著者について
Karim Luccin はソフトウェア エンジニアであり、凝ったビジュアルの愛好家です。 彼は Galaxy Explorer のグラフィックス エンジニアを務めました。 | |
Andy Zibits は宇宙が大好きなアート リードです。彼は Galaxy Explorer の 3D モデリング チームを取りまとめ、パーティクルの増量に取り組みました。 |