以前にUnityのVFX Graphで使用するAttribute Mapを動的に生成する記事を書きました。
- UnityのVFX Graphで使用するAttribute Mapを動的に生成する - Qiita
- UnityのVFX Graphで使用するAttribute MapをCompute Shaderで動的に生成する - Qiita
今回は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を動的に生成したときと同じですが、RenderTexture
のdimension
プロパティとvolumeDeph
プロパティを設定して3次元テクスチャにしています。
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側では、以下のように時間に応じて球とトーラスで滑らかに切り替わるような符号付き距離場を生成しています。
#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
にしておきます。
こうすると以下のようになり、トーラスから円になり、またトーラスに戻るようなSigned Distance Fieldを動的に生成できていることがわかります(若干わかりにくいですが...)。
同じSigned Distance FieldをCollide with Signed Distance Field
にも使ってみました。
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選択時のインスペクターに表示されるようになります)。