2021-09-09 20:42:29 -04:00

276 lines
10 KiB
C#

using System;
using UnityEngine.Serialization;
using UnityEngine.SceneManagement;
namespace UnityEngine.Rendering.HighDefinition
{
internal class ProbeVolumeAsset : ScriptableObject
{
[Serializable]
internal enum AssetVersion
{
First,
AddProbeVolumesAtlasEncodingModes,
// Add new version here and they will automatically be the Current one
Max,
Current = Max - 1
}
[SerializeField] protected internal int m_Version = (int)AssetVersion.Current;
[SerializeField] internal int Version { get => m_Version; }
[SerializeField] internal int instanceID;
// dataSH, dataValidity, and dataOctahedralDepth is from AssetVersion.First. In versions AddProbeVolumesAtlasEncodingModes or greater, this should be null.
[SerializeField] internal SphericalHarmonicsL1[] dataSH = null;
[SerializeField] internal float[] dataValidity = null;
[SerializeField] internal float[] dataOctahedralDepth = null;
[SerializeField] internal ProbeVolumePayload payload = ProbeVolumePayload.zero;
[SerializeField] internal int resolutionX;
[SerializeField] internal int resolutionY;
[SerializeField] internal int resolutionZ;
[SerializeField] internal float backfaceTolerance;
[SerializeField] internal int dilationIterations;
internal bool IsDataAssigned()
{
return payload.dataSHL01 != null;
}
#if UNITY_EDITOR
// Debug only: Uncomment out if you want to manually create a probe volume asset and type in data into the inspector.
// This is not a user facing workflow we are supporting.
// [UnityEditor.MenuItem("Assets/Create/Experimental/Probe Volume", false, 204)]
// protected static void CreateAssetFromMenu()
// {
// CreateAsset();
// }
internal static string GetFileName(int id = -1)
{
string assetName = "ProbeVolumeData";
String assetFileName;
String assetPath;
if (id == -1)
{
assetPath = "Assets";
assetFileName = UnityEditor.AssetDatabase.GenerateUniqueAssetPath(assetName + ".asset");
}
else
{
String scenePath = SceneManagement.SceneManager.GetActiveScene().path;
String sceneDir = System.IO.Path.GetDirectoryName(scenePath);
String sceneName = System.IO.Path.GetFileNameWithoutExtension(scenePath);
assetPath = System.IO.Path.Combine(sceneDir, sceneName);
if (!UnityEditor.AssetDatabase.IsValidFolder(assetPath))
UnityEditor.AssetDatabase.CreateFolder(sceneDir, sceneName);
assetFileName = UnityEditor.AssetDatabase.GenerateUniqueAssetPath(assetName + id + ".asset");
}
assetFileName = System.IO.Path.Combine(assetPath, assetFileName);
return assetFileName;
}
internal static ProbeVolumeAsset CreateAsset(int id = -1)
{
ProbeVolumeAsset asset = ScriptableObject.CreateInstance<ProbeVolumeAsset>();
string assetFileName = GetFileName(id);
UnityEditor.AssetDatabase.CreateAsset(asset, assetFileName);
UnityEditor.AssetDatabase.SaveAssets();
UnityEditor.AssetDatabase.Refresh();
return asset;
}
protected internal static Vector3Int[] s_Offsets = new Vector3Int[]
{
// middle slice
new Vector3Int(1, 0, 0),
new Vector3Int(1, 0, 1),
new Vector3Int(1, 0, -1),
new Vector3Int(-1, 0, 0),
new Vector3Int(-1, 0, 1),
new Vector3Int(-1, 0, -1),
new Vector3Int(0, 0, 1),
new Vector3Int(0, 0, -1),
// upper slice
new Vector3Int(0, 1, 0),
new Vector3Int(1, 1, 0),
new Vector3Int(1, 1, 1),
new Vector3Int(1, 1, -1),
new Vector3Int(-1, 1, 0),
new Vector3Int(-1, 1, 1),
new Vector3Int(-1, 1, -1),
new Vector3Int(0, 1, 1),
new Vector3Int(0, 1, -1),
// lower slice
new Vector3Int(0, -1, 0),
new Vector3Int(1, -1, 0),
new Vector3Int(1, -1, 1),
new Vector3Int(1, -1, -1),
new Vector3Int(-1, -1, 0),
new Vector3Int(-1, -1, 1),
new Vector3Int(-1, -1, -1),
new Vector3Int(0, -1, 1),
new Vector3Int(0, -1, -1),
};
protected internal int ComputeIndex1DFrom3D(Vector3Int pos)
{
return pos.x + pos.y * resolutionX + pos.z * resolutionX * resolutionY;
}
bool OverwriteInvalidProbe(ref ProbeVolumePayload payloadSrc, ref ProbeVolumePayload payloadDst, Vector3Int index3D, float backfaceTolerance)
{
int strideSHL01 = ProbeVolumePayload.GetDataSHL01Stride();
int strideSHL2 = ProbeVolumePayload.GetDataSHL2Stride();
int centerIndex = ComputeIndex1DFrom3D(index3D);
// Account for center sample accumulation weight, already assigned.
float weights = 1.0f - payloadDst.dataValidity[centerIndex];
foreach (Vector3Int offset in s_Offsets)
{
Vector3Int sampleIndex3D = index3D + offset;
if (sampleIndex3D.x < 0 || sampleIndex3D.y < 0 || sampleIndex3D.z < 0
|| sampleIndex3D.x >= resolutionX || sampleIndex3D.y >= resolutionY || sampleIndex3D.z >= resolutionZ)
{
continue;
}
int sampleIndex1D = ComputeIndex1DFrom3D(sampleIndex3D);
float sampleValidity = payloadSrc.dataValidity[sampleIndex1D];
if (sampleValidity > 0.999f)
{
// Sample will have effectively zero contribution. Early out.
continue;
}
float sampleWeight = 1.0f - sampleValidity;
weights += sampleWeight;
for (int c = 0; c < strideSHL01; ++c)
{
payloadDst.dataSHL01[centerIndex * strideSHL01 + c] += payloadSrc.dataSHL01[sampleIndex1D * strideSHL01 + c] * sampleWeight;
}
for (int c = 0; c < strideSHL2; ++c)
{
payloadDst.dataSHL2[centerIndex * strideSHL2 + c] += payloadSrc.dataSHL2[sampleIndex1D * strideSHL2 + c] * sampleWeight;
}
payloadDst.dataValidity[centerIndex] += sampleValidity * sampleWeight;
}
if (weights > 0.0f)
{
float weightsNormalization = 1.0f / weights;
for (int c = 0; c < strideSHL01; ++c)
{
payloadDst.dataSHL01[centerIndex * strideSHL01 + c] *= weightsNormalization;
}
for (int c = 0; c < strideSHL2; ++c)
{
payloadDst.dataSHL2[centerIndex * strideSHL2 + c] *= weightsNormalization;
}
payloadDst.dataValidity[centerIndex] *= weightsNormalization;
return true;
}
else
{
// Haven't managed to overwrite an invalid probe
return false;
}
}
void DilateIntoInvalidProbes(float backfaceTolerance, int dilateIterations)
{
if (dilateIterations == 0)
return;
ProbeVolumePayload payloadBackbuffer = ProbeVolumePayload.zero;
ProbeVolumePayload.Allocate(ref payloadBackbuffer, ProbeVolumePayload.GetLength(ref payload));
int i = 0;
for (; i < dilateIterations; ++i)
{
bool invalidProbesRemaining = false;
// First, copy data from source to destination to seed our center sample.
ProbeVolumePayload.Copy(ref payload, ref payloadBackbuffer);
// Foreach probe, gather neighboring probe data, weighted by validity.
// TODO: "validity" is actually stored as how occluded the surface is, so it is really inverse validity.
// We should probably rename this to avoid confusion.
for (int z = 0; z < resolutionZ; ++z)
{
for (int y = 0; y < resolutionY; ++y)
{
for (int x = 0; x < resolutionX; ++x)
{
Vector3Int index3D = new Vector3Int(x, y, z);
int index1D = ComputeIndex1DFrom3D(index3D);
float validity = payloadBackbuffer.dataValidity[index1D];
if (validity <= backfaceTolerance)
{
// "validity" aka occlusion is low enough for our theshold.
// No need to gather + filter neighbors.
continue;
}
invalidProbesRemaining |= !OverwriteInvalidProbe(ref payload, ref payloadBackbuffer, index3D, backfaceTolerance);
}
}
}
// Swap buffers
(payload, payloadBackbuffer) = (payloadBackbuffer, payload);
if (!invalidProbesRemaining)
break;
}
ProbeVolumePayload.Dispose(ref payloadBackbuffer);
}
internal void Dilate(float backfaceTolerance, int dilationIterations)
{
if (backfaceTolerance == this.backfaceTolerance && dilationIterations == this.dilationIterations)
return;
// Validity data will be overwritten during dilation as a per-probe quality heuristic.
// We want to retain original validity data for use bilateral filter on the GPU at runtime.
float[] validityBackup = new float[payload.dataValidity.Length];
Array.Copy(payload.dataValidity, validityBackup, payload.dataValidity.Length);
DilateIntoInvalidProbes(backfaceTolerance, dilationIterations);
Array.Copy(validityBackup, payload.dataValidity, validityBackup.Length);
this.backfaceTolerance = backfaceTolerance;
this.dilationIterations = dilationIterations;
}
#endif
}
}