手続き型ゲーム開発のための好みのツールと手法
(cprimozic.net)- ブラウザで動作する3Dシーン・レベルのプロジェクトが大きくなるにつれ、ワールド全体を生成するよりも、レベルの特定要素に手続き型・生成的手法を適用する再利用ツールが蓄積されてきた
- テクスチャ作業の中心はTriplanar MappingとHex Tilingで、UVなしでテクスチャを貼ったり、繰り返しパターンを隠したりするために使われる
- どちらの手法でも補間ウェイトに
pow()を適用して支配的な軸や参照結果の比重を高め、一部のテクスチャ参照を省略することで性能負荷を下げている - 高コストなフラグメントシェーダはDepth Pre-Passで緩和でき、overdrawの多いシーンでは性能が30%以上改善することがある
- メッシュ・ジオメトリ周りでは、LoD terrain、ランタイムのメッシュ処理パイプライン、そして今後はConstructive Solid Geometryによって装飾・背景・損傷表現を拡張しようとする流れがある
ブラウザ3Dシーンで蓄積された手続き型ツール群
- 数年にわたってブラウザで動作する3D scenes and levelsを作る中で、カスタムシェーダ中心の独立デモが、相互に接続されたゲームのような形へと大きくなっていった
- 共通したアプローチは、完全な手続き生成ワールドではなく、レベルの特定部分に手続き型・生成的手法を適用する方式である
- 複数のレベルで繰り返し使われる手続き型・半手続き型のツールやエフェクトが自然に蓄積されていった
シェーダとテクスチャ
- ほとんどのテクスチャは両軸方向に継ぎ目なくタイル可能なseamless textureであり、広い領域に展開すると繰り返しパターンが目立つことがある
- Three.JSの
MeshPhysicalMaterialを拡張したカスタムシェーダに複数の機能を追加し、seamless texturingのサポートを改善した -
Triplanar Mapping
- Triplanar Mappingはほぼすべてのレベルで使われるテクスチャリングの中核ツールである
- あらかじめ定義されたUV mapなしでseamless textureをメッシュに適用できるため、手続き生成terrainのようにモデラーがUV mappingを定義する機会のない場合に有用である
- 生成メッシュにも手作業でモデリングしたメッシュにもよく機能する
- 実装は軽量で単純であり、参考実装はtriplanarMapping.tsにある
-
Triplanar Mappingの改善
- 一般的なTriplanar Mappingでは、フラグメントnormalを基準に3軸のテクスチャ参照を線形にブレンドする
- normalが単一軸に近くない領域では、テクスチャが層状に見えることがある
- ウェイトに高い指数の
pow()を適用してから再正規化すると、支配的な軸の比重が増し、遷移領域が小さくなる - この変換により、メッシュの大部分で1つの軸のウェイトが1に近づき、残り2軸のウェイトは0に近づく
- しきい値より小さいウェイトのテクスチャ参照を省略することで、Triplanar Mappingの性能負荷を通常のUVベースのテクスチャリングより少し大きい程度まで下げられる
- normal mapの処理はシェーダコード内で別途考慮が必要で、GPU Gems方式を使っている
- 実装の詳細はNormal Mapping for a Triplanar Shaderで確認できる
-
Hex Tiling
- Hex Tilingは、seamless textureで目立つタイル感や反復を隠すアルゴリズムである
- マテリアルに設定オプションを1つ追加するだけで、シーンが完成度の低いモックアップのように見える状態から、semi-realisticに近い見た目へ変えられる
- 初期実装はFabrice NeyretのShadertoyをベースにしており、Three.JS material system向けに変換してプロジェクトのメインmaterialシェーダに統合した
- その後、許可を得て、Three.JSプロジェクトのbuilt-in materialにHex Tilingを追加できる独立ライブラリthree-hex-tilingへ移植した
- Triplanar Mappingと違って、あらかじめ定義されたUV mappingが必要である
- 2つの手法を併用すると、各mapごとにフラグメントあたり最大27回のテクスチャfetchが発生し、実用的ではない
- Hex Tilingもフラグメントごとに3つの参照結果を線形補間するため、Triplanar Mappingで使った
pow()ウェイトの手法によって性能と結果品質の両方を改善できる
-
Depth Pre-Pass
- 高度なテクスチャリング手法は、大きなシーンで高コストなフラグメントシェーダを生みやすい
- Depth Pre-Passは、シーン全体を非常に単純で低コストなmaterialで先にレンダリングし、各ピクセルのdepthを記録する方式である
- シーンを2回レンダリングするオーバーヘッドはあるが、overdrawの多いシーンでは概してコストを上回る効果がある
- overdrawが多い場合、Depth Pre-Passを追加することで性能が30%以上改善することがある
- pre-passの設定を変更すると、occluded fragmentのみをレンダリングし、pre-pass使用時にスキップされるフラグメントを可視化できる
- Three.JSでの実装と設定の詳細はdedicated articleで確認できる
-
AIベースのPBRテクスチャ合成
- AI-generated textureはほぼすべてのシーンで使われている
- 節度を持って使えば結果はかなり良く見せられ、例示されたシーンのすべてのテクスチャがAI-generatedである
- テクスチャ生成、PBR map生成、アップスケールなしでseamless 4K textureへ結合する過程は別記事で扱っている
- その記事でPBR map生成用として言及されていたウェブサイトは、現在はもう利用できない
- 現在はnormal map生成にDeepBumpを使い、必要に応じて他のmapにはMaterializeのような非AIツールを使っている
-
Volumetric Fog/Clouds
- volumetric renderingはシーンに独特の効果を与えられるため、関心を持ち続けてきた分野である
- どんなThree.JSシーンにもcloudやfogを追加できる、比較的汎用的なシェーダを作成した
- Inigo QuilezのShadertoyに着想を得て、似たLoD noise lookupを使う基本的なvolumetric clouds shaderを作り、その後さらに汎用的で設定可能な形へ拡張した
- このシェーダは、まばらなレベルの空白部分を埋めたり、動くcloudやfogによって静的なレベルに動的な感覚を加えたりするのに有用である
three-good-godraysプロジェクトでn8programsが開発したコードとアプローチも一部利用しているthree-good-godraysもよく使われ、レベルに非常に独特な雰囲気を加える
メッシュとジオメトリ
- ランタイムでのメッシュ生成は、ますます多く扱う領域になっている
- ソフトウェアのseedからワールドが成長していくアイデアは気に入っているが、手続き生成を前面に出した一部のゲームに見られる「無限だが空虚」な状態は避けたいと考えている
- そのため、中核体験全体を手続き生成するのではなく、主にレベルへ装飾、背景、手続き的なflourishを加えることに注力している
-
LoD Terrain
- terrain generationは手続き型ゲーム開発の代表的な領域であり、実装自体は特別なものではない
- 多くの方式と同様に、noise functionでterrain heightmapを作り、それをtriangleへtessellateしてレンダリングする
- テクスチャリングにはTriplanar MappingまたはHex Tilingを使う
- 中核となるのはLoDシステムで、terrainをtile単位で作成し、各tileを複数の解像度で生成する
- tileとcameraの距離に応じて異なる解像度へ動的に切り替える
- このterrain generation systemは頻繁に再利用されており、柔軟で効率的なシステムのおかげで少ない労力で複数のレベルに適用できる
-
手続き的メッシュ処理・操作パイプライン
- 最近もっとも多く取り組んでいるのはprocedural mesh processingパイプラインである
- 初期目標は、動的に生成されたメッシュまで含めてlow-poly meshを手続き的にsubdivideし、deformすることだった
- platform、boulder、大きな構造物のような単純なメッシュをレベルに置いたとき、より現実的または興味深く見せることが目的である
- この作業は、ブラウザランタイム内でraw geometry dataを受け取り、任意に修正し、レンダリング可能な形式へ再出力するソフトウェアパイプラインにつながった
- この過程では、とくにnormal処理において細かな配慮が必要になる
- 実装の詳細はsubdividing meshes for displacementの記事にある
次の実験候補: Constructive Solid Geometry
- ここに挙げたツールの大半は、もともと特定のユースケース向けの一度きりの実装として始まったが、別のレベルや文脈でも繰り返し再利用されるようになった
- 次に試してみたい主なアイデアはConstructive Solid Geometryである
- Constructive Solid Geometryは、3D空間にboolean operatorを適用するシステムである
- 任意の2つのmeshを結合できる
- meshからchunkを切り出せる
- そのほか類似の操作を実行できる
- csg.jsは、約500 LoCのコメント付きJavaScriptファイル1つで、mesh primitive、boolean operator、整理されたAPIを備えるCSG toolkitを実装している
- いつかこのライブラリをRustへ移植しながら、動作原理をより深く理解したいと考えている
- CSGを既存のmesh processing pipelineと組み合わせれば、興味深い結果を生み出せる可能性が高い
- とくにメッシュを手続き的にdamageする機能を試してみたい
- buildingやbridgeからchunkを切り出してdecayやweatheringをシミュレートする
- wallやroadにcrackを生成する
1件のコメント
Hacker News のコメント
以前、手続き型生成を少し触ったことがあり、特に見栄えのよい木を作ろうとしていたのだが、自分に欠けていたピースはジオメトリを簡単につなげる方法だった。
円柱を2つ作るのは簡単だが、2つを自然につなぎ合わせるのは非常に難しかった。
理論上は CSG がこの領域を埋められるが、問題をそう捉えるのは簡単ではなかった。単に頂点を追加するループとして見られず、すべてを3D形状としてモデリングしなければならないからだ。
2つの頂点ループを受け取り、ヒューリスティックに面を追加してつなげるルーチンも作ってみたが、どの頂点同士をつなぐべきかを選ぶのは予想よりずっと難しく、見栄えの悪いつながりが簡単にできてしまった。
いつか、異なるモジュール型の手続き型生成システム同士が、即興性を保ちながら協調するゲームを作りたい。例えば、各システムがワールドの一部を「占有」し、その一部を別のシステムに委譲したり、周囲の要素と自然につなげたりするような形だ。
最近、生成的な木を作る動画 [0] があったが、解法は単に円柱同士を交差させておくことだった。こういうハック的な方法でもうまく機能し、結果も悪くないことがある。
述べられているように CSG も可能だが、過度に複雑になり得る。別の方法としては、木の骨格を作ってからロフティングし、必要なら CSG と組み合わせて幹や樹皮を作ることだ。
良いライブラリは可能性を広げてくれる。円柱の結合やジオメトリの差し引きのようなブーリアン演算をしてくれる3Dジオメトリライブラリを見つければ、新しいアイデアをたくさん試せる。いくつか使ってみたが、その中で多少気に入ったのは JSCAD [1] だけだった。
[0] https://youtu.be/8zMbJmuwEUc?si=KQclrVPeSrIRmsbA
[1] https://github.com/jscad/OpenJSCAD.org
3D形状を関数的に記述して変形する方式に似ている。
デモシーンで人々がこれを使って何をしているかを見るには、YouTube で Mercury Delight を検索してみることを強く勧める。Shadertoy にも例が多く、本当に驚くようなものがたくさんある。
https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Condition...
下の例も参考になる。
https://github.com/MaxBondABE/batteries/blob/master/src/geom...
良い地形生成は、ワールド規模であれ目線の高さの規模であれ、まったく些細なものではない。
単純なアプローチでは、記事に見えるようなでこぼこの高さマップを作ることになり、現実の地形とはほとんど似ておらず、探索してもあまり面白くない。
例えば Dwarf Fortress は基本的な中点変位から始めるが、その後にカスタムの後処理を大量に行う。
この記事に出てくるものの中で、漠然とでも些細と言えるものはない。筆者は GPU シェーダーレンダリングに限れば、地球上のほぼ誰と比べても明らかに100倍開発者に近い。
「単純なアプローチ」だって、本当に単純なのか?
80億人のうち、この記事を始めるのに必要な「Hello World」段階だけでも実装できる人は何パーセントいるだろうか? シェーダーが何かを知っている人はどれほどいるだろうか? OpenGL の仕事がそんなに多いわけでもないのに。「OpenGL って何ですか、うちは Unity しか使いません」みたいな状況もよくある。
オンラインゲーマーならどうだろう? 2024年7月28日午後1時22分 EST 時点で Counter Strike 2 に接続中の 1,021,282人 [1] のうち、自分たちがプレイしているゲームに必要なシェーダーの最初の段階だけでも実装できる人は何パーセントいるだろうか?
単純なコマンドライン C++ プログラムをコンパイルしたり、ブラウザでさらに単純な JavaScript スクリプトを書いたりできる人は何パーセントいるだろうか? 実のところこれは少しひっかけ問題で、ほとんどの人はメールを扱うのがやっとだからだ。
[1] https://steamdb.info/app/730/charts/
AI と同じく、最高の手続き型コンテンツ生成でも、最終成果物にはカスタムの後処理が入るはずだ。
Web で使うさまざまな手続き型技法をよく整理した記事で、本当に役に立つ。
作業中の手続き型 RPG レベルエディタに興味があれば、https://github.com/gamedevgrunt/3D-Action-RPG-JavaScript を参考にしてもよい。
深度プリパスを使ってみた経験はまちまちだった。
何度か試したとき、中級〜高級デスクトップ GPU では目に見える性能向上は見られなかった。
正確な理由は分からないが、Early-Z による除去がピクセルシェーダー呼び出しを減らしてくれたためかもしれないと思っている。通常、不透明メッシュは手前から奥の順にレンダリングする。
ただし私の実験はゲームではなく CAD/CAM アプリケーションの文脈だった。シーンも一般的なゲーム環境とはかなり異なり、テクスチャは少なく、ポリゴン数が非常に多いジオメトリだった。
深度プリパスはしばしばオクルージョンカリングの最初の段階でもあるが、これも同様に状況によって異なる。CAD モデルよりも、複雑な都市景観のほうがはるかに有用である可能性が高い。
深度複雑度が高く、フラグメントシェーダーが重い場合に最も有用だ。公平に言えば、ほとんどのゲームがこれに該当する。
遅延レンダラーでは通常必須ではないが、forward+ ではたいていかなり得になる。