using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Unity.Collections; using UnityEditor; using UnityEngine.Rendering.UnifiedRayTracing; namespace UnityEngine.Rendering { partial class AdaptiveProbeVolumes { /// /// Virtual offset baker /// public abstract class VirtualOffsetBaker : IDisposable { /// The current baking step. public abstract ulong currentStep { get; } /// The total amount of step. public abstract ulong stepCount { get; } /// Array storing the resulting virtual offsets to be applied to probe positions. public abstract NativeArray offsets { get; } /// /// This is called before the start of baking to allow allocating necessary resources. /// /// The baking set that is currently baked. /// The probe positions. public abstract void Initialize(ProbeVolumeBakingSet bakingSet, NativeArray probePositions); /// /// Run a step of virtual offset baking. Baking is considered done when currentStep property equals stepCount. /// /// Return false if bake failed and should be stopped. public abstract bool Step(); /// /// Performs necessary tasks to free allocated resources. /// public abstract void Dispose(); } class DefaultVirtualOffset : VirtualOffsetBaker { static int k_MaxProbeCountPerBatch = 65535; static readonly int _Probes = Shader.PropertyToID("_Probes"); static readonly int _Offsets = Shader.PropertyToID("_Offsets"); // Duplicated in HLSL struct ProbeData { public Vector3 position; public float originBias; public float tMax; public float geometryBias; public int probeIndex; public float validityThreshold; }; int batchPosIdx; NativeArray positions; NativeArray results; Dictionary cellToVolumes; ProbeData[] probeData; Vector3[] batchResult; float scaleForSearchDist; float rayOriginBias; float geometryBias; float validityThreshold; // Output buffer public override NativeArray offsets => results; private AccelStructAdapter m_AccelerationStructure; private GraphicsBuffer probeBuffer; private GraphicsBuffer offsetBuffer; private GraphicsBuffer scratchBuffer; public override ulong currentStep => (ulong)batchPosIdx; public override ulong stepCount => batchResult == null ? 0 : (ulong)positions.Length; public override void Initialize(ProbeVolumeBakingSet bakingSet, NativeArray probePositions) { var voSettings = bakingSet.settings.virtualOffsetSettings; if (!voSettings.useVirtualOffset) return; batchPosIdx = 0; scaleForSearchDist = voSettings.searchMultiplier; rayOriginBias = voSettings.rayOriginBias; geometryBias = voSettings.outOfGeoOffset; validityThreshold = voSettings.validityThreshold; results = new NativeArray(probePositions.Length, Allocator.Persistent); cellToVolumes = GetTouchupsPerCell(out bool hasAppliers); if (scaleForSearchDist == 0.0f) { if (hasAppliers) DoApplyVirtualOffsetsFromAdjustmentVolumes(probePositions, results, cellToVolumes); return; } positions = probePositions; probeData = new ProbeData[k_MaxProbeCountPerBatch]; batchResult = new Vector3[k_MaxProbeCountPerBatch]; var computeBufferTarget = GraphicsBuffer.Target.CopyDestination | GraphicsBuffer.Target.CopySource | GraphicsBuffer.Target.Structured | GraphicsBuffer.Target.Raw; // Create acceletation structure m_AccelerationStructure = BuildAccelerationStructure(voSettings.collisionMask); var virtualOffsetShader = s_TracingContext.shaderVO; probeBuffer = new GraphicsBuffer(computeBufferTarget, k_MaxProbeCountPerBatch, Marshal.SizeOf()); offsetBuffer = new GraphicsBuffer(computeBufferTarget, k_MaxProbeCountPerBatch, Marshal.SizeOf()); scratchBuffer = RayTracingHelper.CreateScratchBufferForBuildAndDispatch(m_AccelerationStructure.GetAccelerationStructure(), virtualOffsetShader, (uint)k_MaxProbeCountPerBatch, 1, 1); var cmd = new CommandBuffer(); m_AccelerationStructure.Build(cmd, ref scratchBuffer); Graphics.ExecuteCommandBuffer(cmd); cmd.Dispose(); } static AccelStructAdapter BuildAccelerationStructure(int mask) { var accelStruct = s_TracingContext.CreateAccelerationStructure(); var contributors = m_BakingBatch.contributors; foreach (var renderer in contributors.renderers) { int layerMask = 1 << renderer.component.gameObject.layer; if ((layerMask & mask) == 0) continue; if (!s_TracingContext.TryGetMeshForAccelerationStructure(renderer.component, out var mesh)) continue; int subMeshCount = mesh.subMeshCount; var maskAndMatDummy = new uint[subMeshCount]; System.Array.Fill(maskAndMatDummy, 0xFFFFFFFF); accelStruct.AddInstance(renderer.component.GetInstanceID(), renderer.component, maskAndMatDummy, maskAndMatDummy, 1); } foreach (var terrain in contributors.terrains) { int layerMask = 1 << terrain.component.gameObject.layer; if ((layerMask & mask) == 0) continue; accelStruct.AddInstance(terrain.component.GetInstanceID(), terrain.component, new uint[1] { 0xFFFFFFFF }, new uint[1] { 0xFFFFFFFF }, 1); } return accelStruct; } public override bool Step() { if (currentStep >= stepCount) return true; float minBrickSize = m_ProfileInfo.minBrickSize; // Prepare batch int probeCountInBatch = 0; do { int subdivLevel = m_BakingBatch.GetSubdivLevelAt(positions[batchPosIdx]); var brickSize = ProbeReferenceVolume.CellSize(subdivLevel); var searchDistance = (brickSize * minBrickSize) / ProbeBrickPool.kBrickCellCount; var distanceSearch = scaleForSearchDist * searchDistance; int cellIndex = PosToIndex(m_ProfileInfo.PositionToCell(positions[batchPosIdx])); if (cellToVolumes.TryGetValue(cellIndex, out var volumes)) { bool adjusted = false; foreach (var (touchup, obb, center, offset) in volumes.appliers) { if (touchup.ContainsPoint(obb, center, positions[batchPosIdx])) { results[batchPosIdx] = offset; adjusted = true; break; } } if (adjusted) continue; foreach (var (touchup, obb, center) in volumes.overriders) { if (touchup.ContainsPoint(obb, center, positions[batchPosIdx])) { rayOriginBias = touchup.rayOriginBias; geometryBias = touchup.geometryBias; validityThreshold = 1.0f - touchup.virtualOffsetThreshold; break; } } } probeData[probeCountInBatch++] = new ProbeData { position = positions[batchPosIdx], originBias = rayOriginBias, tMax = distanceSearch, geometryBias = geometryBias, validityThreshold = validityThreshold, probeIndex = batchPosIdx, }; } while (++batchPosIdx < positions.Length && probeCountInBatch < k_MaxProbeCountPerBatch); if (probeCountInBatch == 0) return true; // Execute job var cmd = new CommandBuffer(); var virtualOffsetShader = s_TracingContext.shaderVO; m_AccelerationStructure.Bind(cmd, "_AccelStruct", virtualOffsetShader); virtualOffsetShader.SetBufferParam(cmd, _Probes, probeBuffer); virtualOffsetShader.SetBufferParam(cmd, _Offsets, offsetBuffer); cmd.SetBufferData(probeBuffer, probeData); virtualOffsetShader.Dispatch(cmd, scratchBuffer, (uint)probeCountInBatch, 1, 1); Graphics.ExecuteCommandBuffer(cmd); cmd.Clear(); offsetBuffer.GetData(batchResult); for (int i = 0; i < probeCountInBatch; i++) results[probeData[i].probeIndex] = batchResult[i]; cmd.Dispose(); return true; } public override void Dispose() { if (results.IsCreated) results.Dispose(); if (batchResult == null) return; m_AccelerationStructure.Dispose(); probeBuffer.Dispose(); offsetBuffer.Dispose(); scratchBuffer?.Dispose(); } } static internal void RecomputeVOForDebugOnly() { var prv = ProbeReferenceVolume.instance; if (prv.perSceneDataList.Count == 0) return; SetBakingContext(prv.perSceneDataList); if (!m_BakingSet.HasBeenBaked()) return; globalBounds = prv.globalBounds; CellCountInDirections(out minCellPosition, out maxCellPosition, prv.MaxBrickSize(), prv.ProbeOffset()); cellCount = maxCellPosition + Vector3Int.one - minCellPosition; m_BakingBatch = new BakingBatch(cellCount); m_ProfileInfo = new ProbeVolumeProfileInfo(); ModifyProfileFromLoadedData(m_BakingSet); var positionList = new NativeList(Allocator.Persistent); Dictionary positionToIndex = new(); foreach (var cell in ProbeReferenceVolume.instance.cells.Values) { var bakingCell = ConvertCellToBakingCell(cell.desc, cell.data); int numProbes = bakingCell.probePositions.Length; int uniqueIndex = positionToIndex.Count; var indices = new int[numProbes]; // DeduplicateProbePositions for (int i = 0; i < numProbes; i++) { var pos = bakingCell.probePositions[i]; int brickSubdiv = bakingCell.bricks[i / 64].subdivisionLevel; int probeHash = m_BakingBatch.GetProbePositionHash(pos); if (positionToIndex.TryGetValue(probeHash, out var index)) { indices[i] = index; int oldBrickLevel = m_BakingBatch.uniqueBrickSubdiv[probeHash]; if (brickSubdiv < oldBrickLevel) m_BakingBatch.uniqueBrickSubdiv[probeHash] = brickSubdiv; } else { positionToIndex[probeHash] = uniqueIndex; indices[i] = uniqueIndex; m_BakingBatch.uniqueBrickSubdiv[probeHash] = brickSubdiv; positionList.Add(pos); uniqueIndex++; } } bakingCell.probeIndices = indices; m_BakingBatch.cells.Add(bakingCell); // We need to force rebuild debug stuff. cell.debugProbes = null; } VirtualOffsetBaker job = virtualOffsetOverride ?? new DefaultVirtualOffset(); job.Initialize(m_BakingSet, positionList.AsArray()); while (job.currentStep < job.stepCount) job.Step(); foreach (var cell in m_BakingBatch.cells) { int numProbes = cell.probePositions.Length; for (int i = 0; i < numProbes; ++i) { int j = cell.probeIndices[i]; cell.offsetVectors[i] = job.offsets[j]; } } job.Dispose(); // Unload it all as we are gonna load back with newly written cells. foreach (var sceneData in prv.perSceneDataList) prv.AddPendingSceneRemoval(sceneData.sceneGUID); // Make sure unloading happens. prv.PerformPendingOperations(); // Write back the assets. WriteBakingCells(m_BakingBatch.cells.ToArray()); m_BakingBatch = null; foreach (var data in prv.perSceneDataList) data.ResolveCellData(); // We can now finally reload. AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); foreach (var sceneData in prv.perSceneDataList) { prv.AddPendingSceneLoading(sceneData.sceneGUID, sceneData.serializedBakingSet); } prv.PerformPendingOperations(); } } partial class AdaptiveProbeVolumes { struct TouchupsPerCell { public List<(ProbeAdjustmentVolume touchup, ProbeReferenceVolume.Volume obb, Vector3 center, Vector3 offset)> appliers; public List<(ProbeAdjustmentVolume touchup, ProbeReferenceVolume.Volume obb, Vector3 center)> overriders; } static Dictionary GetTouchupsPerCell(out bool hasAppliers) { hasAppliers = false; var adjustmentVolumes = s_AdjustmentVolumes != null ? s_AdjustmentVolumes : GetAdjustementVolumes(); Dictionary cellToVolumes = new(); foreach (var adjustment in adjustmentVolumes) { var volume = adjustment.volume; var mode = volume.mode; if (mode != ProbeAdjustmentVolume.Mode.ApplyVirtualOffset && mode != ProbeAdjustmentVolume.Mode.OverrideVirtualOffsetSettings) continue; hasAppliers |= mode == ProbeAdjustmentVolume.Mode.ApplyVirtualOffset; Vector3Int min = Vector3Int.Max(m_ProfileInfo.PositionToCell(adjustment.aabb.min), minCellPosition); Vector3Int max = Vector3Int.Min(m_ProfileInfo.PositionToCell(adjustment.aabb.max), maxCellPosition); for (int x = min.x; x <= max.x; x++) { for (int y = min.y; y <= max.y; y++) { for (int z = min.z; z <= max.z; z++) { var cell = PosToIndex(new Vector3Int(x, y, z)); if (!cellToVolumes.TryGetValue(cell, out var volumes)) cellToVolumes[cell] = volumes = new TouchupsPerCell() { appliers = new(), overriders = new() }; if (mode == ProbeAdjustmentVolume.Mode.ApplyVirtualOffset) volumes.appliers.Add((volume, adjustment.obb, volume.transform.position, volume.GetVirtualOffset())); else volumes.overriders.Add((volume, adjustment.obb, volume.transform.position)); } } } } return cellToVolumes; } static void DoApplyVirtualOffsetsFromAdjustmentVolumes(NativeArray positions, NativeArray offsets, Dictionary cellToVolumes) { for (int i = 0; i < positions.Length; i++) { var cellPos = m_ProfileInfo.PositionToCell(positions[i]); cellPos.Clamp(minCellPosition, maxCellPosition); int cellIndex = PosToIndex(cellPos); if (cellToVolumes.TryGetValue(cellIndex, out var volumes)) { foreach (var (touchup, obb, center, offset) in volumes.appliers) { if (touchup.ContainsPoint(obb, center, positions[i])) { offsets[i] = offset; break; } } } } } enum InstanceFlags { DIRECT_RAY_VIS_MASK = 1, INDIRECT_RAY_VIS_MASK = 2, SHADOW_RAY_VIS_MASK = 4, } private static uint GetInstanceMask(ShadowCastingMode shadowMode) { uint instanceMask = 0u; if (shadowMode != ShadowCastingMode.Off) instanceMask |= (uint)InstanceFlags.SHADOW_RAY_VIS_MASK; if (shadowMode != ShadowCastingMode.ShadowsOnly) { instanceMask |= (uint)InstanceFlags.DIRECT_RAY_VIS_MASK; instanceMask |= (uint)InstanceFlags.INDIRECT_RAY_VIS_MASK; } return instanceMask; } static uint[] GetMaterialIndices(Renderer renderer) { int submeshCount = 1; var meshFilter = renderer.GetComponent(); if (meshFilter) submeshCount = renderer.GetComponent().sharedMesh.subMeshCount; uint[] matIndices = new uint[submeshCount]; for (int i = 0; i < matIndices.Length; ++i) { if (i < renderer.sharedMaterials.Length && renderer.sharedMaterials[i] != null) matIndices[i] = (uint)renderer.sharedMaterials[i].GetInstanceID(); else matIndices[i] = 0; } return matIndices; } } }