どーも、ぐるたか@guru_takaです。
本記事では、C# スクリプトで動的に Attribute Map を生成し、Position Map にセットし、メッシュをパーティクル化していきます。成果物はこちら!
Unity 公式の下動画のように、動的なメッシュに対して VFX Graph を使いたいことがモチベーションです。そのための基本技術と備忘録として、まとめています。
本記事ではシンプルかつミニマムな構成でわかりやすく、動的な Attribute Map の生成方法を紹介したいために、メッシュのパーティクル化をトピックとしています。
・Visual Effect Graph:10.4.0
前提知識
VFX Graph で、メッシュの情報を扱うためには、Attribute Map を作成する必要があります。
Attribute Map とは、パーティクルの固有値(ポジションなどの情報)が格納された Texture2D(データバッファ)です。Texture2D は RGBA まで情報が格納できるため、4次元まで拡張可能。
そして、生成した Attribute Map を Position Map や Vector Map などに渡すことで、様々な頂点における情報(ポジションや法線など)が VFX Graph で使用できます!
VFX Graph によるパーティクル化のノード構成
まず Position Map からメッシュをパーティクル化するノードは下図の通りです。基本的な構成は下記事と殆ど同じになっています。詳細は下記事をご覧ください!
【Unity】VFX Graph で Point Cache(ポイントキャッシュ)を使い、メッシュをパーティクル化する
外部から Texture2D を渡したい時は、blackboard
> Texture2D
とすることで、プロパティ化できます。
全体のコード
後は、動的に Texture2D(Attribute Map)を生成 → PositionMap
にセットする C# スクリプトを記述するだけです。全体像は以下のようになります!
PositinoMap
に渡す全体のコードは以下のようになります。コメントで解説ています。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.VFX;
[RequireComponent(typeof(VisualEffect))]
public class BakeMesh : MonoBehaviour
{
public Texture2D BakedTexture { get; private set; }
private VisualEffect _vfx = null;
private Mesh _mesh = null;
private Color[] _colorBuffer = null;
private void Awake()
{
Initialize();
// プロパティ「PositionMap」 に頂点のポジション情報が保存された Texture2D をセット
_vfx = GetComponent();
_vfx.SetTexture($"PositionMap", BakedTexture);
}
public void Initialize()
{
// アタッチされたオブジェクトの MeshFilter からメッシュの情報を取得
_mesh = GetComponent().mesh;
// 頂点のポジション情報を取得
Vector3[] vertices = _mesh.vertices;
// 頂点数
int count = vertices.Length;
// Texture2D で h × w にポジション情報を落とし込むため、頂点数の平方根を取得
float r = Mathf.Sqrt(count);
// w、h を整数化するため、頂点数の平方根を切り捨て
// 切り上げの場合、 w × h が頂点数より大きくなり、Texture2D に (0,0,0, 0) の情報が余るので注意
int size = (int)Mathf.Floor(r);
// Texture2D に保存するためのバッファ
_colorBuffer = new Color[size * size];
// Texture2D(AttributeMap)のセットアップ
BakedTexture = new Texture2D(size, size, TextureFormat.RGBAFloat, false);
BakedTexture.filterMode = FilterMode.Point;
BakedTexture.wrapMode = TextureWrapMode.Clamp;
// Texture2D に頂点のポジション情報を保存
UpdatePositionMap();
}
private void UpdatePositionMap()
{
// for よりも foreach の方が処理が圧倒的に速いため、foreach を採用
// _mesh.vertices の方が Texture2D のサイズ(w × h)より大きいため、_colorBuffer.Length まで繰り返すように調整
int idx = 0;
foreach (Vector3 vert in _mesh.vertices.Take(_colorBuffer.Length))
{
_colorBuffer[idx] = VectorToColor(vert);
idx++;
}
// Texture2D に頂点のポジション情報を Texture2D にセット
BakedTexture.SetPixels(_colorBuffer);
BakedTexture.Apply();
}
private Color VectorToColor(Vector3 v)
{
return new Color(v.x, v.y, v.z, 0.0f);
}
}
ポイントは以下3つです!
- 頂点数のポジション情報を取得し、バッファに保存
- バッファに保存されたポジション情報を、RGBA として そのまま Texture2D にセット
- ポジション情報が保存された Texture2D を プロパティ
PositionMap
にセット
上記の基本的なポイントさえ押せておきば、特に難しいことはありません!
頂点数の平方根を切り上げする場合は、頂点数よりも w × h のカラーサイズが大きくなるため、注意してください!余るピクセルの (0,0,0,0)
が悪さする恐れがあります。私は最初、平方根の切り上げで、だいぶハマりました。。
また、for 構文ではなく、forwach 構文を使うのも、大事なポイントです。for 構文の場合、Update メソッドで Texture2D を更新する時に、重すぎて使い物にならなくなります。
最後に、下図のようにアタッチすれば、メッシュのパーティクル化が完成です!
最後に
以上です。今回は非常に簡単なケースでしたが、応用範囲は非常に広いと感じます。
ぜひ色々と試してみてください!
コメントを残す