AI生成のロケーションをUnreal Engine 5に取り込む
街区まるごとのGLBとUE5プロジェクトが手元にある状態から。スケール、Nanite、Lumen、コリジョン、そして巨大なActorがストリーミングされない理由まで。
建物40棟ぶんの街区のGLBがContent Drawerに160個のStatic Meshとして着地して、マネキンの頭が二階の窓を越えています。これは単位の話で、直すのに一分もかかりません。一時間後に気づくのは、街区まるごとが一つのActorとして入っていて、World Partitionがそれを常時ロードだと静かに判断していることです。こちらは構造の問題で、レベル側で直すより書き出し側で直すほうがずっと安く済みます。
Unrealはインポートされたジオメトリの扱いがUnityとは違います。Naniteは三角形の予算という言葉の意味を変え、Lumenはライティングパスの支払いを変え、World PartitionはActorの役割を変えます。もう一方のエンジンから来たばかりなら、Unity側の同じ問題は別のトレードオフの束で、身についた習慣はほとんど転用できません。
センチメートルと、扉によるテスト
1 Unreal Unitは1センチメートルです。キャラクターコントローラー、物理、ナビメッシュ、Distance Field、ストリーミンググリッドはすべてそれを前提にしていて、その前提が崩れていても誰も教えてくれません。ただ、なんとなく気持ち悪いレベルができあがります。
十秒で終わる確認
読み込んだメッシュを原点に置き、テンプレートコンテンツからサードパーソンのキャラクターを引っぱり出して、扉の開口に立たせます。既定のキャラクターカプセルは半径34 uu、ハーフハイト88 uu、つまり176 cmの円柱なので、プレイヤーをおよそ180 cmとみなしてください。説得力のある街路の開口は高さ210〜230 uu、幅110〜140 uuです。扉が2.2 uuなら100倍ずれています。扉が220 uuなのに隣の歩道が幅800 uuなら、単位は正しくて比率が間違っている。それはインポーターではなくジェネレーター側の問題です。
100倍はどこから来るのか
FBXはグローバル設定に単位スケール係数を持っていて、Unrealはそれを読みます。センチメートル基準のツールが書き出したFBXが正しい大きさで着地するのはそのためです。glTFには単位のフィールドがそもそもありません。仕様が「数値はメートルである」と宣言しているだけなので、変換は読み取るのではなく仮定するしかない。扉を測り、100倍ずれていればそのインポートのImport Uniform Scaleを100にしてください。修正はアセット側か書き出し側で行い、レベル内でActorをスケールして済ませてはいけません。コリジョンもDistance Fieldもナビゲーションもその歪みを引き継ぎます。
FBXかGLBか、そして各々どう届くか
どちらのフォーマットが一般によいかは別の議論です。ここで重要なのは、UE5のプロジェクトに着地したときに各々が何になるかです。
FBX
FBXはUnrealが長く使ってきたFBXインポーターを通り、アセットになります。Static Mesh、マテリアル、コンテンツフォルダに展開されるテクスチャ。Combine Meshesが一つのメッシュか多数かを決めますが、街区なら多数を選びます。現在のInterchange経路でFBXが渡してくれないのは配置です。Import Into Levelが受け付けないので、手に入るのはメッシュの入ったフォルダで、並べるのは自分の仕事になります。
glTFとGLB
glTFはInterchangeフレームワークが処理します。.glbに埋め込まれたテクスチャとマテリアルは自動的に入ってきますし、読み込まれたマテリアルは新規のマテリアルアセットではなく、Unreal側のglTF親マテリアルのインスタンスとして現れます。自前のマスターマテリアルを持つプロジェクトでは少しだけ面倒なところです。ロケーション全体にGLBを選ぶ理由は、FileからのImport Into Levelです。Interchangeはこれをこの形式について対応していて、ノード階層を読み、ファイルが記述したとおりの配置でActorをレベルに作ります。街区にとって、その配置こそが作業の大半です。Import Into Levelが受け付けるフォーマットはバージョン間で動いてきたので、自分の環境で確認してください。
Cuberta のように、エージェントがロケーションを個別のオブジェクトとして組み立て、テクスチャ込みのGLBまたはFBXで書き出すエディタなら、その階層が最初から入ったファイルが手に入ります。溶接された一枚のメッシュとして届いた街区は、手をつける前にそれを捨ててしまっています。
Nanite:何を変え、何を免除しないか
生成メッシュが恩恵を受ける場合
NaniteはStatic Mesh向けの仮想化ジオメトリのパイプラインで、本当に密なジオメトリで元が取れます。デシメートしたスキャンデータ、キットバッシュを重ねたファサード、実際にモデリングされたディテールを持つ屋根。手作業でLODチェーンを組むのが苦痛な領域です。ドローコールの算術も畳んでくれます。インスタンスごと・マテリアルスロットごとに一回ではなく、Naniteのシーンはおおむねマテリアルごとに一回で済みます。
そもそも有効にできない場合
- スケルタルメッシュと頂点アニメーション。NaniteはStatic Mesh向けです。スキニングされたもの、モーフターゲットで動くもの、頂点シェーダーでアニメーションするものは対象外です。
- 半透明マテリアル。translucentマテリアルを持つNaniteメッシュはまったく描画されません。「アウトライナーにはあるのに画面にない建物」の標準的な説明がこれです。Maskedマテリアルは現行バージョンでは対応済みですが、プロジェクト設定と正しく構成されたマテリアルが必要です。
- ハードウェア。NaniteはDirectX 12とShader Model 6、またはVulkanの相当機能を要求します。
トポロジーは直してくれない
Naniteを有効にしても、何かが溶接されるわけでも、裏返った法線が戻るわけでも、同じ空間を占める二枚のシェルが解消されるわけでもありません。しかも同じソースから粗いフォールバックメッシュが作られ、複雑コリジョンとLightmassが使うのはそのフォールバックです。悪い入力は悪いコリジョンと悪いベイクを生み、ただ見えにくくなるだけです。そして大半の生成街区を構成している数千ポリゴンの箱型の建物では、Naniteも無料ではありません。パイプラインには固定費があり、画面を覆う大きな三角形はそのラスタライザーが最も苦手とするケースです。有効にし、計測し、必要なら戻す覚悟を持ってください。そのジオメトリがこのパイプラインに値するかどうかは、別の最適化の問題です。
マテリアルスロットはいくつまでか
Static Meshはマテリアルスロットごとに一つのセクションを持ちます。Naniteなしなら、それはインスタンスごと・スロットごとのドローコールです。Naniteありなら支払いは固有マテリアルごと一回に近づきますが、それでも各マテリアルはシェーディングされ、それぞれ別のシェーダーパーミュテーションであることは変わりません。生成された建物なら、1つから4つのスロットが健全です。壁、屋根、ガラス、見切り。1棟あたり8つを超えたあたりから、誰も見ないバリエーションに支払っていることになりますし、400棟×10スロットは美術の問題になるはるか手前でテクスチャストリーミングの問題になります。二度考える価値があるのはガラスです。窓が半透明マテリアルなら、そのメッシュはNaniteにできません。ガラスを独立したメッシュに分けてそれだけNaniteから外すか、窓を強い反射を持つ不透明マテリアルにするかのどちらかです。
Lumenと、それでもベイクする場面
新規のUE5プロジェクトは既定でLumenで照らされます。生成コンテンツにとってこの既定値は贈り物です。ライトマップUVも不要、ベイクも不要、正午に街区を作り直して十分後には照らされた状態で見られます。
そのLumenには、生成ジオメトリが絶えず破ってしまう要件が一つあります。ハードウェアレイトレーシングがない場合、LumenはMesh Distance Fieldをトレースしますが、Distance Fieldは極端に薄い形状や、裏から見た片面ジオメトリを表現できません。厚さがおよそ10 cmを切る壁は光が漏れますし、生成された建物は片面のファサードと厚みゼロの壁だらけです。閉じた店先から夕暮れの光が滲んでくるのはそのためです。対処は優先順に、生成時に壁へ実際の厚みを持たせる、問題のメッシュで両面Distance Fieldの生成を有効にする、それでも直らないものはDistance Field Resolution Scaleを上げる。同じ仕組みは統合も罰します。多層の建物を一つのメッシュにすると距離場が粗くなり、自分自身を遮蔽します。
それでもベイクが必要なら、たいていはターゲットのハードウェアがLumenを賄えない場合ですが、時間を正直に見積もってください。すべてのメッシュに重ならないライトマップUVが要り、生成メッシュにはたいていそれがなく、汚いトポロジーに対するUnrealの自動生成こそがUV重複の警告の出どころです。Naniteを有効にしているとLightmassはフォールバックメッシュで働くので、焼き上がるシルエットはビューポートより粗くなります。
コリジョン:複雑コリジョンは罠
インポートしたStatic Meshは複雑コリジョン、つまり自分自身の三角形をトレース用に持った状態で届きます。そしてソースファイルに UCX_ の形状が入っていない限り、シンプルコリジョンはまったくありません。街区はラインドレースに対してだけ固く、他の何に対しても固くない。そして一番わかりやすい解決策が、まさに間違った解決策です。
UseComplexAsSimpleを使っている場合、そのオブジェクトをシミュレートすることはできません。ただし、シミュレートされている(シンプルな)他のオブジェクトと衝突させることはできます。
加えて、街区ぶんの三角形に対するポリゴン単位のクエリは高くつきますし、Naniteを有効にしているならクエリの相手はフォールバックメッシュです。代わりに使うもの。
- 建物:ボックス一つ、あるいは凸包をいくつか。誰のキャラクターコントローラーも軒蛇腹を必要としていません。
- 道路と歩道:平面一枚、または下のランドスケープ。道路メッシュの三角形ではありません。
- プロップ:Static Meshエディタの自動凸分解を、凸包いくつかに収めて。
- 物理シミュレーションするもの:常にシンプルコリジョン。
- 装飾的なジオメトリ:コリジョンなし。生成街区ではしばしばActorの半分がこれです。
- 地形と動かない添景:ここは複雑コリジョンで本当に問題ありません。そのためのものです。
ソース側のツールがレンダーメッシュと並べて UCX_ のコリジョン形状を書き出せるなら、それを使ってください。アセットを一つずつ開いて凸包を作るより速く済みます。
World Partitionと、タイルで書き出す理由
World Partitionは世界を一つの永続レベルに保持し、ランタイムのグリッドセルに分割し、プレイヤーの位置に応じてセルをストリーミングします。ストリーミングの単位はActorであり、生成街区をどう分割すべきかという話はすべてそこから導かれます。
一つのActorとしてインポートされた街区はストリーミングできません。グリッドのセルサイズより大きなActorはグリッド階層の上位へ引き上げられ、セル境界をまたぐActorは近くに誰がいようといまいとロードされたままになります。多数のActorとして、建物ごと、あるいは小さなまとまりごとにインポートすれば、システムは何も言わなくても仕事をします。
だからこそ、一つのファイルではなくタイルで書き出す理由が立ちます。100〜200 m四方に切った街区は、最後まで終わるインポート、Actorの自然な粒度、そして残り三十ブロックを再インポートせずに一ブロックだけ作り直せることをくれます。作業する範囲を制限できるツールなら、たとえばCubertaではエージェントに作らせる場所と作らせない場所を指定できますが、ブロック単位で生成して書き出せます。同じ分割を一歩手前で適用するだけです。
Actorが入り、spatially loadedになったら、遠景としての読めかたを判断する前にBuildからBuild HLODsを走らせてください。HLODはストリーミング範囲外のセルに簡略化されたプロキシを生成するので、スカイラインは消えずにそこに残ります。
短いトラブルシューティング
| 症状 | よくある原因 |
|---|---|
| すべてが100倍ずれている | 書き出し側の単位の仮定。Import Uniform Scaleで応急処置し、エクスポーターを直す |
| アウトライナーにはあるのに画面にない | Naniteメッシュ上の半透明マテリアル、または法線の反転 |
| キャラクターが壁を抜ける | シンプルコリジョンがない。トライメッシュはトレースにしか答えない |
| 閉じた部屋に光が漏れる | 厚さ10 cm未満、または片面の壁 |
| ライトビルドでUV重複の警告 | 汚いトポロジーに対する自動生成のライトマップUV |
| 何もアンロードされない | 街区が一つのActor、またはActorがspatially loadedでない |
| すべてが灰色 | テクスチャが埋め込まれていないか、マテリアルインスタンスが親を失っている |
結果を実際に決めるもの
Naniteのおかげで三角形の議論はめったに必要なくなり、Lumenのおかげでベイクもめったに必要なくなりました。二つ合わせて、大きな環境のインポートを惨めにしていたものの大半が消えています。UE5が許してくれないのは構造のほうです。ファイルがどの単位で書かれたか、街区がいくつのActorでできているか、そして何が固いのかをエンジンに伝えた者がいたかどうか。
三つともImportを押す前に決まっていて、うち二つはそのファイルを書き出した何かが決めています。だからこそ、メートルで考え、建物を個別のオブジェクトのまま保つもので生成する意味があるわけです。美しい一枚のメッシュを生成して、そのあと一週間かけて分解する代わりに。