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"); DynamicSkyPrecomputedDirections.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 = DynamicSkyPrecomputedDirections.GetPrecomputedDirections(); 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 = 65535 * 64; 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("_SobolBuffer"); static readonly int _CPRBuffer = Shader.PropertyToID("_CPRBuffer"); 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; IRayTracingAccelStruct m_AccelerationStructure; GraphicsBuffer scratchBuffer; GraphicsBuffer probePositionsBuffer; GraphicsBuffer sobolBuffer; GraphicsBuffer cprBuffer; // Cranley Patterson rotation 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, skyOcclusionShader, (uint)batchSize, 1, 1); var buildCmd = new CommandBuffer(); m_AccelerationStructure.Build(buildCmd, 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); cprBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, SamplingResources.cranleyPattersonRotationBufferSize, Marshal.SizeOf()); cprBuffer.SetData(SamplingResources.GetCranleyPattersonRotations()); } static IRayTracingAccelStruct 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; var matIndices = GetMaterialIndices(renderer.component); uint mask = GetInstanceMask(renderer.component.shadowCastingMode); int subMeshCount = mesh.subMeshCount; for (int i = 0; i < subMeshCount; ++i) { var instanceDesc = new MeshInstanceDesc(mesh, i); instanceDesc.localToWorldMatrix = renderer.component.transform.localToWorldMatrix; instanceDesc.mask = mask; instanceDesc.materialID = matIndices[i]; instanceDesc.enableTriangleCulling = true; instanceDesc.frontTriangleCounterClockwise = false; accelStruct.AddInstance(instanceDesc); } } foreach (var terrain in contributors.terrains) { uint mask = GetInstanceMask(terrain.component.shadowCastingMode); var terrainDesc = new TerrainDesc(terrain.component); terrainDesc.localToWorldMatrix = terrain.component.transform.localToWorldMatrix; terrainDesc.mask = mask; terrainDesc.materialID = 0; accelStruct.AddTerrain(terrainDesc); } 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); skyOccShader.SetAccelerationStructure(cmd, "_AccelStruct", m_AccelerationStructure); 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.SetBufferParam(cmd, _CPRBuffer, cprBuffer); 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(); cprBuffer?.Dispose(); occlusionResults.Dispose(); if (directionResults.IsCreated) directionResults.Dispose(); m_AccelerationStructure.Dispose(); } } } }