initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,37 @@
#ifndef DECODE_SH
# define DECODE_SH
float3 DecodeSH(float l0, float3 l1)
{
// TODO: We're working on irradiance instead of radiance coefficients
// Add safety margin 2 to avoid out-of-bounds values
const float l1scale = 2.0f; // Should be: 3/(2*sqrt(3)) * 2, but rounding to 2 to issues we are observing.
return (l1 - 0.5f) * 2.0f * l1scale * l0;
}
void DecodeSH_L2(inout float3 l0, inout float4 l2_R, inout float4 l2_G, inout float4 l2_B, inout float4 l2_C)
{
// TODO: We're working on irradiance instead of radiance coefficients
// Add safety margin 2 to avoid out-of-bounds values
const float l2scale = 3.5777088f; // 4/sqrt(5) * 2
l2_R = (l2_R - 0.5f) * l2scale * l0.r;
l2_G = (l2_G - 0.5f) * l2scale * l0.g;
l2_B = (l2_B - 0.5f) * l2scale * l0.b;
l2_C = (l2_C - 0.5f) * l2scale;
l2_C.r *= l0.r;
l2_C.g *= l0.g;
l2_C.b *= l0.b;
// Account for how L2 is encoded.
l0.r -= l2_R.z;
l0.g -= l2_G.z;
l0.b -= l2_B.z;
l2_R.z *= 3.0f;
l2_G.z *= 3.0f;
l2_B.z *= 3.0f;
}
#endif // DECODE_SH

View file

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6646f462aeb8278489c25fda24fd2db8
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
preprocessorOverride: 0
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,467 @@
//#define USE_INDEX_NATIVE_ARRAY
using System;
using System.Diagnostics;
using System.Collections.Generic;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using System.Collections;
using Chunk = UnityEngine.Experimental.Rendering.ProbeBrickPool.BrickChunkAlloc;
using RegId = UnityEngine.Experimental.Rendering.ProbeReferenceVolume.RegId;
namespace UnityEngine.Experimental.Rendering
{
internal class ProbeBrickIndex
{
// a few constants
internal const int kMaxSubdivisionLevels = 7; // 3 bits
internal const int kIndexChunkSize = 243;
BitArray m_IndexChunks;
int m_IndexInChunks;
int m_NextFreeChunk;
ComputeBuffer m_PhysicalIndexBuffer;
int[] m_PhysicalIndexBufferData;
internal int estimatedVMemCost { get; private set; }
[DebuggerDisplay("Brick [{position}, {subdivisionLevel}]")]
[Serializable]
public struct Brick : IEquatable<Brick>
{
public Vector3Int position; // refspace index, indices are cell coordinates at max resolution
public int subdivisionLevel; // size as factor covered elementary cells
internal Brick(Vector3Int position, int subdivisionLevel)
{
this.position = position;
this.subdivisionLevel = subdivisionLevel;
}
public bool Equals(Brick other) => position == other.position && subdivisionLevel == other.subdivisionLevel;
}
[DebuggerDisplay("Brick [{brick.position}, {brick.subdivisionLevel}], {flattenedIdx}")]
struct ReservedBrick
{
public Brick brick;
public int flattenedIdx;
}
struct VoxelMeta
{
public RegId id;
public List<ushort> brickIndices;
}
struct BrickMeta
{
public HashSet<Vector3Int> voxels;
public List<ReservedBrick> bricks;
}
Vector3Int m_CenterRS; // the anchor in ref space, around which the index is defined. [IMPORTANT NOTE! For now we always have it at 0, so is not passed to the shader, but is kept here until development is active in case we find it useful]
Dictionary<Vector3Int, List<VoxelMeta>> m_VoxelToBricks;
Dictionary<RegId, BrickMeta> m_BricksToVoxels;
int GetVoxelSubdivLevel()
{
int defaultVoxelSubdivLevel = 3;
return Mathf.Min(defaultVoxelSubdivLevel, ProbeReferenceVolume.instance.GetMaxSubdivision() - 1);
}
bool m_NeedUpdateIndexComputeBuffer;
int SizeOfPhysicalIndexFromBudget(ProbeVolumeTextureMemoryBudget memoryBudget)
{
switch (memoryBudget)
{
case ProbeVolumeTextureMemoryBudget.MemoryBudgetLow:
// 16 MB - 4 million of bricks worth of space. At full resolution and a distance of 1 meter between probes, this is roughly 474 * 474 * 474 meters worth of bricks. If 0.25x on Y axis, this is equivalent to 948 * 118 * 948 meters
return 16000000;
case ProbeVolumeTextureMemoryBudget.MemoryBudgetMedium:
// 32 MB - 8 million of bricks worth of space. At full resolution and a distance of 1 meter between probes, this is roughly 600 * 600 * 600 meters worth of bricks. If 0.25x on Y axis, this is equivalent to 1200 * 150 * 1200 meters
return 32000000;
case ProbeVolumeTextureMemoryBudget.MemoryBudgetHigh:
// 64 MB - 16 million of bricks worth of space. At full resolution and a distance of 1 meter between probes, this is roughly 756 * 756 * 756 meters worth of bricks. If 0.25x on Y axis, this is equivalent to 1512 * 184 * 1512 meters
return 64000000;
}
return 32000000;
}
internal ProbeBrickIndex(ProbeVolumeTextureMemoryBudget memoryBudget)
{
Profiler.BeginSample("Create ProbeBrickIndex");
m_CenterRS = new Vector3Int(0, 0, 0);
m_VoxelToBricks = new Dictionary<Vector3Int, List<VoxelMeta>>();
m_BricksToVoxels = new Dictionary<RegId, BrickMeta>();
m_NeedUpdateIndexComputeBuffer = false;
m_IndexInChunks = Mathf.CeilToInt((float)SizeOfPhysicalIndexFromBudget(memoryBudget) / kIndexChunkSize);
m_IndexChunks = new BitArray(Mathf.Max(1, m_IndexInChunks));
int physicalBufferSize = m_IndexInChunks * kIndexChunkSize;
m_PhysicalIndexBufferData = new int[physicalBufferSize];
m_PhysicalIndexBuffer = new ComputeBuffer(physicalBufferSize, sizeof(int), ComputeBufferType.Structured);
m_NextFreeChunk = 0;
estimatedVMemCost = physicalBufferSize * sizeof(int);
// Should be done by a compute shader
Clear();
Profiler.EndSample();
}
internal void UploadIndexData()
{
m_PhysicalIndexBuffer.SetData(m_PhysicalIndexBufferData);
m_NeedUpdateIndexComputeBuffer = false;
}
internal void Clear()
{
Profiler.BeginSample("Clear Index");
for (int i = 0; i < m_PhysicalIndexBufferData.Length; ++i)
m_PhysicalIndexBufferData[i] = -1;
m_NeedUpdateIndexComputeBuffer = true;
m_NextFreeChunk = 0;
m_IndexChunks.SetAll(false);
m_VoxelToBricks.Clear();
m_BricksToVoxels.Clear();
Profiler.EndSample();
}
void MapBrickToVoxels(ProbeBrickIndex.Brick brick, HashSet<Vector3Int> voxels)
{
// create a list of all voxels this brick will touch
int brick_subdiv = brick.subdivisionLevel;
int voxels_touched_cnt = (int)Mathf.Pow(3, Mathf.Max(0, brick_subdiv - GetVoxelSubdivLevel()));
Vector3Int ipos = brick.position;
int brick_size = ProbeReferenceVolume.CellSize(brick.subdivisionLevel);
int voxel_size = ProbeReferenceVolume.CellSize(GetVoxelSubdivLevel());
if (voxels_touched_cnt <= 1)
{
Vector3 pos = brick.position;
pos = pos * (1.0f / voxel_size);
ipos = new Vector3Int(Mathf.FloorToInt(pos.x) * voxel_size, Mathf.FloorToInt(pos.y) * voxel_size, Mathf.FloorToInt(pos.z) * voxel_size);
}
for (int z = ipos.z; z < ipos.z + brick_size; z += voxel_size)
for (int y = ipos.y; y < ipos.y + brick_size; y += voxel_size)
for (int x = ipos.x; x < ipos.x + brick_size; x += voxel_size)
{
voxels.Add(new Vector3Int(x, y, z));
}
}
void ClearVoxel(Vector3Int pos, CellIndexUpdateInfo cellInfo)
{
Vector3Int vx_min, vx_max;
ClipToIndexSpace(pos, GetVoxelSubdivLevel(), out vx_min, out vx_max, cellInfo);
UpdatePhysicalIndex(vx_min, vx_max, -1, cellInfo);
}
internal void GetRuntimeResources(ref ProbeReferenceVolume.RuntimeResources rr)
{
// If we are pending an update of the actual compute buffer we do it here
if (m_NeedUpdateIndexComputeBuffer)
{
UploadIndexData();
}
rr.index = m_PhysicalIndexBuffer;
}
internal void Cleanup()
{
CoreUtils.SafeRelease(m_PhysicalIndexBuffer);
m_PhysicalIndexBuffer = null;
}
public struct CellIndexUpdateInfo
{
public int firstChunkIndex;
public int numberOfChunks;
public int minSubdivInCell;
// IMPORTANT, These values should be at max resolution. This means that
// The map to the lower possible resolution is done after. However they are still in local space.
public Vector3Int minValidBrickIndexForCellAtMaxRes;
public Vector3Int maxValidBrickIndexForCellAtMaxResPlusOne;
public Vector3Int cellPositionInBricksAtMaxRes;
}
int MergeIndex(int index, int size)
{
const int mask = kMaxSubdivisionLevels;
const int shift = 28;
return (index & ~(mask << shift)) | ((size & mask) << shift);
}
internal bool AssignIndexChunksToCell(ProbeReferenceVolume.Cell cell, int bricksCount, ref CellIndexUpdateInfo cellUpdateInfo)
{
// We need to better handle the case where the chunks are full, this is where streaming will need to come into place swapping in/out
// Also the current way to find an empty spot might be sub-optimal, when streaming is in place it'd be nice to have this more efficient
// if it is meant to happen frequently.
int numberOfChunks = Mathf.CeilToInt((float)bricksCount / kIndexChunkSize);
// Search for the first empty element with enough space.
int firstValidChunk = -1;
for (int i = 0; i < m_IndexInChunks; ++i)
{
if (!m_IndexChunks[i] && (i + numberOfChunks) < m_IndexInChunks)
{
int emptySlotsStartingHere = 0;
for (int k = i; k < (i + numberOfChunks); ++k)
{
if (!m_IndexChunks[k]) emptySlotsStartingHere++;
else break;
}
if (emptySlotsStartingHere == numberOfChunks)
{
firstValidChunk = i;
break;
}
}
}
if (firstValidChunk < 0) return false;
// This assert will need to go away or do something else when streaming is allowed (we need to find holes in available chunks or stream out stuff)
cellUpdateInfo.firstChunkIndex = firstValidChunk;
cellUpdateInfo.numberOfChunks = numberOfChunks;
for (int i = firstValidChunk; i < (firstValidChunk + numberOfChunks); ++i)
{
Debug.Assert(!m_IndexChunks[i]);
m_IndexChunks[i] = true;
}
m_NextFreeChunk += Mathf.Max(0, (firstValidChunk + numberOfChunks) - m_NextFreeChunk);
return true;
}
public void AddBricks(RegId id, List<Brick> bricks, List<Chunk> allocations, int allocationSize, int poolWidth, int poolHeight, CellIndexUpdateInfo cellInfo)
{
Debug.Assert(bricks.Count <= ushort.MaxValue, "Cannot add more than 65K bricks per RegId.");
int largest_cell = ProbeReferenceVolume.CellSize(kMaxSubdivisionLevels);
// create a new copy
BrickMeta bm = new BrickMeta();
bm.voxels = new HashSet<Vector3Int>();
bm.bricks = new List<ReservedBrick>(bricks.Count);
m_BricksToVoxels.Add(id, bm);
int brick_idx = 0;
// find all voxels each brick will touch
for (int i = 0; i < allocations.Count; i++)
{
Chunk alloc = allocations[i];
int cnt = Mathf.Min(allocationSize, bricks.Count - brick_idx);
for (int j = 0; j < cnt; j++, brick_idx++, alloc.x += ProbeBrickPool.kBrickProbeCountPerDim)
{
Brick brick = bricks[brick_idx];
int cellSize = ProbeReferenceVolume.CellSize(brick.subdivisionLevel);
Debug.Assert(cellSize <= largest_cell, "Cell sizes are not correctly sorted.");
largest_cell = Mathf.Min(largest_cell, cellSize);
MapBrickToVoxels(brick, bm.voxels);
ReservedBrick rbrick = new ReservedBrick();
rbrick.brick = brick;
rbrick.flattenedIdx = MergeIndex(alloc.flattenIndex(poolWidth, poolHeight), brick.subdivisionLevel);
bm.bricks.Add(rbrick);
foreach (var v in bm.voxels)
{
List<VoxelMeta> vm_list;
if (!m_VoxelToBricks.TryGetValue(v, out vm_list)) // first time the voxel is touched
{
vm_list = new List<VoxelMeta>(1);
m_VoxelToBricks.Add(v, vm_list);
}
VoxelMeta vm;
int vm_idx = vm_list.FindIndex((VoxelMeta lhs) => lhs.id == id);
if (vm_idx == -1) // first time a brick from this id has touched this voxel
{
vm.id = id;
vm.brickIndices = new List<ushort>(4);
vm_list.Add(vm);
}
else
{
vm = vm_list[vm_idx];
}
// add this brick to the voxel under its regId
vm.brickIndices.Add((ushort)brick_idx);
}
}
}
foreach (var voxel in bm.voxels)
{
UpdateIndexForVoxel(voxel, cellInfo);
}
}
public void RemoveBricks(RegId id, CellIndexUpdateInfo cellInfo)
{
if (!m_BricksToVoxels.ContainsKey(id))
return;
BrickMeta bm = m_BricksToVoxels[id];
foreach (var v in bm.voxels)
{
List<VoxelMeta> vm_list = m_VoxelToBricks[v];
int idx = vm_list.FindIndex((VoxelMeta lhs) => lhs.id == id);
if (idx >= 0)
{
vm_list.RemoveAt(idx);
if (vm_list.Count > 0)
{
UpdateIndexForVoxel(v, cellInfo);
}
else
{
ClearVoxel(v, cellInfo);
m_VoxelToBricks.Remove(v);
}
}
}
m_BricksToVoxels.Remove(id);
// Clear allocated chunks
for (int i = cellInfo.firstChunkIndex; i < (cellInfo.firstChunkIndex + cellInfo.numberOfChunks); ++i)
{
m_IndexChunks[i] = false;
}
}
void UpdateIndexForVoxel(Vector3Int voxel, CellIndexUpdateInfo cellInfo)
{
ClearVoxel(voxel, cellInfo);
List<VoxelMeta> vm_list = m_VoxelToBricks[voxel];
foreach (var vm in vm_list)
{
// get the list of bricks and indices
List<ReservedBrick> bricks = m_BricksToVoxels[vm.id].bricks;
List<ushort> indcs = vm.brickIndices;
UpdateIndexForVoxel(voxel, bricks, indcs, cellInfo);
}
}
void UpdatePhysicalIndex(Vector3Int brickMin, Vector3Int brickMax, int value, CellIndexUpdateInfo cellInfo)
{
// We need to do our calculations in local space to the cell, so we move the brick to local space as a first step.
// Reminder that at this point we are still operating at highest resolution possible, not necessarily the one that will be
// the final resolution for the chunk.
brickMin = brickMin - cellInfo.cellPositionInBricksAtMaxRes;
brickMax = brickMax - cellInfo.cellPositionInBricksAtMaxRes;
// Since the index is spurious (not same resolution, but varying per cell) we need to bring to the output resolution the brick coordinates
// Before finding the locations inside the Index for the current cell/chunk.
brickMin /= ProbeReferenceVolume.CellSize(cellInfo.minSubdivInCell);
brickMax /= ProbeReferenceVolume.CellSize(cellInfo.minSubdivInCell);
// Verify we are actually in local space now.
int maxCellSizeInOutputRes = ProbeReferenceVolume.CellSize(ProbeReferenceVolume.instance.GetMaxSubdivision() - 1 - cellInfo.minSubdivInCell);
Debug.Assert(brickMin.x >= 0 && brickMin.y >= 0 && brickMin.z >= 0 && brickMax.x >= 0 && brickMax.y >= 0 && brickMax.z >= 0);
Debug.Assert(brickMin.x < maxCellSizeInOutputRes && brickMin.y < maxCellSizeInOutputRes && brickMin.z < maxCellSizeInOutputRes && brickMax.x <= maxCellSizeInOutputRes && brickMax.y <= maxCellSizeInOutputRes && brickMax.z <= maxCellSizeInOutputRes);
// We are now in the right resolution, but still not considering the valid area, so we need to still normalize against that.
// To do so first let's move back the limits to the desired resolution
var cellMinIndex = cellInfo.minValidBrickIndexForCellAtMaxRes / ProbeReferenceVolume.CellSize(cellInfo.minSubdivInCell);
var cellMaxIndex = cellInfo.maxValidBrickIndexForCellAtMaxResPlusOne / ProbeReferenceVolume.CellSize(cellInfo.minSubdivInCell);
// Then perform the rescale of the local indices for min and max.
brickMin -= cellMinIndex;
brickMax -= cellMinIndex;
// In theory now we are all positive since we clipped during the voxel stage. Keeping assert for debugging, but can go later.
Debug.Assert(brickMin.x >= 0 && brickMin.y >= 0 && brickMin.z >= 0 && brickMax.x >= 0 && brickMax.y >= 0 && brickMax.z >= 0);
// Compute the span of the valid part
var size = (cellMaxIndex - cellMinIndex);
// Loop through all touched indices
int chunkStart = cellInfo.firstChunkIndex * kIndexChunkSize;
for (int z = brickMin.z; z < brickMax.z; ++z)
{
for (int y = brickMin.y; y < brickMax.y; ++y)
{
for (int x = brickMin.x; x < brickMax.x; ++x)
{
int localFlatIdx = z * (size.x * size.y) + x * size.y + y;
int actualIdx = chunkStart + localFlatIdx;
m_PhysicalIndexBufferData[actualIdx] = value;
}
}
}
m_NeedUpdateIndexComputeBuffer = true;
}
void ClipToIndexSpace(Vector3Int pos, int subdiv, out Vector3Int outMinpos, out Vector3Int outMaxpos, CellIndexUpdateInfo cellInfo)
{
// to relative coordinates
int cellSize = ProbeReferenceVolume.CellSize(subdiv);
// The position here is in global space, however we want to constraint this voxel update to the valid cell area
var minValidPosition = cellInfo.cellPositionInBricksAtMaxRes + cellInfo.minValidBrickIndexForCellAtMaxRes;
var maxValidPosition = cellInfo.cellPositionInBricksAtMaxRes + cellInfo.maxValidBrickIndexForCellAtMaxResPlusOne - Vector3Int.one;
int minpos_x = pos.x - m_CenterRS.x;
int minpos_y = pos.y;
int minpos_z = pos.z - m_CenterRS.z;
int maxpos_x = minpos_x + cellSize;
int maxpos_y = minpos_y + cellSize;
int maxpos_z = minpos_z + cellSize;
// clip to valid region
minpos_x = Mathf.Max(minpos_x, minValidPosition.x);
minpos_y = Mathf.Max(minpos_y, minValidPosition.y);
minpos_z = Mathf.Max(minpos_z, minValidPosition.z);
maxpos_x = Mathf.Min(maxpos_x, maxValidPosition.x);
maxpos_y = Mathf.Min(maxpos_y, maxValidPosition.y);
maxpos_z = Mathf.Min(maxpos_z, maxValidPosition.z);
outMinpos = new Vector3Int(minpos_x, minpos_y, minpos_z);
outMaxpos = new Vector3Int(maxpos_x, maxpos_y, maxpos_z);
}
void UpdateIndexForVoxel(Vector3Int voxel, List<ReservedBrick> bricks, List<ushort> indices, CellIndexUpdateInfo cellInfo)
{
// clip voxel to index space
Vector3Int vx_min, vx_max;
ClipToIndexSpace(voxel, GetVoxelSubdivLevel(), out vx_min, out vx_max, cellInfo);
foreach (var rbrick in bricks)
{
// clip brick to clipped voxel
int brick_cell_size = ProbeReferenceVolume.CellSize(rbrick.brick.subdivisionLevel);
Vector3Int brick_min = rbrick.brick.position;
Vector3Int brick_max = rbrick.brick.position + Vector3Int.one * brick_cell_size;
brick_min.x = Mathf.Max(vx_min.x, brick_min.x - m_CenterRS.x);
brick_min.y = Mathf.Max(vx_min.y, brick_min.y);
brick_min.z = Mathf.Max(vx_min.z, brick_min.z - m_CenterRS.z);
brick_max.x = Mathf.Min(vx_max.x, brick_max.x - m_CenterRS.x);
brick_max.y = Mathf.Min(vx_max.y, brick_max.y);
brick_max.z = Mathf.Min(vx_max.z, brick_max.z - m_CenterRS.z);
UpdatePhysicalIndex(brick_min, brick_max, rbrick.flattenedIdx, cellInfo);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c1a832f1bbd6c0248844c604b55947b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,416 @@
using System.Diagnostics;
using System.Collections.Generic;
using UnityEngine.Rendering;
using UnityEngine.Profiling;
namespace UnityEngine.Experimental.Rendering
{
internal class ProbeBrickPool
{
[DebuggerDisplay("Chunk ({x}, {y}, {z})")]
public struct BrickChunkAlloc
{
public int x, y, z;
internal int flattenIndex(int sx, int sy) { return z * (sx * sy) + y * sx + x; }
}
public struct DataLocation
{
internal Texture3D TexL0_L1rx;
internal Texture3D TexL1_G_ry;
internal Texture3D TexL1_B_rz;
internal Texture3D TexL2_0;
internal Texture3D TexL2_1;
internal Texture3D TexL2_2;
internal Texture3D TexL2_3;
internal int width;
internal int height;
internal int depth;
internal void Cleanup()
{
CoreUtils.Destroy(TexL0_L1rx);
CoreUtils.Destroy(TexL1_G_ry);
CoreUtils.Destroy(TexL1_B_rz);
CoreUtils.Destroy(TexL2_0);
CoreUtils.Destroy(TexL2_1);
CoreUtils.Destroy(TexL2_2);
CoreUtils.Destroy(TexL2_3);
TexL0_L1rx = null;
TexL1_G_ry = null;
TexL1_B_rz = null;
TexL2_0 = null;
TexL2_1 = null;
TexL2_2 = null;
TexL2_3 = null;
}
}
internal const int kBrickCellCount = 3;
internal const int kBrickProbeCountPerDim = kBrickCellCount + 1;
internal const int kBrickProbeCountTotal = kBrickProbeCountPerDim * kBrickProbeCountPerDim * kBrickProbeCountPerDim;
internal int estimatedVMemCost { get; private set; }
const int kMaxPoolWidth = 1 << 11; // 2048 texels is a d3d11 limit for tex3d in all dimensions
int m_AllocationSize;
ProbeVolumeTextureMemoryBudget m_MemoryBudget;
DataLocation m_Pool;
BrickChunkAlloc m_NextFreeChunk;
Stack<BrickChunkAlloc> m_FreeList;
ProbeVolumeSHBands m_SHBands;
internal ProbeBrickPool(int allocationSize, ProbeVolumeTextureMemoryBudget memoryBudget, ProbeVolumeSHBands shBands)
{
Profiler.BeginSample("Create ProbeBrickPool");
m_NextFreeChunk.x = m_NextFreeChunk.y = m_NextFreeChunk.z = 0;
m_AllocationSize = allocationSize;
m_MemoryBudget = memoryBudget;
m_SHBands = shBands;
m_FreeList = new Stack<BrickChunkAlloc>(256);
int width, height, depth;
DerivePoolSizeFromBudget(allocationSize, memoryBudget, out width, out height, out depth);
int estimatedCost = 0;
m_Pool = CreateDataLocation(width * height * depth, false, shBands, out estimatedCost);
estimatedVMemCost = estimatedCost;
Profiler.EndSample();
}
internal void EnsureTextureValidity()
{
// We assume that if a texture is null, all of them are. In any case we reboot them altogether.
if (m_Pool.TexL0_L1rx == null)
{
m_Pool.Cleanup();
int estimatedCost = 0;
m_Pool = CreateDataLocation(m_Pool.width * m_Pool.height * m_Pool.depth, false, m_SHBands, out estimatedCost);
estimatedVMemCost = estimatedCost;
}
}
internal int GetChunkSize() { return m_AllocationSize; }
internal int GetChunkSizeInProbeCount() { return m_AllocationSize * kBrickProbeCountTotal; }
internal int GetPoolWidth() { return m_Pool.width; }
internal int GetPoolHeight() { return m_Pool.height; }
internal Vector3Int GetPoolDimensions() { return new Vector3Int(m_Pool.width, m_Pool.height, m_Pool.depth); }
internal void GetRuntimeResources(ref ProbeReferenceVolume.RuntimeResources rr)
{
rr.L0_L1rx = m_Pool.TexL0_L1rx;
rr.L1_G_ry = m_Pool.TexL1_G_ry;
rr.L1_B_rz = m_Pool.TexL1_B_rz;
rr.L2_0 = m_Pool.TexL2_0;
rr.L2_1 = m_Pool.TexL2_1;
rr.L2_2 = m_Pool.TexL2_2;
rr.L2_3 = m_Pool.TexL2_3;
}
internal void Clear()
{
m_FreeList.Clear();
m_NextFreeChunk.x = m_NextFreeChunk.y = m_NextFreeChunk.z = 0;
}
internal void Allocate(int numberOfBrickChunks, List<BrickChunkAlloc> outAllocations)
{
while (m_FreeList.Count > 0 && numberOfBrickChunks > 0)
{
outAllocations.Add(m_FreeList.Pop());
numberOfBrickChunks--;
}
for (uint i = 0; i < numberOfBrickChunks; i++)
{
if (m_NextFreeChunk.z >= m_Pool.depth)
{
Debug.Assert(false, "Cannot allocate more brick chunks, probevolume brick pool is full.");
break; // failure case, pool is full
}
outAllocations.Add(m_NextFreeChunk);
m_NextFreeChunk.x += m_AllocationSize * kBrickProbeCountPerDim;
if (m_NextFreeChunk.x >= m_Pool.width)
{
m_NextFreeChunk.x = 0;
m_NextFreeChunk.y += kBrickProbeCountPerDim;
if (m_NextFreeChunk.y >= m_Pool.height)
{
m_NextFreeChunk.y = 0;
m_NextFreeChunk.z += kBrickProbeCountPerDim;
}
}
}
}
internal void Deallocate(List<BrickChunkAlloc> allocations)
{
foreach (var brick in allocations)
m_FreeList.Push(brick);
}
internal void Update(DataLocation source, List<BrickChunkAlloc> srcLocations, List<BrickChunkAlloc> dstLocations, ProbeVolumeSHBands bands)
{
Debug.Assert(srcLocations.Count == dstLocations.Count);
for (int i = 0; i < srcLocations.Count; i++)
{
BrickChunkAlloc src = srcLocations[i];
BrickChunkAlloc dst = dstLocations[i];
for (int j = 0; j < kBrickProbeCountPerDim; j++)
{
int width = Mathf.Min(m_AllocationSize * kBrickProbeCountPerDim, source.width - src.x);
Graphics.CopyTexture(source.TexL0_L1rx, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL0_L1rx, dst.z + j, 0, dst.x, dst.y);
Graphics.CopyTexture(source.TexL1_G_ry, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL1_G_ry, dst.z + j, 0, dst.x, dst.y);
Graphics.CopyTexture(source.TexL1_B_rz, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL1_B_rz, dst.z + j, 0, dst.x, dst.y);
if (bands == ProbeVolumeSHBands.SphericalHarmonicsL2)
{
Graphics.CopyTexture(source.TexL2_0, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL2_0, dst.z + j, 0, dst.x, dst.y);
Graphics.CopyTexture(source.TexL2_1, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL2_1, dst.z + j, 0, dst.x, dst.y);
Graphics.CopyTexture(source.TexL2_2, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL2_2, dst.z + j, 0, dst.x, dst.y);
Graphics.CopyTexture(source.TexL2_3, src.z + j, 0, src.x, src.y, width, kBrickProbeCountPerDim, m_Pool.TexL2_3, dst.z + j, 0, dst.x, dst.y);
}
}
}
}
static Vector3Int ProbeCountToDataLocSize(int numProbes)
{
Debug.Assert(numProbes != 0);
Debug.Assert(numProbes % kBrickProbeCountTotal == 0);
int numBricks = numProbes / kBrickProbeCountTotal;
int poolWidth = kMaxPoolWidth / kBrickProbeCountPerDim;
int width, height, depth;
depth = (numBricks + poolWidth * poolWidth - 1) / (poolWidth * poolWidth);
if (depth > 1)
width = height = poolWidth;
else
{
height = (numBricks + poolWidth - 1) / poolWidth;
if (height > 1)
width = poolWidth;
else
width = numBricks;
}
width *= kBrickProbeCountPerDim;
height *= kBrickProbeCountPerDim;
depth *= kBrickProbeCountPerDim;
return new Vector3Int(width, height, depth);
}
public static DataLocation CreateDataLocation(int numProbes, bool compressed, ProbeVolumeSHBands bands, out int allocatedBytes)
{
Vector3Int locSize = ProbeCountToDataLocSize(numProbes);
int width = locSize.x;
int height = locSize.y;
int depth = locSize.z;
int texelCount = width * height * depth;
DataLocation loc;
allocatedBytes = 0;
loc.TexL0_L1rx = new Texture3D(width, height, depth, GraphicsFormat.R16G16B16A16_SFloat, TextureCreationFlags.None, 1);
allocatedBytes += texelCount * 8;
loc.TexL1_G_ry = new Texture3D(width, height, depth, compressed ? GraphicsFormat.RGBA_BC7_UNorm : GraphicsFormat.R8G8B8A8_UNorm, TextureCreationFlags.None, 1);
allocatedBytes += texelCount * (compressed ? 1 : 4);
loc.TexL1_B_rz = new Texture3D(width, height, depth, compressed ? GraphicsFormat.RGBA_BC7_UNorm : GraphicsFormat.R8G8B8A8_UNorm, TextureCreationFlags.None, 1);
allocatedBytes += texelCount * (compressed ? 1 : 4);
if (bands == ProbeVolumeSHBands.SphericalHarmonicsL2)
{
loc.TexL2_0 = new Texture3D(width, height, depth, compressed ? GraphicsFormat.RGBA_BC7_UNorm : GraphicsFormat.R8G8B8A8_UNorm, TextureCreationFlags.None, 1);
allocatedBytes += texelCount * (compressed ? 1 : 4);
loc.TexL2_1 = new Texture3D(width, height, depth, compressed ? GraphicsFormat.RGBA_BC7_UNorm : GraphicsFormat.R8G8B8A8_UNorm, TextureCreationFlags.None, 1);
allocatedBytes += texelCount * (compressed ? 1 : 4);
loc.TexL2_2 = new Texture3D(width, height, depth, compressed ? GraphicsFormat.RGBA_BC7_UNorm : GraphicsFormat.R8G8B8A8_UNorm, TextureCreationFlags.None, 1);
allocatedBytes += texelCount * (compressed ? 1 : 4);
loc.TexL2_3 = new Texture3D(width, height, depth, compressed ? GraphicsFormat.RGBA_BC7_UNorm : GraphicsFormat.R8G8B8A8_UNorm, TextureCreationFlags.None, 1);
allocatedBytes += texelCount * (compressed ? 1 : 4);
}
else
{
loc.TexL2_0 = null;
loc.TexL2_1 = null;
loc.TexL2_2 = null;
loc.TexL2_3 = null;
}
loc.width = width;
loc.height = height;
loc.depth = depth;
return loc;
}
static void SetPixel(ref Color[] data, int x, int y, int z, int dataLocWidth, int dataLocHeight, Color value)
{
int index = x + dataLocWidth * (y + dataLocHeight * z);
data[index] = value;
}
public static void FillDataLocation(ref DataLocation loc, SphericalHarmonicsL2[] shl2, ProbeVolumeSHBands bands)
{
int numBricks = shl2.Length / kBrickProbeCountTotal;
int shidx = 0;
int bx = 0, by = 0, bz = 0;
Color c = new Color();
Color[] L0L1Rx_locData = new Color[loc.width * loc.height * loc.depth * 2];
Color[] L1GL1Ry_locData = new Color[loc.width * loc.height * loc.depth * 2];
Color[] L1BL1Rz_locData = new Color[loc.width * loc.height * loc.depth * 2];
Color[] L2_0_locData = null;
Color[] L2_1_locData = null;
Color[] L2_2_locData = null;
Color[] L2_3_locData = null;
if (bands == ProbeVolumeSHBands.SphericalHarmonicsL2)
{
L2_0_locData = new Color[loc.width * loc.height * loc.depth];
L2_1_locData = new Color[loc.width * loc.height * loc.depth];
L2_2_locData = new Color[loc.width * loc.height * loc.depth];
L2_3_locData = new Color[loc.width * loc.height * loc.depth];
}
for (int brickIdx = 0; brickIdx < shl2.Length; brickIdx += kBrickProbeCountTotal)
{
for (int z = 0; z < kBrickProbeCountPerDim; z++)
{
for (int y = 0; y < kBrickProbeCountPerDim; y++)
{
for (int x = 0; x < kBrickProbeCountPerDim; x++)
{
int ix = bx + x;
int iy = by + y;
int iz = bz + z;
c.r = shl2[shidx][0, 0]; // L0.r
c.g = shl2[shidx][1, 0]; // L0.g
c.b = shl2[shidx][2, 0]; // L0.b
c.a = shl2[shidx][0, 1]; // L1_R.r
SetPixel(ref L0L1Rx_locData, ix, iy, iz, loc.width, loc.height, c);
c.r = shl2[shidx][1, 1]; // L1_G.r
c.g = shl2[shidx][1, 2]; // L1_G.g
c.b = shl2[shidx][1, 3]; // L1_G.b
c.a = shl2[shidx][0, 2]; // L1_R.g
SetPixel(ref L1GL1Ry_locData, ix, iy, iz, loc.width, loc.height, c);
c.r = shl2[shidx][2, 1]; // L1_B.r
c.g = shl2[shidx][2, 2]; // L1_B.g
c.b = shl2[shidx][2, 3]; // L1_B.b
c.a = shl2[shidx][0, 3]; // L1_R.b
SetPixel(ref L1BL1Rz_locData, ix, iy, iz, loc.width, loc.height, c);
if (bands == ProbeVolumeSHBands.SphericalHarmonicsL2)
{
c.r = shl2[shidx][0, 4];
c.g = shl2[shidx][0, 5];
c.b = shl2[shidx][0, 6];
c.a = shl2[shidx][0, 7];
SetPixel(ref L2_0_locData, ix, iy, iz, loc.width, loc.height, c);
c.r = shl2[shidx][1, 4];
c.g = shl2[shidx][1, 5];
c.b = shl2[shidx][1, 6];
c.a = shl2[shidx][1, 7];
SetPixel(ref L2_1_locData, ix, iy, iz, loc.width, loc.height, c);
c.r = shl2[shidx][2, 4];
c.g = shl2[shidx][2, 5];
c.b = shl2[shidx][2, 6];
c.a = shl2[shidx][2, 7];
SetPixel(ref L2_2_locData, ix, iy, iz, loc.width, loc.height, c);
c.r = shl2[shidx][0, 8];
c.g = shl2[shidx][1, 8];
c.b = shl2[shidx][2, 8];
c.a = 1;
SetPixel(ref L2_3_locData, ix, iy, iz, loc.width, loc.height, c);
}
shidx++;
}
}
}
// update the pool index
bx += kBrickProbeCountPerDim;
if (bx >= loc.width)
{
bx = 0;
by += kBrickProbeCountPerDim;
if (by >= loc.height)
{
by = 0;
bz += kBrickProbeCountPerDim;
Debug.Assert(bz < loc.depth || brickIdx == shl2.Length - kBrickProbeCountTotal, "Location depth exceeds data texture.");
}
}
}
loc.TexL0_L1rx.SetPixels(L0L1Rx_locData);
loc.TexL0_L1rx.Apply(false);
loc.TexL1_G_ry.SetPixels(L1GL1Ry_locData);
loc.TexL1_G_ry.Apply(false);
loc.TexL1_B_rz.SetPixels(L1BL1Rz_locData);
loc.TexL1_B_rz.Apply(false);
if (bands == ProbeVolumeSHBands.SphericalHarmonicsL2)
{
loc.TexL2_0.SetPixels(L2_0_locData);
loc.TexL2_0.Apply(false);
loc.TexL2_1.SetPixels(L2_1_locData);
loc.TexL2_1.Apply(false);
loc.TexL2_2.SetPixels(L2_2_locData);
loc.TexL2_2.Apply(false);
loc.TexL2_3.SetPixels(L2_3_locData);
loc.TexL2_3.Apply(false);
}
}
void DerivePoolSizeFromBudget(int allocationSize, ProbeVolumeTextureMemoryBudget memoryBudget, out int width, out int height, out int depth)
{
// TODO: This is fairly simplistic for now and relies on the enum to have the value set to the desired numbers,
// might change the heuristic later on.
width = (int)memoryBudget;
height = (int)memoryBudget;
depth = kBrickProbeCountPerDim;
}
internal void Cleanup()
{
m_Pool.Cleanup();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 079fb321dd4edfd42bee1376c8c48c05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,151 @@
using System;
using System.Diagnostics;
using System.Collections.Generic;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using Chunk = UnityEngine.Experimental.Rendering.ProbeBrickPool.BrickChunkAlloc;
using RegId = UnityEngine.Experimental.Rendering.ProbeReferenceVolume.RegId;
using Cell = UnityEngine.Experimental.Rendering.ProbeReferenceVolume.Cell;
namespace UnityEngine.Experimental.Rendering
{
internal class ProbeCellIndices
{
const int kUintPerEntry = 3;
internal int estimatedVMemCost { get; private set; }
internal struct IndexMetaData
{
internal Vector3Int minLocalIdx;
internal Vector3Int maxLocalIdx;
internal int firstChunkIndex;
internal int minSubdiv;
internal void Pack(out uint[] vals)
{
vals = new uint[kUintPerEntry];
for (int i = 0; i < kUintPerEntry; ++i)
{
vals[i] = 0;
}
// Note this packing is really really generous, I really think we can get rid of 1 uint at least if we assume we don't go extreme.
// but this is encompassing all scenarios.
//
// UINT 0:
// FirstChunkIndex 29 bit
// MinSubdiv 3 bit
// UINT 1:
// minLocalIdx.x 10 bit
// minLocalIdx.y 10 bit
// minLocalIdx.z 10 bit
// UINT 2:
// maxLocalIdx.x 10 bit
// maxLocalIdx.y 10 bit
// maxLocalIdx.z 10 bit
vals[0] = (uint)firstChunkIndex & 0x1FFFFFFF;
vals[0] |= ((uint)minSubdiv & 0x7) << 29;
vals[1] = (uint)minLocalIdx.x & 0x3FF;
vals[1] |= ((uint)minLocalIdx.y & 0x3FF) << 10;
vals[1] |= ((uint)minLocalIdx.z & 0x3FF) << 20;
vals[2] = (uint)maxLocalIdx.x & 0x3FF;
vals[2] |= ((uint)maxLocalIdx.y & 0x3FF) << 10;
vals[2] |= ((uint)maxLocalIdx.z & 0x3FF) << 20;
}
}
ComputeBuffer m_IndexOfIndicesBuffer;
uint[] m_IndexOfIndicesData;
Vector3Int m_CellCount;
Vector3Int m_CellMin;
int m_CellSizeInMinBricks;
bool m_NeedUpdateComputeBuffer;
internal Vector3Int GetCellIndexDimension() => m_CellCount;
internal Vector3Int GetCellMinPosition() => m_CellMin;
int GetFlatIndex(Vector3Int normalizedPos)
{
return normalizedPos.z * (m_CellCount.x * m_CellCount.y) + normalizedPos.y * m_CellCount.x + normalizedPos.x;
}
internal ProbeCellIndices(Vector3Int cellMin, Vector3Int cellMax, int cellSizeInMinBricks)
{
Vector3Int cellCount = new Vector3Int(Mathf.Abs(cellMax.x - cellMin.x), Mathf.Abs(cellMax.y - cellMin.y), Mathf.Abs(cellMax.z - cellMin.z));
m_CellCount = cellCount;
m_CellMin = cellMin;
m_CellSizeInMinBricks = cellSizeInMinBricks;
int flatCellCount = cellCount.x * cellCount.y * cellCount.z;
flatCellCount = flatCellCount == 0 ? 1 : flatCellCount;
int bufferSize = kUintPerEntry * flatCellCount;
m_IndexOfIndicesBuffer = new ComputeBuffer(flatCellCount, kUintPerEntry * sizeof(uint));
m_IndexOfIndicesData = new uint[bufferSize];
m_NeedUpdateComputeBuffer = false;
estimatedVMemCost = flatCellCount * kUintPerEntry * sizeof(uint);
}
internal int GetFlatIdxForCell(Vector3Int cellPosition)
{
Vector3Int normalizedPos = cellPosition - m_CellMin;
Debug.Assert(normalizedPos.x >= 0 && normalizedPos.y >= 0 && normalizedPos.z >= 0);
return GetFlatIndex(normalizedPos);
}
internal void AddCell(int cellFlatIdx, ProbeBrickIndex.CellIndexUpdateInfo cellUpdateInfo)
{
int minSubdivCellSize = ProbeReferenceVolume.CellSize(cellUpdateInfo.minSubdivInCell);
IndexMetaData metaData = new IndexMetaData();
metaData.minSubdiv = cellUpdateInfo.minSubdivInCell;
metaData.minLocalIdx = cellUpdateInfo.minValidBrickIndexForCellAtMaxRes / minSubdivCellSize;
metaData.maxLocalIdx = cellUpdateInfo.maxValidBrickIndexForCellAtMaxResPlusOne / minSubdivCellSize;
metaData.firstChunkIndex = cellUpdateInfo.firstChunkIndex;
metaData.Pack(out uint[] packedVals);
for (int i = 0; i < kUintPerEntry; ++i)
{
m_IndexOfIndicesData[cellFlatIdx * kUintPerEntry + i] = packedVals[i];
}
m_NeedUpdateComputeBuffer = true;
}
internal void MarkCellAsUnloaded(int cellFlatIdx)
{
for (int i = 0; i < kUintPerEntry; ++i)
{
m_IndexOfIndicesData[cellFlatIdx * kUintPerEntry + i] = 0xFFFFFFFF;
}
m_NeedUpdateComputeBuffer = true;
}
internal void PushComputeData()
{
m_IndexOfIndicesBuffer.SetData(m_IndexOfIndicesData);
m_NeedUpdateComputeBuffer = false;
}
internal void GetRuntimeResources(ref ProbeReferenceVolume.RuntimeResources rr)
{
// If we are pending an update of the actual compute buffer we do it here
if (m_NeedUpdateComputeBuffer)
{
PushComputeData();
}
rr.cellIndices = m_IndexOfIndicesBuffer;
}
internal void Cleanup()
{
CoreUtils.SafeRelease(m_IndexOfIndicesBuffer);
m_IndexOfIndicesBuffer = null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 58562d806ae1e3b4ba9cc4b895239782
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,318 @@
using System.Collections.Generic;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering
{
[GenerateHLSL]
public enum DebugProbeShadingMode
{
SH,
Validity,
ValidityOverDilationThreshold,
Size
}
class ProbeVolumeDebug
{
public bool drawProbes;
public bool drawBricks;
public bool drawCells;
public bool realtimeSubdivision;
public int subdivisionCellUpdatePerFrame = 4;
public float subdivisionDelayInSeconds = 1;
public DebugProbeShadingMode probeShading;
public float probeSize = 1.0f;
public float subdivisionViewCullingDistance = 500.0f;
public float probeCullingDistance = 200.0f;
public int maxSubdivToVisualize = ProbeBrickIndex.kMaxSubdivisionLevels;
public float exposureCompensation;
}
public partial class ProbeReferenceVolume
{
class CellInstancedDebugProbes
{
public List<Matrix4x4[]> probeBuffers;
public List<MaterialPropertyBlock> props;
public Hash128 cellHash;
public Vector3 cellPosition;
}
const int kProbesPerBatch = 1023;
internal ProbeVolumeDebug debugDisplay { get; } = new ProbeVolumeDebug();
/// <summary>Colors that can be used for debug visualization of the brick structure subdivision.</summary>
public Color[] subdivisionDebugColors { get; } = new Color[ProbeBrickIndex.kMaxSubdivisionLevels];
DebugUI.Widget[] m_DebugItems;
Mesh m_DebugMesh;
Material m_DebugMaterial;
List<CellInstancedDebugProbes> m_CellDebugData = new List<CellInstancedDebugProbes>();
Plane[] m_DebugFrustumPlanes = new Plane[6];
internal float dilationValidtyThreshold = 0.25f; // We ned to store this here to access it
// Field used for the realtime subdivision preview
internal Dictionary<ProbeReferenceVolume.Volume, List<ProbeBrickIndex.Brick>> realtimeSubdivisionInfo = new Dictionary<ProbeReferenceVolume.Volume, List<ProbeBrickIndex.Brick>>();
/// <summary>
/// Render Probe Volume related debug
/// </summary>
public void RenderDebug(Camera camera)
{
if (camera.cameraType != CameraType.Reflection && camera.cameraType != CameraType.Preview)
{
if (debugDisplay.drawProbes)
{
DrawProbeDebug(camera);
}
}
}
void InitializeDebug(Mesh debugProbeMesh, Shader debugProbeShader)
{
m_DebugMesh = debugProbeMesh;
m_DebugMaterial = CoreUtils.CreateEngineMaterial(debugProbeShader);
m_DebugMaterial.enableInstancing = true;
// Hard-coded colors for now.
Debug.Assert(ProbeBrickIndex.kMaxSubdivisionLevels == 7); // Update list if this changes.
subdivisionDebugColors[0] = new Color(1.0f, 0.0f, 0.0f);
subdivisionDebugColors[1] = new Color(0.0f, 1.0f, 0.0f);
subdivisionDebugColors[2] = new Color(0.0f, 0.0f, 1.0f);
subdivisionDebugColors[3] = new Color(1.0f, 1.0f, 0.0f);
subdivisionDebugColors[4] = new Color(1.0f, 0.0f, 1.0f);
subdivisionDebugColors[5] = new Color(0.0f, 1.0f, 1.0f);
subdivisionDebugColors[6] = new Color(0.5f, 0.5f, 0.5f);
RegisterDebug();
#if UNITY_EDITOR
UnityEditor.Lightmapping.lightingDataCleared += OnClearLightingdata;
#endif
}
void CleanupDebug()
{
UnregisterDebug(true);
CoreUtils.Destroy(m_DebugMaterial);
#if UNITY_EDITOR
UnityEditor.Lightmapping.lightingDataCleared -= OnClearLightingdata;
#endif
}
void RefreshDebug<T>(DebugUI.Field<T> field, T value)
{
UnregisterDebug(false);
RegisterDebug();
}
void DebugCellIndexChanged<T>(DebugUI.Field<T> field, T value)
{
ClearDebugData();
}
void RegisterDebug()
{
var widgetList = new List<DebugUI.Widget>();
var subdivContainer = new DebugUI.Container() { displayName = "Subdivision Visualization" };
subdivContainer.children.Add(new DebugUI.BoolField { displayName = "Display Cells", getter = () => debugDisplay.drawCells, setter = value => debugDisplay.drawCells = value, onValueChanged = RefreshDebug });
subdivContainer.children.Add(new DebugUI.BoolField { displayName = "Display Bricks", getter = () => debugDisplay.drawBricks, setter = value => debugDisplay.drawBricks = value, onValueChanged = RefreshDebug });
#if UNITY_EDITOR
subdivContainer.children.Add(new DebugUI.BoolField { displayName = "Realtime Update", getter = () => debugDisplay.realtimeSubdivision, setter = value => debugDisplay.realtimeSubdivision = value, onValueChanged = RefreshDebug });
if (debugDisplay.realtimeSubdivision)
{
var cellUpdatePerFrame = new DebugUI.IntField { displayName = "Number Of Cell Update Per Frame", getter = () => debugDisplay.subdivisionCellUpdatePerFrame, setter = value => debugDisplay.subdivisionCellUpdatePerFrame = value, min = () => 1, max = () => 100 };
var delayBetweenUpdates = new DebugUI.FloatField { displayName = "Delay Between Two Updates In Seconds", getter = () => debugDisplay.subdivisionDelayInSeconds, setter = value => debugDisplay.subdivisionDelayInSeconds = value, min = () => 0.1f, max = () => 10 };
subdivContainer.children.Add(new DebugUI.Container { children = { cellUpdatePerFrame, delayBetweenUpdates } });
}
#endif
if (debugDisplay.drawCells || debugDisplay.drawBricks)
{
subdivContainer.children.Add(new DebugUI.FloatField { displayName = "Culling Distance", getter = () => debugDisplay.subdivisionViewCullingDistance, setter = value => debugDisplay.subdivisionViewCullingDistance = value, min = () => 0.0f });
}
var probeContainer = new DebugUI.Container() { displayName = "Probe Visualization" };
probeContainer.children.Add(new DebugUI.BoolField { displayName = "Display Probes", getter = () => debugDisplay.drawProbes, setter = value => debugDisplay.drawProbes = value, onValueChanged = RefreshDebug });
if (debugDisplay.drawProbes)
{
probeContainer.children.Add(new DebugUI.EnumField
{
displayName = "Probe Shading Mode",
getter = () => (int)debugDisplay.probeShading,
setter = value => debugDisplay.probeShading = (DebugProbeShadingMode)value,
autoEnum = typeof(DebugProbeShadingMode),
getIndex = () => (int)debugDisplay.probeShading,
setIndex = value => debugDisplay.probeShading = (DebugProbeShadingMode)value,
onValueChanged = RefreshDebug
});
probeContainer.children.Add(new DebugUI.FloatField { displayName = "Probe Size", getter = () => debugDisplay.probeSize, setter = value => debugDisplay.probeSize = value, min = () => 0.1f, max = () => 10.0f });
if (debugDisplay.probeShading == DebugProbeShadingMode.SH)
probeContainer.children.Add(new DebugUI.FloatField { displayName = "Probe Exposure Compensation", getter = () => debugDisplay.exposureCompensation, setter = value => debugDisplay.exposureCompensation = value });
probeContainer.children.Add(new DebugUI.FloatField { displayName = "Culling Distance", getter = () => debugDisplay.probeCullingDistance, setter = value => debugDisplay.probeCullingDistance = value, min = () => 0.0f });
probeContainer.children.Add(new DebugUI.IntField
{
displayName = "Max subdivision displayed",
getter = () => debugDisplay.maxSubdivToVisualize,
setter = (v) => debugDisplay.maxSubdivToVisualize = Mathf.Min(v, ProbeReferenceVolume.instance.GetMaxSubdivision()),
min = () => 0,
max = () => ProbeReferenceVolume.instance.GetMaxSubdivision(),
});
}
widgetList.Add(subdivContainer);
widgetList.Add(probeContainer);
m_DebugItems = widgetList.ToArray();
var panel = DebugManager.instance.GetPanel("Probe Volume", true);
panel.children.Add(m_DebugItems);
}
void UnregisterDebug(bool destroyPanel)
{
if (destroyPanel)
DebugManager.instance.RemovePanel("Probe Volume");
else
DebugManager.instance.GetPanel("Probe Volume", false).children.Remove(m_DebugItems);
}
bool ShouldCullCell(Vector3 cellPosition, Transform cameraTransform, Plane[] frustumPlanes)
{
var cellSize = MaxBrickSize();
var originWS = GetTransform().posWS;
Vector3 cellCenterWS = cellPosition * cellSize + originWS + Vector3.one * (cellSize / 2.0f);
// We do coarse culling with cell, finer culling later.
float distanceRoundedUpWithCellSize = Mathf.CeilToInt(debugDisplay.probeCullingDistance / cellSize) * cellSize;
if (Vector3.Distance(cameraTransform.position, cellCenterWS) > distanceRoundedUpWithCellSize)
return true;
var volumeAABB = new Bounds(cellCenterWS, cellSize * Vector3.one);
return !GeometryUtility.TestPlanesAABB(frustumPlanes, volumeAABB);
}
void DrawProbeDebug(Camera camera)
{
if (debugDisplay.drawProbes)
{
// TODO: Update data on ref vol changes
if (m_CellDebugData.Count == 0)
CreateInstancedProbes();
GeometryUtility.CalculateFrustumPlanes(camera, m_DebugFrustumPlanes);
m_DebugMaterial.shaderKeywords = null;
if (m_SHBands == ProbeVolumeSHBands.SphericalHarmonicsL1)
m_DebugMaterial.EnableKeyword("PROBE_VOLUMES_L1");
else if (m_SHBands == ProbeVolumeSHBands.SphericalHarmonicsL2)
m_DebugMaterial.EnableKeyword("PROBE_VOLUMES_L2");
foreach (var debug in m_CellDebugData)
{
if (ShouldCullCell(debug.cellPosition, camera.transform, m_DebugFrustumPlanes))
continue;
for (int i = 0; i < debug.probeBuffers.Count; ++i)
{
var probeBuffer = debug.probeBuffers[i];
var props = debug.props[i];
props.SetInt("_ShadingMode", (int)debugDisplay.probeShading);
props.SetFloat("_ExposureCompensation", debugDisplay.exposureCompensation);
props.SetFloat("_ProbeSize", debugDisplay.probeSize);
props.SetFloat("_CullDistance", debugDisplay.probeCullingDistance);
props.SetInt("_MaxAllowedSubdiv", debugDisplay.maxSubdivToVisualize);
props.SetFloat("_ValidityThreshold", dilationValidtyThreshold);
Graphics.DrawMeshInstanced(m_DebugMesh, 0, m_DebugMaterial, probeBuffer, probeBuffer.Length, props, ShadowCastingMode.Off, false, 0, camera, LightProbeUsage.Off, null);
}
}
}
}
void ClearDebugData()
{
m_CellDebugData.Clear();
realtimeSubdivisionInfo.Clear();
}
void CreateInstancedProbes()
{
int maxSubdiv = ProbeReferenceVolume.instance.GetMaxSubdivision() - 1;
foreach (var cell in ProbeReferenceVolume.instance.cells.Values)
{
if (cell.sh == null || cell.sh.Length == 0)
continue;
float largestBrickSize = cell.bricks.Count == 0 ? 0 : cell.bricks[0].subdivisionLevel;
List<Matrix4x4[]> probeBuffers = new List<Matrix4x4[]>();
List<MaterialPropertyBlock> props = new List<MaterialPropertyBlock>();
CellChunkInfo chunks;
if (!m_ChunkInfo.TryGetValue(cell.index, out chunks))
continue;
Vector4[] texels = new Vector4[kProbesPerBatch];
float[] validity = new float[kProbesPerBatch];
float[] relativeSize = new float[kProbesPerBatch];
List<Matrix4x4> probeBuffer = new List<Matrix4x4>();
var debugData = new CellInstancedDebugProbes();
debugData.probeBuffers = probeBuffers;
debugData.props = props;
debugData.cellPosition = cell.position;
int idxInBatch = 0;
for (int i = 0; i < cell.probePositions.Length; i++)
{
var brickSize = cell.bricks[i / 64].subdivisionLevel;
int chunkIndex = i / m_Pool.GetChunkSizeInProbeCount();
var chunk = chunks.chunks[chunkIndex];
int indexInChunk = i % m_Pool.GetChunkSizeInProbeCount();
int brickIdx = indexInChunk / 64;
int indexInBrick = indexInChunk % 64;
Vector2Int brickStart = new Vector2Int(chunk.x + brickIdx * 4, chunk.y);
int indexInSlice = indexInBrick % 16;
Vector3Int texelLoc = new Vector3Int(brickStart.x + (indexInSlice % 4), brickStart.y + (indexInSlice / 4), indexInBrick / 16);
probeBuffer.Add(Matrix4x4.TRS(cell.probePositions[i], Quaternion.identity, Vector3.one * (0.3f * (brickSize + 1))));
validity[idxInBatch] = cell.validity[i];
texels[idxInBatch] = new Vector4(texelLoc.x, texelLoc.y, texelLoc.z, brickSize);
relativeSize[idxInBatch] = (float)brickSize / (float)maxSubdiv;
idxInBatch++;
if (probeBuffer.Count >= kProbesPerBatch || i == cell.probePositions.Length - 1)
{
idxInBatch = 0;
MaterialPropertyBlock prop = new MaterialPropertyBlock();
prop.SetFloatArray("_Validity", validity);
prop.SetFloatArray("_RelativeSize", relativeSize);
prop.SetVectorArray("_IndexInAtlas", texels);
props.Add(prop);
probeBuffers.Add(probeBuffer.ToArray());
probeBuffer = new List<Matrix4x4>();
}
}
m_CellDebugData.Add(debugData);
}
}
void OnClearLightingdata()
{
ClearDebugData();
}
}
}

View file

@ -0,0 +1,16 @@
//
// This file was automatically generated. Please don't edit by hand. Execute Editor command [ Edit > Rendering > Generate Shader Includes ] instead
//
#ifndef PROBEREFERENCEVOLUME_DEBUG_CS_HLSL
#define PROBEREFERENCEVOLUME_DEBUG_CS_HLSL
//
// UnityEngine.Experimental.Rendering.DebugProbeShadingMode: static fields
//
#define DEBUGPROBESHADINGMODE_SH (0)
#define DEBUGPROBESHADINGMODE_VALIDITY (1)
#define DEBUGPROBESHADINGMODE_VALIDITY_OVER_DILATION_THRESHOLD (2)
#define DEBUGPROBESHADINGMODE_SIZE (3)
#endif

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f6e06ceb4cc6a38458f4b614e8000dc8
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2f2e5e8e23a1880449ebe8c1baaa136f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5206b91ddf5158c47918dbb51d045103
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,118 @@
using UnityEngine.Rendering;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.Experimental.Rendering
{
/// <summary>
/// An Asset which holds a set of settings to use with a <see cref="Probe Reference Volume"/>.
/// </summary>
public sealed class ProbeReferenceVolumeProfile : ScriptableObject
{
internal enum Version
{
Initial,
}
[SerializeField]
Version version = CoreUtils.GetLastEnumValue<Version>();
/// <summary>
/// How many levels contains the probes hierarchical structure.
/// </summary>
[Range(2, 5)]
public int simplificationLevels = 3;
/// <summary>
/// The size of a Cell in number of bricks.
/// </summary>
public int cellSizeInBricks => (int)Mathf.Pow(3, simplificationLevels);
/// <summary>
/// The minimum distance between two probes in meters.
/// </summary>
[Min(0.1f)]
public float minDistanceBetweenProbes = 1.0f;
/// <summary>
/// Maximum subdivision in the structure.
/// </summary>
public int maxSubdivision => simplificationLevels + 1; // we add one for the top subdiv level which is the same size as a cell
/// <summary>
/// Minimum size of a brick in meters.
/// </summary>
public float minBrickSize => Mathf.Max(0.01f, minDistanceBetweenProbes * 3.0f);
/// <summary>
/// Size of the cell in meters.
/// </summary>
public float cellSizeInMeters => (float)cellSizeInBricks * minBrickSize;
void OnEnable()
{
if (version != CoreUtils.GetLastEnumValue<Version>())
{
// Migration code
}
}
/// <summary>
/// Determines if the Probe Reference Volume Profile is equivalent to another one.
/// </summary>
/// <param name ="otherProfile">The profile to compare with.</param>
/// <returns>Whether the Probe Reference Volume Profile is equivalent to another one.</returns>
public bool IsEquivalent(ProbeReferenceVolumeProfile otherProfile)
{
return minDistanceBetweenProbes == otherProfile.minDistanceBetweenProbes &&
cellSizeInMeters == otherProfile.cellSizeInMeters &&
simplificationLevels == otherProfile.simplificationLevels;
}
}
#if UNITY_EDITOR
[CanEditMultipleObjects]
[CustomEditor(typeof(ProbeReferenceVolumeProfile))]
internal class ProbeReferenceVolumeProfileEditor : Editor
{
SerializedProperty m_CellSize;
SerializedProperty m_MinDistanceBetweenProbes;
SerializedProperty m_SimplificationLevels;
ProbeReferenceVolumeProfile profile => target as ProbeReferenceVolumeProfile;
static class Styles
{
// TODO: Better tooltip are needed here.
public static readonly GUIContent simplificationLevels = new GUIContent("Simplification levels", "Determine how many bricks are in a streamable unit. Each simplification step adds a brick level above the minimum one.");
public static readonly string simplificationLevelsHighWarning = "High simplification levels have a big memory overhead, they are not recommended except for testing purposes.";
public static readonly GUIContent minDistanceBetweenProbes = new GUIContent("Min Distance Between Probes", "The minimal distance between two probes in meters.");
public static readonly GUIContent indexDimensions = new GUIContent("Index Dimensions", "The dimensions of the index buffer.");
}
void OnEnable()
{
m_CellSize = serializedObject.FindProperty(nameof(ProbeReferenceVolumeProfile.cellSizeInBricks));
m_MinDistanceBetweenProbes = serializedObject.FindProperty(nameof(ProbeReferenceVolumeProfile.minDistanceBetweenProbes));
m_SimplificationLevels = serializedObject.FindProperty(nameof(ProbeReferenceVolumeProfile.simplificationLevels));
}
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
serializedObject.Update();
EditorGUILayout.PropertyField(m_SimplificationLevels, Styles.simplificationLevels);
if (m_SimplificationLevels.intValue == 5)
{
EditorGUILayout.HelpBox(Styles.simplificationLevelsHighWarning, MessageType.Warning);
}
EditorGUILayout.PropertyField(m_MinDistanceBetweenProbes, Styles.minDistanceBetweenProbes);
EditorGUILayout.HelpBox($"The distance between probes will fluctuate between : {profile.minDistanceBetweenProbes}m and {profile.cellSizeInMeters / 3.0f}m", MessageType.Info);
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
}
}
#endif
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4881f9a2c4d568047b316028d20a8dca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,317 @@
using System.Collections.Generic;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.Experimental.Rendering
{
/// <summary>
/// A marker to determine what area of the scene is considered by the Probe Volumes system
/// </summary>
[ExecuteAlways]
[AddComponentMenu("Light/Probe Volume (Experimental)")]
public class ProbeVolume : MonoBehaviour
{
public bool globalVolume = false;
public Vector3 size = new Vector3(10, 10, 10);
[HideInInspector, Range(0f, 2f)]
public float geometryDistanceOffset = 0.2f;
public LayerMask objectLayerMask = -1;
[HideInInspector]
public int lowestSubdivLevelOverride = 0;
[HideInInspector]
public int highestSubdivLevelOverride = -1;
[HideInInspector]
public bool overridesSubdivLevels = false;
[SerializeField] internal bool mightNeedRebaking = false;
[SerializeField] internal Matrix4x4 cachedTransform;
[SerializeField] internal int cachedHashCode;
#if UNITY_EDITOR
/// <summary>
/// Returns the extents of the volume.
/// </summary>
/// <returns>The extents of the ProbeVolume.</returns>
public Vector3 GetExtents()
{
return size;
}
internal void UpdateGlobalVolume(Scene scene)
{
if (gameObject.scene != scene) return;
Bounds bounds = new Bounds();
bool foundABound = false;
bool ContributesToGI(Renderer renderer)
{
var flags = GameObjectUtility.GetStaticEditorFlags(renderer.gameObject) & StaticEditorFlags.ContributeGI;
return (flags & StaticEditorFlags.ContributeGI) != 0;
}
void ExpandBounds(Bounds currBound)
{
if (!foundABound)
{
bounds = currBound;
foundABound = true;
}
else
{
bounds.Encapsulate(currBound);
}
}
var renderers = UnityEngine.GameObject.FindObjectsOfType<Renderer>();
foreach (Renderer renderer in renderers)
{
bool contributeGI = ContributesToGI(renderer) && renderer.gameObject.activeInHierarchy && renderer.enabled;
if (contributeGI && renderer.gameObject.scene == scene)
{
ExpandBounds(renderer.bounds);
}
}
transform.position = bounds.center;
float minBrickSize = ProbeReferenceVolume.instance.MinBrickSize();
Vector3 tmpClamp = (bounds.size + new Vector3(minBrickSize, minBrickSize, minBrickSize));
tmpClamp.x = Mathf.Max(0f, tmpClamp.x);
tmpClamp.y = Mathf.Max(0f, tmpClamp.y);
tmpClamp.z = Mathf.Max(0f, tmpClamp.z);
size = tmpClamp;
}
internal void OnLightingDataAssetCleared()
{
mightNeedRebaking = true;
}
internal void OnBakeCompleted()
{
// We cache the data of last bake completed.
cachedTransform = gameObject.transform.worldToLocalMatrix;
cachedHashCode = GetHashCode();
mightNeedRebaking = false;
}
public override int GetHashCode()
{
int hash = 17;
unchecked
{
hash = hash * 23 + size.GetHashCode();
hash = hash * 23 + overridesSubdivLevels.GetHashCode();
hash = hash * 23 + highestSubdivLevelOverride.GetHashCode();
hash = hash * 23 + lowestSubdivLevelOverride.GetHashCode();
hash = hash * 23 + geometryDistanceOffset.GetHashCode();
hash = hash * 23 + objectLayerMask.GetHashCode();
}
return hash;
}
internal float GetMinSubdivMultiplier()
{
float maxSubdiv = ProbeReferenceVolume.instance.GetMaxSubdivision() - 1;
return overridesSubdivLevels ? Mathf.Clamp(lowestSubdivLevelOverride / maxSubdiv, 0.0f, 1.0f) : 0.0f;
}
internal float GetMaxSubdivMultiplier()
{
float maxSubdiv = ProbeReferenceVolume.instance.GetMaxSubdivision() - 1;
return overridesSubdivLevels ? Mathf.Clamp(highestSubdivLevelOverride / maxSubdiv, 0.0f, 1.0f) : 1.0f;
}
// Momentarily moving the gizmo rendering for bricks and cells to Probe Volume itself,
// only the first probe volume in the scene will render them. The reason is that we dont have any
// other non-hidden component related to APV.
#region APVGizmo
static List<ProbeVolume> sProbeVolumeInstances = new();
MeshGizmo brickGizmos;
MeshGizmo cellGizmo;
void DisposeGizmos()
{
brickGizmos?.Dispose();
brickGizmos = null;
cellGizmo?.Dispose();
cellGizmo = null;
}
void OnEnable()
{
sProbeVolumeInstances.Add(this);
}
void OnDisable()
{
sProbeVolumeInstances.Remove(this);
DisposeGizmos();
}
// Only the first PV of the available ones will draw gizmos.
bool IsResponsibleToDrawGizmo() => sProbeVolumeInstances.Count > 0 && sProbeVolumeInstances[0] == this;
internal bool ShouldCullCell(Vector3 cellPosition, Vector3 originWS = default(Vector3))
{
var cellSizeInMeters = ProbeReferenceVolume.instance.MaxBrickSize();
var debugDisplay = ProbeReferenceVolume.instance.debugDisplay;
if (debugDisplay.realtimeSubdivision)
{
var profile = ProbeReferenceVolume.instance.sceneData.GetProfileForScene(gameObject.scene);
if (profile == null)
return true;
cellSizeInMeters = profile.cellSizeInMeters;
}
var cameraTransform = SceneView.lastActiveSceneView.camera.transform;
Vector3 cellCenterWS = cellPosition * cellSizeInMeters + originWS + Vector3.one * (cellSizeInMeters / 2.0f);
// Round down to cell size distance
float roundedDownDist = Mathf.Floor(Vector3.Distance(cameraTransform.position, cellCenterWS) / cellSizeInMeters) * cellSizeInMeters;
if (roundedDownDist > ProbeReferenceVolume.instance.debugDisplay.subdivisionViewCullingDistance)
return true;
var frustumPlanes = GeometryUtility.CalculateFrustumPlanes(SceneView.lastActiveSceneView.camera);
var volumeAABB = new Bounds(cellCenterWS, cellSizeInMeters * Vector3.one);
return !GeometryUtility.TestPlanesAABB(frustumPlanes, volumeAABB);
}
// TODO: We need to get rid of Handles.DrawWireCube to be able to have those at runtime as well.
void OnDrawGizmos()
{
if (!ProbeReferenceVolume.instance.isInitialized || !IsResponsibleToDrawGizmo() || ProbeReferenceVolume.instance.sceneData == null)
return;
var debugDisplay = ProbeReferenceVolume.instance.debugDisplay;
var cellSizeInMeters = ProbeReferenceVolume.instance.MaxBrickSize();
if (debugDisplay.realtimeSubdivision)
{
var profile = ProbeReferenceVolume.instance.sceneData.GetProfileForScene(gameObject.scene);
if (profile == null)
return;
cellSizeInMeters = profile.cellSizeInMeters;
}
if (debugDisplay.drawBricks)
{
var subdivColors = ProbeReferenceVolume.instance.subdivisionDebugColors;
IEnumerable<ProbeBrickIndex.Brick> GetVisibleBricks()
{
if (debugDisplay.realtimeSubdivision)
{
// realtime subdiv cells are already culled
foreach (var kp in ProbeReferenceVolume.instance.realtimeSubdivisionInfo)
{
var cellVolume = kp.Key;
foreach (var brick in kp.Value)
{
yield return brick;
}
}
}
else
{
foreach (var cell in ProbeReferenceVolume.instance.cells.Values)
{
if (ShouldCullCell(cell.position, ProbeReferenceVolume.instance.GetTransform().posWS))
continue;
if (cell.bricks == null)
continue;
foreach (var brick in cell.bricks)
yield return brick;
}
}
}
if (brickGizmos == null)
brickGizmos = new MeshGizmo((int)(Mathf.Pow(3, ProbeBrickIndex.kMaxSubdivisionLevels) * MeshGizmo.vertexCountPerCube));
brickGizmos.Clear();
foreach (var brick in GetVisibleBricks())
{
if (brick.subdivisionLevel < 0)
continue;
Vector3 scaledSize = Vector3.one * Mathf.Pow(3, brick.subdivisionLevel);
Vector3 scaledPos = brick.position + scaledSize / 2;
brickGizmos.AddWireCube(scaledPos, scaledSize, subdivColors[brick.subdivisionLevel]);
}
brickGizmos.RenderWireframe(ProbeReferenceVolume.instance.GetRefSpaceToWS(), gizmoName: "Brick Gizmo Rendering");
}
if (debugDisplay.drawCells)
{
IEnumerable<Vector3> GetVisibleCellCenters()
{
if (debugDisplay.realtimeSubdivision)
{
foreach (var kp in ProbeReferenceVolume.instance.realtimeSubdivisionInfo)
{
kp.Key.CalculateCenterAndSize(out var center, out var _);
yield return center;
}
}
else
{
foreach (var cell in ProbeReferenceVolume.instance.cells.Values)
{
if (ShouldCullCell(cell.position, ProbeReferenceVolume.instance.GetTransform().posWS))
continue;
var positionF = new Vector3(cell.position.x, cell.position.y, cell.position.z);
var center = positionF * cellSizeInMeters + cellSizeInMeters * 0.5f * Vector3.one;
yield return center;
}
}
}
Matrix4x4 trs = Matrix4x4.TRS(ProbeReferenceVolume.instance.GetTransform().posWS, ProbeReferenceVolume.instance.GetTransform().rot, Vector3.one);
// For realtime subdivision, the matrix from ProbeReferenceVolume.instance can be wrong if the profile changed since the last bake
if (debugDisplay.realtimeSubdivision)
trs = Matrix4x4.TRS(transform.position, Quaternion.identity, Vector3.one);
// Fetching this from components instead of from the reference volume allows the user to
// preview how cells will look before they commit to a bake.
Gizmos.color = new Color(0, 1, 0.5f, 0.2f);
Gizmos.matrix = trs;
if (cellGizmo == null)
cellGizmo = new MeshGizmo();
cellGizmo.Clear();
foreach (var center in GetVisibleCellCenters())
{
Gizmos.DrawCube(center, Vector3.one * cellSizeInMeters);
cellGizmo.AddWireCube(center, Vector3.one * cellSizeInMeters, new Color(0, 1, 0.5f, 1));
}
cellGizmo.RenderWireframe(Gizmos.matrix, gizmoName: "Brick Gizmo Rendering");
}
}
#endregion
#endif // UNITY_EDITOR
}
} // UnityEngine.Experimental.Rendering.HDPipeline

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cded085d155cde949b60f67a11dbc3bd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,443 @@
#ifndef __PROBEVOLUME_HLSL__
#define __PROBEVOLUME_HLSL__
#include "Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ShaderVariablesProbeVolumes.cs.hlsl"
#ifndef DECODE_SH
#include "Packages/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/DecodeSH.hlsl"
#endif
#ifndef UNITY_SHADER_VARIABLES_INCLUDED
SAMPLER(s_linear_clamp_sampler);
SAMPLER(s_point_clamp_sampler);
#endif
struct APVResources
{
StructuredBuffer<int> index;
Texture3D L0_L1Rx;
Texture3D L1G_L1Ry;
Texture3D L1B_L1Rz;
Texture3D L2_0;
Texture3D L2_1;
Texture3D L2_2;
Texture3D L2_3;
};
struct APVSample
{
float3 L0;
float3 L1_R;
float3 L1_G;
float3 L1_B;
#ifdef PROBE_VOLUMES_L2
float4 L2_R;
float4 L2_G;
float4 L2_B;
float3 L2_C;
#endif
#define APV_SAMPLE_STATUS_INVALID -1
#define APV_SAMPLE_STATUS_ENCODED 0
#define APV_SAMPLE_STATUS_DECODED 1
int status;
// Note: at the moment this is called at the moment the struct is built, but it is kept as a separate step
// as ideally should be called as far as possible from sample to allow for latency hiding.
void Decode()
{
if (status == APV_SAMPLE_STATUS_ENCODED)
{
L1_R = DecodeSH(L0.r, L1_R);
L1_G = DecodeSH(L0.g, L1_G);
L1_B = DecodeSH(L0.b, L1_B);
#ifdef PROBE_VOLUMES_L2
float4 outL2_C = float4(L2_C, 0.0f);
DecodeSH_L2(L0, L2_R, L2_G, L2_B, outL2_C);
L2_C = outL2_C.xyz;
#endif
status = APV_SAMPLE_STATUS_DECODED;
}
}
};
// Resources required for APV
StructuredBuffer<int> _APVResIndex;
StructuredBuffer<uint3> _APVResCellIndices;
TEXTURE3D(_APVResL0_L1Rx);
TEXTURE3D(_APVResL1G_L1Ry);
TEXTURE3D(_APVResL1B_L1Rz);
TEXTURE3D(_APVResL2_0);
TEXTURE3D(_APVResL2_1);
TEXTURE3D(_APVResL2_2);
TEXTURE3D(_APVResL2_3);
// -------------------------------------------------------------
// Indexing functions
// -------------------------------------------------------------
bool LoadCellIndexMetaData(int cellFlatIdx, out int chunkIndex, out int stepSize, out int3 minRelativeIdx, out int3 maxRelativeIdx)
{
bool cellIsLoaded = false;
uint3 metaData = _APVResCellIndices[cellFlatIdx];
if (metaData.x != 0xFFFFFFFF)
{
chunkIndex = metaData.x & 0x1FFFFFFF;
stepSize = pow(3, (metaData.x >> 29) & 0x7);
minRelativeIdx.x = metaData.y & 0x3FF;
minRelativeIdx.y = (metaData.y >> 10) & 0x3FF;
minRelativeIdx.z = (metaData.y >> 20) & 0x3FF;
maxRelativeIdx.x = metaData.z & 0x3FF;
maxRelativeIdx.y = (metaData.z >> 10) & 0x3FF;
maxRelativeIdx.z = (metaData.z >> 20) & 0x3FF;
cellIsLoaded = true;
}
else
{
chunkIndex = -1;
stepSize = -1;
minRelativeIdx = -1;
maxRelativeIdx = -1;
}
return cellIsLoaded;
}
uint GetIndexData(APVResources apvRes, float3 posWS)
{
int3 cellPos = floor(posWS / _CellInMeters);
float3 topLeftCellWS = cellPos * _CellInMeters;
// Make sure we start from 0
cellPos -= (int3)_MinCellPosition;
int flatIdx = cellPos.z * (_CellIndicesDim.x * _CellIndicesDim.y) + cellPos.y * _CellIndicesDim.x + cellPos.x;
int stepSize = 0;
int3 minRelativeIdx, maxRelativeIdx;
int chunkIdx = -1;
bool isValidBrick = true;
int locationInPhysicalBuffer = 0;
if (LoadCellIndexMetaData(flatIdx, chunkIdx, stepSize, minRelativeIdx, maxRelativeIdx))
{
float3 residualPosWS = posWS - topLeftCellWS;
int3 localBrickIndex = floor(residualPosWS / (_MinBrickSize * stepSize));
// Out of bounds.
if (any(localBrickIndex < minRelativeIdx || localBrickIndex >= maxRelativeIdx))
{
isValidBrick = false;
}
int3 sizeOfValid = maxRelativeIdx - minRelativeIdx;
// Relative to valid region
int3 localRelativeIndexLoc = (localBrickIndex - minRelativeIdx);
int flattenedLocationInCell = localRelativeIndexLoc.z * (sizeOfValid.x * sizeOfValid.y) + localRelativeIndexLoc.x * sizeOfValid.y + localRelativeIndexLoc.y;
locationInPhysicalBuffer = chunkIdx * _IndexChunkSize + flattenedLocationInCell;
}
else
{
isValidBrick = false;
}
return isValidBrick ? apvRes.index[locationInPhysicalBuffer] : 0xffffffff;
}
// -------------------------------------------------------------
// Loading functions
// -------------------------------------------------------------
APVResources FillAPVResources()
{
APVResources apvRes;
apvRes.index = _APVResIndex;
apvRes.L0_L1Rx = _APVResL0_L1Rx;
apvRes.L1G_L1Ry = _APVResL1G_L1Ry;
apvRes.L1B_L1Rz = _APVResL1B_L1Rz;
apvRes.L2_0 = _APVResL2_0;
apvRes.L2_1 = _APVResL2_1;
apvRes.L2_2 = _APVResL2_2;
apvRes.L2_3 = _APVResL2_3;
return apvRes;
}
bool TryToGetPoolUVWAndSubdiv(APVResources apvRes, float3 posWS, float3 normalWS, float3 viewDirWS, out float3 uvw, out uint subdiv)
{
uvw = 0;
// Note: we could instead early return when we know we'll have invalid UVs, but some bade code gen on Vulkan generates shader warnings if we do.
bool hasValidUVW = true;
float4 posWSForSample = float4(posWS + normalWS * _NormalBias
+ viewDirWS * _ViewBias, 1.0);
uint3 poolDim = (uint3)_PoolDim;
// resolve the index
float3 posRS = posWSForSample.xyz / _MinBrickSize;
uint packed_pool_idx = GetIndexData(apvRes, posWSForSample.xyz);
// no valid brick loaded for this index, fallback to ambient probe
if (packed_pool_idx == 0xffffffff)
{
hasValidUVW = false;
}
// unpack pool idx
// size is encoded in the upper 4 bits
subdiv = (packed_pool_idx >> 28) & 15;
float cellSize = pow(3.0, subdiv);
uint flattened_pool_idx = packed_pool_idx & ((1 << 28) - 1);
uint3 pool_idx;
pool_idx.z = flattened_pool_idx / (poolDim.x * poolDim.y);
flattened_pool_idx -= pool_idx.z * (poolDim.x * poolDim.y);
pool_idx.y = flattened_pool_idx / poolDim.x;
pool_idx.x = flattened_pool_idx - (pool_idx.y * poolDim.x);
uvw = ((float3) pool_idx + 0.5) / _PoolDim;
// calculate uv offset and scale
float3 offset = frac(posRS / (float)cellSize); // [0;1] in brick space
//offset = clamp( offset, 0.25, 0.75 ); // [0.25;0.75] in brick space (is this actually necessary?)
offset *= 3.0 / _PoolDim; // convert brick footprint to texels footprint in pool texel space
uvw += offset; // add the final offset
return hasValidUVW;
}
bool TryToGetPoolUVW(APVResources apvRes, float3 posWS, float3 normalWS, float3 viewDir, out float3 uvw)
{
uint unusedSubdiv;
return TryToGetPoolUVWAndSubdiv(apvRes, posWS, normalWS, viewDir, uvw, unusedSubdiv);
}
APVSample SampleAPV(APVResources apvRes, float3 uvw)
{
APVSample apvSample;
float4 L0_L1Rx = SAMPLE_TEXTURE3D_LOD(apvRes.L0_L1Rx, s_linear_clamp_sampler, uvw, 0).rgba;
float4 L1G_L1Ry = SAMPLE_TEXTURE3D_LOD(apvRes.L1G_L1Ry, s_linear_clamp_sampler, uvw, 0).rgba;
float4 L1B_L1Rz = SAMPLE_TEXTURE3D_LOD(apvRes.L1B_L1Rz, s_linear_clamp_sampler, uvw, 0).rgba;
apvSample.L0 = L0_L1Rx.xyz;
apvSample.L1_R = float3(L0_L1Rx.w, L1G_L1Ry.w, L1B_L1Rz.w);
apvSample.L1_G = L1G_L1Ry.xyz;
apvSample.L1_B = L1B_L1Rz.xyz;
#ifdef PROBE_VOLUMES_L2
apvSample.L2_R = SAMPLE_TEXTURE3D_LOD(apvRes.L2_0, s_linear_clamp_sampler, uvw, 0).rgba;
apvSample.L2_G = SAMPLE_TEXTURE3D_LOD(apvRes.L2_1, s_linear_clamp_sampler, uvw, 0).rgba;
apvSample.L2_B = SAMPLE_TEXTURE3D_LOD(apvRes.L2_2, s_linear_clamp_sampler, uvw, 0).rgba;
apvSample.L2_C = SAMPLE_TEXTURE3D_LOD(apvRes.L2_3, s_linear_clamp_sampler, uvw, 0).rgb;
#endif
apvSample.status = APV_SAMPLE_STATUS_ENCODED;
return apvSample;
}
APVSample SampleAPV(APVResources apvRes, float3 posWS, float3 biasNormalWS, float3 viewDir)
{
APVSample outSample;
float3 pool_uvw;
if (TryToGetPoolUVW(apvRes, posWS, biasNormalWS, viewDir, pool_uvw))
{
outSample = SampleAPV(apvRes, pool_uvw);
}
else
{
ZERO_INITIALIZE(APVSample, outSample);
outSample.status = APV_SAMPLE_STATUS_INVALID;
}
return outSample;
}
APVSample SampleAPV(float3 posWS, float3 biasNormalWS, float3 viewDir)
{
APVResources apvRes = FillAPVResources();
return SampleAPV(apvRes, posWS, biasNormalWS, viewDir);
}
// -------------------------------------------------------------
// Internal Evaluation functions (avoid usage in caller code outside this file)
// -------------------------------------------------------------
float3 EvaluateAPVL0(APVSample apvSample)
{
return apvSample.L0;
}
void EvaluateAPVL1(APVSample apvSample, float3 N, out float3 diffuseLighting)
{
diffuseLighting = SHEvalLinearL1(N, apvSample.L1_R, apvSample.L1_G, apvSample.L1_B);
}
#ifdef PROBE_VOLUMES_L2
void EvaluateAPVL1L2(APVSample apvSample, float3 N, out float3 diffuseLighting)
{
EvaluateAPVL1(apvSample, N, diffuseLighting);
diffuseLighting += SHEvalLinearL2(N, apvSample.L2_R, apvSample.L2_G, apvSample.L2_B, float4(apvSample.L2_C, 0.0f));
}
#endif
// -------------------------------------------------------------
// "Public" Evaluation functions, the one that callers outside this file should use
// -------------------------------------------------------------
void EvaluateAdaptiveProbeVolume(APVSample apvSample, float3 normalWS, float3 backNormalWS, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting)
{
if (apvSample.status != APV_SAMPLE_STATUS_INVALID)
{
apvSample.Decode();
#ifdef PROBE_VOLUMES_L1
EvaluateAPVL1(apvSample, normalWS, bakeDiffuseLighting);
EvaluateAPVL1(apvSample, backNormalWS, backBakeDiffuseLighting);
#elif PROBE_VOLUMES_L2
EvaluateAPVL1L2(apvSample, normalWS, bakeDiffuseLighting);
EvaluateAPVL1L2(apvSample, backNormalWS, backBakeDiffuseLighting);
#endif
bakeDiffuseLighting += apvSample.L0;
backBakeDiffuseLighting += apvSample.L0;
}
else
{
// no valid brick, fallback to ambient probe
bakeDiffuseLighting = EvaluateAmbientProbe(normalWS);
backBakeDiffuseLighting = EvaluateAmbientProbe(backNormalWS);
}
}
void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 backNormalWS, in float3 reflDir, in float3 viewDir,
in float2 positionSS, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting, out float3 lightingInReflDir)
{
APVResources apvRes = FillAPVResources();
if (_PVSamplingNoise > 0)
{
float noise1D_0 = (InterleavedGradientNoise(positionSS, 0) * 2.0f - 1.0f) * _PVSamplingNoise;
posWS += noise1D_0;
}
APVSample apvSample = SampleAPV(posWS, normalWS, viewDir);
if (apvSample.status != APV_SAMPLE_STATUS_INVALID)
{
apvSample.Decode();
#ifdef PROBE_VOLUMES_L1
EvaluateAPVL1(apvSample, normalWS, bakeDiffuseLighting);
EvaluateAPVL1(apvSample, backNormalWS, backBakeDiffuseLighting);
EvaluateAPVL1(apvSample, reflDir, lightingInReflDir);
#elif PROBE_VOLUMES_L2
EvaluateAPVL1L2(apvSample, normalWS, bakeDiffuseLighting);
EvaluateAPVL1L2(apvSample, backNormalWS, backBakeDiffuseLighting);
EvaluateAPVL1L2(apvSample, reflDir, lightingInReflDir);
#endif
bakeDiffuseLighting += apvSample.L0;
backBakeDiffuseLighting += apvSample.L0;
lightingInReflDir += apvSample.L0;
}
else
{
bakeDiffuseLighting = EvaluateAmbientProbe(normalWS);
backBakeDiffuseLighting = EvaluateAmbientProbe(backNormalWS);
lightingInReflDir = -1;
}
}
void EvaluateAdaptiveProbeVolume(in float3 posWS, in float3 normalWS, in float3 backNormalWS, in float3 viewDir,
in float2 positionSS, out float3 bakeDiffuseLighting, out float3 backBakeDiffuseLighting)
{
bakeDiffuseLighting = float3(0.0, 0.0, 0.0);
backBakeDiffuseLighting = float3(0.0, 0.0, 0.0);
if (_PVSamplingNoise > 0)
{
float noise1D_0 = (InterleavedGradientNoise(positionSS, 0) * 2.0f - 1.0f) * _PVSamplingNoise;
posWS += noise1D_0;
}
APVSample apvSample = SampleAPV(posWS, normalWS, viewDir);
EvaluateAdaptiveProbeVolume(apvSample, normalWS, backNormalWS, bakeDiffuseLighting, backBakeDiffuseLighting);
}
void EvaluateAdaptiveProbeVolume(in float3 posWS, in float2 positionSS, out float3 bakeDiffuseLighting)
{
APVResources apvRes = FillAPVResources();
if (_PVSamplingNoise > 0)
{
float noise1D_0 = (InterleavedGradientNoise(positionSS, 0) * 2.0f - 1.0f) * _PVSamplingNoise;
posWS += noise1D_0;
}
float3 uvw;
if (TryToGetPoolUVW(apvRes, posWS, 0, 0, uvw))
{
bakeDiffuseLighting = SAMPLE_TEXTURE3D_LOD(apvRes.L0_L1Rx, s_linear_clamp_sampler, uvw, 0).rgb;
}
else
{
bakeDiffuseLighting = EvaluateAmbientProbe(0);
}
}
// -------------------------------------------------------------
// Reflection Probe Normalization functions
// -------------------------------------------------------------
// Same idea as in Rendering of COD:IW [Drobot 2017]
float EvaluateReflectionProbeSH(float3 sampleDir, float4 reflProbeSHL0L1, float4 reflProbeSHL2_1, float reflProbeSHL2_2)
{
float outFactor = 0;
float L0 = reflProbeSHL0L1.x;
float L1 = dot(reflProbeSHL0L1.yzw, sampleDir);
outFactor = L0 + L1;
#ifdef PROBE_VOLUMES_L2
// IMPORTANT: The encoding is unravelled C# side before being sent
float4 vB = sampleDir.xyzz * sampleDir.yzzx;
// First 4 coeff.
float L2 = dot(reflProbeSHL2_1, vB);
float vC = sampleDir.x * sampleDir.x - sampleDir.y * sampleDir.y;
L2 += reflProbeSHL2_2 * vC;
outFactor += L2;
#endif
return outFactor;
}
float GetReflectionProbeNormalizationFactor(float3 lightingInReflDir, float3 sampleDir, float4 reflProbeSHL0L1, float4 reflProbeSHL2_1, float reflProbeSHL2_2)
{
float refProbeNormalization = EvaluateReflectionProbeSH(sampleDir, reflProbeSHL0L1, reflProbeSHL2_1, reflProbeSHL2_2);
float localNormalization = Luminance(lightingInReflDir);
return SafeDiv(localNormalization, refProbeNormalization);
}
#endif // __PROBEVOLUME_HLSL__

View file

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 688129ab15b222340a2c7fa427269889
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
preprocessorOverride: 0
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,103 @@
using System;
using UnityEngine.Serialization;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
namespace UnityEngine.Experimental.Rendering
{
[PreferBinarySerialization]
internal class ProbeVolumeAsset : ScriptableObject
{
[Serializable]
internal enum AssetVersion
{
First,
AddProbeVolumesAtlasEncodingModes,
PV2,
ChunkBasedIndex,
Max,
Current = Max - 1
}
[SerializeField] protected internal int m_Version = (int)AssetVersion.Current;
[SerializeField] public int Version { get => m_Version; }
[SerializeField] internal List<ProbeReferenceVolume.Cell> cells = new List<ProbeReferenceVolume.Cell>();
[SerializeField] internal Vector3Int maxCellPosition;
[SerializeField] internal Vector3Int minCellPosition;
[SerializeField] internal Bounds globalBounds;
[SerializeField] internal ProbeVolumeSHBands bands;
[SerializeField] string m_AssetFullPath = "UNINITIALIZED!";
// Profile info
[SerializeField] internal int cellSizeInBricks;
[SerializeField] internal float minDistanceBetweenProbes;
[SerializeField] internal int simplificationLevels;
internal int maxSubdivision => simplificationLevels + 1; // we add one for the top subdiv level which is the same size as a cell
internal float minBrickSize => Mathf.Max(0.01f, minDistanceBetweenProbes * 3.0f);
internal bool CompatibleWith(ProbeVolumeAsset otherAsset)
{
return (maxSubdivision == otherAsset.maxSubdivision) && (minBrickSize == otherAsset.minBrickSize) && (cellSizeInBricks == otherAsset.cellSizeInBricks);
}
public string GetSerializedFullPath()
{
return m_AssetFullPath;
}
#if UNITY_EDITOR
internal static string GetFileName(Scene scene)
{
string assetName = "ProbeVolumeData";
String scenePath = scene.path;
String sceneDir = System.IO.Path.GetDirectoryName(scenePath);
String sceneName = System.IO.Path.GetFileNameWithoutExtension(scenePath);
String assetPath = System.IO.Path.Combine(sceneDir, sceneName);
if (!UnityEditor.AssetDatabase.IsValidFolder(assetPath))
UnityEditor.AssetDatabase.CreateFolder(sceneDir, sceneName);
String assetFileName = UnityEditor.AssetDatabase.GenerateUniqueAssetPath(assetName + ".asset");
assetFileName = System.IO.Path.Combine(assetPath, assetFileName);
return assetFileName;
}
public static ProbeVolumeAsset CreateAsset(Scene scene)
{
ProbeVolumeAsset asset = ScriptableObject.CreateInstance<ProbeVolumeAsset>();
string assetFileName = GetFileName(scene);
UnityEditor.AssetDatabase.CreateAsset(asset, assetFileName);
asset.m_AssetFullPath = assetFileName;
return asset;
}
internal void StoreProfileData(ProbeReferenceVolumeProfile profile)
{
cellSizeInBricks = profile.cellSizeInBricks;
simplificationLevels = profile.simplificationLevels;
minDistanceBetweenProbes = profile.minDistanceBetweenProbes;
}
#endif
}
#if UNITY_EDITOR
[UnityEditor.CustomEditor(typeof(ProbeVolumeAsset))]
class ProbeVolumeAssetEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{}
}
#endif
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c1043e08cdc375146bdb853794c4c0fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,33 @@
using UnityEngine.Rendering;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.Experimental.Rendering
{
[System.Serializable]
internal struct ProbeDilationSettings
{
public bool enableDilation;
public float dilationDistance;
public float dilationValidityThreshold;
public int dilationIterations;
public bool squaredDistWeighting;
}
[System.Serializable]
internal struct VirtualOffsetSettings
{
public bool useVirtualOffset;
public float outOfGeoOffset;
public float searchMultiplier;
}
// TODO: Use this structure in the actual authoring component rather than just a mean to group output parameters.
[System.Serializable]
internal struct ProbeVolumeBakingProcessSettings
{
public ProbeDilationSettings dilationSettings;
public VirtualOffsetSettings virtualOffsetSettings;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ca179cc72ecf4f4fa7d4ed3bb83ffd2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,144 @@
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using System.IO;
using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.Experimental.Rendering
{
// TMP to be moved to ProbeReferenceVolume when we define the concept, here it is just to make stuff compile
enum ProbeVolumeState
{
Default = 0,
Invalid = 999
}
[ExecuteAlways]
[AddComponentMenu("")] // Hide.
internal class ProbeVolumePerSceneData : MonoBehaviour, ISerializationCallbackReceiver
{
[System.Serializable]
struct SerializableAssetItem
{
[SerializeField] public ProbeVolumeState state;
[SerializeField] public ProbeVolumeAsset asset;
}
internal Dictionary<ProbeVolumeState, ProbeVolumeAsset> assets = new Dictionary<ProbeVolumeState, ProbeVolumeAsset>();
[SerializeField] List<SerializableAssetItem> serializedAssets;
ProbeVolumeState m_CurrentState = ProbeVolumeState.Default;
ProbeVolumeState m_PreviousState = ProbeVolumeState.Invalid;
/// <summary>
/// OnAfterDeserialize implementation.
/// </summary>
public void OnAfterDeserialize()
{
if (serializedAssets == null) return;
assets = new Dictionary<ProbeVolumeState, ProbeVolumeAsset>();
foreach (var assetItem in serializedAssets)
{
assets.Add(assetItem.state, assetItem.asset);
}
}
/// <summary>
/// OnBeforeSerialize implementation.
/// </summary>
public void OnBeforeSerialize()
{
if (assets == null || serializedAssets == null) return;
serializedAssets.Clear();
foreach (var k in assets.Keys)
{
SerializableAssetItem item;
item.state = k;
item.asset = assets[k];
serializedAssets.Add(item);
}
}
internal void StoreAssetForState(ProbeVolumeState state, ProbeVolumeAsset asset)
{
assets[state] = asset;
}
internal void InvalidateAllAssets()
{
foreach (var asset in assets.Values)
{
if (asset != null)
ProbeReferenceVolume.instance.AddPendingAssetRemoval(asset);
}
assets.Clear();
}
internal ProbeVolumeAsset GetCurrentStateAsset()
{
if (assets.ContainsKey(m_CurrentState)) return assets[m_CurrentState];
else return null;
}
internal void QueueAssetLoading()
{
var refVol = ProbeReferenceVolume.instance;
if (assets.ContainsKey(m_CurrentState) && assets[m_CurrentState] != null)
{
refVol.AddPendingAssetLoading(assets[m_CurrentState]);
#if UNITY_EDITOR
if (refVol.sceneData != null)
{
refVol.dilationValidtyThreshold = refVol.sceneData.GetBakeSettingsForScene(gameObject.scene).dilationSettings.dilationValidityThreshold;
}
#endif
m_PreviousState = m_CurrentState;
}
}
internal void QueueAssetRemoval()
{
if (assets.ContainsKey(m_CurrentState) && assets[m_CurrentState] != null)
ProbeReferenceVolume.instance.AddPendingAssetRemoval(assets[m_CurrentState]);
}
void OnEnable()
{
QueueAssetLoading();
}
void OnDisable()
{
QueueAssetRemoval();
}
void OnDestroy()
{
QueueAssetRemoval();
}
void Update()
{
// Query state from ProbeReferenceVolume.instance.
// This is temporary here until we implement a state system.
m_CurrentState = ProbeVolumeState.Default;
if (m_PreviousState != m_CurrentState)
{
if (assets.ContainsKey(m_PreviousState) && assets[m_PreviousState] != null)
ProbeReferenceVolume.instance.AddPendingAssetRemoval(assets[m_PreviousState]);
QueueAssetLoading();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a83d2f7ae04ab6f4f99b0d85377be998
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,102 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEditor;
namespace UnityEngine.Experimental.Rendering
{
using Brick = ProbeBrickIndex.Brick;
using RefTrans = ProbeReferenceVolume.RefVolTransform;
internal static class ProbeVolumePositioning
{
internal static Vector3[] m_Axes = new Vector3[6];
// TODO: Take refvol translation and rotation into account
public static ProbeReferenceVolume.Volume CalculateBrickVolume(in RefTrans refTrans, Brick brick)
{
float scaledSize = Mathf.Pow(3, brick.subdivisionLevel);
Vector3 scaledPos = refTrans.refSpaceToWS.MultiplyPoint(brick.position);
var bounds = new ProbeReferenceVolume.Volume(
scaledPos,
refTrans.refSpaceToWS.GetColumn(0) * scaledSize,
refTrans.refSpaceToWS.GetColumn(1) * scaledSize,
refTrans.refSpaceToWS.GetColumn(2) * scaledSize
);
return bounds;
}
public static bool OBBIntersect(in RefTrans refTrans, Brick brick, in ProbeReferenceVolume.Volume volume)
{
var transformed = CalculateBrickVolume(in refTrans, brick);
return OBBIntersect(in transformed, in volume);
}
public static bool OBBIntersect(in ProbeReferenceVolume.Volume a, in ProbeReferenceVolume.Volume b)
{
// First we test if the bounding spheres intersects, in which case we case do the more complex OBB test
a.CalculateCenterAndSize(out var aCenter, out var aSize);
b.CalculateCenterAndSize(out var bCenter, out var bSize);
var aRadius = aSize.sqrMagnitude / 2.0f;
var bRadius = bSize.sqrMagnitude / 2.0f;
if (Vector3.SqrMagnitude(aCenter - bCenter) > aRadius + bRadius)
return false;
m_Axes[0] = a.X.normalized;
m_Axes[1] = a.Y.normalized;
m_Axes[2] = a.Z.normalized;
m_Axes[3] = b.X.normalized;
m_Axes[4] = b.Y.normalized;
m_Axes[5] = b.Z.normalized;
foreach (Vector3 axis in m_Axes)
{
Vector2 aProj = ProjectOBB(in a, axis);
Vector2 bProj = ProjectOBB(in b, axis);
if (aProj.y < bProj.x || bProj.y < aProj.x)
{
return false;
}
}
return true;
}
static Vector2 ProjectOBB(in ProbeReferenceVolume.Volume a, Vector3 axis)
{
float min = Vector3.Dot(axis, a.corner);
float max = min;
for (int x = 0; x < 2; x++)
{
for (int y = 0; y < 2; y++)
{
for (int z = 0; z < 2; z++)
{
Vector3 vert = a.corner + a.X * x + a.Y * y + a.Z * z;
float proj = Vector3.Dot(axis, vert);
if (proj < min)
{
min = proj;
}
else if (proj > max)
{
max = proj;
}
}
}
}
return new Vector2(min, max);
}
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a55ec696682683040ab8b67b9175f1a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,559 @@
using System;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Reflection;
using System.Linq;
namespace UnityEngine.Experimental.Rendering
{
// Add Profile and baking settings.
[System.Serializable]
/// <summary> A class containing info about the bounds defined by the probe volumes in various scenes. </summary>
public class ProbeVolumeSceneData : ISerializationCallbackReceiver
{
static PropertyInfo s_SceneGUID = typeof(Scene).GetProperty("guid", System.Reflection.BindingFlags.NonPublic | BindingFlags.Instance);
string GetSceneGUID(Scene scene)
{
Debug.Assert(s_SceneGUID != null, "Reflection for scene GUID failed");
return (string)s_SceneGUID.GetValue(scene);
}
[System.Serializable]
struct SerializableBoundItem
{
[SerializeField] public string sceneGUID;
[SerializeField] public Bounds bounds;
}
[System.Serializable]
struct SerializableHasPVItem
{
[SerializeField] public string sceneGUID;
[SerializeField] public bool hasProbeVolumes;
}
[System.Serializable]
struct SerializablePVProfile
{
[SerializeField] public string sceneGUID;
[SerializeField] public ProbeReferenceVolumeProfile profile;
}
[System.Serializable]
struct SerializablePVBakeSettings
{
[SerializeField] public string sceneGUID;
[SerializeField] public ProbeVolumeBakingProcessSettings settings;
}
[System.Serializable]
internal class BakingSet
{
public string name;
public List<string> sceneGUIDs = new List<string>();
public ProbeVolumeBakingProcessSettings settings;
public ProbeReferenceVolumeProfile profile;
}
[SerializeField] List<SerializableBoundItem> serializedBounds;
[SerializeField] List<SerializableHasPVItem> serializedHasVolumes;
[SerializeField] List<SerializablePVProfile> serializedProfiles;
[SerializeField] List<SerializablePVBakeSettings> serializedBakeSettings;
[SerializeField] List<BakingSet> serializedBakingSets;
internal Object parentAsset = null;
internal string parentSceneDataPropertyName;
/// <summary> A dictionary containing the Bounds defined by probe volumes for each scene (scene path is the key of the dictionary). </summary>
public Dictionary<string, Bounds> sceneBounds;
internal Dictionary<string, bool> hasProbeVolumes;
internal Dictionary<string, ProbeReferenceVolumeProfile> sceneProfiles;
internal Dictionary<string, ProbeVolumeBakingProcessSettings> sceneBakingSettings;
internal List<BakingSet> bakingSets;
/// <summary>Constructor for ProbeVolumeSceneData. </summary>
/// <param name="parentAsset">The asset holding this ProbeVolumeSceneData, it will be dirtied every time scene bounds or settings are changed. </param>
/// <param name="parentSceneDataPropertyName">The name of the property holding the ProbeVolumeSceneData in the parentAsset.</param>
public ProbeVolumeSceneData(Object parentAsset, string parentSceneDataPropertyName)
{
this.parentAsset = parentAsset;
this.parentSceneDataPropertyName = parentSceneDataPropertyName;
sceneBounds = new Dictionary<string, Bounds>();
hasProbeVolumes = new Dictionary<string, bool>();
sceneProfiles = new Dictionary<string, ProbeReferenceVolumeProfile>();
sceneBakingSettings = new Dictionary<string, ProbeVolumeBakingProcessSettings>();
bakingSets = new List<BakingSet>();
serializedBounds = new List<SerializableBoundItem>();
serializedHasVolumes = new List<SerializableHasPVItem>();
serializedProfiles = new List<SerializablePVProfile>();
serializedBakeSettings = new List<SerializablePVBakeSettings>();
UpdateBakingSets();
}
/// <summary>Set a reference to the object holding this ProbeVolumeSceneData.</summary>
/// <param name="parentAsset">The object holding this ProbeVolumeSceneData, it will be dirtied every time scene bounds or settings are changed. </param>
/// <param name="parentSceneDataPropertyName">The name of the property holding the ProbeVolumeSceneData in the parentAsset.</param>
public void SetParentObject(Object parent, string parentSceneDataPropertyName)
{
parentAsset = parent;
this.parentSceneDataPropertyName = parentSceneDataPropertyName;
UpdateBakingSets();
}
/// <summary>
/// OnAfterDeserialize implementation.
/// </summary>
public void OnAfterDeserialize()
{
// We haven't initialized the bounds, no need to do anything here.
if (serializedBounds == null || serializedHasVolumes == null ||
serializedProfiles == null || serializedBakeSettings == null) return;
sceneBounds = new Dictionary<string, Bounds>();
hasProbeVolumes = new Dictionary<string, bool>();
sceneProfiles = new Dictionary<string, ProbeReferenceVolumeProfile>();
sceneBakingSettings = new Dictionary<string, ProbeVolumeBakingProcessSettings>();
bakingSets = new List<BakingSet>();
foreach (var boundItem in serializedBounds)
{
sceneBounds.Add(boundItem.sceneGUID, boundItem.bounds);
}
foreach (var boundItem in serializedHasVolumes)
{
hasProbeVolumes.Add(boundItem.sceneGUID, boundItem.hasProbeVolumes);
}
foreach (var profileItem in serializedProfiles)
{
sceneProfiles.Add(profileItem.sceneGUID, profileItem.profile);
}
foreach (var settingsItem in serializedBakeSettings)
{
sceneBakingSettings.Add(settingsItem.sceneGUID, settingsItem.settings);
}
foreach (var set in serializedBakingSets)
bakingSets.Add(set);
}
// This function must not be called during the serialization (because of asset creation)
void UpdateBakingSets()
{
foreach (var set in serializedBakingSets)
{
// Small migration code to ensure that old sets have correct settings
if (set.profile == null)
InitializeBakingSet(set, set.name);
}
// Initialize baking set in case it's empty:
if (bakingSets.Count == 0)
{
var set = CreateNewBakingSet("Default");
set.sceneGUIDs = serializedProfiles.Select(s => s.sceneGUID).ToList();
}
SyncBakingSetSettings();
}
/// <summary>
/// OnBeforeSerialize implementation.
/// </summary>
public void OnBeforeSerialize()
{
// We haven't initialized the bounds, no need to do anything here.
if (sceneBounds == null || hasProbeVolumes == null || sceneBakingSettings == null || sceneProfiles == null ||
serializedBounds == null || serializedHasVolumes == null || serializedBakeSettings == null || serializedProfiles == null
|| serializedBakingSets == null) return;
serializedBounds.Clear();
serializedHasVolumes.Clear();
serializedProfiles.Clear();
serializedBakeSettings.Clear();
serializedBakingSets.Clear();
foreach (var k in sceneBounds.Keys)
{
SerializableBoundItem item;
item.sceneGUID = k;
item.bounds = sceneBounds[k];
serializedBounds.Add(item);
}
foreach (var k in hasProbeVolumes.Keys)
{
SerializableHasPVItem item;
item.sceneGUID = k;
item.hasProbeVolumes = hasProbeVolumes[k];
serializedHasVolumes.Add(item);
}
foreach (var k in sceneBakingSettings.Keys)
{
SerializablePVBakeSettings item;
item.sceneGUID = k;
item.settings = sceneBakingSettings[k];
serializedBakeSettings.Add(item);
}
foreach (var k in sceneProfiles.Keys)
{
SerializablePVProfile item;
item.sceneGUID = k;
item.profile = sceneProfiles[k];
serializedProfiles.Add(item);
}
foreach (var set in bakingSets)
serializedBakingSets.Add(set);
}
internal BakingSet CreateNewBakingSet(string name)
{
BakingSet set = new BakingSet();
// Initialize new baking set settings
InitializeBakingSet(set, name);
bakingSets.Add(set);
return set;
}
void InitializeBakingSet(BakingSet set, string name)
{
var newProfile = ScriptableObject.CreateInstance<ProbeReferenceVolumeProfile>();
#if UNITY_EDITOR
ProjectWindowUtil.CreateAsset(newProfile, name + ".asset");
#endif
set.name = name;
set.profile = newProfile;
set.settings = new ProbeVolumeBakingProcessSettings
{
dilationSettings = new ProbeDilationSettings
{
enableDilation = true,
dilationDistance = 1,
dilationValidityThreshold = 0.25f,
dilationIterations = 1,
squaredDistWeighting = true,
},
virtualOffsetSettings = new VirtualOffsetSettings
{
useVirtualOffset = true,
outOfGeoOffset = 0.01f,
searchMultiplier = 0.2f,
}
};
}
internal void SyncBakingSetSettings()
{
// Sync all the scene settings in the set to avoid config mismatch.
foreach (var set in bakingSets)
{
foreach (var guid in set.sceneGUIDs)
{
sceneBakingSettings[guid] = set.settings;
sceneProfiles[guid] = set.profile;
}
}
}
#if UNITY_EDITOR
private int FindInflatingBrickSize(Vector3 size, ProbeVolume pv)
{
var refVol = ProbeReferenceVolume.instance;
float minSizedDim = Mathf.Min(size.x, Mathf.Min(size.y, size.z));
float minBrickSize = refVol.MinBrickSize();
float minSideInBricks = Mathf.CeilToInt(minSizedDim / minBrickSize);
int absoluteMaxSubdiv = ProbeReferenceVolume.instance.GetMaxSubdivision() - 1;
minSideInBricks = Mathf.Max(minSideInBricks, Mathf.Pow(3, absoluteMaxSubdiv - (pv.overridesSubdivLevels ? pv.highestSubdivLevelOverride : 0)));
int subdivLevel = Mathf.FloorToInt(Mathf.Log(minSideInBricks, 3));
return subdivLevel;
}
private void InflateBound(ref Bounds bounds, ProbeVolume pv)
{
Bounds originalBounds = bounds;
// Round the probe volume bounds to cell size
float cellSize = ProbeReferenceVolume.instance.MaxBrickSize();
// Expand the probe volume bounds to snap on the cell size grid
bounds.Encapsulate(new Vector3(cellSize * Mathf.Floor(bounds.min.x / cellSize),
cellSize * Mathf.Floor(bounds.min.y / cellSize),
cellSize * Mathf.Floor(bounds.min.z / cellSize)));
bounds.Encapsulate(new Vector3(cellSize * Mathf.Ceil(bounds.max.x / cellSize),
cellSize * Mathf.Ceil(bounds.max.y / cellSize),
cellSize * Mathf.Ceil(bounds.max.z / cellSize)));
// calculate how much padding we need to remove according to the brick generation in ProbePlacement.cs:
var cellSizeVector = new Vector3(cellSize, cellSize, cellSize);
var minPadding = (bounds.min - originalBounds.min);
var maxPadding = (bounds.max - originalBounds.max);
minPadding = cellSizeVector - new Vector3(Mathf.Abs(minPadding.x), Mathf.Abs(minPadding.y), Mathf.Abs(minPadding.z));
maxPadding = cellSizeVector - new Vector3(Mathf.Abs(maxPadding.x), Mathf.Abs(maxPadding.y), Mathf.Abs(maxPadding.z));
// Find the size of the brick we can put for every axis given the padding size
float rightPaddingSubdivLevel = ProbeReferenceVolume.instance.BrickSize(FindInflatingBrickSize(new Vector3(maxPadding.x, originalBounds.size.y, originalBounds.size.z), pv));
float leftPaddingSubdivLevel = ProbeReferenceVolume.instance.BrickSize(FindInflatingBrickSize(new Vector3(minPadding.x, originalBounds.size.y, originalBounds.size.z), pv));
float topPaddingSubdivLevel = ProbeReferenceVolume.instance.BrickSize(FindInflatingBrickSize(new Vector3(originalBounds.size.x, maxPadding.y, originalBounds.size.z), pv));
float bottomPaddingSubdivLevel = ProbeReferenceVolume.instance.BrickSize(FindInflatingBrickSize(new Vector3(originalBounds.size.x, minPadding.y, originalBounds.size.z), pv));
float forwardPaddingSubdivLevel = ProbeReferenceVolume.instance.BrickSize(FindInflatingBrickSize(new Vector3(originalBounds.size.x, originalBounds.size.y, maxPadding.z), pv));
float backPaddingSubdivLevel = ProbeReferenceVolume.instance.BrickSize(FindInflatingBrickSize(new Vector3(originalBounds.size.x, originalBounds.size.y, minPadding.z), pv));
// Remove the extra padding caused by cell rounding
bounds.min = bounds.min + new Vector3(
leftPaddingSubdivLevel * Mathf.Floor(Mathf.Abs(bounds.min.x - originalBounds.min.x) / (float)leftPaddingSubdivLevel),
bottomPaddingSubdivLevel * Mathf.Floor(Mathf.Abs(bounds.min.y - originalBounds.min.y) / (float)bottomPaddingSubdivLevel),
backPaddingSubdivLevel * Mathf.Floor(Mathf.Abs(bounds.min.z - originalBounds.min.z) / (float)backPaddingSubdivLevel)
);
bounds.max = bounds.max - new Vector3(
rightPaddingSubdivLevel * Mathf.Floor(Mathf.Abs(bounds.max.x - originalBounds.max.x) / (float)rightPaddingSubdivLevel),
topPaddingSubdivLevel * Mathf.Floor(Mathf.Abs(bounds.max.y - originalBounds.max.y) / (float)topPaddingSubdivLevel),
forwardPaddingSubdivLevel * Mathf.Floor(Mathf.Abs(bounds.max.z - originalBounds.max.z) / (float)forwardPaddingSubdivLevel)
);
}
internal void UpdateSceneBounds(Scene scene)
{
var volumes = UnityEngine.GameObject.FindObjectsOfType<ProbeVolume>();
// If we have not yet loaded any asset, we haven't initialized the probe reference volume with any info from the profile.
// As a result we need to prime with the profile info directly stored here.
{
var profile = GetProfileForScene(scene);
if (profile == null)
{
if (volumes.Length > 0)
{
Debug.LogWarning("A probe volume is present in the scene but a profile has not been set. Please configure a profile for your scene in the Probe Volume Baking settings.");
}
return;
}
ProbeReferenceVolume.instance.SetMinBrickAndMaxSubdiv(profile.minBrickSize, profile.maxSubdivision);
}
var sceneGUID = GetSceneGUID(scene);
bool boundFound = false;
Bounds newBound = new Bounds();
foreach (var volume in volumes)
{
if (volume.globalVolume)
volume.UpdateGlobalVolume(scene);
var volumeSceneGUID = GetSceneGUID(volume.gameObject.scene);
if (volumeSceneGUID == sceneGUID)
{
var pos = volume.gameObject.transform.position;
var extent = volume.GetExtents();
Bounds localBounds = new Bounds(pos, extent);
InflateBound(ref localBounds, volume);
if (!boundFound)
{
newBound = localBounds;
boundFound = true;
}
else
{
newBound.Encapsulate(localBounds);
}
}
}
if (boundFound)
{
if (sceneBounds == null)
{
sceneBounds = new Dictionary<string, Bounds>();
hasProbeVolumes = new Dictionary<string, bool>();
}
if (sceneBounds.ContainsKey(sceneGUID))
{
sceneBounds[sceneGUID] = newBound;
}
else
{
sceneBounds.Add(sceneGUID, newBound);
}
}
if (hasProbeVolumes.ContainsKey(sceneGUID))
hasProbeVolumes[sceneGUID] = boundFound;
else
hasProbeVolumes.Add(sceneGUID, boundFound);
if (parentAsset != null)
{
EditorUtility.SetDirty(parentAsset);
}
}
internal void EnsureSceneHasProbeVolumeIsValid(Scene scene)
{
var sceneGUID = GetSceneGUID(scene);
var volumes = UnityEngine.GameObject.FindObjectsOfType<ProbeVolume>();
foreach (var volume in volumes)
{
if (GetSceneGUID(volume.gameObject.scene) == sceneGUID)
{
hasProbeVolumes[sceneGUID] = true;
return;
}
}
hasProbeVolumes[sceneGUID] = false;
}
// It is important this is called after UpdateSceneBounds is called!
internal void EnsurePerSceneData(Scene scene)
{
var sceneGUID = GetSceneGUID(scene);
if (hasProbeVolumes.ContainsKey(sceneGUID) && hasProbeVolumes[sceneGUID])
{
var perSceneData = UnityEngine.GameObject.FindObjectsOfType<ProbeVolumePerSceneData>();
bool foundPerSceneData = false;
foreach (var data in perSceneData)
{
if (GetSceneGUID(data.gameObject.scene) == sceneGUID)
{
foundPerSceneData = true;
}
}
if (!foundPerSceneData)
{
GameObject go = new GameObject("ProbeVolumePerSceneData");
go.hideFlags |= HideFlags.HideInHierarchy;
go.AddComponent<ProbeVolumePerSceneData>();
SceneManager.MoveGameObjectToScene(go, scene);
}
}
}
internal void EnsureSceneIsInBakingSet(Scene scene)
{
var sceneGUID = GetSceneGUID(scene);
foreach (var set in bakingSets)
if (set.sceneGUIDs.Contains(sceneGUID))
return;
// The scene is not in a baking set, we need to add it
if (bakingSets.Count == 0)
return; // Technically shouldn't be possible since it's blocked in the UI
bakingSets[0].sceneGUIDs.Add(sceneGUID);
SyncBakingSetSettings();
}
internal string GetFirstProbeVolumeSceneGUID(ProbeVolumeSceneData.BakingSet set)
{
foreach (var guid in set.sceneGUIDs)
{
if (sceneBakingSettings.ContainsKey(guid) && sceneProfiles.ContainsKey(guid))
return guid;
}
return null;
}
internal void OnSceneSaved(Scene scene)
{
EnsureSceneHasProbeVolumeIsValid(scene);
EnsureSceneIsInBakingSet(scene);
EnsurePerSceneData(scene);
UpdateSceneBounds(scene);
}
internal void SetProfileForScene(Scene scene, ProbeReferenceVolumeProfile profile)
{
if (sceneProfiles == null) sceneProfiles = new Dictionary<string, ProbeReferenceVolumeProfile>();
var sceneGUID = GetSceneGUID(scene);
sceneProfiles[sceneGUID] = profile;
}
internal void SetProfileForScene(string sceneGUID, ProbeReferenceVolumeProfile profile)
{
if (sceneProfiles == null) sceneProfiles = new Dictionary<string, ProbeReferenceVolumeProfile>();
sceneProfiles[sceneGUID] = profile;
}
internal void SetBakeSettingsForScene(Scene scene, ProbeDilationSettings dilationSettings, VirtualOffsetSettings virtualOffsetSettings)
{
if (sceneBakingSettings == null) sceneBakingSettings = new Dictionary<string, ProbeVolumeBakingProcessSettings>();
var sceneGUID = GetSceneGUID(scene);
ProbeVolumeBakingProcessSettings settings = new ProbeVolumeBakingProcessSettings();
settings.dilationSettings = dilationSettings;
settings.virtualOffsetSettings = virtualOffsetSettings;
sceneBakingSettings[sceneGUID] = settings;
}
internal ProbeReferenceVolumeProfile GetProfileForScene(Scene scene)
{
var sceneGUID = GetSceneGUID(scene);
if (sceneProfiles != null && sceneProfiles.ContainsKey(sceneGUID))
return sceneProfiles[sceneGUID];
return null;
}
internal bool BakeSettingsDefinedForScene(Scene scene)
{
var sceneGUID = GetSceneGUID(scene);
return sceneBakingSettings.ContainsKey(sceneGUID);
}
internal ProbeVolumeBakingProcessSettings GetBakeSettingsForScene(Scene scene)
{
var sceneGUID = GetSceneGUID(scene);
if (sceneBakingSettings != null && sceneBakingSettings.ContainsKey(sceneGUID))
return sceneBakingSettings[sceneGUID];
return new ProbeVolumeBakingProcessSettings();
}
// This is sub-optimal, but because is called once when kicking off a bake
internal BakingSet GetBakingSetForScene(Scene scene)
{
var sceneGUID = GetSceneGUID(scene);
foreach (var set in bakingSets)
{
foreach (var guidInSet in set.sceneGUIDs)
{
if (guidInSet == sceneGUID)
return set;
}
}
return null;
}
internal bool SceneHasProbeVolumes(Scene scene)
{
var sceneGUID = GetSceneGUID(scene);
return hasProbeVolumes != null && hasProbeVolumes.ContainsKey(sceneGUID) && hasProbeVolumes[sceneGUID];
}
#endif
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 04fb4740d8c6dbc469086e13c435cd4b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,30 @@
using UnityEngine.Rendering;
namespace UnityEngine.Rendering
{
/// <summary>
/// Defines the constant buffer register that will be used as binding point for the Probe Volumes constant buffer.
/// </summary>
public enum APVConstantBufferRegister
{
GlobalRegister = 5
}
[GenerateHLSL(needAccessors = false, generateCBuffer = true, constantRegister = (int)APVConstantBufferRegister.GlobalRegister)]
internal unsafe struct ShaderVariablesProbeVolumes
{
public Vector3 _PoolDim;
public float _ViewBias;
public Vector3 _MinCellPosition;
public float _PVSamplingNoise;
public Vector3 _CellIndicesDim;
public float _CellInMeters;
public float _CellInMinBricks;
public float _MinBrickSize;
public int _IndexChunkSize;
public float _NormalBias;
}
}

View file

@ -0,0 +1,23 @@
//
// This file was automatically generated. Please don't edit by hand. Execute Editor command [ Edit > Rendering > Generate Shader Includes ] instead
//
#ifndef SHADERVARIABLESPROBEVOLUMES_CS_HLSL
#define SHADERVARIABLESPROBEVOLUMES_CS_HLSL
// Generated from UnityEngine.Rendering.ShaderVariablesProbeVolumes
// PackingRules = Exact
GLOBAL_CBUFFER_START(ShaderVariablesProbeVolumes, b5)
float3 _PoolDim;
float _ViewBias;
float3 _MinCellPosition;
float _PVSamplingNoise;
float3 _CellIndicesDim;
float _CellInMeters;
float _CellInMinBricks;
float _MinBrickSize;
int _IndexChunkSize;
float _NormalBias;
CBUFFER_END
#endif

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: dcbd446e83bc87d40b82dd90f8814c3c
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6df0bea367f474d46865e94282c99503
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: