宣伝
今年のCEDEC2026に弊社のリアルタイムVFXツール「SPARKGEAR2」の公演があります。
7/22(水)の15時から、現地では第11会場でお披露目です。
Unreal EngineやUnityなど、様々な開発エンジンに互換性を持つエフェクトを作成できるVFXツールなSPARKGEARが2に刷新されます。
GPUパーティクルや流体シミュレーションなどの最先端技術に加え、生成AIを活用したエフェクト生成など、様々な新機能を盛り込んだ次世代のリニューアルとなっています。
SPARKGEAR自体の詳細や基本機能については、ホームページが一番わかりやすいので、気になった方はぜひそちらをチェックしてみてください。
今回の講演では、これらの最新技術の紹介や、私たちが描く今後のビジョンについてお話しする予定です。
オンライン配信でもご視聴いただけますので、少しでも興味のある方はぜひ受講登録をお願いします。
はじめに
こんにちは、クライアントエンジニアの下野です。
今回は前回に引き続きPCGで、PCGのエレメントを色々紹介します。
前提
エンジンのバージョンは5.7です。バージョンによっては処理が変わったりするので完全に処理を一致させても動かない場合があります。
PCGはUE5.7になってBetaからProduction-Readyになったので、以降のバージョンで破壊的変更は無いはずですが、過去バージョンを使う際は注意してください。
PCG(Procedural Content Generation Framework)
前回の記事を読んだら大体は理解できる気がします。
記事一覧
第2回(今回) tech.spark-creative.co.jp
前回までのPCG
前回はPCGの基本的な動かし方として、Surface Sampler で地形に点を撒き、Static Mesh Spawner を使って実際に草を生やす手順を解説しました。
また、プロパティやアトリビュートなど点が保持しているデータについてや、グラフパラメータなど基礎的なことを解説しました。
前回の設定だけでもランドスケープ上にメッシュを配置することはできましたが、草の向きや大きさが全部同じで不自然に見えたり、崖のような急斜面や、アクターが置いてある場所には草を生やしたくない等の意見も出ます。
ただ点を撒いてメッシュを出すだけでは、ゲームの背景として用的なクオリティに達しないということです。
そこで重要になるのが、PCGのP(Procedural)の部分を担う各種ノード(PCGではエレメントと呼ぶ)です。
PCGの本質は、サンプリングした点群をさまざまなエレメントに通し、移動・回転・拡縮・間引きなどの理想の配置ルールを作り上げていくことにあります。
第2回となる今回は、プロシージャルな背景制作に欠かせないエレメントの解説をしていきます。
PCGのエレメントについて
まずは、前回作成したグラフと生成結果を見てみます。


ランドスケープから点を取ってメッシュを生やしただけなのでシンプルですね。
全てが同じサイズ・同じ向きなため、規則性すら感じます。
最初にも書きましたが、自然界の植物がこんなに綺麗に整列して生えることはまずありません。
このままだと、どうしてもCGで作りましたという不自然さが残ってしまいます。
ゲームの背景として馴染むリアルで自然な景色を作るためには、配置されるメッシュにランダムなばらつきを与えたり、生える場所のメリハリをつけたりする必要があります。
回転・拡縮
まずは規則性を無くすために自然な見た目のばらつきが欲しいので、回転や拡縮を弄るためにTransform Pointsを使用します。

デフォルトではすべて0なので、任意の値を入れてあげます。

木だと回転が分かりにくかったので、メッシュを壁に変更しました。
このようなメッシュの変更も、前回紹介したグラフパラメータだと簡単に上書きできます。

サイズや回転も自由になるので、石などメッシュを使いまわしても気にならなくなります。
配置・間引き
Transform Pointsを通したことで、メッシュの向きや大きさにランダムなばらつきが生まれ、見た目のCGっぽさはかなり薄れたかと思います。
しかし、エリアを引きで見た際に、どこを見ても同じような密度で満遍なく草が生えているという点が気になります。
実際の自然界では、日当たりの良い場所に草が密集していたり、逆に岩の周りや踏み固められたエリアには何も生えていなかったりと、配置に必ずメリハリがあります。
また、このままでは「設置した建物や道路を突き抜けて草が生えてしまう」といった、致命的な問題も発生してしまいます。
せっかく自然にばらつかせたので、次は生える場所や密度も調整していきましょう。
間引きには前回も使用したDensity Filterを使用します。

このエレメントはアトリビュート内にあるDensityの値を使用します。

インスペクターから最小値と最大値を設定することで、範囲外の値を持つ点を除外します。

Surface Samplerをそのまま使用する場合、Apply Density to Pointsがデフォルトでは有効なのでランダムな値が入ってくれます。
Falseにすると1.0固定になって別のエレメントを噛ます必要が出るので確認して下さい。

デバッグ表示で確認してみます。
デバッグのメッシュは通常、Densityの値が0に近いほど黒く、1に近いほど白くなります。
今回は、0.5 ~ 1.0の値にはメッシュを生やし、その他は除外する設定を入れたため、暗いところにはメッシュが生えていないのがわかると思います。

このエレメントで、密度が低い箇所のメッシュを完全に消したり、逆に密度が高い箇所をすべて消したりできます。
これで、草地を限定したり密集させたりが可能になりました。
しかしまだ懸念点があります。でかい岩を生やした際に、岩に草が突き抜けていたら自然ではありません。
そのため次は、PCG関係ないメッシュを見て点を除外してみます。
除外にはDifferenceを使用します。

SourceとDifferencesの点のBoundingBox(プロパティにある)から重なりを計算し、重複した点を削除してくれます。
Sourceには今まで通り点群を入れればいいです。
Differenceには除外したい点を突っ込まないといけないため、最初に例を用意します。

この例では、ど真ん中にCubeを生やしてみました。
そのままだとPCG生成のメッシュがCubeを突き抜けているので抑制します。
まず、アクターから点を作るためにはGet Actor Dataを使用します。

タグやクラスなどからアクターを特定します。

アクターフィルターで検索範囲を決めて、タグなどで絞り込みます。
フィルターはParentみたいに選択が範囲狭いほうが処理速いですが、今回はアクターの数が少ないのでAll World Actorsで行います。

All World ActorsにするとActorのTagを入れる項目がでるので、Cubeのほうに適当にTagをつけて、検索できるようにします。


複数アクターがある場合はComponentのほうのTagで切り分けてください
正しく入力ができると、↓のようにCubeを避けてメッシュが生成されるようになります。

これで、岩などを避けて草を生やすなどの技術も可能になります。
ここまででPCGグラフは↓のようになりました。

さらに高度な配置ルール
Density Filterで配置にメリハリをつけ、Differenceで障害物を避けることで、かなり自然な絵作りに近づいてきたのではないでしょうか。
ここまでできれば基本は終わりですが、せっかくなので一歩踏み込んだ地形のルールやアセット同士の細かな距離感の制御まで行います。
具体的には、どれだけ密度を調整しても、崖のような急斜面に草が生えてしまうのは不自然だから自動で弾きたいとか、PCGで作ったメッシュ同士が重なってしまうので除外をしたいといった、クオリティをもう一段引き上げるためのこだわりです。
これを手動で調整し直すのは大変ですが、PCGなら地形の形状やアセットの大きさを検知して、すべて自動で計算させることができます。
ということで、Normal To Densityと Bounds Modifierについて解説します。
Normal To Density
今まででは平坦な場所に草を生やしていました。
しかし、ランドスケープを使うなら山や谷など勾配が複雑な地形を使用したいです。
その際に、急斜面に草を生やしたら地面から浮いたなどはあってはいけません。
そのために対応をするのがNormal To Densityです。

まず、平坦な地形から多少勾配があるところに場所を移動して、メッシュを木に戻しました。

デフォルトのSurface Samplerは急斜面でもメッシュを生やしてくれます。
右の急勾配でも一律に木が生えているので、これを抑制してみます。
Normal To Densityは点のTransformとインスペクターのNormalで比較して、一致していたら1.0、真横や反転していたら0.0になるように値を作ります。(内部では内積とってるので-1.0 ~ 1.0になりそうですが、Densityに値を入れる関係でClampが入るので0.0~1.0になります。)

Offsetはどのくらいの角度から値を減らし始めるかです。
Strengthは密度の変化度合いを変更できます。値を大きくするとちょっとの傾斜で密度がすぐに変わり、崖際の判定を精密にできます。
密度モードはSetでDensityに値を上書き、Multiplyで乗算など、Densityにどのように値を入れるかです。
今回の傾斜は結構緩いので、Strengthの値を0.4くらいに緩めてみます。


Density Filterが入っているので、Densityが0.5以下の点は削除されています。

これを使うことで、勾配に対して点を生やすかをコントロールできます。
Bounds Modifier
次は、Bounds Modifierについてです。
PCGポイントが持つBoundsの大きさを変更、調整します。
Boundsの説明の前に、Self Pruningについて簡単に解説を入れます。
先ほどまでの例で使っていたグラフでは、↓のようにメッシュ同士が重なることがあります。


これは、点同士の重なり除外を入れてないのが原因なので、最初に除外を入れてみます。
点同士の重なり除外にはSelf Pruningを使用します。

Self Pruningは点群内でお互いのデータの比較ができます。アトリビュートもプロパティも指定でき、Extentを指定してあげると重なり判定を行えるので便利です。

このように重なっている2点から除外して1つのメッシュだけ生やすことができます。

さて、Boundsの説明に入ります。
Debugで出ているCubeのサイズがその点が持つBoundsのサイズになります。
先ほどの画像を見ていただければわかりますが、全然メッシュとサイズが合っていません。
これでは、上記のようにExtentsから重なりをみて点を消すグラフを作る際に、正しい判定で除外ができず枝が道に入り込んだり、Boundsよりメッシュが大きい際には道にめり込んだ状態で木が生えてくる不具合が出ます。
これを解決するのがBounds Modifierです。

このエレメントはBoundsのサイズを拡縮したりする際に使用しますが、現在のBoundsはメッシュに全然あってないので、最初にメッシュからBoundsを取ります。

アトリビュートからメッシュを参照してBoundsを作るエレメントなので、以前作成したグラフパラメータの後に入れておいてください。(@Lastはアトリビュートの末尾を自動参照します、アトリビュート名をちゃんと書いてあげてもいいです)


この状態でデバッグすると、Boundsがメッシュに沿います。Zファイティング起きてメッシュがちらついています。

このままだと余裕がなさ過ぎて、道の際など本当にギリギリの箇所にメッシュが生えてしまいます。余裕を持たせたいところです
ここでようやくBounds Modifierの出番です。
Boundsに対してScaleで拡縮や、Setで代入、Includeで和集合(現在のBoundsを維持しつつ、指定したサイズも包み込める大きさまで拡張)など様々な条件でBoundsを変更できます。

今回は1.5倍までBoundsを拡張してみます。
MinMaxがあるのでランダムにもできますが、必要ないので、全て定数の1.5を入れます。

最初の画像だと、メッシュとBoundsが同じサイズだったのでZファイティングでメッシュも若干見えていましたが、メッシュを完全に内包できるようになったのでBoundsしか映らなくなりました。
画像だとわかりにくいと思うので、ぜひお手元で再現してみてください。

終わり
いかがでしたか?
エレメントの組み合わせで、手動配置では時間がかかるようなマップの構築を可能にできます。
PCGはオープンワールドに限らず、今後のマップ制作に必須級の機能なので使いこなせるようにしましょう。
と言いたいところですが、機能の一端を解説するだけでこのボリュームなので全てを理解するのは相当時間かかると思います。
商用エンジンって大変ですね。
ほんとはSubGraphとかBPでエレメント作るやつもやる予定でしたが、下書きがごっちゃになったので次回にやります。