【UE5】Geometry Scripting：プロシージャルに石畳や岩場をつくる【後編】

[article-part1]: https://qiita.com/Rinderon/items/70765fb914d2242a448e

:::note info
対象バージョン：UE5.2.1
:::

:::note info
Geometry Scriptingを使うには「Geometry Script」プラグインを有効にする必要があります。
:::

# はじめに

Geometry Scriptingを用いてプロシージャルに石畳や岩場を作る方法を紹介する。例えば次の画像のようなメッシュを作ることができる。
![20230919_0405_Both.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/6fe62aa4-f80f-154e-3031-af7966c9d83c.jpeg)
[【前編】][article-part1]では、ベースとなるアイデアとして、平面を押し出し(extrudeして)、それにSubdivisionを掛けることで、丸みを帯びた立体を生成する方法を紹介した。また、UVエディタを使ってUVをUnwrapする方法を紹介した。
この【後編】では、
- [【前編】][article-part1]では四角形から作ったが、もっとランダムな多角形をから石や岩を作る
- 石や岩を一個ずつ作るのではなく、ひとまとまりの「石畳」や「岩場」を作る

を実現するために、Sobol乱数とVoronoi図を利用する方法を紹介する。

# Sobol乱数

Sobol乱数については、過去の記事「[Sobolの森(Random Sobol Cell 2D/3Dについて)](https://qiita.com/Rinderon/items/b0dcbf18d1c204b71651)」でも紹介している。
Sobol乱数は、ｎ×ｍマスに区切られたグリッド内にランダムに点を生成する方法である。
次のGIF動画は、５×５マスに区切られたグリッドそれぞれに１個ずつ、ランダムに点を生成する例である。
![20230920_07_Sobol.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/d44346f4-d8b0-00ff-5dcf-33c2a621281a.gif)
通常の乱数を使ったときに起きる狭いエリアに点が集中しすぎるような状態は発生せず、マス目という拘束のおかげで、なんとなく領域全体に散らばってくれる。

Sobol乱数本体は「Random Sobol Cell 2D」である。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/fff674dd-d2a6-94a3-7a9a-57bdeb72938a.png)
引数に与える値は下の表のようにする。

|引数名|説明|
|:---|:---|
|Index|現在のセルの中の何番目の点か|
|Num Cells|Sobol Cellの総数 (X方向のマス数×Y方向のマス数)|
|Cell|現在のセルのインデックスをVector2Dで与える|
|Seed|X方向、Y方向の乱数の種をVector2Dで与える|

この引数を作るためには、次の３つのループが必要となる。
1. Y方向のマスのインデックスのループ
1. X方向のマスのインデックスのループ
1. １つのセルの中に生成する点の個数のループ

X,Y方向のインデックスのループから三番目の引数Cellを作り、１つのセルの中に生成する点の個数のループのループインデックスを一番目の引数Indexに与える。

# ボロノイ図 (Voronoi Diagram)

次の図はSobolで作った点列を使って「ボロノイ図」を作ったものである。
![20230921_01_SobolAndVoronoi.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/484e3c4d-e5bd-b381-3895-7336130293a8.jpeg)
ボロノイ図とは、とある点が他の点よりも自分に近い領域に区切った図のことである。周囲の点との垂直二等分線で区切ったエリアがボロノイ領域となる。
この見た目でも分かる通り、石畳を作れそうな形をしている。あとは各々のボロノイ領域を[【前編】][article-part1]でやったようにExtrude→Subdivisionをすれば、石畳や岩場になってくれそうだ。

ボロノイ図を作るにはAppend Voronoi Diagram 2Dノードを利用する。
入力引数Voronoi SitesにVector2Dの点列(配列)を渡してやれば、ボロノイ図を出力してくれる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/70b23a2d-efa0-ae26-91d2-adbf87146ba1.png)

:::note warn
なお、上記の図は見やすく加工したもので、Append Voronoi Diagram 2Dノードで生成されるボロノイ図は、ボロノイ領域間に境界線は引かれておらず、見た目上は単なる四角形である。
特に加工をせずボロノイ図の様子を確認するには、Modelingモードに切り替えて、Poly Modelカテゴリの「PolyEd」を選択すると良い。
![20230921_02_VoronoiInModelingMode.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/b9518d3a-7f1a-2779-b7d1-5bead1166841.jpeg)
:::

# ブループリントグラフ

さて、必要なものはあらかた揃った。以下に石畳や岩場を作るブループリントグラフを紹介する。
なお、Geometry Scriptingを使うためには、「Geometry Script」プラグインを有効にし、「Generated Dynamic Mesh Actor」を継承したアクタ上で実装する必要がある。

## Construction Script

Construction Scriptは補助的な機能を実装しているのみである。
- Vector型の変数Cornerの位置から、ボックスのサイズを算出する。
    - CornerはShow 3D WidgetをTrueにして、レベルエディタ上でドラッグ移動できるようにしてある。
- Cornerの移動に合わせてBoxコリジョンの大きさを変更。このBoxコリジョンは大きさを見て分かりやすくするためだけのものであり、実際にコリジョンを取っているわけではない。

![20230921_Graph_ConstructionScript.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/ffa1f8f3-1cca-9c82-a7c5-7f2f1dd9b2b7.jpeg)

## Event Graph

Geometry Scriptingのメインとなるイベント「Event On Rebuild Generated Mesh」を実装する。
ローカル変数が使いたいのもあるが、別の個所からも呼び出したい(後述)ので、処理一式は一つの関数にまとめてある。
![20230921_Graph_EventGraph.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/71290a6e-34ab-eb98-9f0f-c82cf2f55a70.jpeg)

## Rebuild Generated Mesh関数

やっていること：
- Random StreamにRandom Seedを与えて初期化。これはSobol乱数生成の際に使う。
- Sobol乱数で点列を生成する。実際のSobol点列の生成はMake Voronoi Sites By Sobol関数でやらせている。
- Append Voronoi Diagram 2Dでボロノイ図の作成
- ボロノイ領域の一個一個に対して、石っぽくするための編集。[【前編】][article-part1]で書いたようなExtrude→Subdivisionを行う。これはEdit Voronoi Region関数にやらせている。
- 平面投影でUVを作成している。

![20230921_Graph_RebuildGeneratedMesh.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/900b5d27-a163-d175-65d3-37f77c675a49.jpeg)

## Make Voronoi Sites By Sobol関数

Rebuild Generated Mesh関数から呼び出されている。
この関数ではSobol乱数を生成し、それをVector2Dの配列に格納し、その配列を返している。

![20230921_Graph_MakeVoronoiSitesBySobol.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/4dedbfa5-7a78-1b0d-2f44-a636bb34a5ed.jpeg)

## Edit Voronoi Region関数

Rebuild Generated Mesh関数から呼び出されている。
ボロノイ領域一個分の加工を行う。
1. Apply Mesh Linear Extrudeで平面のボロノイ領域を押し出して立体にする。
1. 押し出された上面をランダムでスケールをかけたり回転をかけたりして、立体の性状をばらつかせる。
    - 押し出された上面の選択：Select Mesh Elements With Planeで、押し出した高さよりやや低い平面より上にある面を選択する。
    - 押し出された上面のバウンディングボックスの中心を算出。
    - 押し出された上面のバウンディングボックスの中心を基準に、スケールや回転をかける
1. Apply PN Tessellationで細分割
1. Aplly Triangle Loop SubDでサブディビジョンをかける(丸くする)
1. このボロノイ領域の各頂点に頂点カラーをランダムで設定する。
    - マテリアルでこの頂点カラーを取得して、石の色味を変化させる(後述)

![20230921_Graph_EditVoronoiRegion.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/7b621c04-702d-efe4-29c4-fba755cf2746.jpeg)

以下にこれらの処理の途中経過を画像で紹介する。

### ⓪ボロノイ分割直後の状態

![20230922_01_AfterVoronoi.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/9b53dd3f-911a-06b3-72b0-f825ef237f50.jpeg)

### ①各ボロノイ領域を上方向に押し出した後の状態

![20230922_02_AfterExtruded.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/46c9f56b-80ff-5097-9d8c-45e9f926bf07.jpeg)

### ③押し出された上面にランダムなスケールや回転をかけた状態

![20230922_03_AfterRandomizedTopFace.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/57fdb6b8-7169-dd71-af95-c9fe6f2c9729.jpeg)

### ③テセレーションと④サブディビジョンをかけた状態

左がテセレーションなしでサブディビジョンをかけた状態、右がテセレーションありでサブディビジョンをかけた状態。
テセレーションの有り無しで角張りぐあいが調整できることが分かる。
![20230922_06_TesSubdiv.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/e8eee1dc-32bf-324b-3ee0-55b934129347.jpeg)

### ⑤頂点カラー

頂点カラーを可視化したもの。
Redチャンネルに0～1の範囲で頂点カラーを与えている。
![20230922_07_VtxCol.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/022a5b84-b040-733b-6ca7-06f0cf136df7.jpeg)

頂点カラーに応じて岩の色味を変えるようなマテリアルを適用したもの。
Redチャンネルの値0～1を６分割して、６種類の色味を出している。
黒っぽいもの、青っぽいもの、茶色っぽいものが混ざってランダム感が増している。
![20230922_08_VtxCol.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/70ae1538-4671-c3b1-ce96-570a6c983261.jpeg)

## Randomize関数

Call In Editor関数で、詳細パネルにボタンとして表示される。
このボタンを押すと、乱数の種を変更して、それに応じた形状を再生成する。
![20230922_Graph_Randomize.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/814aa507-5080-9d9b-f2fc-2eff0d48b955.jpeg)

![20230922_13_Randomize.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/54f01028-e59a-e4ee-c257-6e0b929e48e2.gif)

# マテリアルグラフ

ベースはStarter ContentのM_RockBasalt。
緑色のコメントで囲った部分が頂点カラーに応じて６種類の色味を選べるように追加した箇所になる。

![20230922_09_Material.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/ea6aacb5-2977-449b-cfb6-96181c48065d.jpeg)

以下はマテリアルグラフ内で使用しているマテリアル関数のグラフ。

## MF_IntSwitcher6

0～1の値を６倍して、６種類の出力のいづれかに１を、その他には０を出力するもの。
頂点カラーの値0～1の値から、６種類を選択するためにつくったもの。
![20230922_10_MF_IntSwitcher6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/1ff3dd6d-53fe-218b-dd82-a2a9be16c7ff.jpeg)

## MF_MacroTextureVariation

Starter Contentのマテリアルでよく使われている「汚し」処理をマテリアル関数化したもの。
![20230922_11_MF_MacroTextureVariation.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/e9e1a046-60be-fb63-6ef7-8901cf293398.jpeg)

## MF_DepthGradient

Starter Contentのマテリアルでよく使われている、近距離では法線テクスチャを使い、遠距離では法線マップをフラットにし、中間距離では両者の間をグラデーションするもの。
![20230922_12_MF_DepthGradient.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/a84dabcc-b968-847e-e231-c251f12d0a07.jpeg)

# UVのアンラップ 
(手作業)


ここまでの処理では、UVは平面投影されていて、特に垂直面でテクスチャの延びが目立ってしまう。[【前編】][article-part1]にも述べたように、
残念ながらUVをええ感じにしてくれる関数は無いようなので、最後の仕上げは手作業となる。


:::note warn
Geometry Scripting内でUnwrapする方法を見つけたので、手動でやる必要はなくなった。
これについては[【UE5】Geometry Scripting：プロシージャルに石畳や岩場を作る【追記】](https://qiita.com/Rinderon/items/c078dfb9a5be1a0ac548)に書いたので、そちらも参照されたい。
:::

1. ModelingモードのTransformカテゴリのConvertコマンドを実行して、Static Meshアクタに変換する。
1. ActorメニューのUVエディタを開いて、Unwrapコマンドを実行する。
1. 必要に応じて UVを拡大し、タイリング具合を調整する。

※詳しい手順は[【前編】][article-part1]を参照のこと。

下の画像は、左がUnwrap前(平面投影)、右がUnwrap後である。Unwrapを行うと、垂直に近い面ほど延びがちだったテクスチャが、均等に張られるようになっていることが分かる。
![20230922_14_AfterUnwrapped.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/194abbcd-900a-ea72-4b15-b7deed295c5e.jpeg)

# おわりに（課題として）

- TessellationもSubdivisionもメッシュを細かく分割する処理なので、ポリゴン数などがかなり増えてしまう。Naniteがあるから別にいいじゃん…と思わなくもないが、実際のゲームに使うには、メッシュのシンプル化を行った方が良いだろう。Geometry Scriptingにメッシュのシンプル化のノードもある。
- せっかくメッシュが細かく分かれているので、頂点座標にノイズなどをのせて、より自然物っぽくしても良いだろう。