Unity提供のスプラインに沿ってメッシュを変形させる方法を知りたいの。
スクリプトで自作する必要があるけど可能だわ。今回は基本的な部分を解説していくね。
Unity公式のスプライン(Splinesパッケージ)に沿ってメッシュを変形させる方法の解説記事です。
次のように、既存のメッシュをスプライン曲線に沿って変形させるところを目標とします。
処理の実装方法は一通りではありませんが、本記事では次の方法でメッシュ変形を実現するものとします。
- スクリプトからMeshオブジェクトにアクセスし、頂点と法線を書き換える
- スプライン上の位置や傾きなどはSplineオブジェクトから取得する
- 全てメインスレッドのCPU上で処理する
スプラインに沿ってメッシュを作成するコンポーネントとしてSpline Extrudeがありますが、これはチューブ状のメッシュを生成する機能のため、上記のメッシュを変形させるためにはスクリプトで独自実装するなどの手段を取る必要があります。
参考:Extrude a mesh along a spline | Splines | 2.5.2
本記事では、スプラインに沿って既存のメッシュを変形する方法を解説していきます。
本記事ではメッシュ変形処理を全てCPU上で計算する方法に絞って解説します。
ランタイムで使用する場合は、パフォーマンスのネックになる可能性があるためご注意ください。
- Unity 2023.2.19f1
- Splines 2.5.2
目次 非表示
前提条件
本記事の内容を実践するためには、Unity 2022.1以降でプロジェクトを作成し、Splinesパッケージがインストールされている必要があります。
ここまでの導入方法が分からない方は以下記事をご覧ください。
また、本記事を読み進めるにあたっては、スプラインをスクリプトから扱う方法を押さえておくと理解がスムーズです。
本記事では、次のような線路のメッシュを変形させることを例に解説していきます。
こちらのモデルは以下アセットのものを使用させていただきました。
メッシュ変形の基本的な考え方
手順に入る前に、スプラインに沿ってメッシュを変形させる考え方について解説します。不要な方は次の章までスキップしてください。
頂点の変形
メッシュの頂点座標は、オブジェクトのローカル空間で表現されます。
この頂点座標をスプライン曲線のデータに基づいて変換していきます。
まず、メッシュ頂点のxyz成分のうち、どの軸をスプラインに沿わせるかを定義します。本記事では、頂点座標のz軸をスプライン曲線に沿わせるように変形するものとして解説を進めます。
次に、スプライン曲線の始点からメッシュ頂点のz成分だけ進んだ位置を求めます。
同時に、得られたスプライン位置における接線と上向きベクトルも計算しておきます。
スプライン上の位置・接線・上向きベクトルは、後述するSplinesパッケージのAPIから簡単に計算できます。また、接線(前方ベクトル)と上向きベクトルから、右向きベクトルを求めることが出来ます。
最後に、右向きベクトル方向にx軸成分、上向きベクトル方向にy軸成分だけ頂点位置をずらして完了です。
位置ずらしの計算は、得られたスプライン位置における回転(クォータニオン)を用いて計算できます。回転は接線と上向きベクトルから求められます。
ずらし位置の計算は、右向きベクトル、上向きベクトルそれぞれに対してxy成分の内積を取ることで求めることも可能です。
本記事は後述する法線ベクトルでクォータニオンが必要になるため、クォータニオンでずらし位置を計算するものとします。
法線の変形
元メッシュの法線ベクトルの変形は、前述で計算されたスプライン位置における回転を掛けることで実現できます。
この回転は、前述のスプラインの接線と上向きベクトルから求めたクォータニオンを流用すれば良いです。
メッシュの準備
ここからメッシュを変形させるための手順の解説に入ります。
まず、変形対象のメッシュを用意します。本記事では以下の線路モデルとします。
メッシュ変形は頂点操作によって実現するため、ある程度ポリゴンが分割されている必要があります。
ポリゴンの分割が粗いと、カーブしたときに角ばった変形になってしまう可能性が高くなります。
考え方の解説ではz軸成分をスプラインの移動方向としていましたが、後述する例ではどの向きでも対応可能にするため、xyz軸いずれかに沿っていれば問題ありません。
また、スクリプトからメッシュ操作するためには、モデルアセット(FBXファイル等)のプロパティのMeshes > Read/Write項目にチェックを入れる必要があります。
これは、GPUだけでなくシステムメモリ(CPUからアクセス可能)にもメッシュデータを保持する設定です。
Meshes > Read/Write項目を有効にする設定は、実行時におけるシステムメモリ使用量を増大させる要因となるため、不要な場合はOFFにしておくことをお勧めします。デフォルト設定ではOFFになっています。
シーンの準備
メッシュをどのように変形させるかを指定するためのスプラインを作成します。既に作成済みの場合はスキップして構いません。
ヒエラルキーウィンドウの左上の「+」アイコン(またはトップメニューのComponent)からSplines > Draw Splines Tool …を選択し、スプラインの制御点をクリックで配置し、ドラッグで接線を調整します。
「Esc」キーで配置を終了できます。
参考:Create a spline | Splines | 2.6.1
また、変形対象のゲームオブジェクトもシーンの適当な場所に配置しておきます。
最終的に、スプラインとメッシュオブジェクトがシーンに配置されていれば良いです。
変形処理の実装
ここまで準備したスプラインとメッシュを元に、メッシュをスプラインに沿って変形させるスクリプトを実装していきます。
最初に実装例を示し、その後に処理の流れを解説します。
サンプルスクリプト
以下、実行時にメッシュをスプラインに沿って動的に変更するスクリプトの実装例です。
上記をSplineMeshDeform.csという名前でUnityプロジェクトに保存し、メッシュを変形させる対象のオブジェクトに追加し、インスペクターのContainerにスプラインオブジェクトを指定してください。
また、必要に応じてメッシュをどの向きに変形させるかの情報を指定します。
Forward Axis項目にスプラインに沿わせる方向の軸のベクトルを、Up Axis項目に上向きのベクトルを設定してください。
例では線路がy軸に沿っていて、上向きがz軸正方向になっていたため、Forward Axisに(0, 1, 0)を、Up Axisに(0, 0, 1)を設定することとしました。
実行結果
Spline Mesh DeformコンポーネントのTの値をインスペクターから変更すると、リアルタイムにメッシュが変形されていることが確認できます。
Unityの再生を停止すると、メッシュが元に戻ります。
スクリプトの説明
最初に示したサンプルスクリプトの各処理について解説していきます。
変形処理の前準備
まず、初期化時に変形対象のメッシュ情報にアクセスしたいため、MeshFilterコンポーネントを取得して保持しておきます。MeshColliderがアタッチされている可能性もあるため、あれば一緒に取得します。
そして、その直後にメッシュの変形処理を実行します。
Deformメソッドは、独自実装したpublicメソッドです。このメソッドの中では、最初に変形元のメッシュの頂点と法線の情報をコピーして保持しておきます。
_deformedMesh = _meshFilter.meshの行でMeshインスタンスを複製して_deformedMeshフィールドに保持する挙動になります。
これは、複製されていない状態でmeshプロパティをgetするとインスタンスが複製されるためです。
参考:MeshFilter-mesh – Unity スクリプトリファレンス
meshプロパティにアクセスしてMeshインスタンスを複製した場合、明示的にDestroyで破棄する必要があります。
次に、変形の計算過程ではどの軸に沿って変形させるかの基準軸が変わる可能性があるため、基準軸を元に回転(クォータニオン)を計算しておきます。
また、スプラインに一致させてメッシュを配置させるためには、座標変換を行う必要があります。
スプラインの制御点およびメッシュはそれぞれ別々のゲームオブジェクトのローカル座標であるため、その変換をするための行列を計算しておきます。
変換結果を格納するための配列を確保します。
そして、座標系変換されたスプラインをNativeSpline構造体として作成します。
コンストラクタの第1引数にスプライン(Spline型インスタンス)、第2引数に前述で計算した座標系の変換行列を指定します。
参考:Struct NativeSpline | Splines | 2.6.1
スプライン全体の長さも予め計算して保持しておきます。
参考:Class SplineUtility | Splines | 2.6.1
ここまでが変形処理までの前準備処理です。
頂点の変形処理
実際の変形処理は、以下ループ内で各頂点毎に実施していきます。
まず、変形の基準軸を変更するため、最初に計算しておいた基準軸の回転クォータニオンを掛けて頂点に補正をかけます。
これで、必ずz軸方向がスプライン方向、y軸正方向が上向きであることが保証されるようになります。
参考:Method mul | Mathematics | 1.3.2
予め計算しておいたスプラインの長さ用いて、スプラインにおける割合tを計算します。
割合はスプラインの始点が0、終点が1と正規化された値として指定する必要があります。zの位置を割合に変換するために長さで割る必要があります。
また、スプラインはループ(始点と終点が接続)している可能性もあるため、その場合は割合tもループするようにします。
参考:Mathf-Repeat – Unity スクリプトリファレンス
そして、実際に割合tにおけるスプライン位置・接線・上向きベクトルを計算する処理は以下部分です。
Spline.Evaluateメソッドにより、引数から一度に3つの結果を受け取れます。
参考:Class SplineUtility | Splines | 2.6.1
得られたスプライン位置に対して、メッシュをxy方向にずらす必要があります。これは、次の部分です。
接線と上向きベクトルから回転のクォータニオンを求め、これをローカルのxy座標に適用しています。これにより、最終的なずらし位置offsetが計算できます。
最後に、法線もずらし位置同様に回転させる必要があるため、前述のクォータニオンを掛けています。
変換結果の適用
変換した頂点と法線をMeshインスタンスに適用します。
また、頂点の移動によりバウンディングボックスも変わる可能性があるため、忘れずに再計算しておきます。
参考:Mesh-RecalculateBounds – Unity スクリプトリファレンス
もしMeshColliderがあれば、それも更新しておきます。
最後に、変換処理で一時的に確保したバッファを開放して終了です。
後処理
変形されたメッシュはスクリプトから複製されたインスタンスのため、最後に明示的に破棄するようにしています。
アセットファイルとして保存出来るようにする
ここまで解説した方法は、実行時にメッシュを変形する方法でした。
実行時ではなく、実行前に変形しておきたい場合、変形結果のMeshインスタンスをアセットとして保持しておく方法があります。
2つ目の例では、元のメッシュを編集することなく、変形したメッシュをアセットとして保持しておく方法を紹介します。
なお、変形結果のメッシュのアセット化の実装は、Splinesパッケージ提供のSplinesExtrudeクラスの内部実装を参考にさせていただきました。
参考:Class SplineExtrude | Splines | 2.6.1
サンプルスクリプト
1つ目の例を元に、変形結果をアセットとして保持するように修正した例です。
上記をSplineMeshDeformStatic.csという名前で保存し、メッシュを変形したいゲームオブジェクトにアタッチし、インスペクターから必要な設定を行ってください。
1つ目の例との相違点は、変形元と変形先のメッシュをインスペクターから指定する部分です。
また、スクリプトをアタッチする際に自動でメッシュを複製するようにしました。
実行結果
ゲームを実行していない状態でも変形できるようになりました。また、変形するとアセット側にも反映されていることが分かります。
スクリプトの説明
スクリプトが追加されたときなどに、変形後のメッシュアセットの作成や、MeshFilterなどのメッシュ差し替えを自動化するため、次のResetイベントでその処理を行っています。
参考:MonoBehaviour-Reset() – Unity スクリプトリファレンス
メッシュの作成処理では、メッシュを複製し、シーン名とオブジェクト名に基づきパスを決定し、アセットとして保存しています。
メッシュアセットを作成すると、そのアセットを選択状態にするためにEditorGUIUtility.PingObjectメソッドを使用しています。
参考:EditorGUIUtility-PingObject – Unity スクリプトリファレンス
インスペクターから割合tなどを操作したときに反映するため、以下OnValidateイベントでメッシュの変形処理を実施しています。
実行時とそうでない場合で処理を分けています。もしメッシュの複製がまだなら、複製してから変形を実行するようにしています。
さいごに
スプラインに沿ってメッシュを変形させるには、SplinesパッケージのAPIを活用して頂点を変形すれば実現できます。
本記事ではCPUのメインスレッドで全て実施しているため、実行時に変形処理を行うケースではパフォーマンスに注意する必要があります。
これを回避するためには、アセット化して事前に変形したり、Compute Shaderや頂点シェーダーなどのGPUリソースを活用するなどの手段があります。
GPUリソースを用いて高速化する方法は、別記事で改めて解説させていただきます。