UnityのVFX Graphで使用するAttribute Mapを動的に生成する

UnityのVFX Graphで使用するAttribute Mapをスクリプトで動的に生成してみます。

使用したバージョンは以下のようになっています。

- Unity: 2019.3.10f1 Personal
- Visual Effect Graph: 7.3.1

---

今回作成するサンプルは以下のようにXY平面を円状に動く球の表面にパーティクルを生成するシンプルなものです。色は移動する円に対する角度によってHSVの色相を変更することで決定しています。
![GenerateDynamicAttributeMaps_002.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/824e4fa2-fe80-00af-3964-b10a9875fb2e.gif)


サンプルは単純なので動的にAttribute Mapを生成せずにVFX Graphのノードグラフだけでも組むことができます。ですが、今回は位置と色に関するAttribute Mapをスクリプトで生成して、VFX Graphの`Initialize Particle`Contextの`Set Position from Map`Blockと`Set Color from Map`Blockで位置と色を決定するようにします。

まず、次のような動的にAttribute Mapを生成するスクリプトを作成して、`Visual Effect`コンポーネントを持つGame Objectに追加します。ここでは、サンプリング候補となる位置と色を持つ`Texture2D`をそれぞれ作成し、`SetTexture`メソッドで`Position Map`と`Color Map`という名前のプロパティに作成した`Texture2D`を設定しています。

```csharp:GenerateDynamicAttributeMaps.cs
using UnityEngine;
using UnityEngine.VFX;

[ExecuteInEditMode]
[RequireComponent(typeof(VisualEffect))]
public class GenerateDynamicAttributeMaps : MonoBehaviour
{

    [SerializeField]
    int width = 32;

    [SerializeField]
    int height = 32;

    VisualEffect vfx;
    Texture2D positionMap;
    Texture2D colorMap;

    void Start()
    {
        vfx = GetComponent<VisualEffect>();
        CreateAndSetAttributeMaps();
    }

    void Update()
    {
        if (width != positionMap.width || height != positionMap.height) {
            CreateAndSetAttributeMaps();
        }
        UpdateAttributeMaps();
    }

    void CreateAndSetAttributeMaps()
    {
        positionMap = new Texture2D(width, height, TextureFormat.RGBAFloat, false);
        positionMap.filterMode = FilterMode.Point;
        positionMap.wrapMode = TextureWrapMode.Clamp;
        colorMap = new Texture2D(width, height, TextureFormat.RGBAFloat, false);
        colorMap.filterMode = FilterMode.Point;
        colorMap.wrapMode = TextureWrapMode.Clamp;
        vfx.SetTexture("Position Map", positionMap);
        vfx.SetTexture("Color Map", colorMap);
    }

    void UpdateAttributeMaps()
    {
        Vector3 center = 3.0f * new Vector3(Mathf.Cos(Time.time), Mathf.Sin(Time.time), 0.0f);
        int count = width * height;
        Color[] positions = new Color[count];
        Color[] colors = new Color[count];
        for (int i = 0; i < count; i++)
        {
            Vector3 position = center + Random.onUnitSphere;
            positions[i] = new Color(position.x, position.y, position.z, 0.0f);
            colors[i] = Color.HSVToRGB((Mathf.Atan2(position.y, position.x) + Mathf.PI) / (2.0f * Mathf.PI), 1.0f, 1.0f);
        }
        positionMap.SetPixels(positions);
        positionMap.Apply();
        colorMap.SetPixels(colors);
        colorMap.Apply();
    }
}
```

VFX Graph側では以下のように`SetTexture`メソッドで使用したものと同じ`Position Map`と`Color Map`という名前の`Texture2D`形式のプロパティを作成して、`Exposed`にしておきます。
![generate_dyanamic_attribute_maps.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/be133e9b-1909-6e9d-1359-beb0c3dc583a.png)

---

今回のサンプルのように単純なエフェクトの場合、わざわざAttribute Mapを動的に生成するありがたみはないですが、Unityの他の機能や外部データと組み合わせると面白いことができそうです。
また、今回のサンプルでは生成するAttribute Mapはサイズが小さいのでC#スクリプトで生成しましたが、サイズが大きい場合はCompute Shaderを使う必要がありそうです。

---

以下の記事を参考にさせて頂きました。

- [keijiro/Rsvfx: An example that shows how to connect RealSense depth camera to Unity VFX Graph](https://github.com/keijiro/Rsvfx)
- [VFX Graph の可能性を探る！PointCache動的生成、VAT拡張｜fuqunaga｜note](https://note.com/fuqunaga/n/n8095638b6b14)
- [【Unity】Face Tracking with ARKit \+ Visual Effect Graph\(VFX Graph\)で遊んでみる \- Qiita](https://qiita.com/mao_/items/0fffc84b2b16cf680f80)