140.1K Views
December 22, 23
スライド概要
UE5.2から搭載されたProcedural Content Generation Framework (PCG)は、優れたサンプルプロジェクトとその内容にDeep Diveした資料が充実し、活用が進められています。
そこで、本スライドでは既存のDeep Dive動画やCEDEC講演とは異なる切り口で、PCGを海抜0地点から改めてDiveしていきたいと思います。
※本ドキュメントは UE5 Deep Dive 2023にて講演した資料を配布用に編集したものです
Unreal Engineを開発・提供しているエピック ゲームズ ジャパンによる公式アカウントです。 勉強会や配信などで行った講演資料を公開しています。 公式サイトはこちら https://www.unrealengine.com/ja/
Unreal Engine 5で極める! プロシージャル技術 Kazuhisa Minato Epic Games Japan, Developer Relations 2023 Private & Confidential
PCGにDeep Dive~!と行きたいが…… PCGもう結構Deep Diveされてる問題 ● Electric Dreams 環境サンプルプロジェクト (https://www.unrealengine.com/ja/electric-d reams-environment) ● Deep Dive into the Electric Dreams Project | Inside Unreal ● Unreal Engine 5 ElectricDreams環境サンプル にみるプロシージャル生成テクニック 【CEDEC 2023】 Photo by Jakob Boman on Unsplash
そこで座標を少しずらしてDeep Diveを開始したい 目次 ● プロシージャルの概要 ● プロシージャルの基礎知識 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/発展編 ● 決定論 ● 非同期処理 ● C++実装の勘所 Photo by Stella Ribeiro on Unsplash
目次 ● プロシージャルの概要 ● プロシージャルの基礎知識 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/発展編 ● 決定論 ● 非同期処理 ● C++実装の勘所 Photo by Stella Ribeiro on Unsplash
プロシージャルの概要 【プロシージャル】 “一定のアルゴリズムに従って、特定のアセットを自動生成すること” (「ゲームメーカーズ」用語集より)
プロシージャルの概要 プロシージャルの私的分類 プロシージャルによる組み立て プロシージャルによるモデリング モジュラーパーツの組み合わせを手動で パラメータにあわせてその場でユニーク はなく、構築プログラムを用いて行うもの。 なジオメトリが生成されるもの。ゲームエ 建物が典型例。 ディタの統合例はまだ少ない。 プロシージャルによる配置 環境アーティストが手で行っている環境ア セット、プロップ、建築物などの配置を、あ る理論や分布データに基づいてプログラ ムが自動実行するもの。
*GDC講演などの公開情報による推測 プロシージャルの概要 世間のプロシージャルの実現パターン 独自のプロシージャル機能を自 Houdiniなどのプロシージャル 『Houdini Engine』を用いて に強い外部DCCツールをサ Houdiniの機能をゲームエディ 前実装 ポートに利用 タに統合
そして新たな選択肢… Procedural Content Generation Framework (PCG) UE5内で完結した汎用プロシージャルコンテンツ生成ツール
プロシージャルの概要 プロシージャルの使い方も千差万別 この講演はここにLove 惑星全体をカバーするプロシージャルもあ 街の区画情報を読み込ませて、広域マッ れば… プを一気にプロシージャル生成するもの もあれば… あくまでスプラインツールやペイントツール でマップを「デザイン」し、それにコンテンツ が追従/相互反応するプロシージャルア プローチもある。 出典:『Procedural World Generation of Far Cry 5』(GDC2018)
目次 ● プロシージャルの概要 ● プロシージャルの基礎知識 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/中級編 ● 決定論 ● 非同期処理 ● C++実装の勘所 Photo by Stella Ribeiro on Unsplash
プロシージャルの基礎知識 PCGお初な人でも本講演が理解できる PCGの根底にある概念のご紹介
今日初めてプロシージャル/PCGを見る方に贈る プロシージャル基本3単語 ● ポイント ● ボリューム ● アトリビュート
ポイント
プロシージャルの基礎知識/ポイント ゲームのマップで森を作ることを考えてみよう 木をテキトーな場所にたくさん配置すれば よいハズ!? この手続きをコンピュータで処理するの だから...こう? // 20回繰り返す for (int i = 0; i < 20; i++) { // テキトーな位置に Locate(Rand(), Rand()); // 木を植える Populate(Tree); } ● ● ● これでもよい しかし… HoudiniやPCGで主流のアプロー チではない
プロシージャルの基礎知識/ポイント HoudiniやPCGでメジャーなやり方とは…? 1. 地面に点を散布(スキャッター) 2. ノイズ関数で点に濃淡(確率密度)をつける 3. 一定密度以上の点をフィルタリング 4. 残った点にオブジェクトをスポーン 点を種、密度を発芽確率と置き換えると 分かりやすいかもしれない 点が群れをなすことから、 これらの点を点群(ポイントクラウド)と呼ぶ 1 2 3 4
プロシージャルの基礎知識/ポイント ボリューム空間への木の散布例 PCGプラグインは標準では有効化されて いないので、まず有効化とエディタの再起 動を行う。 アクタの配置パネルにPCGVolumeが加 わる。このボリュームの有効範囲にPCG コンテンツを生成できる。 PCGグラフは、PCGVolumeのPCGコン ポーネントのGraphプロパティから割り当 て(あるいは新規作成)を行える。
プロシージャルの基礎知識/ポイント ボリューム空間への木の散布例
プロシージャルの基礎知識/ポイント スプライン上への点の散布例 スプラインのセグメントごとに点をサンプルして監視塔を建て、 スプラインに沿って1000UUごとに点をサンプルして擁壁アセット(幅1000)を設置
豆知識 スプラインの記述 Modeling ModeのDraw Splineツール を使えば、一般のアートツールに似た操 作感でスプラインを引ける 閉スプラインを作るにはLoopにチェック 点を打つ際に、既存オブジェクトの表面へ のスナップ具合を調整するにはRaycast Tagetsオプションを使う
ボリューム
プロシージャルの基礎知識/ボリューム 自然界を考えてみる… ● 風や鳥が種を運び、大地に種が散布される… ● 種は花を咲かせた後、新たに種を地面に散布し て、次の世代の花を咲かせる…… ● この繰り返しである
プロシージャルの基礎知識/ボリューム そこに木が生えた ● 季節はめぐり、時に別の種類の種がやってくること もあるだろう ● それは長い年月を経て力強く成長し、 ● 近くの種の成長を阻み、 ● 結果、でかい木の根元に花が咲かなくなることも あるだろう
● こうしたことは現実世界では自然に起こること ● しかし、ビデオゲーム開発の文脈では、点を撒い た時点で右図のような最終像を決定しておくのが 望ましい 発芽予定 発芽予定 発芽せず 発芽予定 発芽せず
えっ何を根拠にどうやって…? 「点」情報だけじゃ無理では
そこでボリュームの出番です ボリューム 点だけでは無理でも 体積付きなら!
● 点だけで考えると、全部、発芽して育ちそうな感じ がする
● しかし、育ち切ったときのボリューム感は、それぞ れこんな感じだと我々は知っている
● そこで、点の位置にそのボリューム感を重ねてみ る ● すると、ボリューム同士が重なっているところが分 かる
● ボリューム情報を根拠に、花の点群から木の点群 と交差する点を除去
● これなら全部発芽してオッケー 発芽予定 発芽予定 発芽予定
● 各点にオブジェクトを配置 ● ハイ、一発でいきました〜!
プロシージャルの基礎知識/ボリューム ボリュームを利用した点の除去の利用例
プロシージャルの基礎知識/ボリューム ボリュームを利用した点の除去の利用例 木の点群から要塞内点群との交差点を除去す れば… タグを使えばワールドにあ る適合するスプラインを読 み取れる。 On Interiorモードでサンプル すると、スプラインの内側が点 でみっちり満たされる。
アトリビュート
プロシージャルの基礎知識/アトリビュート 点は「大きさがなく位置だけをもつ図形」…… ではない in プロシージャル界 濃淡情報でオブジェクト配置の有無を決 定したり… ボリューム情報でほかの点群との共存具 →プロシージャル界の「点」は付随情報 合を決定したり… (アトリビュート)をたくさん持っており、み んなそれでよろしくやっている。 一般にこうしたアトリビュートは、開発者側 で自由に追加できる。
プロシージャルの基礎知識/アトリビュート アトリビュートの利用法 ● こうしたプロシージャルコンテンツでは、河口から の距離を属性として点に加えれば、点群処理の文 脈で処理可能 ● 距離をアセットのスケールに変換したり、距離に よってスポーンするアセットの種類を変えてもいい 川上ほど石が大きくゴ ツゴツで 川下にいくほど小さくて 丸い
プロシージャルの基礎知識/アトリビュート アトリビュートの利用例 木の点群と要塞内に敷き詰めた 点群の最近傍の距離をDistance アトリビュートに取り、一定距離の 点をフィルタリング
プロシージャルの基礎知識/アトリビュート アトリビュートの利用例 木の点群を指定して、要塞の内側に敷き Point Filterを利用して、距離が一定以 詰めた点群(の最近傍の点)との距離を測 上の点を次の処理に繋げるようにノー る。 ディングする。 Point Filterの条件式では、最後に追加さ れたアトリビュートを指定する記法があ る。これはナイスな記法だが、分かりづら ければ、「Length」とアトリビュート名を明 記するようにしよう。
目次 ● プロシージャルの概要 ● プロシージャルの基礎知識 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/中級編 ● 決定論 ● 非同期処理 ● C++実装の勘所 Photo by Stella Ribeiro on Unsplash
プロシージャルコンテンツのカ プセル化
プロシージャルコンテンツのカプセル化 モチベーション 一見、様々な要素が配置されている並木道……これをプロ シージャルに配置するのは大変そう、だが…… レンガ、地面、落ち葉、樹木をワンセットにした複合的オブ ジェクトを並べてるともいえる →そこで、大域配置ツールは複合オブジェクトを1つのオブ ジェクトとみなしてザックリ配置を行い、 →複合オブジェクト側でもプロシージャルを稼働させて個性 を出す、という仕事の分け方が考えられる。
プロシージャルコンテンツのカプセル化 本日紹介するPCGにおけるカプセル化手法 アセンブリ:レベルデータから点群情報をアセットとして書き 出し、PCGグラフでインスタンシングしていくアプローチ。 Construction Script:古くからある、エディタやCook時 に実行されるオブジェクト構築用スクリプト。 厳密には配置情報の点群の1点1点に、アセンブリ側の点 群を複製配置し、巨大な一連の点群を得ることでこれを行 う。 PCGグラフからConstruction Script付きのアクタを Spawnすれば、大域配置をPCG、ローカルの構築を Construction Scriptという具合に分業できる。
目次 ● プロシージャルの概要 ● プロシージャルの基礎知識 ● プロシージャルコンテンツのカプセル化 ● アセンブリ ● Construction Script ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/発展編 ● 決定論 ● 非同期処理 ● C++実装の勘所 Photo by Stella Ribeiro on Unsplash
アセンブリ アセンブリの手順 (1/2) まずレベル上で組み立て済み、かつ、再 利用したい配置物を選択し、 その際、Pivot Typeを慎重に選択して、 合理的な原点を持つようにする。 Level > Create Level Instance… レベルアセット(図左)を右クリック Scripted Asset Actions > PCG - Level To PCG Settings で、点群アセット(図右)を生成。 で独立したレベルアセットに書き出す ↑地上の配置物であればCenter Min Z(底部中央原点)を選択す れば問題ないはず。ただし、それでも「浮く」場合があるので、レベル アセットを開いて確認するのが無難。
アセンブリ アセンブリの手順 (2/2) 生成したアセットをPCGグラフにドラッグ& Copy Points を利用して、Targetの点相 Static Mesh SpawnerでMesh ドロップして、点群を出力するノードを設 対でアセンブリの点群を増殖配置する。 Selector TypeとAttribute Nameを適 置。 切に設定すると、点群に書き出された各 アクタの位置やメッシュでスポーンが実施 される。 →真面目に階層関係を考慮するともう少し複雑になる。詳 しくはElectric Dreamのサンプルを参照。
アセンブリ アセンブリをバラつかせるテクニック ソースとなるレベルデータのアクタにタグ をつけてからPCG Settingsに書き出すと … アトリビュートが追加され、タグのついた アクタの点において、その値が1となる このアトリビュートをフィルタリングして、 PCGグラフ上でバリエーション作りのため の後加工を行える
アセンブリ 一例 確率密度を振りなおして フィルタリング タグの有無で点群を2つに振り 分ける 点群を再合流させる
目次 ● プロシージャルの概要 ● プロシージャルの基礎知識 ● プロシージャルコンテンツのカプセル化 ● アセンブリ ● Construction Script ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/中級編 ● 決定論 ● 非同期処理 ● C++によるPCG Elementの自作 Photo by Stella Ribeiro on Unsplash
Construction Script Construction Script付きアクタのPCGによる配置 Spawn Static Meshの代わりに、 Spawn Actorノードを使用すれば、アク タをPCGで配置できる。 Construction Scriptを正しく機能させる には、OptionをCollapse Actors以外に 変更する必要がある。 Actor Overridesを使えば、点が持つア トリビュート値をアクタのプロパティ値に反 映できる!
Construction Script 覚えた技術で塔を生やしてみた Heightプロパティでプロシージャルに高さ ランダムで高さの異なるバウンスを持つ が変えられる、Construction Script パ 点を散布。 ワードの「塔」のクラスを準備! SpawnActorで塔アクタを生成し、Actor Overridesを利用して、PCGポイント側の BoundsMaxで、塔クラス側のHeightプロ パティをオーバーライド。
Construction Script 参考:Spawn Actorの注意点 Construction Scriptの実行後、アクタの スケールはPCGポイントのScale値で上 書きされてしまう。 場合によってはConstruction Scriptで 行ったルートコンポーネントへのスケーリ ング操作がリセットされてしまう。 Post Spawn Function Name設定を使 うと任意のメンバ関数をポスト処理で呼 び出せるので、必要ならこれで回避を ←この機能、うまく利用すれば構築スクリ プトをConstruction Scriptから別のエ ディタ関数へ移動させ、PIE実行時のオー バーヘッドを緩和できるかも…
そこで座標を少しずらしてDeep Diveを開始したい 目次 ● プロシージャルの概要 ● プロシージャルの基礎知識 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/発展編 ● 決定論 ● 非同期処理 ● C++実装の勘所 Photo by Stella Ribeiro on Unsplash
World Partition
World Partition World Partition制御 PCGパワーで広域にコンテンツを撒いて も、システム上は大型のアクタ1つという 扱いになる。広域度次第では、World Partitionのトップグリッドレベルに常駐す る可能性が高い。 PCGコンポーネントのIs Partitionedに チェックを入れると、PCGをグリッドで分 割して、ストリーミングロードできるように なる。 そのパーティショングリッドサイズはPCG を配置・生成レベルに自動出現する PCGWorldActorのPartition Grid Size プロパティで調整する。
World Partition Hierarchical Generation モチベーション:一つのPCGグラフがボ リューム感の大きく異なるコンテンツを配 置するとき、コンテンツの種類ごとに異な るグリッドサイズを採用したくなる。 PCGグラフ内でグリッドサイズを使い分 けるには、グラフ設定のUse Hierarchal Generation機能を有効に する。 この機能を有効にすると PCGWorldActorのPartition Grid Sizeは効かなくなるので注意! これにより、PCGグラフのノードネットワー クにGrid Sizeノードを挟むことで、その下 流の生成物が属するグリッドサイズを指 定できるようになる。 Gird Sizeが明示されないノードネットワー クの生成物はHiGen Default Grid Size で指示されたグリッドサイズに従う。
World Partition Hierarchical Generation Hierarchical Generationを使うと PCGWorldActorの下に複数の粒度で分 割されたPCGPartitionActorが生成され る。 これらのPCGPartitionActorはオリジナ ルのPCGアクタからPCG Componentを 引継ぐが、生成時には対応するグリッド サイズに属するノードネットワークのみを 稼働させる。 また、生成にあたっては、より大きなグ リッドサイズのPartionActorから順番に 実行していく。 計算量最適化のために、自身より大きな グリッドサイズの生成物はキャッシュを利 用する。 一方、自身より小さなグリッドサイズの生 成物は(まだ生成が終わってないため)参 照できない。 つまり、Grid Sizeを導入した時点で階層 関係が生じ、親(大きなグリッド)は子(小 さなグリッド)に依存した処理を実行できな くなる。
World Partition 備考:Spawn ActorとHLOD Partition化したPCGはHLODとの相性 バッチリ。 Construction Scriptタイプのプロシー ジャルコンテンツもPCG Partitionおよび HLODに対応可能。 配置したConstruction Script付きアクタ デフォルトコンポーネントから をHLODに混ぜるには、可動性が「スタ Construction Scriptで追加するStatic ティック」である必要がある。 Mesh Componentに至るまで、可動性を できるだけ「スタティック」に設定しておきた い。
目次 ● プロシージャルの概要 ● プロシージャルの基礎知識 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Element実装の概要 ● 入力されたデータの扱い方 ● データの出力方法 ● 複数の入出力への対応 ● BPによるPCG Elementの自作/発展編 ● 決定論 ● 非同期処理 Photo by Stella Ribeiro on Unsplash
BPによるPCG Elementの自作 基礎編
BPによるPCG実装の概要 モチベーション PCGグラフにおけるノードをPCGエレメン トという。 PCGは標準で汎用的なPCGエレメントを 数多く提供。 プロシージャルが得意な人はこうした汎 用ノードを組み合わせて魔法のようにプ ロシージャルコンテンツを生み出せる。 でも、魔法使いでなくても大丈夫! 自分でカスタムPCGエレメントを作ればよ い 特定の状況、特定の点群にズブズブに依 存した独自のPCGエレメントノードを作れ ば、まるでチートのようにプロシージャルコ ンテンツを生み出せる。
BPによるPCG実装の概要 BPによるPCG Element実装の概要 PCGエレメントはC++およびブループリン トで実装可能。 また、PCG標準ノードの中には元々 BPで実装され、のちにネイティブ化さ れたものもある。 プラグインフォルダに、これらの現役およ び退役済みBP PCG Elementsが格納さ れており、参考にできる。 (TAの頑張りどころです!) BPによるPCG Element開発の基本を押さえるために、 入力を左から右へそのまま流すパススルーノードを作ってみよう
BPによるPCG実装の概要 BPによるPCG Element実装の最初のポイント PCGBlueprintElementを親クラスに指 定して新しいクラスBPを作成。 PCGグラフのメニューに自作ノードを出 現させるにはClass DefaultsのExpose to Libraryをチェック ExecuteWithContext関数をオーバーラ イドして、PCG Elementの中身の実装を 開始する
目次 ● プロシージャルの概要 ● プロシージャルの基礎知識 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Element実装の概要 ● 入力データの取り回し ● データの出力方法 ● 複数の入出力への対応 ● BPによるPCG Elementの自作/中級編 ● 決定論 ● C++によるPCG Elementの自作 Photo by Stella Ribeiro on Unsplash
入力データの取り回し 入力ピン周りの設定 ノードの入出力ピンはClass Defaultsの Input & Outputからカスタマイズできる 主にラベル(ピン名)と型を指定 (設定にあたってはHas Default In Pin/Has Default Out Pinはチェックを 外すとよい) マルチデータは1つ以上の接続から複数 のデータを入力することを許可するかどう かの設定 マルチコネクションは複数ワイヤーの接続 を許すマルチデータ・オプション デフォルトのAny型はワイルドカード型だが、型を指定 しておくと実装がやりやすくなる。
入力データの取り回し PCG Elementの入力を理解しよう (2/3) BP PCG Elementでは、設定で入力ピン を増やしても、入力ピンは常に1つだけと なる。 自力で入力のPCGData Collection Structureに格納されたTagged Dataか らピンに適合したデータを検索するか、 Get Typed Inputs by Pin Labelなどの ラベル名などを指定して適合するデータを 探してくれる機能を使って、キャスト済み のデータをReturnしてもらう
PCG Data Collection Structure 1点の中身を見てみよう 単純化のためにPoint Dataで考える Spatial Data プロパティ Metadata Transform, Size, Bounds, Seed, Density などの共通メンバ PCG Point の配列 PCG Point InPin 1 InPin 2 アトリビュート 追加データメンバ。 読み書きにはデータ側のメ タデータが必要 入力を取るとは、この構造を理化 して任意のデータに辿り着く術を 駆使すること。なあに、ヘルパ関 数の助けがあるさ!
入力データの取り回し PCG Elementの入力を理解しよう (3/3) マルチデータ入力を許可している場合、ピンから取得 できるPCG Spatial Data(この場合はPCG Point Data)が複数あることに注意 PCG Point Dataがもつ点群から、1点ずつ取り出して 処理を書くため、ここに2番目のループがくる(ネスト ループ構造)
目次 ● プロシージャルの概要 ● プロシージャルの基礎知識 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Element実装の概要 ● 入力されたデータの扱い方 ● データの出力方法 ● 複数の入出力への対応 ● BPによるPCG Elementの自作/発展編 ● 決定論 ● 非同期処理 Photo by Stella Ribeiro on Unsplash
データの出力方法 PCG Elementの出力を理解しよう (1/5) PCGPointの出力用配列を準備する 出力のためのコンテナとして、PCG Point Dataを構築する 入力のPCG Point Dataを出力に流用で きない点に注意 データを作成したら、入力のPCG Point Dataを利用して初期化を行う。これによ り、メタデータやアトリビュートが継承され る。
データの出力方法 PCG Elementの出力を理解しよう (2/5) もっとも単純な実装では、出力用のPCG Pointを蓄積する配列を作って、そこに入力から得たPointを回す。マルチデータ入力、 マルチデータ出力の場合は、PCG PointDataをイテレートする(For Each Loopする)たびに配列をクリアして、結果もイテレート のたびにまとめる必要がある点に注意
データの出力方法 PCG Elementの出力を理解しよう (3/5) 出力のためのコンテナとして、PCG Point Dataを構築する 入力のPCG Point Dataを出力に流用で きない点に注意 データを作成したら、入力のPCG Point Dataを利用して初期化を行う。これによ り、メタデータやアトリビュートが継承され る。
出力用に入力とは別のPCG Point Dataを作成する必要があるが… PCG Point Data PCG Point Data Metadata PCG Point の配列 Copy! PCG Point の配列 InPin 1 プロパティ プロパティ アトリビュート アトリビュート その際、メタデータの引継ぎを行わないと、入力 データからコピーしたポイントであってもアトリビュー トは保持されない
Initialize from Dataを使い、入力の PCG Point Dataからメタデータを引き 継ぎつつ初期化をかけるのが重要 PCG Point Data PCG Point Data 継承! Metadata PCG Point の配列 Metadata PCG Point の配列 InPin 1 プロパティ プロパティ アトリビュート アトリビュート (注)一枚前のスライドでは、出力用PCG Point Dataにはメタデータがないかのように書いた。しかし、 実際には出力用PCG Point Dataにも(固有の)メタデータがあるにはある。ただし、この状態だと入力 から受け取ったポイントを正確に解釈できないので、初期化時にメタデータを継承させる。
データの出力方法 PCG Elementの出力を理解しよう (4/5)
データの出力方法 PCG Elementの出力を理解しよう (5/5) 作成した出力用のPCG Point Dataにメイ ンロジックで書き出した出力用のポイント 配列をセット 入力時にPCGData Collection Structureからデータを取り出したときと 逆の理屈で、出力ピンへの紐づけを行う *この変数は昇格かなんかで作ろう こうして構築した出力用PCGData Collection Structureをリターンノードか ら返す(このリターンの位置は次スライド 参考)
目次 ● プロシージャルの概要 ● PCGの基礎 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Element実装の概要 ● 入力されたデータの扱い方 ● データの出力方法 ● 複数の入出力への対応 ● BPによるPCG Elementの自作/発展編 ● 決定論 ● 非同期処理 Photo by Stella Ribeiro on Unsplash
複数の入出力への対応 複数の入出力への対応 Custom InputやCustom Outputをさら に追加すれば複数の入出力ピンを設ける ことができる データ入出力時に対応するピンのラベル をしっかり指定すれば、難しいことはない 変数をInstance Editableにすると、自動 的に入力ピンに露出する(取得処理は不 要)。
複数の入出力への対応 覚えた知識で問題を解決してみよう! ワールドに塔が多すぎる! 分布密度や確率密度による制御ではな く、この領域に最大10箇所、のような絶 対の上限数をつけてみたい。 パススルーを改造し、Num入力で指定さ れたNum個のポイントをランダムで選択 し、Outから出力するノードを作る。また、 Num個に選ばれなかった点も、Others から出力することにする。
複数の入出力への対応 パススルーノードを改造/入力の追加
複数の入出力への対応 パススルーノードを改造/出力の追加 Output Pinsの設定を追加し、 パススルーで組んだOut Pointsの書き出しを複製してOthers用の書 き出しを組み立てる。 2 1
目次 ● プロシージャルの概要 ● PCGの基礎 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/発展編 ● ループ処理のフレームワーク ● アトリビュートの読み込み ● アトリビュートの書き込み Photo by Stella Ribeiro on Unsplash ● 決定論 ● 非同期処理 ● C++実装の勘所
BPによるPCG Elementの自作 発展編
ループ処理のフレームワーク ループ処理実装のフレームワーク ポイントデータに格納されたポイントをFor Each Loopで回しながら処理するケース では、ループの代わりにPoint Loopノー ドを導入可能。 その実際の処理(実装)はPoint Loop オーバーライド関数内でReturnしたポイン Body関数をオーバーライドすることで行 トは自動的にポイントデータに集約され、 う。関数では渡される一つのポイントにつ そのまま出力コレクションに追加できる。 いて処理する。
ループ処理のフレームワーク 覚えた知識で問題を解決してみよう 塔に30m~100mの範囲で高さを変えて いるのに、なんか低い気がする これは座標点の位置から、上下にボ リュームが伸張しているのが原因 (Extendsあるある) つまりExtendsMaxではボリュームの全 体の高さの半分しかとれない。 ボリュームの底部を座標点の位置まで引 き上げる、アンジュレーションを行うノード を作ってみよう。
ループ処理のフレームワーク Execute with Context
ループ処理のフレームワーク Point Loop Body
ループ処理のフレームワーク 適用結果
ループ処理のフレームワーク ふりかえり:ループ実装側の基本構造 Point Loop Body関数の実装側では、入 力ノードから入ってくるIn Pointを元に Return NodeのOut Pointに加工した結 果を返す。 リターンノードから本当に点を返す場合 は、Return ValueにTrueをセットしなけ ればならない。 逆に、点を返すかどうかわからない、いわ ゆるフィルタリングタイプのPCG Element を作るのであれば、Return Valueの True/Falseで制御できる。
ループ処理のフレームワーク ふりかえり:ポイントのデータ要素の読み書き ポイントは構造体データであるため、その メンバ(各構成データ)を読み出すには Break PCGPointを利用する。 ポイントのメンバの一部を書き換える場 合は、Set members in PCGPointノー ドを使う。 このノードは詳細パネルで、どのメンバを 書き換えるかを指定して、入力ピンを動的 に露出させる仕組みである点に注意。 ※上記7つのメンバを正式には プロパティ といい、アトリ ビュートと区別される。アトリビュートの読み書きについ ては次のセクションで詳述する。
ループ処理のフレームワーク 注意点:点のコピー Set members in PCGPointは、PCG Point構造体を参照形式で取得し、参照 形式で出力する。 そのため、入力ポイントデータから情報を Copy Pointノードを使って、入力ポイント 集めつつ、出力用ポイントを作るために のコピーを取り、これを加工してReturnす あれこれ加工を加えていくと、思わぬ落 るのが無難。 とし穴にハマることがある。
ループ処理のフレームワーク 注意点:自作BP関数の呼び出し ループ処理の内部ではたとえ純粋関数で あっても、呼び出すことができない。 Point Loop Body内から自作関数を呼 び出す場合は、その関数のAdvanced 設定で、関数をConstに指定する。 備考:Constは関数内でオブジェクトの状 態を変更することがないとする特別な宣 言。 直観的にはPureに近いが、別物の設定 項目である。
ループ処理のフレームワーク ループ処理三銃士を連れてきたよ! PointLoopはIn Dataで指定したPoint Dataに含まれるPointの数だけ処理を行 う。一般BPのFor Each Loopに近い。 Iteration LoopはNum Iterationsで指 定した数だけループを行う。一般BPの For Loopに近い。 Nested Loopは2つのPoint Dataを渡し て入れ子のループを処理する。点群Aの 数だけ点群Bをコピーする等の処理で使 用する。 え、こんなのいらねーよ、ループくらい自分で組むわ と思うが、この枠組みに乗っかると自動で非同期処理がかかる(後述)
ループ処理のフレームワーク 覚えた技術で問題解決をしてみよう スプライン上にアセットの横幅距離で点を 打っても… アセットのピボットがセンターの場合は、ス プライン範囲を飛び出してしまう。
ループ処理のフレームワーク 覚えた技術で問題解決をしてみよう そこで、渡された連続する点群から2点を 読み取り、中間地点に点を置く新たな点 群を作るのはどうだろう。 これは本来サンプリング処理にあたるが (BPではサンプラーは作れないので)、スプライ ンサンプラーが読み取った点群に依存し て、疑似的な再サンプリングを行う。 (状況ズブズブ処理!) このとき、配置するアセットの終端と次の アセットの始端が繋がるように、向きの補 正も行うと、フェンスなどがかなり綺麗に 並べられるようになる。
ループ処理のフレームワーク Execution with Context の実装
ループ処理のフレームワーク Iteration Loop Body の実装
ループ処理のフレームワーク 余談:閉スプライン対応するには? ブーリアン変数Loopedなどを作って、 PCG Elementに閉スプライン上にサンプ リングした点群を食わせているかどうかを 指定できるようにして補う。 (なお、C++で自前サンプラーを書く場合は直接 Spline Dataを食えるので、閉スプラインかどうかは データから判定できる) 開放タイプのスプラインでは、ループ回 数は入力点群の点数マイナス1となる が、閉スプラインではループ回数は入力 点群の点数と等しくなる。 Iteration Loop Bodyの実装側では、「次 のインデックスの点群」を決める際に、点 群数で剰余を取ればOK。
目次 ● プロシージャルの概要 ● PCGの基礎 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/発展編 ● ループ処理のフレームワーク ● アトリビュートの読み込み ● アトリビュートの追加/書き込み Photo by Stella Ribeiro on Unsplash ● 決定論 ● 非同期処理 ● C++実装の勘所
アトリビュートの読み込み アトリビュートの読み込み プロパティにあたらない「アトリビュート」部 分のデータを読むには、Breakではだめ で、「Get <型名> Attribute」ノードを使う 必要がある プロパティ PCG Point アトリビュート また、ポイントのコンテナであるPCG Point Dataのメタデータが必要。そのた め、まず「Const Metadata」(Read Only状態のメタデータ)でメタデータを取 得する。 そして、Const Metadataを「Get <型名> Attribute」ノードに接続し、アトリビュート 名を指定すると、ポイントに含まれるアトリ ビュート値を読みだせる。
アトリビュートの読み込み 覚えた知識で問題解決してみよう 要塞の点群とDistanceをとり、一定距離 の木を一律すべて刈り取るというのではミ ステリーサークル感がすごい。 Distanceを読み取り、Densityにバイア スをかける(負のペナルティを与える)よ うにすれば、刈り取り範囲でも多少木が 残るようになる。 数学演算を駆使すれば標準ノードでも実 現できる要件だがーーー PCG Elementとして実装できれば手っ取 り早い!
アトリビュートの読み込み 覚えた知識で問題解決してみよう
目次 ● プロシージャルの概要 ● PCGの基礎 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/発展編 ● ループ処理実装のフレームワーク ● アトリビュートの読み込み ● アトリビュートの追加/書き込み Photo by Stella Ribeiro on Unsplash ● 決定論 ● 非同期処理 ● C++実装の勘所
アトリビュートの追加/書き込み アトリビュートの追加 (アトリビュートの宣言) 出力データに新しいアトリビュートを追加するには、先に出 力用データを構築する。 (Initialize from Dataも忘れずに) 作成されたデータのMutable Metadataを呼び出して(書 き込み可能状態の)メタデータ参照にアクセスし、「Create <型名> Attribute」を実行すれば、メタデータにアトリ ビュートが登録される。
アトリビュートの追加/書き込み アトリビュートの書き込み PCGのFlow Controlを呼び出す際、 Optional Out Dataピンにアトリビュート 追加済みのデータを接続 Flow Controlの実装側ではOut Metadata入力を利用し、「Set <型名> Attribute」を呼び出して、アトリビュート の書き込みを実行 補足:Create Attributeを実行済みのメタ データでなければ、Set Attributeは単に 無視される。 これを利用すれば、アトリビュート追加の 有無オプションを容易に実装可能。 (標準ノードDistanceToNeighborsを参 照)
アトリビュートの追加/書き込み 覚えた技術で問題解決してみよう スプライン上にアセットの長さLで点を散布 そこで、中間点生成PCG Elementを改 しても、隣接する点間の距離がLぴったり 造し、2点間の実際の距離を「Length」ア とならず、隙間が開くことがある。 トリビュートとして、出力ポイントに追加す ることにする。 特にカーブスプライン上に直線のアセットをあてはめた場 合は、必然的にこの問題が起こる。 出力されたLengthをアセットの長さLで 割って、X軸のスケールに代入すれば、ア セットを敷き詰めたときの隙間や重複を解 消できる。
出力用PCG Point Dataの先行構築 Iteration Loopへ引き渡し Iteration Loop呼び出し前に、出力用 これまで放置してきたOptional Out PCG DataをConstructし、Initialize Dataピンに先行構築した出力用PCG from Dataを済ませた後、Float型のアトリ Point Dataを接続。 ビュートを追加する。 Loop内でAttribute値を書き込み 関数のエントリーノードから供給される Out Metadataを利用して、PCG Pointに アトリビュートを書き込む。
アトリビュートの追加/書き込み 補足:Optional Out Dataの仕組み あらかじめセットアップした出力用ポイント データを指定することで、実装側で対応す るメタデータが利用できるようになる。 Optional Out Dataを省略した場合は、 その場でポイントデータが作られ、入力 データからメタデータを継承する。 一貫性のために、出力用データを先行構 築した場合も、省略した場合も、ループ ノードのOut Data出力ピンからデータを 回収して続きの処理を行うのがおススメ
アトリビュートの追加/書き込み 補足:アトリビュート指定プロパティ 標準ノードでよく利用されている、アトリ FPCGAttributePropertyInputSelector ビュート指定専用の入力フォームを使いた 型のプロパティを宣言してEditableにす かったら…? る。 PCGElementのBP側ではGet Attribute Nameなどの関数を通じて指定された名 前を文字列で取得できる。 PCG Elementでアトリビュートを決め打ちするのはよくないので、基本的にこのや り方を使いたい
目次 ● プロシージャルの概要 ● PCGの基礎 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/発展編 ● 決定論 ● 非同期処理 ● C++実装の勘所 Photo by Stella Ribeiro on Unsplash
決定論 【けっていろん】あらゆる出来事は、その出来事に先行する出来事のみによって決定している
決定論 「入力が同じなら出力も同じ」 決定論的な野球ゲームでCPU vs CPUを観戦するとして…… 試合場、両軍のスタメン、ベンチメンバを 決定して試合をスタートすると、 何千回試合しても常に同じイニング展開、 同じ勝敗結果になる。 実際に野球ゲーがあったら、QAが血相を変えて開発チームに飛びこんでくると 思われるが、プロシージャルコンテンツ生成では特段の事情がない限り、こうし たデターミニズムを確保したい。
決定論 決定性が保証されない場合の問題点 レベルを開くたびに、コンテンツ生成の結 果が異なったり、隣の席の人と生成結果 が違うと開発上問題になる。 大規模ワールドを分割してビルドマシン ビルドマシンと開発者の席で生成内容が で処理しているとき、ビルドマシンAとBで 異なると、夜間生成したナビメッシュが食 生成評価が異なると、分割境界面のつな い違いを起こすことも。 ぎ目で問題が生じる。 より根源的には、生成結果へのコントロールを失うことが問題
決定論 お前はもう保証されている PCGには決定論を保証する多種多様な枠組みがある PCGコンポーネントやエレメントは原則と して、固定シード値を持つ(変更可能)。 散布点もそこから派生した(決定論的に 定まる)シード値を持つ。 計算の走り方(評価される順番)次第で出 力が変わる場合、データの依存性を制御 できる。
決定論 PCG ElementにおけるRandom Streamの利用 PCG Element内のBPではそもそも Randomノードを呼び出せない(見つから ない)ように作られている。 *ただし、配列のシャッフル関数のように 暗黙的にRandomを使っている機能が呼 び出せてしまうことがある。 一方、再現性のあるランダム値を生成す るRandom Stream系のランダムノード は自由に利用でき、PCG Element内で はコンテキストからRandom Streamを 取得できる この手順で取得されたRandom Stream では、PCGの文脈から求められる決定論 的なシード値が自動的に設定される。
決定論 覚えた知識で問題を解決してみよう 現在、PCGの計算が走るたびに、塔の位 置が変わってしまっている。 これはNum個の塔を選択するNum Filterの配列シャッフルがランダムを使用 しているのが原因。 Shuffle from Streamに差し替えれば、 配列のシャッフルが決定論的になり、問題 を解決できる。
決定論 覚えた知識でConstruction Scriptを改造してみた Construction Scriptのプロシージャル生 成処理に含まれるRandomを、Random Streamに差し替え、シード値も調整でき るようにする。 Actor Overrideを使って、PCG側の 備考:シードの指定やシードの引継ぎが面 Seed値をアクタのSeedプロパティに送り 倒であれば、アクタの座標から決定論的 込めば、PCGの文脈にのっとって決定性 に生成する方法も考えられる。 を確保できる。 この実装はConstruction Script付きアク タを単体運用した際の利便性を高める。 アルゴリズムは PCGHelpers::ComputeSeedを参照!
目次 ● プロシージャルの概要 ● PCGの基礎 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/中級編 ● 決定論 ● 非同期処理 ● C++実装の勘所 Photo by Stella Ribeiro on Unsplash
非同期処理
非同期処理 非同期処理&マルチスレッド ブループリントで実装できるあらゆるFlow Control系関数は、すべて非同期呼び出 しがかかる 落とし穴:イテレーション数や要素数が少 ない場合は結果として1スレッドでの実行 になり、シンプルなテストでは非同期特有 の問題が露呈しにくいので注意。 マルチスレッド実行も行える。競合問題を 避けるために、ループ関数内では変数へ 例えば、Point Loopでは256要素ごとに の書き込みを避け、処理結果は スレッドを分ける(FPGCAsyncのヘル OutPointを通じた出力に留めること。 パー関数群を参照)。 これにより、少なくともタイムスライス効 果が期待できる
非同期処理 マルチスレッドの注意点 事前にライトバッファ=最終出力結果を確 保し、出力データ1点ずつについて、非同 期処理を実行するスタイルである点に注 意 つまり、1回のイテレーションで複数の点 データを生成するタイプの処理では、 ループ処理のフレームワークを利用でき ない。 ただし、Return ValueでFalseを返すと、 出力がなかったことになるため、At Max でイテレーション回数を決めて処理するこ とは一応可能…
おまけ フェンスや壁の隙間/重なりを解決したい スプラインでフェンスパスを引いても、ア セットが直線であれば、そのフェンスの輪 郭は多角形状になる。 特定の長さのアセットを並べるだけで、 フェンスのパスにぴったり収まるはずが ないので、どこかに歪みが現れる この歪みを一か所に集中するのではなく、 フェンスを構成する全アセットで分散すれ ば、とっても収まりがよいのでは?
セグメント間の長さを、アセットの幅(理想 の幅)で割った値をRoundで丸める。この 値が拡縮込みで打てる点の数になる。 セグメント間の長さを丸めた値で割りなお アセットのピボットが中央にあるのであれ すと、このセグメント間にアセットを配置 ば、それに合わせて点の位置を計出する するときに用いるべき現実的な幅が分か ようにロジックを調整する。 る。
目次 ● プロシージャルの概要 ● PCGの基礎 ● プロシージャルコンテンツの「カプセル化」 ● World Partition ● BPによるPCG Elementの自作/基礎編 ● BPによるPCG Elementの自作/中級編 ● 決定論 ● 非同期処理 ● C++実装の勘所 Photo by Stella Ribeiro on Unsplash
C++実装の勘所
C++実装の勘所 C++によるPCG Element実装の概要 C++によるPCG Elementの実装では、設 定(データ)と機能実装でそれぞれ別のク ラスを宣言、実装する。 ● ● データ→ UPCGSettingsのサブクラス 実装→ IPCGElementの実装クラス アンリアルエディタからNew C++ Class する際は、UPCGSettingsを親クラスに 選択。 ノードが実行されたときのロジックは、 IPCGElementを継承したクラスに実装す る。 こちらのクラスをUClass宣言し、 BlueprintTypeをつける。 シンプルなベースクラス、 FSimplePCGElementを継承して、その ExecuteInternal()を実装するケースが多 い。
C++実装の勘所 Density Filter の実装を覗いてみよう ノード名、ノードタイトル、ノードタイプは上 記枠の関数をオーバーライドして定義す る。 ノードタイプはPCGグラフ上のノードの表 示色に関係し、機能に関係しない。
BP版ではGUIで設定できた入出力ピンは、C++では上記関数をオーバーライドして適切なFPCGPinProperties配列を返すことで 設定する。例えば、2つのPoint型入力ピンをとるCopyPointsの実装は以下のとおり。
UPCGSettingsのCreateElementを通じて、実際の処理が記述されたIPCGElementの実装クラスのインスタンスを返す。
C++実装の勘所 プロパティのピン公開とアクセス BPで変数をEditableに設定するとPCG このmeta指定がないUPropertyは、グ Elementの入力ピンとして公開されたよう ラフ側にピンとして公開されないが、詳細 に、C++でもSettingsクラスのUProperty パネルからの入力は行える。 にmeta=PCG_Overridableを指定する と、入力ピンに公開される。 SettingsクラスのUPropertyはロジックが 書かれたIPCGElementからは見えない。 ロジックの実行局面でこれらのプロパティ の値を入手するには、コンテキストを経由 する。
C++実装の勘所 C++ PCG Elementの非同期処理 BP版には特定の関数を実装するだけで 非同期化できるというフレームワークがあ るが、C++にはない。 FPCGAsyncで実装されている非同期化 ただし、PointLoopのみ、 ヘルパー関数などを利用して自前で実装 FPCGPointProcessingElementBaseを しなければならない。 継承して、そのProcessPoints()関数をラ ムダ付きで呼び出す形で、BPと同じ感覚 で非同期化が可能。 詳しくはBounds Modifierなどの FPCGPointProcessingElementBase継 承クラスの実装を参照。
最後に お勧め資料の紹介と考察
お勧め資料①: Assassinʼs Creed Syndicate: London wasnʼt build in a day かなり古いが、建造物、道路のプロシージャルな敷設の基本をすべてさ らっている、色褪せぬ良資料
お勧め資料: Procedural World Generation of Far Cry 5 Houdini Engineベースだが、UE5でも再現可能な技術が多く、また自然 への考察とプロシージャルへの落とし込みが素晴らしい資料
考察 SplineMeshActorなど、従来のスプライ また、PCG側でSplineMeshAct内のの ンベースのプロシージャル実装は依然とし スプラインを読み取ることは(タグなどを て強力で、PCGに置き換えることは難し 介して)可能。 い。 これを利用して、道路敷設範囲のPCGコ 一方、Assassin’s Creedのような街並み ンテンツをあらかじめ除去しておくなどす 設置ツール(スプラインに沿ってキッチリ、 れば、2つの技術を組み合わせられる。 高さの違う建物を敷き詰める)を実装する のには、PCGの方が強力と思われる。 また、PCGではPCG Point Dataから PCG Spline Dataを作成する標準機能が ある。さらにPCG Spline DataをSpline Component用のデータに書き出す実装 を自前で書くというアイデアも考えられる だろう。 これを実現すれば、道路のスプラインを PCGボリュームのコンテンツが遮蔽する、 などのより高度なプロシージャル開発が 可能になる。
ご清聴ありがとうございました 2023 Private & Confidential