using System; using System.Runtime.InteropServices; using UnityEngine.Rendering.Sampling; using UnityEngine.Rendering.UnifiedRayTracing; using Unity.Collections; namespace UnityEngine.Rendering { partial class AdaptiveProbeVolumes { /// /// Sky occlusion baker /// public abstract class SkyOcclusionBaker : IDisposable { /// The current baking step. public abstract ulong currentStep { get; } /// The total amount of step. public abstract ulong stepCount { get; } /// Array storing the sky occlusion per probe. Expects Layout DC, x, y, z. public abstract NativeArray occlusion { get; } /// Array storing the sky shading direction per probe. public abstract NativeArray shadingDirections { 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 sky occlusion 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(); internal NativeArray encodedDirections; internal void Encode() { encodedDirections = EncodeShadingDirection(shadingDirections); } static int k_MaxProbeCountPerBatch = 65535; static readonly int _SkyShadingPrecomputedDirection = Shader.PropertyToID("_SkyShadingPrecomputedDirection"); static readonly int _SkyShadingDirections = Shader.PropertyToID("_SkyShadingDirections"); static readonly int _SkyShadingIndices = Shader.PropertyToID("_SkyShadingIndices"); static readonly int _ProbeCount = Shader.PropertyToID("_ProbeCount"); internal static NativeArray EncodeShadingDirection(NativeArray directions) { var cs = GraphicsSettings.GetRenderPipelineSettings().skyOcclusionCS; int kernel = cs.FindKernel("EncodeShadingDirection"); ProbeVolumeConstantRuntimeResources.Initialize(); var precomputedShadingDirections = ProbeReferenceVolume.instance.GetRuntimeResources().SkyPrecomputedDirections; int probeCount = directions.Length; int batchSize = Mathf.Min(k_MaxProbeCountPerBatch, probeCount); int batchCount = CoreUtils.DivRoundUp(probeCount, k_MaxProbeCountPerBatch); var directionBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, batchSize, Marshal.SizeOf()); var encodedBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, batchSize, Marshal.SizeOf()); var directionResults = new NativeArray(probeCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); for (int batchIndex = 0; batchIndex < batchCount; batchIndex++) { int batchOffset = batchIndex * k_MaxProbeCountPerBatch; int probeInBatch = Mathf.Min(probeCount - batchOffset, k_MaxProbeCountPerBatch); directionBuffer.SetData(directions, batchOffset, 0, probeInBatch); cs.SetBuffer(kernel, _SkyShadingPrecomputedDirection, precomputedShadingDirections); cs.SetBuffer(kernel, _SkyShadingDirections, directionBuffer); cs.SetBuffer(kernel, _SkyShadingIndices, encodedBuffer); cs.SetInt(_ProbeCount, probeInBatch); cs.Dispatch(kernel, CoreUtils.DivRoundUp(probeCount, 64), 1, 1); var batchResult = directionResults.GetSubArray(batchOffset, probeInBatch); AsyncGPUReadback.RequestIntoNativeArray(ref batchResult, encodedBuffer, probeInBatch * sizeof(uint), 0).WaitForCompletion(); } directionBuffer.Dispose(); encodedBuffer.Dispose(); return directionResults; } internal static uint EncodeSkyShadingDirection(Vector3 direction) { var precomputedDirections = ProbeVolumeConstantRuntimeResources.GetSkySamplingDirections(); uint indexMax = 255; float bestDot = -10.0f; uint bestIndex = 0; for (uint index = 0; index < indexMax; index++) { float currentDot = Vector3.Dot(direction, precomputedDirections[index]); if (currentDot > bestDot) { bestDot = currentDot; bestIndex = index; } } return bestIndex; } } class DefaultSkyOcclusion : SkyOcclusionBaker { const int k_MaxProbeCountPerBatch = 128 * 1024; const float k_SkyOcclusionOffsetRay = 0.015f; const int k_SampleCountPerStep = 16; static readonly int _SampleCount = Shader.PropertyToID("_SampleCount"); static readonly int _SampleId = Shader.PropertyToID("_SampleId"); static readonly int _MaxBounces = Shader.PropertyToID("_MaxBounces"); static readonly int _OffsetRay = Shader.PropertyToID("_OffsetRay"); static readonly int _ProbePositions = Shader.PropertyToID("_ProbePositions"); static readonly int _SkyOcclusionOut = Shader.PropertyToID("_SkyOcclusionOut"); static readonly int _SkyShadingOut = Shader.PropertyToID("_SkyShadingOut"); static readonly int _AverageAlbedo = Shader.PropertyToID("_AverageAlbedo"); static readonly int _BackFaceCulling = Shader.PropertyToID("_BackFaceCulling"); static readonly int _BakeSkyShadingDirection = Shader.PropertyToID("_BakeSkyShadingDirection"); static readonly int _SobolBuffer = Shader.PropertyToID("_SobolMatricesBuffer"); int skyOcclusionBackFaceCulling; float skyOcclusionAverageAlbedo; int probeCount; ulong step; // Input data NativeArray probePositions; int currentJob; int sampleIndex; int batchIndex; public BakeJob[] jobs; // Output buffers GraphicsBuffer occlusionOutputBuffer; GraphicsBuffer shadingDirectionBuffer; NativeArray occlusionResults; NativeArray directionResults; public override NativeArray occlusion => occlusionResults; public override NativeArray shadingDirections => directionResults; AccelStructAdapter m_AccelerationStructure; GraphicsBuffer scratchBuffer; GraphicsBuffer probePositionsBuffer; GraphicsBuffer sobolBuffer; public override ulong currentStep => step; public override ulong stepCount => (ulong)probeCount; public override void Initialize(ProbeVolumeBakingSet bakingSet, NativeArray positions) { skyOcclusionAverageAlbedo = bakingSet.skyOcclusionAverageAlbedo; skyOcclusionBackFaceCulling = 0; // see PR #40707 currentJob = 0; sampleIndex = 0; batchIndex = 0; step = 0; probeCount = bakingSet.skyOcclusion ? positions.Length : 0; probePositions = positions; if (stepCount == 0) return; // Allocate array storing results occlusionResults = new NativeArray(probeCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); if (bakingSet.skyOcclusionShadingDirection) directionResults = new NativeArray(probeCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); // Create acceleration structure m_AccelerationStructure = BuildAccelerationStructure(); var skyOcclusionShader = s_TracingContext.shaderSO; bool skyDirection = shadingDirections.IsCreated; int batchSize = Mathf.Min(k_MaxProbeCountPerBatch, probeCount); probePositionsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, batchSize, Marshal.SizeOf()); occlusionOutputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, batchSize, Marshal.SizeOf()); shadingDirectionBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, skyDirection ? batchSize : 1, Marshal.SizeOf()); scratchBuffer = RayTracingHelper.CreateScratchBufferForBuildAndDispatch(m_AccelerationStructure.GetAccelerationStructure(), skyOcclusionShader, (uint)batchSize, 1, 1); var buildCmd = new CommandBuffer(); m_AccelerationStructure.Build(buildCmd, ref scratchBuffer); Graphics.ExecuteCommandBuffer(buildCmd); buildCmd.Dispose(); int sobolBufferSize = (int)(SobolData.SobolDims * SobolData.SobolSize); sobolBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, sobolBufferSize, Marshal.SizeOf()); sobolBuffer.SetData(SobolData.SobolMatrices); } static AccelStructAdapter BuildAccelerationStructure() { var accelStruct = s_TracingContext.CreateAccelerationStructure(); var contributors = m_BakingBatch.contributors; foreach (var renderer in contributors.renderers) { if (!s_TracingContext.TryGetMeshForAccelerationStructure(renderer.component, out var mesh)) continue; int subMeshCount = mesh.subMeshCount; var matIndices = GetMaterialIndices(renderer.component); var perSubMeshMask = new uint[subMeshCount]; Array.Fill(perSubMeshMask, GetInstanceMask(renderer.component.shadowCastingMode)); accelStruct.AddInstance(renderer.component.GetInstanceID(), renderer.component, perSubMeshMask, matIndices, 1); } foreach (var terrain in contributors.terrains) { uint mask = GetInstanceMask(terrain.component.shadowCastingMode); accelStruct.AddInstance(terrain.component.GetInstanceID(), terrain.component, new uint[1] { mask }, new uint[1] { 0 }, 1); } return accelStruct; } public override bool Step() { if (currentStep >= stepCount) return true; ref var job = ref jobs[currentJob]; if (job.probeCount == 0) { currentJob++; return true; } var cmd = new CommandBuffer(); var skyOccShader = s_TracingContext.shaderSO; // Divide the job into batches of 128k probes to reduce memory usage. int batchCount = CoreUtils.DivRoundUp(job.probeCount, k_MaxProbeCountPerBatch); int batchOffset = batchIndex * k_MaxProbeCountPerBatch; int batchSize = Mathf.Min(job.probeCount - batchOffset, k_MaxProbeCountPerBatch); if (sampleIndex == 0) { cmd.SetBufferData(probePositionsBuffer, probePositions.GetSubArray(job.startOffset + batchOffset, batchSize)); } s_TracingContext.BindSamplingTextures(cmd); m_AccelerationStructure.Bind(cmd, "_AccelStruct", skyOccShader); skyOccShader.SetIntParam(cmd, _BakeSkyShadingDirection, shadingDirections.IsCreated ? 1 : 0); skyOccShader.SetIntParam(cmd, _BackFaceCulling, skyOcclusionBackFaceCulling); skyOccShader.SetFloatParam(cmd, _AverageAlbedo, skyOcclusionAverageAlbedo); skyOccShader.SetFloatParam(cmd, _OffsetRay, k_SkyOcclusionOffsetRay); skyOccShader.SetBufferParam(cmd, _ProbePositions, probePositionsBuffer); skyOccShader.SetBufferParam(cmd, _SkyOcclusionOut, occlusionOutputBuffer); skyOccShader.SetBufferParam(cmd, _SkyShadingOut, shadingDirectionBuffer); skyOccShader.SetBufferParam(cmd, _SobolBuffer, sobolBuffer); skyOccShader.SetIntParam(cmd, _SampleCount, job.skyOcclusionBakingSamples); skyOccShader.SetIntParam(cmd, _MaxBounces, job.skyOcclusionBakingBounces); // Sample multiple paths in one step for (int i = 0; i < k_SampleCountPerStep; i++) { skyOccShader.SetIntParam(cmd, _SampleId, sampleIndex); skyOccShader.Dispatch(cmd, scratchBuffer, (uint)batchSize, 1, 1); sampleIndex++; Graphics.ExecuteCommandBuffer(cmd); cmd.Clear(); // If we computed all the samples for this batch, continue with the next one if (sampleIndex >= job.skyOcclusionBakingSamples) { FetchResults(in job, batchOffset, batchSize); batchIndex++; sampleIndex = 0; if (batchIndex >= batchCount) { currentJob++; batchIndex = 0; } // Progress bar step += (ulong)batchSize; break; } } cmd.Dispose(); return true; } void FetchResults(in BakeJob job, int batchOffset, int batchSize) { var batchOcclusionResults = occlusionResults.GetSubArray(job.startOffset + batchOffset, batchSize); var req1 = AsyncGPUReadback.RequestIntoNativeArray(ref batchOcclusionResults, occlusionOutputBuffer, batchSize * 4 * sizeof(float), 0); if (directionResults.IsCreated) { var batchDirectionResults = directionResults.GetSubArray(job.startOffset + batchOffset, batchSize); var req2 = AsyncGPUReadback.RequestIntoNativeArray(ref batchDirectionResults, shadingDirectionBuffer, batchSize * 3 * sizeof(float), 0); req2.WaitForCompletion(); } // TODO: use double buffering to hide readback latency req1.WaitForCompletion(); } public override void Dispose() { if (m_AccelerationStructure == null) return; occlusionOutputBuffer?.Dispose(); shadingDirectionBuffer?.Dispose(); scratchBuffer?.Dispose(); probePositionsBuffer?.Dispose(); sobolBuffer?.Dispose(); occlusionResults.Dispose(); if (directionResults.IsCreated) directionResults.Dispose(); m_AccelerationStructure.Dispose(); } } } }