using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using Unity.Collections;
using UnityEditor;
using UnityEngine.LightTransport;
using UnityEngine.LightTransport.PostProcessing;
using UnityEngine.Rendering.Sampling;
using UnityEngine.Rendering.UnifiedRayTracing;
using UnityEngine.SceneManagement;
using TouchupVolumeWithBoundsList = System.Collections.Generic.List<(UnityEngine.Rendering.ProbeReferenceVolume.Volume obb, UnityEngine.Bounds aabb, UnityEngine.Rendering.ProbeAdjustmentVolume volume)>;
namespace UnityEngine.Rendering
{
partial class AdaptiveProbeVolumes
{
///
/// Lighting baker
///
public abstract class LightingBaker : IDisposable
{
/// Indicates that the Step method can be safely called from a thread.
public virtual bool isThreadSafe => false;
/// Set to true when the main thread cancels baking.
public static bool cancel { get; internal set; }
/// The current baking step.
public abstract ulong currentStep { get; }
/// The total amount of step.
public abstract ulong stepCount { get; }
/// Array storing the probe lighting as Spherical Harmonics.
public abstract NativeArray irradiance { get; }
/// Array storing the probe validity. A value of 1 means a probe is invalid.
public abstract NativeArray validity { get; }
/// Array storing 4 light occlusion values for each probe.
public abstract NativeArray occlusion { get; }
///
/// This is called before the start of baking to allow allocating necessary resources.
///
/// Whether to bake occlusion for mixed lights for each probe.
/// The probe positions. Also contains reflection probe positions used for normalization.
public abstract void Initialize(bool bakeProbeOcclusion, NativeArray probePositions);
///
/// This is called before the start of baking to allow allocating necessary resources.
///
/// Whether to bake occlusion for mixed lights for each probe.
/// The probe positions. Also contains reflection probe positions used for normalization.
/// The rendering layer masks assigned to each probe. It is used when fixing seams between subdivision levels
public abstract void Initialize(bool bakeProbeOcclusion, NativeArray probePositions, NativeArray bakedRenderingLayerMasks);
///
/// Run a step of light baking. Baking is considered done when currentStep property equals stepCount.
/// If isThreadSafe is true, this method may be called from a different thread.
///
/// 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 DefaultLightTransport : LightingBaker
{
public override bool isThreadSafe => true;
int bakedProbeCount;
NativeArray positions;
InputExtraction.BakeInput input;
bool bakeProbeOcclusion;
public BakeJob[] jobs;
// Outputs
public NativeArray irradianceResults;
public NativeArray validityResults;
public NativeArray occlusionResults;
// Baked in a other job, but used in this one if available when fixing seams
private NativeArray renderingLayerMasks;
public override ulong currentStep => (ulong)bakedProbeCount;
public override ulong stepCount => (ulong)positions.Length;
public override NativeArray irradiance => irradianceResults;
public override NativeArray validity => validityResults;
public override NativeArray occlusion => occlusionResults;
public override void Initialize(bool bakeProbeOcclusion, NativeArray probePositions)
{
if (!InputExtraction.ExtractFromScene(out input, true))
{
Debug.LogError("InputExtraction.ExtractFromScene failed.");
return;
}
bakedProbeCount = 0;
positions = probePositions;
irradianceResults = new NativeArray(positions.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
validityResults = new NativeArray(positions.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
this.bakeProbeOcclusion = bakeProbeOcclusion;
if (bakeProbeOcclusion)
occlusionResults = new NativeArray(positions.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
}
public override void Initialize(bool bakeProbeOcclusion, NativeArray probePositions, NativeArray bakedRenderingLayerMasks)
{
renderingLayerMasks.Dispose();
if (bakedRenderingLayerMasks.IsCreated)
{
renderingLayerMasks = new NativeArray(bakedRenderingLayerMasks.Length, Allocator.Persistent);
renderingLayerMasks.CopyFrom(bakedRenderingLayerMasks);
}
Initialize(bakeProbeOcclusion, probePositions);
}
public override bool Step()
{
if (input == null)
return false;
var context = BakeContext.New(input, positions, bakeProbeOcclusion);
if (!context.isCreated)
return false;
try
{
for (int i = 0; i < jobs.Length; i++)
{
ref var job = ref jobs[i];
if (job.probeCount != 0)
{
if (!context.Bake(job, ref irradianceResults, ref validityResults, ref occlusionResults))
return false;
bakedProbeCount += job.probeCount;
}
}
}
finally
{
context.Dispose();
}
return true;
}
public override void Dispose()
{
irradianceResults.Dispose();
validityResults.Dispose();
if (bakeProbeOcclusion)
occlusionResults.Dispose();
renderingLayerMasks.Dispose();
}
}
struct BakeJob
{
public Bounds aabb;
public ProbeReferenceVolume.Volume obb;
public ProbeAdjustmentVolume touchup;
public int startOffset;
public int probeCount;
public int directSampleCount;
public int indirectSampleCount;
public int validitySampleCount;
public int occlusionSampleCount;
public int maxBounces;
public int skyOcclusionBakingSamples;
public int skyOcclusionBakingBounces;
public float indirectScale;
public bool ignoreEnvironement;
public BakeProgressState progress;
public ulong currentStep => (ulong)Mathf.Min(progress.Progress() * 0.01f / (float)(directSampleCount + indirectSampleCount + validitySampleCount), stepCount); // this is how the progress is computed in c++
public ulong stepCount => (ulong)probeCount;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Create(ProbeVolumeBakingSet bakingSet, LightingSettings lightingSettings, bool ignoreEnvironement)
{
skyOcclusionBakingSamples = bakingSet != null ? bakingSet.skyOcclusionBakingSamples : 0;
skyOcclusionBakingBounces = bakingSet != null ? bakingSet.skyOcclusionBakingBounces : 0;
int indirectSampleCount = Math.Max(lightingSettings.indirectSampleCount, lightingSettings.environmentSampleCount);
Create(lightingSettings, ignoreEnvironement, lightingSettings.directSampleCount, indirectSampleCount,
(int)lightingSettings.lightProbeSampleCountMultiplier, lightingSettings.maxBounces);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Create(LightingSettings lightingSettings, bool ignoreEnvironement, (ProbeReferenceVolume.Volume obb, Bounds aabb, ProbeAdjustmentVolume touchup) volume)
{
obb = volume.obb;
aabb = volume.aabb;
touchup = volume.touchup;
skyOcclusionBakingSamples = touchup.skyOcclusionSampleCount;
skyOcclusionBakingBounces = touchup.skyOcclusionMaxBounces;
Create(lightingSettings, ignoreEnvironement, touchup.directSampleCount, touchup.indirectSampleCount, touchup.sampleCountMultiplier, touchup.maxBounces);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Create(LightingSettings lightingSettings, bool ignoreEnvironement, int directSampleCount, int indirectSampleCount, int sampleCountMultiplier, int maxBounces)
{
// We could preallocate wrt touchup aabb volume, or total brick count for the global job
progress = new BakeProgressState();
this.directSampleCount = directSampleCount * sampleCountMultiplier;
this.indirectSampleCount = indirectSampleCount * sampleCountMultiplier;
this.validitySampleCount = indirectSampleCount * sampleCountMultiplier;
this.occlusionSampleCount = directSampleCount * sampleCountMultiplier;
this.maxBounces = maxBounces;
this.indirectScale = lightingSettings.indirectScale;
this.ignoreEnvironement = ignoreEnvironement;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(Vector3 point)
{
return touchup.ContainsPoint(obb, aabb.center, point);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
progress.Dispose();
}
}
struct BakeContext
{
internal class LightTransportBakingProfiling : BakingProfiling, IDisposable
{
//protected override string LogFile => "BakeGI";
protected override bool ShowProgressBar => false;
public enum Stages
{
BakeGI,
IntegrateDirectRadiance,
IntegrateIndirectRadiance,
IntegrateValidity,
IntegrateOcclusion,
Postprocess,
ReadBack,
None
}
static Stages currentStage = Stages.None;
public LightTransportBakingProfiling(Stages stage) : base(stage, ref currentStage) { }
public override Stages GetLastStep() => Stages.None;
public static void GetProgressRange(out float progress0, out float progress1) { float s = 1 / (float)Stages.None; progress0 = (float)currentStage * s; progress1 = progress0 + s; }
public void Dispose() { OnDispose(ref currentStage); }
}
public IDeviceContext ctx;
public IProbeIntegrator integrator;
public IWorld world;
public IProbePostProcessor postProcessor;
public BufferID positionsBufferID;
public BufferID directRadianceBufferId;
public BufferID indirectRadianceBufferId;
public BufferID validityBufferId;
public BufferID perProbeLightIndicesId;
public BufferID occlusionBufferId;
public BufferID windowedDirectSHBufferId;
public BufferID boostedIndirectSHBufferId;
public BufferID combinedSHBufferId;
public BufferID irradianceBufferId;
public bool allocatedBuffers;
public bool isCreated => allocatedBuffers;
const float k_PushOffset = 0.0001f;
const int k_MaxProbeCountPerBatch = 128 * 1024;
const int maxOcclusionLightsPerProbe = 4;
static readonly int sizeOfFloat = 4;
static readonly int SHL2RGBElements = 3 * 9;
static readonly int sizeSHL2RGB = sizeOfFloat * SHL2RGBElements;
int[] perProbeShadowmaskIndices;
bool bakeProbeOcclusion;
public static BakeContext New(InputExtraction.BakeInput input, NativeArray probePositions, bool bakeProbeOcclusion)
{
var ctx = new BakeContext
{
ctx = new RadeonRaysContext(),
integrator = new RadeonRaysProbeIntegrator(),
world = new RadeonRaysWorld(),
postProcessor = new RadeonRaysProbePostProcessor(),
};
if (!ctx.ctx.Initialize())
{
Debug.LogError("Failed to initialize context.");
return ctx;
}
using var inputProgress = new BakeProgressState();
if (!InputExtraction.PopulateWorld(input, inputProgress, ctx.ctx, ctx.world))
{
Debug.LogError("Failed to extract inputs.");
return ctx;
}
if (!ctx.postProcessor.Initialize(ctx.ctx))
{
Debug.LogError("Failed to initialize postprocessor.");
return ctx;
}
ctx.bakeProbeOcclusion = bakeProbeOcclusion;
ctx.CreateBuffers(probePositions.Length);
// Upload probe positions
var positionsSlice = new BufferSlice(ctx.positionsBufferID, 0);
var positionWriteEvent = ctx.ctx.CreateEvent();
ctx.ctx.WriteBuffer(positionsSlice, probePositions, positionWriteEvent);
if (bakeProbeOcclusion)
{
// Upload per probe light indices
int[] perProbeLightIndicesArray = InputExtraction.ComputeOcclusionLightIndicesFromBakeInput(input, probePositions.ToArray(), (uint)maxOcclusionLightsPerProbe);
using var perProbeLightIndices = new NativeArray(perProbeLightIndicesArray, Allocator.TempJob);
var perProbeLightIndicesSlice = new BufferSlice(ctx.perProbeLightIndicesId, 0);
var perProbeLightIndicesWriteEvent = ctx.ctx.CreateEvent();
ctx.ctx.WriteBuffer(perProbeLightIndicesSlice, perProbeLightIndices, perProbeLightIndicesWriteEvent);
ctx.ctx.Wait(perProbeLightIndicesWriteEvent);
ctx.ctx.DestroyEvent(perProbeLightIndicesWriteEvent);
// Store per-probe shadowmask indices. They will be used to swizzle the occlusion buffer.
ctx.perProbeShadowmaskIndices = InputExtraction.GetShadowmaskChannelsFromLightIndices(input, perProbeLightIndicesArray);
}
// Wait for writes to finish
ctx.ctx.Wait(positionWriteEvent);
ctx.ctx.DestroyEvent(positionWriteEvent);
return ctx;
}
private void CreateBuffers(int probeCount)
{
// Allocate shared position and light index buffer for all jobs
positionsBufferID = ctx.CreateBuffer((ulong)probeCount, (ulong)(3 * sizeOfFloat));
int batchSize = Mathf.Min(k_MaxProbeCountPerBatch, probeCount);
var shBytes = (ulong)(sizeSHL2RGB * batchSize);
var validityBytes = (ulong)(sizeOfFloat * batchSize);
directRadianceBufferId = ctx.CreateBuffer((ulong)(batchSize * SHL2RGBElements), (ulong)sizeOfFloat);
indirectRadianceBufferId = ctx.CreateBuffer((ulong)(batchSize * SHL2RGBElements), (ulong)sizeOfFloat);
validityBufferId = ctx.CreateBuffer((ulong)batchSize, (ulong)sizeOfFloat);
windowedDirectSHBufferId = ctx.CreateBuffer((ulong)(batchSize * SHL2RGBElements), (ulong)sizeOfFloat);
boostedIndirectSHBufferId = ctx.CreateBuffer((ulong)(batchSize * SHL2RGBElements), (ulong)sizeOfFloat);
combinedSHBufferId = ctx.CreateBuffer((ulong)(batchSize * SHL2RGBElements), (ulong)sizeOfFloat);
irradianceBufferId = ctx.CreateBuffer((ulong)(batchSize * SHL2RGBElements), (ulong)sizeOfFloat);
if (bakeProbeOcclusion)
{
var lightIndicesBytes = (ulong)(sizeOfFloat * maxOcclusionLightsPerProbe * probeCount);
perProbeLightIndicesId = ctx.CreateBuffer((ulong)(maxOcclusionLightsPerProbe * probeCount), (ulong)sizeOfFloat);
var occlusionBytes = (ulong)(sizeOfFloat * maxOcclusionLightsPerProbe * batchSize);
occlusionBufferId = ctx.CreateBuffer((ulong)(maxOcclusionLightsPerProbe * batchSize), (ulong)sizeOfFloat);
}
allocatedBuffers = true;
}
public bool Bake(in BakeJob job, ref NativeArray irradianceResults, ref NativeArray validityResults, ref NativeArray occlusionResults)
{
// Divide the job into batches of 128k probes to reduce memory usage.
int batchCount = CoreUtils.DivRoundUp(job.probeCount, k_MaxProbeCountPerBatch);
// Get slices for all buffers because the API require those
// All jobs use overlapping slices as they are not run simultaneously
var directRadianceSlice = new BufferSlice(directRadianceBufferId, 0);
var indirectRadianceSlice = new BufferSlice(indirectRadianceBufferId, 0);
var validitySlice = new BufferSlice(validityBufferId, 0);
var occlusionSlice = new BufferSlice(occlusionBufferId, 0);
var windowedDirectRadianceSlice = new BufferSlice(windowedDirectSHBufferId, 0);
var boostedIndirectRadianceSlice = indirectRadianceSlice;
var combinedSHSlice = new BufferSlice(combinedSHBufferId, 0);
var irradianceSlice = new BufferSlice(irradianceBufferId, 0);
// Loop over all batches
for (int batchIndex = 0; batchIndex < batchCount; batchIndex++)
{
int batchOffset = batchIndex * k_MaxProbeCountPerBatch;
int probeCount = Mathf.Min(job.probeCount - batchOffset, k_MaxProbeCountPerBatch);
// Get the correct slice of position and light indices as all jobs share the same array.
var positionsSlice = new BufferSlice(positionsBufferID, (ulong)(job.startOffset + batchOffset));
var perProbeLightIndicesSlice = new BufferSlice(perProbeLightIndicesId, (ulong)(job.startOffset + batchOffset) * maxOcclusionLightsPerProbe);
/// Baking
// Prepare integrator.
integrator.Prepare(ctx, world, positionsSlice, k_PushOffset, job.maxBounces);
integrator.SetProgressReporter(job.progress);
// Bake direct radiance
using (new LightTransportBakingProfiling(LightTransportBakingProfiling.Stages.IntegrateDirectRadiance))
{
var integrationResult = integrator.IntegrateDirectRadiance(ctx, 0, probeCount, job.directSampleCount, job.ignoreEnvironement, directRadianceSlice);
if (integrationResult.type != IProbeIntegrator.ResultType.Success) return false;
if (LightingBaker.cancel) return true;
}
// Bake indirect radiance
using (new LightTransportBakingProfiling(LightTransportBakingProfiling.Stages.IntegrateIndirectRadiance))
{
var integrationResult = integrator.IntegrateIndirectRadiance(ctx, 0, probeCount, job.indirectSampleCount, job.ignoreEnvironement, indirectRadianceSlice);
if (integrationResult.type != IProbeIntegrator.ResultType.Success) return false;
if (LightingBaker.cancel) return true;
}
// Bake validity
using (new LightTransportBakingProfiling(LightTransportBakingProfiling.Stages.IntegrateValidity))
{
var validityResult = integrator.IntegrateValidity(ctx, 0, probeCount, job.validitySampleCount, validitySlice);
if (validityResult.type != IProbeIntegrator.ResultType.Success) return false;
if (LightingBaker.cancel) return true;
}
// Bake occlusion
if (bakeProbeOcclusion)
{
using (new LightTransportBakingProfiling(LightTransportBakingProfiling.Stages.IntegrateOcclusion))
{
var occlusionResult = integrator.IntegrateOcclusion(ctx, 0, probeCount, job.occlusionSampleCount, (int)maxOcclusionLightsPerProbe, perProbeLightIndicesSlice, occlusionSlice.SafeReinterpret());
if (occlusionResult.type != IProbeIntegrator.ResultType.Success) return false;
if (LightingBaker.cancel) return true;
}
}
/// Postprocess
using (new LightTransportBakingProfiling(LightTransportBakingProfiling.Stages.Postprocess))
{
// Apply windowing to direct component.
if (!postProcessor.WindowSphericalHarmonicsL2(ctx, directRadianceSlice, windowedDirectRadianceSlice, probeCount))
return false;
// Apply indirect intensity multiplier to indirect radiance
if (job.indirectScale.Equals(1.0f) == false)
{
boostedIndirectRadianceSlice = new BufferSlice(boostedIndirectSHBufferId, 0);
if (!postProcessor.ScaleSphericalHarmonicsL2(ctx, indirectRadianceSlice, boostedIndirectRadianceSlice, probeCount, job.indirectScale))
return false;
}
// Combine direct and indirect radiance
if (!postProcessor.AddSphericalHarmonicsL2(ctx, windowedDirectRadianceSlice, boostedIndirectRadianceSlice, combinedSHSlice, probeCount))
return false;
// Convert radiance to irradiance
if (!postProcessor.ConvolveRadianceToIrradiance(ctx, combinedSHSlice, irradianceSlice, probeCount))
return false;
// Transform to the format expected by the Unity renderer
if (!postProcessor.ConvertToUnityFormat(ctx, irradianceSlice, combinedSHSlice, probeCount))
return false;
// Apply de-ringing to combined SH
if (!postProcessor.DeringSphericalHarmonicsL2(ctx, combinedSHSlice, combinedSHSlice, probeCount))
return false;
}
/// Read results
var jobIrradianceResults = irradianceResults.GetSubArray(job.startOffset + batchOffset, probeCount);
var jobValidityResults = validityResults.GetSubArray(job.startOffset + batchOffset, probeCount);
var jobOcclusionResults = default(NativeArray);
if (bakeProbeOcclusion)
jobOcclusionResults = occlusionResults.GetSubArray(job.startOffset + batchOffset, probeCount);
// Schedule read backs to get results back from GPU memory into CPU memory.
var irradianceReadEvent = ctx.CreateEvent();
ctx.ReadBuffer(combinedSHSlice, jobIrradianceResults, irradianceReadEvent);
var validityReadEvent = ctx.CreateEvent();
ctx.ReadBuffer(validitySlice, jobValidityResults, validityReadEvent);
var occlusionReadEvent = default(EventID);
if (bakeProbeOcclusion)
{
occlusionReadEvent = ctx.CreateEvent();
ctx.ReadBuffer(occlusionSlice, jobOcclusionResults, occlusionReadEvent);
}
if (!ctx.Flush()) return false;
using (new LightTransportBakingProfiling(LightTransportBakingProfiling.Stages.ReadBack))
{
// Wait for read backs to complete.
bool waitResult = ctx.Wait(irradianceReadEvent) && ctx.Wait(validityReadEvent) && (!bakeProbeOcclusion || ctx.Wait(occlusionReadEvent));
if (!waitResult) return false;
}
ctx.DestroyEvent(irradianceReadEvent);
ctx.DestroyEvent(validityReadEvent);
if (bakeProbeOcclusion)
{
ctx.DestroyEvent(occlusionReadEvent);
// Swizzle occlusion buffer so it is indexed by shadowmask channel.
// This is the format expected by shader code.
int baseProbeIdx = job.startOffset + batchOffset;
for (int probeIdx = 0; probeIdx < probeCount; probeIdx++)
{
Vector4 original = jobOcclusionResults[probeIdx];
Vector4 swizzled = Vector3.zero;
for (int lightIdx = 0; lightIdx < maxOcclusionLightsPerProbe; lightIdx++)
{
int shadowmaskIdx = perProbeShadowmaskIndices[(baseProbeIdx + probeIdx) * maxOcclusionLightsPerProbe + lightIdx];
if (shadowmaskIdx >= 0)
{
swizzled[shadowmaskIdx] = original[lightIdx];
}
}
jobOcclusionResults[probeIdx] = swizzled;
}
}
if (LightingBaker.cancel)
return true;
}
return true;
}
public void Dispose()
{
if (allocatedBuffers)
{
ctx.DestroyBuffer(positionsBufferID);
ctx.DestroyBuffer(directRadianceBufferId);
ctx.DestroyBuffer(indirectRadianceBufferId);
ctx.DestroyBuffer(validityBufferId);
if (bakeProbeOcclusion)
{
ctx.DestroyBuffer(occlusionBufferId);
ctx.DestroyBuffer(perProbeLightIndicesId);
}
ctx.DestroyBuffer(windowedDirectSHBufferId);
ctx.DestroyBuffer(boostedIndirectSHBufferId);
ctx.DestroyBuffer(combinedSHBufferId);
ctx.DestroyBuffer(irradianceBufferId);
}
postProcessor.Dispose();
world.Dispose();
integrator.Dispose();
ctx.Dispose();
}
}
// The contribution from all Baked and Mixed lights in the scene should be disabled to avoid double contribution.
static void UpdateLightStatus()
{
var lightingSettings = ProbeVolumeLightingTab.GetLightingSettings();
var sceneLights = new Dictionary>();
// Modify each baked light, take note of which scenes they belong to.
var allLights = Object.FindObjectsByType(FindObjectsSortMode.None);
foreach (var light in allLights)
{
if (light.lightmapBakeType != LightmapBakeType.Realtime)
{
var bakingOutput = light.bakingOutput;
bakingOutput.isBaked = true;
bakingOutput.lightmapBakeType = light.lightmapBakeType;
bakingOutput.mixedLightingMode = lightingSettings.mixedBakeMode;
light.bakingOutput = bakingOutput;
}
// Take note of the lights from each scene
var scene = light.gameObject.scene;
if (!sceneLights.TryGetValue(scene, out var sceneLightList))
{
sceneLightList = new List();
sceneLights.Add(scene, sceneLightList);
}
sceneLightList.Add(light);
}
// Now we make the modifications persistent by modifying Lighting Data Assets (LDA) on disk.
string ldaFolderPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(m_BakingSet));
for (int i = 0; i < m_BakingSet.sceneGUIDs.Count; i++)
{
string guid = m_BakingSet.sceneGUIDs[i];
Scene scene = SceneManager.GetSceneByPath(AssetDatabase.GUIDToAssetPath(guid));
if (!scene.isLoaded)
continue;
LightingDataAsset prevLDA = Lightmapping.GetLightingDataAssetForScene(scene);
LightingDataAsset newLDA = prevLDA;
// If the scene has no (modifiable) LDA, create a new one.
bool isDefaultLDA = prevLDA && prevLDA.hideFlags.HasFlag(HideFlags.NotEditable);
if (prevLDA == null || isDefaultLDA)
{
newLDA = new LightingDataAsset(scene);
}
// Update the LDA with the new light settings
if (sceneLights.TryGetValue(scene, out var lights))
newLDA.SetLights(lights.ToArray());
else
newLDA.SetLights(Array.Empty());
// If the scene was using the builtin/default LDA before, copy over environment lighting, so it doesn't change.
if (prevLDA != null)
{
newLDA.SetAmbientProbe(prevLDA.GetAmbientProbe());
newLDA.SetDefaultReflectionCubemap(prevLDA.GetDefaultReflectionCubemap());
}
// Save the LDA to disk and assign it to the scene.
if (newLDA != prevLDA)
{
string ldaPath = $"{ldaFolderPath}/LightingData-{i}.asset".Replace('\\', '/');
AssetDatabase.CreateAsset(newLDA, ldaPath);
Lightmapping.SetLightingDataAssetForScene(scene, newLDA);
}
}
}
// Helper struct to manage tracing backend
struct APVRTContext
{
RayTracingContext m_Context;
RayTracingBackend m_Backend;
SamplingResources m_SamplingResources;
RayTracingResources m_RayTracingResources;
static IRayTracingShader m_ShaderVO = null;
static IRayTracingShader m_ShaderSO = null;
static IRayTracingShader m_ShaderRL = null;
const string k_PackageLightTransport = "Packages/com.unity.render-pipelines.core";
internal AccelStructAdapter CreateAccelerationStructure()
{
var c = context;
return new AccelStructAdapter(c.CreateAccelerationStructure(new AccelerationStructureOptions
{
// Use PreferFastBuild to avoid bug triggered with big meshes (UUM-52552));
buildFlags = BuildFlags.PreferFastBuild
}),
m_RayTracingResources
);
}
public RayTracingContext context
{
get
{
if (m_Context == null)
{
m_RayTracingResources = new RayTracingResources();
m_RayTracingResources.Load();
m_Backend = RayTracingContext.IsBackendSupported(RayTracingBackend.Hardware) ? RayTracingBackend.Hardware : RayTracingBackend.Compute;
m_Context = new RayTracingContext(m_Backend, m_RayTracingResources);
}
return m_Context;
}
}
public IRayTracingShader shaderVO
{
get
{
if (m_ShaderVO == null)
{
var bakingResources = GraphicsSettings.GetRenderPipelineSettings();
m_ShaderVO = m_Context.CreateRayTracingShader(m_Backend switch
{
RayTracingBackend.Hardware => bakingResources.traceVirtualOffsetRT,
RayTracingBackend.Compute => bakingResources.traceVirtualOffsetCS,
_ => null
});
}
return m_ShaderVO;
}
}
public IRayTracingShader shaderSO
{
get
{
if (m_ShaderSO == null)
{
var bakingResources = GraphicsSettings.GetRenderPipelineSettings();
m_ShaderSO = m_Context.CreateRayTracingShader(m_Backend switch
{
RayTracingBackend.Hardware => bakingResources.skyOcclusionRT,
RayTracingBackend.Compute => bakingResources.skyOcclusionCS,
_ => null
});
}
return m_ShaderSO;
}
}
public IRayTracingShader shaderRL
{
get
{
if (m_ShaderRL == null)
{
var bakingResources = GraphicsSettings.GetRenderPipelineSettings();
m_ShaderRL = m_Context.CreateRayTracingShader(m_Backend switch
{
RayTracingBackend.Hardware => bakingResources.renderingLayerRT,
RayTracingBackend.Compute => bakingResources.renderingLayerCS,
_ => null
});
}
return m_ShaderRL;
}
}
public void BindSamplingTextures(CommandBuffer cmd)
{
if (m_SamplingResources == null)
{
m_SamplingResources = new SamplingResources();
m_SamplingResources.Load();
}
SamplingResources.Bind(cmd, m_SamplingResources);
}
public bool TryGetMeshForAccelerationStructure(Renderer renderer, out Mesh mesh)
{
mesh = null;
if (renderer.isPartOfStaticBatch)
{
Debug.LogError("Static batching is not supported when baking APV.");
return false;
}
mesh = renderer.GetComponent().sharedMesh;
if (mesh == null)
return false;
// This would error out later in LoadIndexBuffer in LightTransport package
if ((mesh.indexBufferTarget & GraphicsBuffer.Target.Raw) == 0 && (mesh.GetIndices(0) == null || mesh.GetIndices(0).Length == 0))
return false;
return true;
}
public void Dispose()
{
if (m_Context != null)
{
m_Context.Dispose();
m_Context = null;
// The lifetime of these shaders are bound to the lifetime of the context.
m_ShaderRL = null;
m_ShaderSO = null;
m_ShaderVO = null;
}
m_SamplingResources?.Dispose();
m_SamplingResources = null;
}
}
// Helper functions to bake a subset of the probes
internal static void BakeProbes(Vector3[] positionValues, SphericalHarmonicsL2[] shValues, float[] validityValues)
{
int numProbes = positionValues.Length;
var positionsInput = new NativeArray(positionValues, Allocator.Temp);
var lightingJob = lightingOverride ?? new DefaultLightTransport();
lightingJob.Initialize(false, positionsInput);
var defaultJob = lightingJob as DefaultLightTransport;
if (defaultJob != null)
{
var job = new BakeJob();
job.Create(null, ProbeVolumeLightingTab.GetLightingSettings(), false);
job.probeCount = numProbes;
defaultJob.jobs = new BakeJob[] { job };
}
while (lightingJob.currentStep < lightingJob.stepCount)
lightingJob.Step();
lightingJob.irradiance.CopyTo(shValues);
lightingJob.validity.CopyTo(validityValues);
if (defaultJob != null)
{
foreach (var job in defaultJob.jobs)
job.Dispose();
}
lightingJob.Dispose();
positionsInput.Dispose();
}
internal static void BakeAdjustmentVolume(ProbeVolumeBakingSet bakingSet, ProbeAdjustmentVolume touchup)
{
var prv = ProbeReferenceVolume.instance;
var scenario = bakingSet.lightingScenario;
if (!bakingSet.scenarios.TryGetValue(scenario, out var scenarioData) || !scenarioData.ComputeHasValidData(prv.shBands))
{
Debug.LogError($"Lighting for scenario '{scenario}' is not baked. You need to Generate Lighting from the Lighting Window before updating baked data");
return;
}
float cellSize = bakingSet.cellSizeInMeters;
var cellCount = bakingSet.maxCellPosition + Vector3Int.one - bakingSet.minCellPosition;
int savedLevels = bakingSet.simplificationLevels;
float savedDistance = bakingSet.minDistanceBetweenProbes;
bool savedSkyOcclusion = bakingSet.skyOcclusion;
bool savedSkyDirection = bakingSet.skyOcclusionShadingDirection;
bool savedVirtualOffset = bakingSet.settings.virtualOffsetSettings.useVirtualOffset;
bool savedRenderingLayers = bakingSet.useRenderingLayers;
{
// Patch baking set as we are not gonna use a mix of baked values and new values
bakingSet.simplificationLevels = bakingSet.bakedSimplificationLevels;
bakingSet.minDistanceBetweenProbes = bakingSet.bakedMinDistanceBetweenProbes;
bakingSet.skyOcclusion = bakingSet.bakedSkyOcclusion;
bakingSet.skyOcclusionShadingDirection = bakingSet.bakedSkyShadingDirection;
bakingSet.settings.virtualOffsetSettings.useVirtualOffset = bakingSet.supportOffsetsChunkSize != 0;
bakingSet.useRenderingLayers = bakingSet.bakedMaskCount == 1 ? false : true;
m_BakingSet = bakingSet;
m_BakingBatch = new BakingBatch(cellCount, ProbeReferenceVolume.instance);
m_ProfileInfo = new ProbeVolumeProfileInfo();
ModifyProfileFromLoadedData(m_BakingSet);
m_CellPosToIndex.Clear();
m_CellsToDilate.Clear();
}
Debug.Assert(bakingSet.CheckCompatibleCellLayout());
// Clear loaded data
foreach (var data in prv.perSceneDataList)
data.QueueSceneRemoval();
prv.Clear();
// Recreate baking cells
var prevSHBands = prv.shBands;
prv.ForceNoDiskStreaming(true);
prv.ForceSHBand(ProbeVolumeSHBands.SphericalHarmonicsL2);
var touchupVolumesAndBounds = GetAdjustementVolumes();
int currentCell = 0;
var bakingCells = new BakingCell[bakingSet.cellDescs.Count];
var cellVolumes = new TouchupVolumeWithBoundsList[bakingSet.cellDescs.Count];
foreach (var cell in bakingSet.cellDescs.Values)
{
var bakingCell = ConvertCellToBakingCell(cell, bakingSet.GetCellData(cell.index));
bakingCell.ComputeBounds(cellSize);
bakingCells[currentCell] = bakingCell;
cellVolumes[currentCell] = bakingCell.SelectIntersectingAdjustmentVolumes(touchupVolumesAndBounds);
currentCell++;
m_CellPosToIndex.Add(bakingCell.position, bakingCell.index);
}
// Find probe positions
List<(int, int, int)> bakedProbes = new();
Dictionary positionToIndex = new();
NativeList uniquePositions = new NativeList(Allocator.Persistent);
touchup.GetOBBandAABB(out var obb, out var aabb);
var job = new BakeJob();
if (touchup.isActiveAndEnabled && touchup.mode == ProbeAdjustmentVolume.Mode.OverrideSampleCount)
job.Create(ProbeVolumeLightingTab.GetLightingSettings(), bakingSet.bakedSkyOcclusion, (obb, aabb, touchup));
else
job.Create(bakingSet, ProbeVolumeLightingTab.GetLightingSettings(), bakingSet.bakedSkyOcclusion);
for (int c = 0; c < bakingCells.Length; c++)
{
ref var cell = ref bakingCells[c];
if (touchup.IntersectsVolume(obb, aabb, cell.bounds))
{
for (int i = 0; i < cell.probePositions.Length; i++)
{
var pos = cell.probePositions[i];
if (!touchup.ContainsPoint(obb, aabb.center, pos))
continue;
int probeHash = m_BakingBatch.GetProbePositionHash(pos);
int subdivLevel = cell.bricks[i / 64].subdivisionLevel;
if (!positionToIndex.TryGetValue(probeHash, out var index))
{
index = uniquePositions.Length;
positionToIndex[probeHash] = index;
m_BakingBatch.uniqueBrickSubdiv[probeHash] = subdivLevel;
job.probeCount++;
uniquePositions.Add(pos);
}
else
m_BakingBatch.uniqueBrickSubdiv[probeHash] = Mathf.Min(subdivLevel, m_BakingBatch.uniqueBrickSubdiv[probeHash]);
bakedProbes.Add((index, c, i));
m_CellsToDilate[cell.index] = cell;
}
}
}
if (uniquePositions.Length != 0)
{
bool failed = false;
var jobs = new BakeJob[] { job };
// Apply virtual offset
var virtualOffsetJob = virtualOffsetOverride ?? new DefaultVirtualOffset();
virtualOffsetJob.Initialize(bakingSet, uniquePositions.AsArray());
while (!failed && virtualOffsetJob.currentStep < virtualOffsetJob.stepCount)
failed |= !virtualOffsetJob.Step();
if (!failed && virtualOffsetJob.offsets.IsCreated)
{
for (int i = 0; i < uniquePositions.Length; i++)
uniquePositions[i] += virtualOffsetJob.offsets[i];
}
// Bake sky occlusion
var skyOcclusionJob = skyOcclusionOverride ?? new DefaultSkyOcclusion();
skyOcclusionJob.Initialize(bakingSet, uniquePositions.AsArray());
if (skyOcclusionJob is DefaultSkyOcclusion defaultSOJob)
defaultSOJob.jobs = jobs;
while (!failed && skyOcclusionJob.currentStep < skyOcclusionJob.stepCount)
failed |= !skyOcclusionJob.Step();
if (!failed && skyOcclusionJob.shadingDirections.IsCreated)
skyOcclusionJob.Encode();
// Bake rendering layers
var layerMaskJob = renderingLayerOverride ?? new DefaultRenderingLayer();
layerMaskJob.Initialize(bakingSet, uniquePositions.AsArray());
while (!failed && layerMaskJob.currentStep < layerMaskJob.stepCount)
failed |= !layerMaskJob.Step();
// Bake probe SH
var lightingJob = lightingOverride ?? new DefaultLightTransport();
lightingJob.Initialize(ProbeVolumeLightingTab.GetLightingSettings().mixedBakeMode != MixedLightingMode.IndirectOnly, uniquePositions.AsArray(), layerMaskJob.renderingLayerMasks);
if (lightingJob is DefaultLightTransport defaultLightingJob)
defaultLightingJob.jobs = jobs;
while (!failed && lightingJob.currentStep < lightingJob.stepCount)
failed |= !lightingJob.Step();
// Upload new data in cells
foreach ((int uniqueProbeIndex, int cellIndex, int i) in bakedProbes)
{
ref var cell = ref bakingCells[cellIndex];
cell.SetBakedData(m_BakingSet, m_BakingBatch, cellVolumes[cellIndex], i, uniqueProbeIndex,
lightingJob.irradiance[uniqueProbeIndex], lightingJob.validity[uniqueProbeIndex],
layerMaskJob.renderingLayerMasks, virtualOffsetJob.offsets,
skyOcclusionJob.occlusion, skyOcclusionJob.encodedDirections, lightingJob.occlusion);
}
skyOcclusionJob.encodedDirections.Dispose();
virtualOffsetJob.Dispose();
skyOcclusionJob.Dispose();
lightingJob.Dispose();
layerMaskJob.Dispose();
if (!failed)
{
// Validate baking cells size before any global state modifications
var chunkSizeInProbes = ProbeBrickPool.GetChunkSizeInProbeCount();
var hasVirtualOffsets = m_BakingSet.settings.virtualOffsetSettings.useVirtualOffset;
var hasRenderingLayers = m_BakingSet.useRenderingLayers;
if (ValidateBakingCellsSize(bakingCells, chunkSizeInProbes, hasVirtualOffsets, hasRenderingLayers))
{
for (int c = 0; c < bakingCells.Length; c++)
{
ref var cell = ref bakingCells[c];
ComputeValidityMasks(cell);
}
// Attempt to write the result to disk
if (WriteBakingCells(bakingCells))
{
// Reload everything
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
if (m_BakingSet.hasDilation)
{
// Force reloading of data
foreach (var data in prv.perSceneDataList)
data.Initialize();
InitDilationShaders();
PerformDilation();
}
}
}
}
}
job.Dispose();
uniquePositions.Dispose();
prv.ForceNoDiskStreaming(false);
prv.ForceSHBand(prevSHBands);
{
// Restore values
bakingSet.simplificationLevels = savedLevels;
bakingSet.minDistanceBetweenProbes = savedDistance;
bakingSet.skyOcclusion = savedSkyOcclusion;
bakingSet.skyOcclusionShadingDirection = savedSkyDirection;
bakingSet.settings.virtualOffsetSettings.useVirtualOffset = savedVirtualOffset;
bakingSet.useRenderingLayers = savedRenderingLayers;
m_BakingBatch?.Dispose();
m_BakingBatch = null;
m_BakingSet = null;
}
if (ProbeVolumeLightingTab.instance == null)
AdaptiveProbeVolumes.Dispose();
}
}
}