321 lines
12 KiB
C#
321 lines
12 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Experimental.VFX;
|
|
using UnityEngine.VFX;
|
|
|
|
namespace Beyond
|
|
{
|
|
[ExecuteAlways]
|
|
public class MeshToSDF : MonoBehaviour
|
|
{
|
|
public bool executeInEditMode = false;
|
|
|
|
public ComputeShader JFAImplementation;
|
|
public ComputeShader MtVImplementation;
|
|
|
|
//[HideInInspector]
|
|
public RenderTexture sdfRenderTexture;
|
|
public RenderTexture voxelRenderTexture;
|
|
public RenderTexture debugTexture;
|
|
|
|
[Tooltip("Mesh to convert to SDF. One of Mesh or Skinned Mesh Renderer must be set")]
|
|
public Mesh mesh;
|
|
|
|
[Tooltip("Skinned mesh renderer to bake mesh from before converting to SDF.")]
|
|
public SkinnedMeshRenderer skinnedMeshRenderer;
|
|
|
|
[Tooltip("Material whose property to set with the output SDF texture")]
|
|
public Material materialOutput;
|
|
public string materialProperty = "_Texture3D";
|
|
|
|
[Tooltip("Visual effect whose property to set with the output SDF texture")]
|
|
public VisualEffect vfxOutput;
|
|
public string vfxProperty = "Texture3D";
|
|
|
|
// Need to change this when any of the following
|
|
// settings are changed in a build
|
|
// unlikely usecase but we support it :)
|
|
[HideInInspector]
|
|
public bool fieldsChanged = true;
|
|
|
|
[Tooltip("Signed distance field resoluton")]
|
|
public int sdfResolution = 64;
|
|
|
|
[Tooltip("Offset the mesh before voxelization")]
|
|
public Vector3 offset;
|
|
[Tooltip("Scale the mesh before voxelization")]
|
|
public float scaleBy = 1.0f;
|
|
[Tooltip("Number of points to sample on each triangle when voxeling")]
|
|
public uint samplesPerTriangle = 10;
|
|
[Tooltip("Thicken the signed distance field by this amount")]
|
|
public float postProcessThickness = 0.01f;
|
|
|
|
Mesh prevMesh;
|
|
|
|
// kernel ids
|
|
int JFA;
|
|
int Preprocess;
|
|
int Postprocess;
|
|
int DebugSphere;
|
|
int MtV;
|
|
int Zero;
|
|
|
|
#if UNITY_EDITOR
|
|
private void OnValidate()
|
|
{
|
|
Awake();
|
|
// debugTexture = MakeDebugTexture();
|
|
}
|
|
#endif
|
|
|
|
private void Awake()
|
|
{
|
|
JFA = JFAImplementation.FindKernel("JFA");
|
|
Preprocess = JFAImplementation.FindKernel("Preprocess");
|
|
Postprocess = JFAImplementation.FindKernel("Postprocess");
|
|
DebugSphere = JFAImplementation.FindKernel("DebugSphere");
|
|
|
|
MtV = MtVImplementation.FindKernel("MeshToVoxel");
|
|
Zero = MtVImplementation.FindKernel("Zero");
|
|
// set to nearest power of 2
|
|
sdfResolution = Mathf.CeilToInt(Mathf.Pow(2, Mathf.Ceil(Mathf.Log(sdfResolution, 2))));
|
|
if (sdfRenderTexture != null) sdfRenderTexture.Release();
|
|
sdfRenderTexture = null;
|
|
fieldsChanged = true;
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (mesh == null)
|
|
{
|
|
mesh = new Mesh();
|
|
}
|
|
skinnedMeshRenderer = skinnedMeshRenderer ?? GetComponent<SkinnedMeshRenderer>();
|
|
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!Application.IsPlaying(gameObject) && !executeInEditMode) return;
|
|
|
|
float t = Time.time;
|
|
|
|
Mesh mesh;
|
|
if (skinnedMeshRenderer)
|
|
{
|
|
mesh = new Mesh();
|
|
skinnedMeshRenderer.BakeMesh(mesh);
|
|
}
|
|
else
|
|
{
|
|
mesh = this.mesh;
|
|
}
|
|
|
|
if (skinnedMeshRenderer || prevMesh != mesh || fieldsChanged)
|
|
{
|
|
prevMesh = mesh;
|
|
fieldsChanged = false;
|
|
MeshToVoxel(sdfResolution, mesh, offset, scaleBy, samplesPerTriangle);
|
|
|
|
FloodFillToSDF();
|
|
}
|
|
|
|
if (materialOutput)
|
|
{
|
|
if (!materialOutput.HasProperty(materialProperty))
|
|
{
|
|
Debug.LogError(string.Format("Material output doesn't have property {0}", materialProperty));
|
|
}
|
|
else
|
|
{
|
|
materialOutput.SetTexture(materialProperty, sdfRenderTexture);
|
|
}
|
|
}
|
|
|
|
if (vfxOutput)
|
|
{
|
|
if (!vfxOutput.HasTexture(vfxProperty))
|
|
{
|
|
Debug.LogError(string.Format("Vfx Output doesn't have property {0}", vfxProperty));
|
|
}
|
|
else
|
|
{
|
|
vfxOutput.SetTexture(vfxProperty, sdfRenderTexture);
|
|
}
|
|
}
|
|
|
|
if (debugTexture != null)
|
|
{
|
|
if (materialOutput)
|
|
{
|
|
if (!materialOutput.HasProperty(materialProperty))
|
|
{
|
|
Debug.LogError(string.Format("Material output doesn't have property {0}", materialProperty));
|
|
}
|
|
else
|
|
{
|
|
materialOutput.SetTexture(materialProperty, debugTexture);
|
|
}
|
|
}
|
|
|
|
if (vfxOutput)
|
|
{
|
|
if (!vfxOutput.HasTexture(vfxProperty))
|
|
{
|
|
Debug.LogError(string.Format("Vfx Output doesn't have property {0}", vfxProperty));
|
|
}
|
|
else
|
|
{
|
|
vfxOutput.SetTexture(vfxProperty, debugTexture);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (sdfRenderTexture != null) sdfRenderTexture.Release();
|
|
if (voxelRenderTexture != null) voxelRenderTexture.Release();
|
|
cachedBuffers[0]?.Dispose();
|
|
cachedBuffers[1]?.Dispose();
|
|
}
|
|
|
|
public void FloodFillToSDF()
|
|
{
|
|
int dispatchCubeSize = voxelRenderTexture.width;
|
|
JFAImplementation.SetInt("dispatchCubeSide", dispatchCubeSize);
|
|
|
|
JFAImplementation.SetTexture(Preprocess, "Voxels", voxelRenderTexture);
|
|
JFAImplementation.Dispatch(Preprocess, numGroups(voxelRenderTexture.width, 8),
|
|
numGroups(voxelRenderTexture.height, 8), numGroups(voxelRenderTexture.volumeDepth, 8));
|
|
|
|
JFAImplementation.SetTexture(JFA, "Voxels", voxelRenderTexture);
|
|
|
|
/*JFAImplementation.Dispatch(JFA, numGroups(voxels.width, 8),
|
|
numGroups(voxels.height, 8), numGroups(voxels.volumeDepth, 8)); */
|
|
|
|
for (int offset = voxelRenderTexture.width / 2; offset >= 1; offset /= 2)
|
|
{
|
|
JFAImplementation.SetInt("samplingOffset", offset);
|
|
JFAImplementation.Dispatch(JFA, numGroups(voxelRenderTexture.width, 8),
|
|
numGroups(voxelRenderTexture.height, 8), numGroups(voxelRenderTexture.volumeDepth, 8));
|
|
}
|
|
|
|
JFAImplementation.SetFloat("postProcessThickness", postProcessThickness);
|
|
JFAImplementation.SetTexture(Postprocess, "Voxels", voxelRenderTexture);
|
|
JFAImplementation.SetTexture(Postprocess, "SDF", sdfRenderTexture);
|
|
|
|
JFAImplementation.Dispatch(Postprocess, numGroups(voxelRenderTexture.width, 8),
|
|
numGroups(voxelRenderTexture.height, 8), numGroups(voxelRenderTexture.volumeDepth, 8));
|
|
}
|
|
|
|
ComputeBuffer[] cachedBuffers = new ComputeBuffer[2];
|
|
|
|
ComputeBuffer cachedComputeBuffer(int count, int stride, int cacheSlot)
|
|
{
|
|
cacheSlot = cacheSlot == 0 ? 0 : 1;
|
|
var buffer = cachedBuffers[cacheSlot];
|
|
if (buffer == null || (buffer.stride != stride || buffer.count != count))
|
|
{
|
|
if (buffer != null) buffer.Dispose();
|
|
buffer = new ComputeBuffer(count, stride);
|
|
cachedBuffers[cacheSlot] = buffer;
|
|
return buffer;
|
|
}
|
|
else
|
|
{
|
|
return buffer;
|
|
}
|
|
}
|
|
|
|
private Vector3 Div(Vector3 a, Vector3 b)
|
|
{
|
|
return new Vector3(a.x / b.x, a.y / b.y, a.z / b.z);
|
|
}
|
|
|
|
public void MeshToVoxel(int voxelResolution, Mesh mesh,
|
|
Vector3 offset, float scaleMeshBy, uint numSamplesPerTriangle)
|
|
{
|
|
var indicies = mesh.triangles;
|
|
var numIdxes = indicies.Length;
|
|
var numTris = numIdxes / 3;
|
|
var indicesBuffer = cachedComputeBuffer(numIdxes, sizeof(uint), 0);
|
|
indicesBuffer.SetData(indicies);
|
|
|
|
var vertexBuffer = cachedComputeBuffer(mesh.vertexCount, sizeof(float) * 3, 1);
|
|
var verts = mesh.vertices;
|
|
if (skinnedMeshRenderer != null)
|
|
{
|
|
var smr = skinnedMeshRenderer;
|
|
for (int i = 0; i < verts.Length; i++)
|
|
{
|
|
verts[i] = Div(verts[i], smr.transform.lossyScale) - smr.rootBone.localPosition;
|
|
}
|
|
}
|
|
vertexBuffer.SetData(verts);
|
|
|
|
MtVImplementation.SetBuffer(MtV, "IndexBuffer", indicesBuffer);
|
|
MtVImplementation.SetBuffer(MtV, "VertexBuffer", vertexBuffer);
|
|
MtVImplementation.SetInt("tris", numTris);
|
|
MtVImplementation.SetFloats("offset", offset.x, offset.y, offset.z);
|
|
MtVImplementation.SetInt("numSamples", (int)numSamplesPerTriangle);
|
|
MtVImplementation.SetFloat("scale", scaleMeshBy);
|
|
MtVImplementation.SetInt("voxelSide", (int)voxelResolution);
|
|
|
|
if (sdfRenderTexture == null)
|
|
{
|
|
sdfRenderTexture = new RenderTexture(voxelResolution, voxelResolution,
|
|
0, RenderTextureFormat.RFloat);
|
|
sdfRenderTexture.dimension = UnityEngine.Rendering.TextureDimension.Tex3D;
|
|
sdfRenderTexture.enableRandomWrite = true;
|
|
sdfRenderTexture.useMipMap = false;
|
|
sdfRenderTexture.volumeDepth = voxelResolution;
|
|
sdfRenderTexture.Create();
|
|
|
|
voxelRenderTexture = new RenderTexture(voxelResolution, voxelResolution,
|
|
0, RenderTextureFormat.ARGBFloat);
|
|
voxelRenderTexture.dimension = UnityEngine.Rendering.TextureDimension.Tex3D;
|
|
voxelRenderTexture.enableRandomWrite = true;
|
|
voxelRenderTexture.useMipMap = false;
|
|
voxelRenderTexture.volumeDepth = voxelResolution;
|
|
voxelRenderTexture.Create();
|
|
|
|
}
|
|
|
|
|
|
MtVImplementation.SetBuffer(Zero, "IndexBuffer", indicesBuffer);
|
|
MtVImplementation.SetBuffer(Zero, "VertexBuffer", vertexBuffer);
|
|
MtVImplementation.SetTexture(Zero, "Voxels", voxelRenderTexture);
|
|
MtVImplementation.Dispatch(Zero, numGroups(voxelResolution, 8),
|
|
numGroups(voxelResolution, 8), numGroups(voxelResolution, 8));
|
|
|
|
MtVImplementation.SetTexture(MtV, "Voxels", voxelRenderTexture);
|
|
MtVImplementation.Dispatch(MtV, numGroups(numTris, 512), 1, 1);
|
|
}
|
|
|
|
RenderTexture MakeDebugTexture()
|
|
{
|
|
var debugTex = new RenderTexture(64, 64, 0, RenderTextureFormat.RFloat);
|
|
debugTex.dimension = UnityEngine.Rendering.TextureDimension.Tex3D;
|
|
debugTex.enableRandomWrite = true;
|
|
debugTex.useMipMap = false;
|
|
debugTex.volumeDepth = 64;
|
|
debugTex.Create();
|
|
int dispatchCubeSize = debugTex.width;
|
|
JFAImplementation.SetInt("dispatchCubeSide", dispatchCubeSize);
|
|
JFAImplementation.SetTexture(DebugSphere, "Debug", debugTex);
|
|
JFAImplementation.Dispatch(DebugSphere, numGroups(debugTex.width, 8),
|
|
numGroups(debugTex.height, 8), numGroups(debugTex.volumeDepth, 8));
|
|
return debugTex;
|
|
}
|
|
|
|
// number of groups for a dispatch with totalThreads and groups of size
|
|
// numThreadsForDim
|
|
int numGroups(int totalThreads, int groupSize)
|
|
{
|
|
return (totalThreads + (groupSize - 1)) / groupSize;
|
|
}
|
|
}
|
|
}
|