Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
6
Help us understand the problem. What is going on with this article?
@aa_debdeb

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

More than 1 year has passed since last update.

以前に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選択時のインスペクターに表示されるようになります)。

6
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
aa_debdeb
Engineer, Creative Coder。Dentsu Craft Tokyo所属。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
6
Help us understand the problem. What is going on with this article?