LoginSignup
12
10

More than 3 years have passed since last update.

UnityのVFX Graphで使用するSigned Distance FieldをCompute Shaderで動的に生成する

Posted at

以前にUnityのVFX Graphで使用するAttribute Mapを動的に生成する記事を書きました。

今回はUnityのVFX Graphで使用するSigned Distance FieldをCompute Shaderで動的に生成したいと思います。

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

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

まずそもそもですが、VFX GraphにおいてSigned Distance FieldはUpdate Particle ContextのConform to Signed DistanceField BlockまたはCollide with Signed Distance Field Blockで使用することができます。これらのBlockではSigned Distance FieldとしてTexture3Dを設定します。

ということで、3次元テクスチャの各ボクセル位置における符号付き距離をCompute Shaderで書き込み、そのテクスチャをVFX GraphのTexture3Dのプロパティに設定すればよさそうです。


まず、C#スクリプトは以下のようになります。ほとんど Attribute Mapを動的に生成したときと同じですが、RenderTexturedimensionプロパティとvolumeDephプロパティを設定して3次元テクスチャにしています。

GenerateSignedDistanceField.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.VFX;

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

    struct ThreadSize
    {
        public int x;
        public int y;
        public int z;

        public ThreadSize(uint x, uint y, uint z)
        {
            this.x = (int)x;
            this.y = (int)y;
            this.z = (int)z;
        }
    }

    [SerializeField]
    ComputeShader shader;

    [SerializeField]
    int width = 64;

    [SerializeField]
    int height = 64;

    [SerializeField]
    int depth = 64;

    VisualEffect vfx;
    RenderTexture signedDistanceField;
    int kernel;
    ThreadSize threadSize;

    void Start()
    {
        if (!SystemInfo.supportsComputeShaders)
        {
            return;
        }
        vfx = GetComponent<VisualEffect>();
        SetupSignedDistanceField();
    }

    void Update()
    {
        if (!SystemInfo.supportsComputeShaders)
        {
            return;
        }

        if (width != signedDistanceField.width || height != signedDistanceField.height || depth != signedDistanceField.volumeDepth)
        {
            SetupSignedDistanceField();
        }

        UpdateSignedDistanceField();
    }

    void SetupSignedDistanceField()
    {
        signedDistanceField = new RenderTexture(width, height, 0, RenderTextureFormat.RFloat);
        signedDistanceField.enableRandomWrite = true;
        signedDistanceField.dimension = TextureDimension.Tex3D;
        signedDistanceField.volumeDepth = depth;
        signedDistanceField.Create();

        kernel = shader.FindKernel("GenerateDynamicSignedDistanceField");
        uint threadSizeX, threadSizeY, threadSizeZ;
        shader.GetKernelThreadGroupSizes(kernel, out threadSizeX, out threadSizeY, out threadSizeZ);
        threadSize = new ThreadSize(threadSizeX, threadSizeY, threadSizeZ);

        shader.SetTexture(kernel, "SignedDistanceField", signedDistanceField);

        vfx.SetTexture("Signed Distance Field", signedDistanceField);
    }

    void UpdateSignedDistanceField()
    {
        shader.SetFloat("Time", Time.time);
        shader.Dispatch(kernel, Mathf.CeilToInt(width / threadSize.x), Mathf.CeilToInt(height / threadSize.y), Mathf.CeilToInt(depth / threadSize.z));
    }
}

Compute Shader側では、以下のように時間に応じて球とトーラスで滑らかに切り替わるような符号付き距離場を生成しています。

GenerateDynamicSignedDistanceField.compute
#pragma kernel GenerateDynamicSignedDistanceField

RWTexture3D<float> SignedDistanceField;

float Time;

float SdSphere(float3 p, float r)
{
    return length(p) - r;
}

float SdTorus(float3 p, float2 t)
{
    return length(float2(length(p.xz) - t.x, p.y)) - t.y;
}

[numthreads(8,8,8)]
void GenerateDynamicSignedDistanceField (uint3 id : SV_DispatchThreadID)
{
    float width, height, depth;
    SignedDistanceField.GetDimensions(width, height, depth);
    float3 size = float3(width, height, depth);

    float3 p = id / size * 2.0 - 1.0;
    float ds = SdSphere(p, 0.8);
    float dt = SdTorus(p, float2(0.7, 0.2));
    float t = sin(Time) * 0.5 + 0.5;
    SignedDistanceField[id] = lerp(ds, dt, t);
}

VFX Graph側は次のようにSigned Distance Fieldという名前のTexture3Dプロパティを作成してExposedにしておきます。
generate_dynamic_signed_distance_field_conform.PNG

こうすると以下のようになり、トーラスから円になり、またトーラスに戻るようなSigned Distance Fieldを動的に生成できていることがわかります(若干わかりにくいですが...)。
GenerateDynamicSignedDistanceField_005.gif

同じSigned Distance FieldをCollide with Signed Distance Fieldにも使ってみました。
generate_dynamic_signed_distance_field_collide.PNG
GenerateDynamicSignedDistanceField_004.gif


VFX Graphで使用するSigned Distance FieldをCompute Shaderで動的に生成してみました。

VFX GraphのSigned Distance Fieldについては調べてもあまり情報がなく、どういうフォーマットでTexture3Dに格納すればいいのか不明でした。例えば、メッシュからSigned Distnace Fieldを生成するSDFrというツールでは[-1, 1]の範囲に正規化しているようですが、理由はよくわかりませんでした。

ただ、Conform to Signed Distance Field BlockとCollide with Signed Distance Field Blockで生成されるコードを見た感じでは、その地点の符号付き距離の値自体というよりもその地点の符号付き距離を微分したベクトル(つまり符号付き距離が0になる地点への最短方向)のほうが大事そうでした。そのベクトルは距離関数が正規化されていなくても求まるはずなので、細かいことは考えずにTexture3Dに符号付き距離を入れればよさそうです(生成されるコードはEdit > Prefrences > Visual Effects > Show Additional Debug infoにチェックを入れるとBlock選択時のインスペクターに表示されるようになります)。

12
10
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
10