You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

385 lines
19 KiB

using System.Collections.Generic;
using UnityEditor;
using Cell = UnityEngine.Rendering.ProbeReferenceVolume.Cell;
namespace UnityEngine.Rendering
{
partial class AdaptiveProbeVolumes
{
static ComputeShader dilationShader;
static int dilationKernel = -1;
static void InitDilationShaders()
{
if (dilationShader == null)
{
dilationShader = GraphicsSettings.GetRenderPipelineSettings<ProbeVolumeBakingResources>().dilationShader;
dilationKernel = dilationShader.FindKernel("DilateCell");
}
}
static void GetProbeAndChunkIndex(int globalProbeIndex, out int chunkIndex, out int chunkProbeIndex)
{
var chunkSizeInProbeCount = ProbeBrickPool.GetChunkSizeInProbeCount();
chunkIndex = globalProbeIndex / chunkSizeInProbeCount;
chunkProbeIndex = globalProbeIndex - chunkIndex * chunkSizeInProbeCount;
}
[GenerateHLSL(needAccessors = false)]
struct DilatedProbe
{
public Vector3 L0;
public Vector3 L1_0;
public Vector3 L1_1;
public Vector3 L1_2;
public Vector3 L2_0;
public Vector3 L2_1;
public Vector3 L2_2;
public Vector3 L2_3;
public Vector3 L2_4;
public Vector4 SO_L0L1;
public Vector3 SO_Direction;
void ToSphericalHarmonicsL2(ref SphericalHarmonicsL2 sh)
{
SphericalHarmonicsL2Utils.SetCoefficient(ref sh, 0, L0);
SphericalHarmonicsL2Utils.SetCoefficient(ref sh, 1, L1_0);
SphericalHarmonicsL2Utils.SetCoefficient(ref sh, 2, L1_1);
SphericalHarmonicsL2Utils.SetCoefficient(ref sh, 3, L1_2);
SphericalHarmonicsL2Utils.SetCoefficient(ref sh, 4, L2_0);
SphericalHarmonicsL2Utils.SetCoefficient(ref sh, 5, L2_1);
SphericalHarmonicsL2Utils.SetCoefficient(ref sh, 6, L2_2);
SphericalHarmonicsL2Utils.SetCoefficient(ref sh, 7, L2_3);
SphericalHarmonicsL2Utils.SetCoefficient(ref sh, 8, L2_4);
}
void FromSphericalHarmonicsL2(ref SphericalHarmonicsL2 sh)
{
L0 = new Vector3(sh[0, 0], sh[1, 0], sh[2, 0]);
L1_0 = new Vector3(sh[0, 1], sh[1, 1], sh[2, 1]);
L1_1 = new Vector3(sh[0, 2], sh[1, 2], sh[2, 2]);
L1_2 = new Vector3(sh[0, 3], sh[1, 3], sh[2, 3]);
L2_0 = new Vector3(sh[0, 4], sh[1, 4], sh[2, 4]);
L2_1 = new Vector3(sh[0, 5], sh[1, 5], sh[2, 5]);
L2_2 = new Vector3(sh[0, 6], sh[1, 6], sh[2, 6]);
L2_3 = new Vector3(sh[0, 7], sh[1, 7], sh[2, 7]);
L2_4 = new Vector3(sh[0, 8], sh[1, 8], sh[2, 8]);
}
internal void FromSphericalHarmonicsShaderConstants(ProbeReferenceVolume.Cell cell, int probeIdx)
{
var sh = new SphericalHarmonicsL2();
GetProbeAndChunkIndex(probeIdx, out var chunkIndex, out var index);
var cellChunkData = GetCellChunkData(cell.data, chunkIndex);
ReadFromShaderCoeffsL0L1(ref sh, cellChunkData.shL0L1RxData, cellChunkData.shL1GL1RyData, cellChunkData.shL1BL1RzData, index * 4);
ReadFromShaderCoeffsL2(ref sh, cellChunkData.shL2Data_0, cellChunkData.shL2Data_1, cellChunkData.shL2Data_2, cellChunkData.shL2Data_3, index * 4);
FromSphericalHarmonicsL2(ref sh);
if (cellChunkData.skyOcclusionDataL0L1.Length != 0)
ReadFromShaderCoeffsSkyOcclusion(ref SO_L0L1, cellChunkData.skyOcclusionDataL0L1, index);
if (cellChunkData.skyShadingDirectionIndices.Length != 0)
{
int id = cellChunkData.skyShadingDirectionIndices[index];
var directions = DynamicSkyPrecomputedDirections.GetPrecomputedDirections();
SO_Direction = id == 255 ? Vector3.zero : directions[id];
}
}
internal void ToSphericalHarmonicsShaderConstants(ProbeReferenceVolume.Cell cell, int probeIdx)
{
var sh = new SphericalHarmonicsL2();
ToSphericalHarmonicsL2(ref sh);
GetProbeAndChunkIndex(probeIdx, out var chunkIndex, out var index);
var cellChunkData = GetCellChunkData(cell.data, chunkIndex);
WriteToShaderCoeffsL0L1(sh, cellChunkData.shL0L1RxData, cellChunkData.shL1GL1RyData, cellChunkData.shL1BL1RzData, index * 4);
WriteToShaderCoeffsL2(sh, cellChunkData.shL2Data_0, cellChunkData.shL2Data_1, cellChunkData.shL2Data_2, cellChunkData.shL2Data_3, index * 4);
if (cellChunkData.skyOcclusionDataL0L1.Length != 0)
WriteToShaderSkyOcclusion(SO_L0L1, cellChunkData.skyOcclusionDataL0L1, index * 4);
if (cellChunkData.skyShadingDirectionIndices.Length != 0)
cellChunkData.skyShadingDirectionIndices[index] = (byte)SkyOcclusionBaker.EncodeSkyShadingDirection(SO_Direction);
}
}
struct DataForDilation
{
public ComputeBuffer positionBuffer { get; }
public ComputeBuffer outputProbes { get; }
public ComputeBuffer needDilatingBuffer { get; }
DilatedProbe[] dilatedProbes;
ProbeReferenceVolume.Cell cell;
public DataForDilation(ProbeReferenceVolume.Cell cell, float defaultThreshold)
{
this.cell = cell;
var cellData = cell.data;
var cellDesc = cell.desc;
int probeCount = cellData.probePositions.Length;
positionBuffer = new ComputeBuffer(probeCount, System.Runtime.InteropServices.Marshal.SizeOf<Vector3>());
outputProbes = new ComputeBuffer(probeCount, System.Runtime.InteropServices.Marshal.SizeOf<DilatedProbe>());
needDilatingBuffer = new ComputeBuffer(probeCount, sizeof(int));
// Init with pre-dilated SH so we don't need to re-fill from sampled data from texture (that might be less precise).
dilatedProbes = new DilatedProbe[probeCount];
int[] needDilating = new int[probeCount];
for (int i = 0; i < probeCount; ++i)
{
dilatedProbes[i].FromSphericalHarmonicsShaderConstants(cell, i);
needDilating[i] = m_BakingBatch.customDilationThresh.ContainsKey((cellDesc.index, i)) ?
(cellData.validity[i] > m_BakingBatch.customDilationThresh[(cellDesc.index, i)] ? 1 : 0) : (cellData.validity[i] > defaultThreshold ? 1 : 0);
}
outputProbes.SetData(dilatedProbes);
positionBuffer.SetData(cellData.probePositions);
needDilatingBuffer.SetData(needDilating);
}
public void ExtractDilatedProbes()
{
outputProbes.GetData(dilatedProbes);
int probeCount = cell.data.probePositions.Length;
for (int i = 0; i < probeCount; ++i)
{
dilatedProbes[i].ToSphericalHarmonicsShaderConstants(cell, i);
}
}
public void Dispose()
{
positionBuffer.Dispose();
outputProbes.Dispose();
needDilatingBuffer.Dispose();
}
}
static readonly int _ProbePositionsBuffer = Shader.PropertyToID("_ProbePositionsBuffer");
static readonly int _NeedDilating = Shader.PropertyToID("_NeedDilating");
static readonly int _DilationParameters = Shader.PropertyToID("_DilationParameters");
static readonly int _DilationParameters2 = Shader.PropertyToID("_DilationParameters2");
static readonly int _OutputProbes = Shader.PropertyToID("_OutputProbes");
// Can definitively be optimized later on.
// Also note that all the bookkeeping of all the reference volumes will likely need to change when we move to
// proper UX.
internal static void PerformDilation()
{
var prv = ProbeReferenceVolume.instance;
var perSceneDataList = prv.perSceneDataList;
if (perSceneDataList.Count == 0) return;
SetBakingContext(perSceneDataList);
List<Cell> tempLoadedCells = new List<Cell>();
if (m_BakingSet.hasDilation)
{
var dilationSettings = m_BakingSet.settings.dilationSettings;
// Make sure all assets are loaded.
prv.PerformPendingOperations();
// TODO: This loop is very naive, can be optimized, but let's first verify if we indeed want this or not.
for (int iterations = 0; iterations < dilationSettings.dilationIterations; ++iterations)
{
// Try to load all available cells to the GPU. Might not succeed depending on the memory budget.
prv.LoadAllCells();
// Dilate all cells
List<Cell> dilatedCells = new List<Cell>(prv.cells.Values.Count);
bool everythingLoaded = !prv.hasUnloadedCells;
if (everythingLoaded)
{
foreach (var cell in prv.cells.Values)
{
if (m_CellsToDilate.ContainsKey(cell.desc.index))
{
PerformDilation(cell, m_BakingSet);
dilatedCells.Add(cell);
}
}
}
else
{
// When everything does not fit in memory, we are going to dilate one cell at a time.
// To do so, we load the cell and all its neighbours and then dilate.
// This is an inefficient use of memory but for now most of the time is spent in reading back the result anyway so it does not introduce any performance regression.
// Free All memory to make room for each cell and its neighbors for dilation.
prv.UnloadAllCells();
foreach (var cell in prv.cells.Values)
{
if (!m_CellsToDilate.ContainsKey(cell.desc.index))
continue;
var cellPos = cell.desc.position;
// Load the cell and all its neighbors before doing dilation.
for (int x = -1; x <= 1; ++x)
{
for (int y = -1; y <= 1; ++y)
{
for (int z = -1; z <= 1; ++z)
{
Vector3Int pos = cellPos + new Vector3Int(x, y, z);
if (m_CellPosToIndex.TryGetValue(pos, out var cellToLoadIndex))
{
if (prv.cells.TryGetValue(cellToLoadIndex, out var cellToLoad))
{
if (prv.LoadCell(cellToLoad))
{
tempLoadedCells.Add(cellToLoad);
}
else
Debug.LogError($"Not enough memory to perform dilation for cell {cell.desc.index}");
}
}
}
}
}
PerformDilation(cell, m_BakingSet);
dilatedCells.Add(cell);
// Free memory again.
foreach (var cellToUnload in tempLoadedCells)
prv.UnloadCell(cellToUnload);
tempLoadedCells.Clear();
}
}
// Now write back the assets.
WriteDilatedCells(dilatedCells);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
// Reload data
foreach (var sceneData in perSceneDataList)
{
sceneData.QueueSceneRemoval();
sceneData.QueueSceneLoading();
}
prv.PerformPendingOperations();
}
}
}
static void PerformDilation(ProbeReferenceVolume.Cell cell, ProbeVolumeBakingSet bakingSet)
{
InitDilationShaders();
ProbeDilationSettings settings = bakingSet.settings.dilationSettings;
DataForDilation data = new DataForDilation(cell, settings.dilationValidityThreshold);
var cmd = CommandBufferPool.Get("Cell Dilation");
cmd.SetComputeBufferParam(dilationShader, dilationKernel, _ProbePositionsBuffer, data.positionBuffer);
cmd.SetComputeBufferParam(dilationShader, dilationKernel, _OutputProbes, data.outputProbes);
cmd.SetComputeBufferParam(dilationShader, dilationKernel, _NeedDilating, data.needDilatingBuffer);
// There's an upper limit on the number of bricks supported inside a single cell
int probeCount = Mathf.Min(cell.data.probePositions.Length, ushort.MaxValue * ProbeBrickPool.kBrickProbeCountTotal);
cmd.SetComputeVectorParam(dilationShader, _DilationParameters, new Vector4(probeCount, settings.dilationValidityThreshold, settings.dilationDistance, ProbeReferenceVolume.instance.MinBrickSize()));
cmd.SetComputeVectorParam(dilationShader, _DilationParameters2, new Vector4(settings.squaredDistWeighting ? 1 : 0, bakingSet.skyOcclusion ? 1 : 0, bakingSet.skyOcclusionShadingDirection ? 1 : 0, 0));
var refVolume = ProbeReferenceVolume.instance;
ProbeReferenceVolume.RuntimeResources rr = refVolume.GetRuntimeResources();
bool validResources = rr.index != null && rr.L0_L1rx != null && rr.L1_G_ry != null && rr.L1_B_rz != null;
if (validResources)
{
cmd.SetGlobalBuffer(ProbeReferenceVolume.ShaderIDs._APVResIndex, rr.index);
cmd.SetGlobalBuffer(ProbeReferenceVolume.ShaderIDs._APVResCellIndices, rr.cellIndices);
cmd.SetGlobalTexture(ProbeReferenceVolume.ShaderIDs._APVResL0_L1Rx, rr.L0_L1rx);
cmd.SetGlobalTexture(ProbeReferenceVolume.ShaderIDs._APVResL1G_L1Ry, rr.L1_G_ry);
cmd.SetGlobalTexture(ProbeReferenceVolume.ShaderIDs._APVResL1B_L1Rz, rr.L1_B_rz);
cmd.SetGlobalTexture(ProbeReferenceVolume.ShaderIDs._APVResL2_0, rr.L2_0);
cmd.SetGlobalTexture(ProbeReferenceVolume.ShaderIDs._APVResL2_1, rr.L2_1);
cmd.SetGlobalTexture(ProbeReferenceVolume.ShaderIDs._APVResL2_2, rr.L2_2);
cmd.SetGlobalTexture(ProbeReferenceVolume.ShaderIDs._APVResL2_3, rr.L2_3);
cmd.SetComputeTextureParam(dilationShader, dilationKernel, ProbeReferenceVolume.ShaderIDs._SkyOcclusionTexL0L1, rr.SkyOcclusionL0L1 ?? (RenderTargetIdentifier)CoreUtils.blackVolumeTexture);
cmd.SetComputeTextureParam(dilationShader, dilationKernel, ProbeReferenceVolume.ShaderIDs._SkyShadingDirectionIndicesTex, rr.SkyShadingDirectionIndices ?? (RenderTargetIdentifier)CoreUtils.blackVolumeTexture);
cmd.SetComputeBufferParam(dilationShader, dilationKernel, ProbeReferenceVolume.ShaderIDs._SkyPrecomputedDirections, rr.SkyPrecomputedDirections);
}
ProbeVolumeShadingParameters parameters;
parameters.normalBias = 0;
parameters.viewBias = 0;
parameters.scaleBiasByMinDistanceBetweenProbes = false;
parameters.samplingNoise = 0;
parameters.weight = 1f;
parameters.leakReductionMode = APVLeakReductionMode.None;
parameters.minValidNormalWeight = 0.0f;
parameters.frameIndexForNoise = 0;
parameters.reflNormalizationLowerClamp = 0.1f;
parameters.reflNormalizationUpperClamp = 1.0f;
parameters.skyOcclusionIntensity = 0.0f;
parameters.skyOcclusionShadingDirection = false;
parameters.regionCount = 1;
parameters.regionLayerMasks = 1;
ProbeReferenceVolume.instance.UpdateConstantBuffer(cmd, parameters);
int groupCount = (probeCount + 63) / 64;
cmd.DispatchCompute(dilationShader, dilationKernel, groupCount, 1, 1);
cmd.WaitAllAsyncReadbackRequests();
Graphics.ExecuteCommandBuffer(cmd);
data.ExtractDilatedProbes();
data.Dispose();
}
// NOTE: This is somewhat hacky and is going to likely be slow (or at least slower than it could).
// It is only a first iteration of the concept that won't be as impactful on memory as other options.
internal static void RevertDilation()
{
if (m_BakingSet == null)
{
if (ProbeReferenceVolume.instance.perSceneDataList.Count == 0) return;
SetBakingContext(ProbeReferenceVolume.instance.perSceneDataList);
}
var dilationSettings = m_BakingSet.settings.dilationSettings;
var blackProbe = new SphericalHarmonicsL2();
int chunkSizeInProbes = ProbeBrickPool.GetChunkSizeInProbeCount();
foreach (var cell in ProbeReferenceVolume.instance.cells.Values)
{
for (int i = 0; i < cell.data.validity.Length; ++i)
{
if (dilationSettings.enableDilation && dilationSettings.dilationDistance > 0.0f && cell.data.validity[i] > dilationSettings.dilationValidityThreshold)
{
GetProbeAndChunkIndex(i, out var chunkIndex, out var index);
var cellChunkData = GetCellChunkData(cell.data, chunkIndex);
WriteToShaderCoeffsL0L1(blackProbe, cellChunkData.shL0L1RxData, cellChunkData.shL1GL1RyData, cellChunkData.shL1BL1RzData, index * 4);
WriteToShaderCoeffsL2(blackProbe, cellChunkData.shL2Data_0, cellChunkData.shL2Data_1, cellChunkData.shL2Data_2, cellChunkData.shL2Data_3, index * 4);
}
}
}
}
}
}