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

375 lines
16 KiB

using System.Linq;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine.SceneManagement;
using UnityEditor;
using Brick = UnityEngine.Rendering.ProbeBrickIndex.Brick;
namespace UnityEngine.Rendering
{
class ProbeVolumeProfileInfo
{
public int simplificationLevels;
public float minDistanceBetweenProbes;
public Vector3 probeOffset;
public int maxSubdivision => ProbeVolumeBakingSet.GetMaxSubdivision(simplificationLevels);
public float minBrickSize => ProbeVolumeBakingSet.GetMinBrickSize(minDistanceBetweenProbes);
public int cellSizeInBricks => ProbeVolumeBakingSet.GetCellSizeInBricks(simplificationLevels);
public float cellSizeInMeters => (float)cellSizeInBricks * minBrickSize;
public Vector3Int PositionToCell(Vector3 position) => Vector3Int.FloorToInt((position - probeOffset) / cellSizeInMeters);
}
public partial class AdaptiveProbeVolumes
{
static internal ProbeVolumeProfileInfo m_ProfileInfo = null;
static void FindWorldBounds()
{
var prv = ProbeReferenceVolume.instance;
prv.clearAssetsOnVolumeClear = true;
var activeScene = SceneManager.GetActiveScene();
var activeSet = ProbeVolumeBakingSet.GetBakingSetForScene(activeScene);
bool hasFoundBounds = false;
foreach (var sceneGUID in activeSet.sceneGUIDs)
{
var bakeData = activeSet.GetSceneBakeData(sceneGUID);
if (bakeData.hasProbeVolume)
{
if (hasFoundBounds)
{
globalBounds.Encapsulate(bakeData.bounds);
}
else
{
globalBounds = bakeData.bounds;
hasFoundBounds = true;
}
}
}
ProbeReferenceVolume.instance.globalBounds = globalBounds;
}
static List<ProbeVolumePerSceneData> GetPerSceneDataList()
{
var fullPerSceneDataList = ProbeReferenceVolume.instance.perSceneDataList;
if (!isBakingSceneSubset)
return fullPerSceneDataList;
List<ProbeVolumePerSceneData> usedPerSceneDataList = new ();
foreach (var sceneData in fullPerSceneDataList)
{
if (partialBakeSceneList.Contains(ProbeReferenceVolume.GetSceneGUID(sceneData.gameObject.scene)))
usedPerSceneDataList.Add(sceneData);
}
return usedPerSceneDataList;
}
internal static List<ProbeVolume> GetProbeVolumeList()
{
var fullPvList = GameObject.FindObjectsByType<ProbeVolume>(FindObjectsSortMode.InstanceID);
List<ProbeVolume> usedPVList;
if (isBakingSceneSubset)
{
usedPVList = new List<ProbeVolume>();
foreach (var pv in fullPvList)
{
if (pv.isActiveAndEnabled && partialBakeSceneList.Contains(ProbeReferenceVolume.GetSceneGUID(pv.gameObject.scene)))
usedPVList.Add(pv);
}
}
else
{
usedPVList = new List<ProbeVolume>(fullPvList);
}
return usedPVList;
}
static ProbeVolumeProfileInfo GetProfileInfoFromBakingSet(ProbeVolumeBakingSet set)
{
var result = new ProbeVolumeProfileInfo();
result.minDistanceBetweenProbes = set.minDistanceBetweenProbes;
result.simplificationLevels = set.simplificationLevels;
result.probeOffset = set.probeOffset;
return result;
}
static int PosToIndex(Vector3Int pos)
{
Vector3Int normalizedPos = pos - minCellPosition;
return normalizedPos.z * (cellCount.x * cellCount.y) + normalizedPos.y * cellCount.x + normalizedPos.x;
}
static internal bool CanFreezePlacement()
{
if (!ProbeReferenceVolume.instance.supportLightingScenarios)
return false;
// Check if all the scene datas in the scene have a baking set, if not then we cannot enable this option.
var sceneDataList = GetPerSceneDataList();
if (sceneDataList.Count == 0)
return false;
foreach (var sceneData in sceneDataList)
{
if (sceneData.serializedBakingSet == null || sceneData.serializedBakingSet.GetSceneCellIndexList(sceneData.sceneGUID) == null)
return false;
}
return true;
}
static NativeList<Vector3> RunPlacement()
{
// Overwrite loaded settings with data from profile. Note that the m_BakingSet.profile is already patched up if isFreezingPlacement
float prevBrickSize = ProbeReferenceVolume.instance.MinBrickSize();
int prevMaxSubdiv = ProbeReferenceVolume.instance.GetMaxSubdivision();
Vector3 prevOffset = ProbeReferenceVolume.instance.ProbeOffset();
ProbeReferenceVolume.instance.SetSubdivisionDimensions(m_ProfileInfo.minBrickSize, m_ProfileInfo.maxSubdivision, m_ProfileInfo.probeOffset);
// All probes need to be baked only once for the whole batch and not once per cell
// The reason is that the baker is not deterministic so the same probe position baked in two different cells may have different values causing seams artefacts.
m_BakingBatch = new BakingBatch(cellCount);
// Run subdivision
ProbeSubdivisionResult result;
using (new BakingSetupProfiling(BakingSetupProfiling.Stages.BakeBricks))
result = GetWorldSubdivision();
// Compute probe positions
NativeList<Vector3> positions;
using (new BakingSetupProfiling(BakingSetupProfiling.Stages.ApplySubdivisionResults))
positions = ApplySubdivisionResults(result);
// Restore loaded asset settings
ProbeReferenceVolume.instance.SetSubdivisionDimensions(prevBrickSize, prevMaxSubdiv, prevOffset);
return positions;
}
static ProbeSubdivisionResult GetWorldSubdivision()
{
if (isFreezingPlacement)
return GetBricksFromLoaded();
var ctx = PrepareProbeSubdivisionContext();
return BakeBricks(ctx, m_BakingBatch.contributors);
}
static NativeList<Vector3> ApplySubdivisionResults(ProbeSubdivisionResult results)
{
int cellIdx = 0, freq = 10; // Don't refresh progress bar at every iteration because it's slow
BakingSetupProfiling.GetProgressRange(out float progress0, out float progress1);
var positions = new NativeList<Vector3>(Allocator.Persistent);
Dictionary<int, int> positionToIndex = new();
foreach ((var position, var bounds, var bricks) in results.cells)
{
if (++cellIdx % freq == 0)
EditorUtility.DisplayProgressBar("Baking Probe Volumes", $"Subdividing cell {cellIdx} out of {results.cells.Count}", Mathf.Lerp(progress0, progress1, cellIdx / (float)results.cells.Count));
int positionStart = positions.Length;
ConvertBricksToPositions(bricks, out var probePositions, out var brickSubdivLevels);
DeduplicateProbePositions(in probePositions, in brickSubdivLevels, positionToIndex, m_BakingBatch, positions, out var probeIndices);
BakingCell cell = new BakingCell()
{
index = PosToIndex(position),
position = position,
bounds = bounds,
bricks = bricks,
probePositions = probePositions,
probeIndices = probeIndices,
};
m_BakingBatch.cells.Add(cell);
m_BakingBatch.cellIndex2SceneReferences[cell.index] = new HashSet<string>(results.scenesPerCells[cell.position]);
}
return positions;
}
private static void DeduplicateProbePositions(in Vector3[] probePositions, in int[] brickSubdivLevel, Dictionary<int, int> positionToIndex, BakingBatch batch,
NativeList<Vector3> uniquePositions, out int[] indices)
{
indices = new int[probePositions.Length];
int uniqueIndex = positionToIndex.Count;
for (int i = 0; i < probePositions.Length; i++)
{
var pos = probePositions[i];
var brickSubdiv = brickSubdivLevel[i];
int probeHash = batch.GetProbePositionHash(pos);
if (positionToIndex.TryGetValue(probeHash, out var index))
{
indices[i] = index;
int oldBrickLevel = batch.uniqueBrickSubdiv[probeHash];
if (brickSubdiv < oldBrickLevel)
batch.uniqueBrickSubdiv[probeHash] = brickSubdiv;
}
else
{
positionToIndex[probeHash] = uniqueIndex;
indices[i] = uniqueIndex;
batch.uniqueBrickSubdiv[probeHash] = brickSubdiv;
uniquePositions.Add(pos);
uniqueIndex++;
}
}
}
static ProbeSubdivisionResult GetBricksFromLoaded()
{
var dataList = GetPerSceneDataList();
var result = new ProbeSubdivisionResult();
foreach (var data in dataList)
{
var cellSize = m_ProfileInfo.minDistanceBetweenProbes * 3.0f * m_ProfileInfo.cellSizeInBricks;
Vector3 cellDimensions = new Vector3(cellSize, cellSize, cellSize);
// Loop through cells in asset, we need to be careful as there'll be duplicates.
// As we go through the cells we fill ProbeSubdivisionResult as we go.
var cells = m_BakingSet.GetSceneCellIndexList(data.sceneGUID);
foreach (var cellIndex in cells)
{
var cellDesc = m_BakingSet.GetCellDesc(cellIndex);
var cellData = m_BakingSet.GetCellData(cellIndex);
var cellPos = cellDesc.position;
if (!result.scenesPerCells.ContainsKey(cellPos))
{
result.scenesPerCells[cellPos] = new HashSet<string>();
var center = new Vector3((cellPos.x + 0.5f) * cellSize, (cellPos.y + 0.5f) * cellSize, (cellPos.z + 0.5f) * cellSize);
result.cells.Add((cellPos, new Bounds(center, cellDimensions), cellData.bricks.ToArray()));
}
result.scenesPerCells[cellPos].Add(data.sceneGUID);
}
}
return result;
}
static internal ProbeSubdivisionContext PrepareProbeSubdivisionContext(bool liveContext = false)
{
ProbeSubdivisionContext ctx = new ProbeSubdivisionContext();
// Prepare all the information in the scene for baking GI.
Vector3 refVolOrigin = Vector3.zero; // TODO: This will need to be center of the world bounds.
var perSceneDataList = GetPerSceneDataList();
if (m_BakingSet == null)
{
if (perSceneDataList.Count == 0) return ctx;
SetBakingContext(perSceneDataList);
}
var profileInfo = m_ProfileInfo;
if (liveContext || m_ProfileInfo == null)
profileInfo = GetProfileInfoFromBakingSet(m_BakingSet);
ctx.Initialize(m_BakingSet, profileInfo, refVolOrigin);
return ctx;
}
static internal ProbeSubdivisionResult BakeBricks(ProbeSubdivisionContext ctx, in GIContributors contributors)
{
var result = new ProbeSubdivisionResult();
if (ctx.probeVolumes.Count == 0)
return result;
using (var gpuResources = ProbePlacement.AllocateGPUResources(ctx.probeVolumes.Count, ctx.profile))
{
// subdivide all the cells and generate brick positions
foreach (var cell in ctx.cells)
{
var scenesInCell = new HashSet<string>();
// Calculate overlaping probe volumes to avoid unnecessary work
var overlappingProbeVolumes = new List<(ProbeVolume component, ProbeReferenceVolume.Volume volume, Bounds bounds)>();
foreach (var probeVolume in ctx.probeVolumes)
{
if (ProbeVolumePositioning.OBBAABBIntersect(probeVolume.volume, cell.bounds, probeVolume.bounds))
{
overlappingProbeVolumes.Add(probeVolume);
scenesInCell.Add(ProbeReferenceVolume.GetSceneGUID(probeVolume.component.gameObject.scene));
}
}
// Calculate valid renderers to avoid unnecessary work (a renderer needs to overlap a probe volume and match the layer)
var filteredContributors = contributors.Filter(ctx.bakingSet, cell.bounds, overlappingProbeVolumes);
if (filteredContributors.Count == 0 && !overlappingProbeVolumes.Any(v => v.component.fillEmptySpaces))
continue;
var bricks = ProbePlacement.SubdivideCell(cell.bounds, ctx, gpuResources, filteredContributors, overlappingProbeVolumes);
if (bricks.Length == 0)
continue;
foreach (var renderer in filteredContributors.renderers)
scenesInCell.Add(ProbeReferenceVolume.GetSceneGUID(renderer.component.gameObject.scene));
foreach (var terrain in filteredContributors.terrains)
scenesInCell.Add(ProbeReferenceVolume.GetSceneGUID(terrain.component.gameObject.scene));
result.cells.Add((cell.position, cell.bounds, bricks));
result.scenesPerCells[cell.position] = scenesInCell;
}
}
return result;
}
static void ModifyProfileFromLoadedData(ProbeVolumeBakingSet bakingSet)
{
m_ProfileInfo.simplificationLevels = bakingSet.bakedSimplificationLevels;
m_ProfileInfo.minDistanceBetweenProbes = bakingSet.bakedMinDistanceBetweenProbes;
m_ProfileInfo.probeOffset = bakingSet.bakedProbeOffset;
globalBounds = bakingSet.globalBounds;
}
// Converts brick information into positional data at kBrickProbeCountPerDim * kBrickProbeCountPerDim * kBrickProbeCountPerDim resolution
internal static void ConvertBricksToPositions(Brick[] bricks, out Vector3[] outProbePositions, out int[] outBrickSubdiv)
{
int posIdx = 0;
float scale = ProbeReferenceVolume.instance.MinBrickSize() / ProbeBrickPool.kBrickCellCount;
Vector3 offset = ProbeReferenceVolume.instance.ProbeOffset();
outProbePositions = new Vector3[bricks.Length * ProbeBrickPool.kBrickProbeCountTotal];
outBrickSubdiv = new int[bricks.Length * ProbeBrickPool.kBrickProbeCountTotal];
foreach (var b in bricks)
{
int brickSize = ProbeReferenceVolume.CellSize(b.subdivisionLevel);
Vector3Int brickOffset = b.position * ProbeBrickPool.kBrickCellCount;
for (int z = 0; z < ProbeBrickPool.kBrickProbeCountPerDim; z++)
{
for (int y = 0; y < ProbeBrickPool.kBrickProbeCountPerDim; y++)
{
for (int x = 0; x < ProbeBrickPool.kBrickProbeCountPerDim; x++)
{
var probeOffset = brickOffset + new Vector3Int(x, y, z) * brickSize;
outProbePositions[posIdx] = offset + (Vector3)probeOffset * scale;
outBrickSubdiv[posIdx] = b.subdivisionLevel;
posIdx++;
}
}
}
}
}
}
}