278 lines
11 KiB
C#
278 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace SDFr
|
|
{
|
|
public enum Visualisation { Normal, IntensitySteps, HeatmapSteps, Distance }
|
|
|
|
[ExecuteInEditMode] //required for previewing
|
|
public class SDFBaker : AVolumeBaker<SDFVolume,SDFData>
|
|
{
|
|
[SerializeField] private int raySamples = 256;
|
|
[SerializeField] private int jitterSeed = 555;
|
|
[SerializeField] private float jitterScale = 0.75f;
|
|
[SerializeField] private bool sdfFlip; //invert sign of preview
|
|
[SerializeField] private float previewEpsilon = 0.003f;
|
|
[SerializeField] private float previewNormalDelta = 0.02f;
|
|
[SerializeField] private Visualisation previewMode = Visualisation.Normal;
|
|
|
|
|
|
[SerializeField] private SDFData sdfData;
|
|
[SerializeField] private Texture3D debugTex3D; //for viewing existing texture3D not baked with SDFr
|
|
|
|
public ComputeShader volumeComputeMethodsShader;
|
|
|
|
public override int MaxDimension => 256;
|
|
|
|
public bool CanLogDistances => (sdfData != null && sdfData.sdfTexture != null) &&
|
|
sdfData.sdfTexture.width * sdfData.sdfTexture.height * sdfData.sdfTexture.depth <= 512;
|
|
|
|
public void LogDistances()
|
|
{
|
|
// No debugging if the volume is > 8^3 as thats more than 512 entries!
|
|
if ( sdfData.sdfTexture.width * sdfData.sdfTexture.height * sdfData.sdfTexture.depth > 512 ) return;
|
|
|
|
float[] data = VolumeComputeMethods.ExtractVolumeFloatData( sdfData.sdfTexture, volumeComputeMethodsShader);
|
|
Vector3Int dim = new Vector3Int( sdfData.sdfTexture.width, sdfData.sdfTexture.height, sdfData.sdfTexture.depth);
|
|
string log = $"{this.name} Dimension: {dim} MaxLength: {sdfData.maxDistance} Mag: {bounds.size.magnitude}\n";
|
|
|
|
for ( int z = 0; z < sdfData.sdfTexture.depth; z++ )
|
|
{
|
|
for ( int y = 0; y < sdfData.sdfTexture.height; y++ )
|
|
{
|
|
for ( int x = 0; x < sdfData.sdfTexture.width; x++ )
|
|
{
|
|
log += string.Format( "{0:F6}, ", data[ z * dim.y * dim.x + y * dim.x + x ] * bounds.size.magnitude );
|
|
}
|
|
log += "\n";
|
|
}
|
|
log += "\n";
|
|
}
|
|
|
|
Debug.Log(log);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
private const string _sdfPreviewShaderName = "XRA/SDFr";
|
|
private static Shader _shader; //TODO better way
|
|
|
|
public override AVolumePreview<SDFData> CreatePreview()
|
|
{
|
|
if (_shader == null)
|
|
{
|
|
_shader = Shader.Find(_sdfPreviewShaderName);
|
|
}
|
|
|
|
AVolumePreview<SDFData> sdf = new SDFPreview(sdfData, _shader, transform);
|
|
if (debugTex3D != null)
|
|
{
|
|
(sdf as SDFPreview).debugTex3D = debugTex3D;
|
|
}
|
|
|
|
return sdf;
|
|
}
|
|
|
|
/// <summary>
|
|
/// renders a procedural quad
|
|
/// TODO fit the bounds of the SDF Volume
|
|
/// </summary>
|
|
private void OnRenderObject()
|
|
{
|
|
if (!IsPreviewing) return;
|
|
|
|
//try to get active camera...
|
|
Camera cam = Camera.main;
|
|
|
|
// lastActiveSceneView
|
|
if (SceneView.currentDrawingSceneView != null) cam = SceneView.currentDrawingSceneView.camera;
|
|
|
|
SDFPreview preview = _aPreview as SDFPreview;
|
|
|
|
preview?.Draw(cam,sdfFlip,previewEpsilon,previewNormalDelta);
|
|
}
|
|
|
|
public override void Bake()
|
|
{
|
|
if (bakedRenderers == null)
|
|
{
|
|
bakedRenderers = new List<Renderer>();
|
|
}
|
|
bakedRenderers.Clear();
|
|
|
|
AVolumeSettings settings = new AVolumeSettings(bounds, dimensions);
|
|
|
|
//first check if any objects are parented to this object
|
|
//if anything is found, try to use renderers from those instead of volume overlap
|
|
if (!GetChildRenderersAndEncapsulate( ref settings, ref bakedRenderers, transform))
|
|
{
|
|
//otherwise try to get renderers intersecting the volume
|
|
//get mesh renderers within volume
|
|
if (!GetMeshRenderersIntersectingVolume( settings, transform, ref bakedRenderers))
|
|
{
|
|
//TODO display error?
|
|
return;
|
|
}
|
|
}
|
|
|
|
SDFVolume sdfVolume = AVolume<SDFVolume>.CreateVolume(transform, settings);
|
|
|
|
sdfVolume.Bake( raySamples, bakedRenderers, BakeComplete );
|
|
|
|
sdfVolume.Dispose();
|
|
}
|
|
|
|
//TODO improve asset saving
|
|
private void BakeComplete( SDFVolume sdfVolume, float[] distances, float maxDistance, object passthrough )
|
|
{
|
|
//update the bounds since they may have been adjusted during bake
|
|
bounds = sdfVolume.Settings.BoundsLocal;
|
|
|
|
string path = "";
|
|
if (sdfData != null)
|
|
{
|
|
//use path of existing sdfData
|
|
path = AssetDatabase.GetAssetPath(sdfData);
|
|
|
|
//check if asset at path
|
|
Object obj = AssetDatabase.LoadAssetAtPath<SDFData>(path);
|
|
if (obj != null)
|
|
{
|
|
if (((SDFData) obj).sdfTexture != null)
|
|
{
|
|
//destroy old texture
|
|
Object.DestroyImmediate(((SDFData) obj).sdfTexture,true);
|
|
}
|
|
//destroy old asset
|
|
//TODO this will break references...
|
|
Object.DestroyImmediate(obj,true);
|
|
}
|
|
}
|
|
|
|
// If not exisiting asset get path to save sdfData to.
|
|
if ( string.IsNullOrEmpty( path ) )
|
|
{
|
|
string suggestedName = "sdfData_" + this.name;
|
|
|
|
path = EditorUtility.SaveFilePanelInProject( "Save As...", suggestedName, "asset", "Save the SDF Data" );
|
|
|
|
if ( string.IsNullOrEmpty( path ) )
|
|
{
|
|
if ( EditorUtility.DisplayDialog( "Error", "Path was invalid, retry?", "ok", "cancel" ) )
|
|
{
|
|
path = EditorUtility.SaveFilePanelInProject( "Save As...", suggestedName, "asset", "Save the SDF Data" );
|
|
}
|
|
|
|
if ( string.IsNullOrEmpty( path ) ) return;
|
|
}
|
|
}
|
|
|
|
|
|
//create new SDFData
|
|
sdfData = ScriptableObject.CreateInstance<SDFData>();
|
|
sdfData.bounds = sdfVolume.Settings.BoundsLocal;
|
|
sdfData.voxelSize = sdfVolume.Settings.VoxelSize;
|
|
sdfData.dimensions = sdfVolume.Settings.Dimensions;
|
|
|
|
float minAxis = Mathf.Min( sdfData.bounds.size.x, Mathf.Min( sdfData.bounds.size.y, sdfData.bounds.size.z ) );
|
|
sdfData.nonUniformScale = new Vector3( sdfData.bounds.size.x/minAxis, sdfData.bounds.size.y/minAxis, sdfData.bounds.size.z/minAxis );
|
|
|
|
bool mipmaps = true;
|
|
|
|
// Create Texture3D and set name to filename of sdfData
|
|
Texture3D newTex = new Texture3D( sdfData.dimensions.x, sdfData.dimensions.y, sdfData.dimensions.z, TextureFormat.RHalf, mipmaps);
|
|
newTex.name = System.IO.Path.GetFileNameWithoutExtension(path);
|
|
|
|
//TODO improve
|
|
Color[] colorBuffer = new Color[distances.Length];
|
|
for (int i = 0; i < distances.Length; i++)
|
|
{
|
|
//NOTE for compatibility with Visual Effect Graph,
|
|
//the distance must be negative inside surfaces.
|
|
//normalize the distance for better support of scaling bounds
|
|
//Max Distance is always the Magnitude of the baked bound size
|
|
float normalizedDistance = distances[i] / maxDistance;
|
|
colorBuffer[i] = new Color(normalizedDistance,0f,0f,0f);
|
|
}
|
|
newTex.SetPixels(colorBuffer);
|
|
newTex.Apply();
|
|
|
|
|
|
sdfData.sdfTexture = newTex;
|
|
sdfData.maxDistance = maxDistance;
|
|
|
|
EditorUtility.SetDirty(sdfData);
|
|
|
|
//create it
|
|
AssetDatabase.CreateAsset(sdfData, path);
|
|
AssetDatabase.AddObjectToAsset(newTex,sdfData);
|
|
|
|
AssetDatabase.SaveAssets();
|
|
AssetDatabase.Refresh();
|
|
|
|
if (IsPreviewing)
|
|
TogglePreview();
|
|
if (!IsPreviewing )
|
|
TogglePreview();
|
|
}
|
|
|
|
public void BakeHalfSizeTest()
|
|
{
|
|
SDFData sdfmip = ScriptableObject.CreateInstance<SDFData>();
|
|
sdfmip.bounds = sdfData.bounds;
|
|
sdfmip.voxelSize = sdfData.voxelSize * 2;
|
|
sdfmip.dimensions = new Vector3Int( sdfData.dimensions.x/2, sdfData.dimensions.y/2, sdfData.dimensions.z/2 );
|
|
|
|
float minAxis = Mathf.Min( sdfmip.bounds.size.x, Mathf.Min( sdfmip.bounds.size.y, sdfmip.bounds.size.z ) );
|
|
sdfmip.nonUniformScale = new Vector3( sdfmip.bounds.size.x/minAxis, sdfmip.bounds.size.y/minAxis, sdfmip.bounds.size.z/minAxis );
|
|
|
|
|
|
// Save sdf
|
|
string suggestedName = "sdfData_" + this.name + "_HalfBake";
|
|
string path = EditorUtility.SaveFilePanelInProject( "Save As...", suggestedName, "asset", "Save the SDF Data" );
|
|
|
|
if ( string.IsNullOrEmpty( path ) )
|
|
{
|
|
if ( EditorUtility.DisplayDialog( "Error", "Path was invalid, retry?", "ok", "cancel" ) )
|
|
path = EditorUtility.SaveFilePanelInProject( "Save As...", suggestedName, "asset", "Save the SDF Data" );
|
|
|
|
if ( string.IsNullOrEmpty( path ) ) return;
|
|
}
|
|
|
|
// GetDistances from srcVolume
|
|
float[] distances = VolumeComputeMethods.ExtractVolumeFloatData( sdfData.sdfTexture, volumeComputeMethodsShader);
|
|
Vector3Int dim = new Vector3Int( sdfData.sdfTexture.width, sdfData.sdfTexture.height, sdfData.sdfTexture.depth);
|
|
|
|
// Get half size data
|
|
Color[] colorBuffer = new Color[distances.Length/2/2/2];
|
|
|
|
int index = 0;
|
|
|
|
for ( int z = 0; z < sdfData.sdfTexture.depth; z+=2 )
|
|
for ( int y = 0; y < sdfData.sdfTexture.height; y+=2 )
|
|
for ( int x = 0; x < sdfData.sdfTexture.width; x+=2 )
|
|
colorBuffer[index++] = new Color( distances[ z * dim.y * dim.x + y * dim.x + x ] ,0f, 0f, 0f);
|
|
|
|
|
|
// Create Texture3D and set name to filename of sdfData
|
|
Texture3D newTex = new Texture3D( sdfmip.dimensions.x, sdfmip.dimensions.y, sdfmip.dimensions.z, TextureFormat.RHalf, false);
|
|
newTex.name = System.IO.Path.GetFileNameWithoutExtension(path);
|
|
newTex.SetPixels(colorBuffer);
|
|
newTex.Apply();
|
|
|
|
sdfmip.sdfTexture = newTex;
|
|
sdfmip.maxDistance = sdfData.maxDistance; // TODO - IS this still constant?
|
|
|
|
EditorUtility.SetDirty(sdfmip);
|
|
|
|
//create it
|
|
AssetDatabase.CreateAsset(sdfmip, path);
|
|
AssetDatabase.AddObjectToAsset(newTex,sdfmip);
|
|
|
|
AssetDatabase.SaveAssets();
|
|
AssetDatabase.Refresh();
|
|
}
|
|
#endif
|
|
}
|
|
} |