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

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

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

# はじめに

Geometry Scriptingでそれっぽく石畳や岩場っぽいものを作ることができた。今回はその内容を紹介する。
なお、この記事は長くなってしまったので【前編】と[【後編】](https://qiita.com/Rinderon/items/ff59cc272d759876f246)に分かれている。

石畳：
![20230919_04_CobblestoneFinal.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/a2956ba1-ebc1-e719-2b17-02a8e2cebac9.jpeg)

岩場：
![20230919_05_StonesFinal.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/819d11a2-1ed9-21f3-d178-834f20752b44.jpeg)

どちらも、パラメータを変えただけの同じプログラムで生成されている。

# ベースとなるアイデア

平面をZ方向に押し出した(extrudeした)立体に、Subdivisionをかけることで、丸みを帯びた立体を作ることができる。
ポリゴンの押し出し(extrude)とは、平面ポリゴンを引き延ばして立体化する操作のことである。
![20230919_07_Extrude.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/65133f04-0022-3f1f-7bab-3a11ed41b162.gif)
ExtrudeにはApply Mesh Linear Extrude Facesノードを使う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/74c6099d-2f87-6ae9-31e9-6353d3b94558.png)
さらにSubdivisionをかけて丸くする。SubdivisionにはApply Triangle Loop SubDノードを使う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/d49eef2d-4de4-26c3-78ad-abff6fcb38e0.png)
Subdivisionを掛けた結果は次の画像のようになる。
![20230919_03_SubD_Tes0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/573f6346-96ac-a67a-51f1-4bc5facab146.jpeg)
この図は、左から順番にSubdivision Level=0, 1, 2となり、右に行くほどSubdivisionのレベルが高い。このように、Subdivisionは元の立体(一番左)をポリゴンを増やして丸く滑らかにする。

また、元の立体のポリゴンを密にすると、丸まりにくくなる。
次の画像は、元の立体にテセレーションをかけてポリゴンを密にした後、Subdivisionをかけたものである。
![20230919_03_SubD_Tes1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/190027a1-1f52-f059-271c-ffa99366a697.jpeg)
より元の立体に近く、角だけが丸まっているような形状になる。
テセレーションにはApply PN Tessellationノードを使う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/966ff930-1b27-f75a-8094-598a9a39b293.png)

これらを踏まえて、
- ランダムな平面ポリゴンを押し出して(extrudeして)立体にする。
- そこに Subdivisionをかけて丸める。
    - Tessellationをかけてから Subdivisionをかけるとより元の立体に近い、平べったい形状になる。(石畳に使いやすそう)
- これに岩のテクスチャを貼れば、石や岩っぽくなるのでは？
…が今回のベースとなるアイデアである。

ここまでに紹介したメッシュを作るブループリントのグラフは以下のようになる。
なお、Geometry Scriptingを使うためには、「Geometry Script」プラグインを有効にし、「Generated Dynamic Mesh Actor」を継承したアクタ上で実装する必要がある。

## Construction Script

Construction Scriptは補助的な機能を実装しているのみである。
- Vector型の変数Cornerの位置から、ボックスのサイズを算出する。
    - CornerはShow 3D WidgetをTrueにして、レベルエディタ上でドラッグ移動できるようにしてある。
- Cornerの移動に合わせてBoxコリジョンの大きさを変更。このBoxコリジョンは大きさを見て分かりやすくするためだけのものであり、実際にコリジョンを取っているわけではない。
- Boxの上 (Dynamic Meshの上)にSubdivisionレベルとTessellationレベルを表示する。
![20230919_Graph_ConstructionScript.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/9f3e688e-3914-dd08-4b08-42513ad735af.jpeg)

## Event Graph

Geometry Scriptingのメインとなるイベント「Event On Rebuild Generated Mesh」を実装する。
Event Graph上に直接組んでも問題ないが、ここでは個人的な好み(ローカル変数が使いたい)で、処理の本体は関数に実装している。
![20230919_Graph_EventGraph.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/b85ea954-bb9f-a212-60ca-66b5c2c15013.jpeg)

## 関数 Rebuild Generated Mesh

![20230919_Graph_RebuildGeneratedMesh.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/001776d8-23e3-ddb8-204b-c74703faba48.jpeg)

ここが本体となる。以下の順でメッシュを加工している：
1. <b>Append Rectangle XY</b>：XY平面上に四角形を生成する。
1. <b>Apply Mesh Linear Extrude Faces</b>：その四角形をZ方向に引き延ばす。
1. <b>Apply PN Tessellation</b>：現状のポリゴンを細かく分割する。
    - 法線の再計算はあとで手動でやるつもりなので、Optionsにある「Recompute Normals」はFalseにしてある。
1. <b>Apply Triangle Loop Sub D</b>：Subdivisionをかける。形状が丸くなる。
1. <b>Recompute Normals</b>：メッシュ加工が終わったので、法線の再計算を行う。
1. <b>Set Mesh UVs From Planar Projection</b>：XY平面に対する投影でUV座標を作る。
    - 以前[UVプロジェクション関数のTransformの使いかた](https://qiita.com/Rinderon/items/d5eb81da779f0a06bcea)に書いたように、Transformのスケール値にメッシュの実際の大きさ(cm単位)を入れてやる。そうすると実際のメッシュの大きさがテクスチャ１枚分になる。ここではさらにfloat型の変数UV Scaleを掛けて、タイリングの密度を調整できるようにしてある。
    - XY平面での投影では、Z方向に高い岩の場合、垂直面に対してはテクスチャが伸びてしまう。
これは「仕方ない」としておく。あとで、UEのUVエディタで「Unwrap」してやるとええ感じになる。


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

# 岩のマテリアルをつける

マテリアルを変更するだけなら特にプログラムを組む必要はない。
レベルエディタ上で選択し、詳細パネルのマテリアルの項目を好みのマテリアルにするだけである。
今回はStarter ContentのM_Rock_Basaltを使用した。
![20230920_01_RockBasalt.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/bdb23f29-fe49-8de2-9240-747515e4b567.jpeg)
前述したとおり、Set Mesh UVs From Planar Projectionで平面投影を行っているので、垂直な面のUVが延びてしまっている。
残念ながら、Geometry Scriptingでこれを良い感じに調整する方法は見つけられなかった。

ここでは、UEのUVエディタで調整することにする。

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

## Static Mesh化する

残念ながら、Dynamic MeshのUVをUVエディタで操作することはできない。Dynamic Mesh選択中は、メニューからUVエディタの項目が消えてしまう。なので、Dynamic MeshをStatic Meshに変換してから、UV操作を行うことにする。

ここでは「[Dynamic MeshをStatic Meshとして保存する３つの方法](https://qiita.com/Rinderon/items/217d12b59568407e2c43)」に書いた、１番目の方法「ModelingモードのConvertコマンド」を使う。
詳しくは、上記リンクを見て頂くとして、手順の概要は以下のような感じである。
1. コンバートしたいDynamic Meshを選択する。
1. モデリングモードに切り替える。
1. Transformカテゴリにある、Convertコマンドをクリックする。
1. ビューポート下辺中央の「Accept」ボタンを押すとコンバートされ、カレントフォルダ(など)にStatic Meshが保存される。

【補足】Modelingモードはエディタ左上のコンボボックスで選択できる(Shift+5)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/44441d29-d2ab-42f6-6526-9d90bedc7b6d.png)
【補足】ConvertコマンドはTransformカテゴリにある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/d119b322-31bb-f3b8-decb-77e002b443a7.png)

:::note warn
Modelingモード > Transform > Convertは、レベル上で選択されたDynamic Mesh Actorを、コンバート後のStatic Mesh Actorに置き換えてしまう。これを避けるにはDynamic Mesh ActorをAlt＋ドラッグなどで複製してから、複製されたDynamic Mesh Actorをコンバートすると良い。
:::

Static Mesh化された後の図。このStatic MeshのUVを編集する。
![20230920_02_ToStaticMesh.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/bed0d758-5861-e5ba-9b3d-ee9cdcedc905.jpeg)

## UVをUnwrapする

次にUEのUVエディタでUVを調整する。UVエディタを開くには：
1. UV編集したいStatic Mesh Actorを選択する。
1. Actorメニューの一番下のUV Editorを選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/37741d97-75c7-191c-425c-ddb91e62b861.png)
そうするとUVエディタが開く。
![20230920_03_UVEditor.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/b0a4b0c7-ff9d-2de4-c40f-393af344ff29.jpeg)
Set Mesh UVs From Planar ProjectionでUVを作ったので、XY平面に投影した形でUVが作られている。つまりXY平面に垂直な面に対しては、ポリゴンの面積に対してUV上での面積が小さく、テクスチャが延びてしまっている。ここで「Unwrap」コマンドを実行する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/42454c01-0e1f-e056-91de-f64e66a449a8.png)
Unwrapコマンドで垂直面にも面積に応じて程よくUVが載るように調整してくれるはずである。
UVビューポートの下部中央に出ている「Accept」を押すと確定される。
![20230920_04_Unwrapped.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/e6043f69-4236-940c-560f-3f69141a8b56.jpeg)
ところで、Unwrapコマンドは結果を(0,0)～(1,1)に収めてしまう。今回は、メッシュ１個の大きさ＝テクスチャ１枚分よりは細かくタイリングしたい。なので、次はUVを拡大する。UVの拡大にはTransformコマンドを使う。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/b8697915-41ab-6266-54ff-c355e4458a38.png)
ここでは、例えばScale=(4, 4)にする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/b77cc124-6f3a-00fb-a5ed-95bf9020c24f.png)
右側のビューポートで程よくタイリングされているのを確認したら、UVビューポートの下部中央の「Accept」を押す。
後は、左上の「セーブ」ボタンもしくは「Apply」ボタンを押すと、結果が保存され、ビューポートのStatic Mesh Actorにも反映される。
最終的に次の画像のようになった。UVが均一に張られて、違和感が無くなっている。
![20230920_06_Unwrapped.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/94715283-33f1-45ec-774b-e1ec620d3c06.jpeg)

# 次回予告

長くなったので、いったんページを切ることにする。
次にやることは以下のような感じである。
- 今回は四角形から作ったが、もっとランダムな多角形から作りたい。
- 石や岩１個ずつじゃなくて、まとめて作りたい。その際、石同士が干渉しないようにしたい。

[【後編】では、](https://qiita.com/Rinderon/items/ff59cc272d759876f246)これらを解決するのに、Sobol乱数とVoronoi図を利用した。次の記事ではSobol乱数とVoronoi図を使って、ランダムに並んだ石畳や岩場を作る。