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.
1134 lines
49 KiB
1134 lines
49 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.Collections;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
using Unity.IO.LowLevel.Unsafe;
|
|
using Unity.Mathematics;
|
|
using UnityEngine.Serialization;
|
|
|
|
using CellData = UnityEngine.Rendering.ProbeReferenceVolume.CellData;
|
|
using CellDesc = UnityEngine.Rendering.ProbeReferenceVolume.CellDesc;
|
|
|
|
namespace UnityEngine.Rendering
|
|
{
|
|
internal class LogarithmicAttribute : PropertyAttribute
|
|
{
|
|
public int min;
|
|
public int max;
|
|
|
|
public LogarithmicAttribute(int min, int max)
|
|
{
|
|
this.min = min;
|
|
this.max = max;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// An Asset which holds a set of settings to use with a <see cref="Probe Reference Volume"/>.
|
|
/// </summary>
|
|
[CoreRPHelpURL("probevolumes-usebakingsets", "com.unity.render-pipelines.high-definition")]
|
|
public sealed partial class ProbeVolumeBakingSet : ScriptableObject, ISerializationCallbackReceiver
|
|
{
|
|
internal enum Version
|
|
{
|
|
Initial,
|
|
RemoveProbeVolumeSceneData,
|
|
AssetsAlwaysReferenced,
|
|
}
|
|
|
|
[Serializable]
|
|
internal class PerScenarioDataInfo
|
|
{
|
|
public void Initialize(ProbeVolumeSHBands shBands)
|
|
{
|
|
m_HasValidData = ComputeHasValidData(shBands);
|
|
}
|
|
|
|
public bool IsValid()
|
|
{
|
|
return cellDataAsset != null && cellDataAsset.IsValid(); // if cellDataAsset is valid optional data (if available) should always be valid.
|
|
}
|
|
|
|
public bool HasValidData(ProbeVolumeSHBands shBands)
|
|
{
|
|
#if UNITY_EDITOR
|
|
return ComputeHasValidData(shBands);
|
|
#else
|
|
return m_HasValidData;
|
|
#endif
|
|
}
|
|
|
|
public bool ComputeHasValidData(ProbeVolumeSHBands shBands)
|
|
{
|
|
return cellDataAsset.FileExists() && (shBands == ProbeVolumeSHBands.SphericalHarmonicsL1 || cellOptionalDataAsset.FileExists());
|
|
}
|
|
|
|
public int sceneHash;
|
|
public ProbeVolumeStreamableAsset cellDataAsset; // Contains L0 L1 SH data
|
|
public ProbeVolumeStreamableAsset cellOptionalDataAsset; // Contains L2 SH data
|
|
public ProbeVolumeStreamableAsset cellProbeOcclusionDataAsset; // Contains per-probe occlusion for up to to 4 lights in range [0;1]
|
|
|
|
bool m_HasValidData;
|
|
}
|
|
|
|
[Serializable]
|
|
internal struct CellCounts
|
|
{
|
|
public int bricksCount;
|
|
public int chunksCount;
|
|
|
|
public void Add(CellCounts o)
|
|
{
|
|
bricksCount += o.bricksCount;
|
|
chunksCount += o.chunksCount;
|
|
}
|
|
}
|
|
|
|
// Baking Set Data
|
|
[SerializeField] internal bool singleSceneMode = true;
|
|
[SerializeField] internal bool dialogNoProbeVolumeInSetShown = false;
|
|
[SerializeField] internal ProbeVolumeBakingProcessSettings settings;
|
|
|
|
internal bool hasDilation => settings.dilationSettings.enableDilation && settings.dilationSettings.dilationDistance > 0.0f;
|
|
|
|
// We keep a separate list with only the guids for the sake of convenience when iterating from outside this class.
|
|
[SerializeField] private List<string> m_SceneGUIDs = new List<string>();
|
|
[SerializeField, Obsolete("This is now contained in the SceneBakeData structure. #from(2023.3)"), FormerlySerializedAs("scenesToNotBake")] internal List<string> obsoleteScenesToNotBake = new List<string>();
|
|
[SerializeField, FormerlySerializedAs("lightingScenarios")] internal List<string> m_LightingScenarios = new List<string>();
|
|
|
|
/// <summary>The list of scene GUIDs.</summary>
|
|
public IReadOnlyList<string> sceneGUIDs => m_SceneGUIDs;
|
|
/// <summary>The list of lighting scenarios.</summary>
|
|
public IReadOnlyList<string> lightingScenarios => m_LightingScenarios;
|
|
|
|
// List of cell descriptors.
|
|
[SerializeField] internal SerializedDictionary<int, CellDesc> cellDescs = new SerializedDictionary<int, CellDesc>();
|
|
|
|
internal Dictionary<int, CellData> cellDataMap = new Dictionary<int, CellData>();
|
|
List<int> m_TotalIndexList = new List<int>();
|
|
|
|
[Serializable]
|
|
struct SerializedPerSceneCellList
|
|
{
|
|
public string sceneGUID;
|
|
public List<int> cellList;
|
|
}
|
|
[SerializeField] List<SerializedPerSceneCellList> m_SerializedPerSceneCellList;
|
|
|
|
// Can't use SerializedDictionary here because we can't serialize a List of List T_T
|
|
internal Dictionary<string, List<int>> perSceneCellLists = new Dictionary<string, List<int>>();
|
|
|
|
// Assets containing actual cell data (SH, Validity, etc)
|
|
// This data will be streamed from disk to the GPU.
|
|
[SerializeField] internal ProbeVolumeStreamableAsset cellSharedDataAsset = null; // Contains validity data
|
|
[SerializeField] internal SerializedDictionary<string, PerScenarioDataInfo> scenarios = new SerializedDictionary<string, PerScenarioDataInfo>();
|
|
// This data will be streamed from disk but is only needed in CPU memory.
|
|
[SerializeField] internal ProbeVolumeStreamableAsset cellBricksDataAsset; // Contains bricks data
|
|
[SerializeField] internal ProbeVolumeStreamableAsset cellSupportDataAsset = null; // Contains debug data
|
|
|
|
[SerializeField] internal int chunkSizeInBricks;
|
|
[SerializeField] internal Vector3Int maxCellPosition;
|
|
[SerializeField] internal Vector3Int minCellPosition;
|
|
[SerializeField] internal Bounds globalBounds;
|
|
[SerializeField] internal int bakedSimplificationLevels = -1;
|
|
[SerializeField] internal float bakedMinDistanceBetweenProbes = -1.0f;
|
|
[SerializeField] internal bool bakedProbeOcclusion = false;
|
|
[SerializeField] internal int bakedSkyOcclusionValue = -1;
|
|
[SerializeField] internal int bakedSkyShadingDirectionValue = -1;
|
|
[SerializeField] internal Vector3 bakedProbeOffset = Vector3.zero;
|
|
[SerializeField] internal int bakedMaskCount = 1;
|
|
[SerializeField] internal uint4 bakedLayerMasks;
|
|
[SerializeField] internal int maxSHChunkCount = -1; // Maximum number of SH chunk for a cell in this set.
|
|
[SerializeField] internal int L0ChunkSize;
|
|
[SerializeField] internal int L1ChunkSize;
|
|
[SerializeField] internal int L2TextureChunkSize; // Optional. Size of the chunk for one texture (4 textures for all data)
|
|
[SerializeField] internal int ProbeOcclusionChunkSize; // Optional. Size of the chunk for one texture
|
|
[SerializeField] internal int sharedValidityMaskChunkSize; // Shared
|
|
[SerializeField] internal int sharedSkyOcclusionL0L1ChunkSize; // Shared
|
|
[SerializeField] internal int sharedSkyShadingDirectionIndicesChunkSize;
|
|
[SerializeField] internal int sharedDataChunkSize;
|
|
[SerializeField] internal int supportPositionChunkSize;
|
|
[SerializeField] internal int supportValidityChunkSize;
|
|
[SerializeField] internal int supportTouchupChunkSize;
|
|
[SerializeField] internal int supportLayerMaskChunkSize;
|
|
[SerializeField] internal int supportOffsetsChunkSize;
|
|
[SerializeField] internal int supportDataChunkSize;
|
|
|
|
internal bool bakedSkyOcclusion
|
|
{
|
|
get => bakedSkyOcclusionValue <= 0 ? false : true;
|
|
set => bakedSkyOcclusionValue = value ? 1 : 0;
|
|
}
|
|
internal bool bakedSkyShadingDirection
|
|
{
|
|
get => bakedSkyShadingDirectionValue <= 0 ? false : true;
|
|
set => bakedSkyShadingDirectionValue = value ? 1 : 0;
|
|
}
|
|
|
|
[SerializeField] internal string lightingScenario = ProbeReferenceVolume.defaultLightingScenario;
|
|
string m_OtherScenario = null;
|
|
float m_ScenarioBlendingFactor = 0.0f;
|
|
|
|
internal string otherScenario => m_OtherScenario;
|
|
internal float scenarioBlendingFactor => m_ScenarioBlendingFactor;
|
|
|
|
ReadCommandArray m_ReadCommandArray = new ReadCommandArray();
|
|
NativeArray<ReadCommand> m_ReadCommandBuffer = new NativeArray<ReadCommand>();
|
|
Stack<NativeArray<byte>> m_ReadOperationScratchBuffers = new Stack<NativeArray<byte>>();
|
|
List<int> m_PrunedIndexList = new List<int>();
|
|
List<int> m_PrunedScenarioIndexList = new List<int>();
|
|
|
|
internal const int k_MaxSkyOcclusionBakingSamples = 8192;
|
|
|
|
// Baking Profile
|
|
[SerializeField]
|
|
Version version = CoreUtils.GetLastEnumValue<Version>();
|
|
|
|
[SerializeField]
|
|
internal bool freezePlacement = false;
|
|
|
|
/// <summary>
|
|
/// Offset on world origin used during baking. Can be used to have cells on positions that are not multiples of the probe spacing.
|
|
/// </summary>
|
|
[SerializeField]
|
|
public Vector3 probeOffset = Vector3.zero;
|
|
|
|
/// <summary>
|
|
/// How many levels contains the probes hierarchical structure.
|
|
/// </summary>
|
|
[Range(2, 5)]
|
|
public int simplificationLevels = 3;
|
|
|
|
/// <summary>
|
|
/// The size of a Cell in number of bricks.
|
|
/// </summary>
|
|
public int cellSizeInBricks => GetCellSizeInBricks(bakedSimplificationLevels);
|
|
|
|
/// <summary>
|
|
/// The minimum distance between two probes in meters.
|
|
/// </summary>
|
|
[Min(0.1f)]
|
|
public float minDistanceBetweenProbes = 1.0f;
|
|
|
|
/// <summary>
|
|
/// Maximum subdivision in the structure.
|
|
/// </summary>
|
|
public int maxSubdivision => GetMaxSubdivision(bakedSimplificationLevels);
|
|
|
|
/// <summary>
|
|
/// Minimum size of a brick in meters.
|
|
/// </summary>
|
|
public float minBrickSize => GetMinBrickSize(bakedMinDistanceBetweenProbes);
|
|
|
|
/// <summary>
|
|
/// Size of the cell in meters.
|
|
/// </summary>
|
|
public float cellSizeInMeters => (float)cellSizeInBricks * minBrickSize;
|
|
|
|
/// <summary>
|
|
/// Layer mask filter for all renderers.
|
|
/// </summary>
|
|
public LayerMask renderersLayerMask = -1;
|
|
|
|
/// <summary>
|
|
/// Specifies the minimum bounding box volume of renderers to consider placing probes around.
|
|
/// </summary>
|
|
[Min(0)]
|
|
public float minRendererVolumeSize = 0.1f;
|
|
|
|
/// <summary>
|
|
/// Specifies whether the baking set will have sky handled dynamically.
|
|
/// </summary>
|
|
public bool skyOcclusion = false;
|
|
|
|
/// <summary>
|
|
/// Controls the number of samples per probe for dynamic sky baking.
|
|
/// </summary>
|
|
[Logarithmic(1, k_MaxSkyOcclusionBakingSamples)]
|
|
public int skyOcclusionBakingSamples = 2048;
|
|
|
|
/// <summary>
|
|
/// Controls the number of bounces per light path for dynamic sky baking.
|
|
/// </summary>
|
|
[Range(0, 5)]
|
|
public int skyOcclusionBakingBounces = 2;
|
|
|
|
/// <summary>
|
|
/// Average albedo for dynamic sky bounces
|
|
/// </summary>
|
|
[Range(0, 1)]
|
|
public float skyOcclusionAverageAlbedo = 0.6f;
|
|
|
|
/// <summary>
|
|
/// Sky Occlusion backface culling
|
|
/// </summary>
|
|
public bool skyOcclusionBackFaceCulling = false;
|
|
|
|
/// <summary>
|
|
/// Bake sky shading direction.
|
|
/// </summary>
|
|
public bool skyOcclusionShadingDirection = false;
|
|
|
|
[Serializable]
|
|
internal struct ProbeLayerMask
|
|
{
|
|
public RenderingLayerMask mask;
|
|
public string name;
|
|
}
|
|
|
|
[SerializeField]
|
|
internal bool useRenderingLayers = false;
|
|
[SerializeField]
|
|
internal ProbeLayerMask[] renderingLayerMasks;
|
|
|
|
internal uint4 ComputeRegionMasks()
|
|
{
|
|
uint4 masks = 0;
|
|
if (!useRenderingLayers || renderingLayerMasks == null)
|
|
masks.x = 0xFFFFFFFF;
|
|
else
|
|
{
|
|
for (int i = 0; i < renderingLayerMasks.Length; i++)
|
|
masks[i] = renderingLayerMasks[i].mask;
|
|
|
|
}
|
|
return masks;
|
|
}
|
|
|
|
internal static int GetCellSizeInBricks(int simplificationLevels) => (int)Mathf.Pow(3, simplificationLevels);
|
|
internal static int GetMaxSubdivision(int simplificationLevels) => simplificationLevels + 1; // we add one for the top subdiv level which is the same size as a cell
|
|
internal static float GetMinBrickSize(float minDistanceBetweenProbes) => Mathf.Max(0.01f, minDistanceBetweenProbes * 3.0f);
|
|
|
|
bool m_HasSupportData = false;
|
|
bool m_SharedDataIsValid = false;
|
|
bool m_UseStreamingAsset = true;
|
|
|
|
private void OnValidate()
|
|
{
|
|
singleSceneMode &= m_SceneGUIDs.Count <= 1;
|
|
|
|
if (m_LightingScenarios.Count == 0)
|
|
m_LightingScenarios = new List<string>() { ProbeReferenceVolume.defaultLightingScenario };
|
|
|
|
settings.Upgrade();
|
|
}
|
|
|
|
void OnEnable()
|
|
{
|
|
Migrate();
|
|
|
|
m_HasSupportData = ComputeHasSupportData();
|
|
m_SharedDataIsValid = ComputeHasValidSharedData();
|
|
}
|
|
|
|
internal void Migrate()
|
|
{
|
|
if (version != CoreUtils.GetLastEnumValue<Version>())
|
|
{
|
|
#pragma warning disable 618 // Type or member is obsolete
|
|
if (version < Version.RemoveProbeVolumeSceneData)
|
|
{
|
|
#if UNITY_EDITOR
|
|
var sceneData = ProbeReferenceVolume.instance.sceneData;
|
|
if (sceneData == null)
|
|
return;
|
|
|
|
foreach (var scene in m_SceneGUIDs)
|
|
{
|
|
SceneBakeData newSceneData = new SceneBakeData();
|
|
sceneData.obsoleteSceneBounds.TryGetValue(scene, out newSceneData.bounds);
|
|
sceneData.obsoleteHasProbeVolumes.TryGetValue(scene, out newSceneData.hasProbeVolume);
|
|
newSceneData.bakeScene = !obsoleteScenesToNotBake.Contains(scene);
|
|
m_SceneBakeData.Add(scene, newSceneData);
|
|
}
|
|
|
|
version = Version.RemoveProbeVolumeSceneData;
|
|
UnityEditor.EditorUtility.SetDirty(this);
|
|
#endif
|
|
}
|
|
|
|
// Upgrade baking sets from before we always stored asset references.
|
|
if (version < Version.AssetsAlwaysReferenced && ProbeReferenceVolume.instance.isInitialized)
|
|
{
|
|
#if UNITY_EDITOR
|
|
cellBricksDataAsset.EnsureAssetLoaded();
|
|
cellSharedDataAsset.EnsureAssetLoaded();
|
|
cellSupportDataAsset.EnsureAssetLoaded();
|
|
foreach (var scenario in scenarios)
|
|
{
|
|
scenario.Value.cellDataAsset.EnsureAssetLoaded();
|
|
scenario.Value.cellOptionalDataAsset.EnsureAssetLoaded();
|
|
scenario.Value.cellProbeOcclusionDataAsset.EnsureAssetLoaded();
|
|
}
|
|
|
|
version = Version.AssetsAlwaysReferenced;
|
|
|
|
// Save immediately since these references must be written to disk for certain functionality
|
|
// to work, such as exporting a .unitypackage. Changing in memory is not enough.
|
|
UnityEditor.EditorUtility.SetDirty(this);
|
|
UnityEditor.AssetDatabase.SaveAssetIfDirty(this);
|
|
#endif
|
|
}
|
|
#pragma warning restore 618
|
|
}
|
|
|
|
if (sharedValidityMaskChunkSize == 0)
|
|
sharedValidityMaskChunkSize = sizeof(byte) * ProbeBrickPool.GetChunkSizeInProbeCount();
|
|
|
|
if (settings.virtualOffsetSettings.validityThreshold == 0.0f)
|
|
settings.virtualOffsetSettings.validityThreshold = 0.25f;
|
|
}
|
|
|
|
// For functions below:
|
|
// In editor users can delete asset at any moment, so we need to compute the result from scratch all the time.
|
|
// In builds however, we want to avoid the expensive I/O operations.
|
|
bool ComputeHasValidSharedData()
|
|
{
|
|
return cellSharedDataAsset != null && cellSharedDataAsset.FileExists() && cellBricksDataAsset.FileExists();
|
|
}
|
|
|
|
internal bool HasValidSharedData()
|
|
{
|
|
#if UNITY_EDITOR
|
|
return ComputeHasValidSharedData();
|
|
#else
|
|
return m_SharedDataIsValid;
|
|
#endif
|
|
}
|
|
|
|
// Return true if baking settings are incompatible with already baked data
|
|
internal bool CheckCompatibleCellLayout()
|
|
{
|
|
return simplificationLevels == bakedSimplificationLevels &&
|
|
minDistanceBetweenProbes == bakedMinDistanceBetweenProbes &&
|
|
skyOcclusion == bakedSkyOcclusion &&
|
|
skyOcclusionShadingDirection == bakedSkyShadingDirection &&
|
|
settings.virtualOffsetSettings.useVirtualOffset == (supportOffsetsChunkSize != 0) &&
|
|
useRenderingLayers == (bakedMaskCount != 1);
|
|
}
|
|
|
|
bool ComputeHasSupportData()
|
|
{
|
|
return cellSupportDataAsset != null && cellSupportDataAsset.IsValid() && cellSupportDataAsset.FileExists();
|
|
}
|
|
|
|
internal bool HasSupportData()
|
|
{
|
|
#if UNITY_EDITOR
|
|
return ComputeHasSupportData();
|
|
#else
|
|
return m_HasSupportData;
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests if the baking set data has already been baked.
|
|
/// </summary>
|
|
/// <param name="scenario">The name of the scenario to test. If null or if scenarios are disabled, the function will test for the default scenario.</param>
|
|
/// <returns>True if the baking set data has been baked.</returns>
|
|
public bool HasBakedData(string scenario = null)
|
|
{
|
|
if (scenario == null)
|
|
return scenarios.ContainsKey(ProbeReferenceVolume.defaultLightingScenario);
|
|
|
|
if (!ProbeReferenceVolume.instance.supportLightingScenarios && scenario != ProbeReferenceVolume.defaultLightingScenario)
|
|
return false;
|
|
return scenarios.ContainsKey(scenario);
|
|
}
|
|
|
|
/// <summary>Called after deserializing</summary>
|
|
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
|
{
|
|
if (!m_LightingScenarios.Contains(lightingScenario))
|
|
{
|
|
if (m_LightingScenarios.Count != 0)
|
|
lightingScenario = m_LightingScenarios[0];
|
|
else
|
|
lightingScenario = ProbeReferenceVolume.defaultLightingScenario;
|
|
}
|
|
|
|
perSceneCellLists.Clear();
|
|
foreach(var scene in m_SerializedPerSceneCellList)
|
|
{
|
|
perSceneCellLists.Add(scene.sceneGUID, scene.cellList);
|
|
}
|
|
|
|
if (m_OtherScenario == "")
|
|
m_OtherScenario = null;
|
|
|
|
if (bakedSimplificationLevels == -1)
|
|
{
|
|
bakedSimplificationLevels = simplificationLevels;
|
|
bakedMinDistanceBetweenProbes = minDistanceBetweenProbes;
|
|
}
|
|
|
|
if (bakedSkyOcclusionValue == -1)
|
|
bakedSkyOcclusion = false;
|
|
|
|
if (bakedSkyShadingDirectionValue == -1)
|
|
bakedSkyShadingDirection = false;
|
|
|
|
|
|
// Hack T_T
|
|
// Added the new bricksCount in Disk Streaming PR to have everything ready in the serialized desc but old data does not have it so we need to recompute it...
|
|
// Might as well not serialize it but it's bad to have non-serialized data in the serialized desc.
|
|
if (cellDescs.Count != 0)
|
|
{
|
|
// Check first cell to see if we need to recompute for all cells.
|
|
var enumerator = cellDescs.Values.GetEnumerator();
|
|
enumerator.MoveNext();
|
|
var cellDesc = enumerator.Current;
|
|
if (cellDesc.bricksCount == 0)
|
|
{
|
|
foreach(var value in cellDescs.Values)
|
|
value.bricksCount = value.probeCount / ProbeBrickPool.kBrickProbeCountTotal;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Called before serializing</summary>
|
|
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
|
{
|
|
m_SerializedPerSceneCellList = new List<SerializedPerSceneCellList>();
|
|
foreach (var kvp in perSceneCellLists)
|
|
{
|
|
m_SerializedPerSceneCellList.Add(new SerializedPerSceneCellList { sceneGUID = kvp.Key, cellList = kvp.Value });
|
|
}
|
|
}
|
|
|
|
internal void Initialize(bool useStreamingAsset)
|
|
{
|
|
// Would have been better in OnEnable but unfortunately, ProbeReferenceVolume.instance.shBands might not be initialized yet when it's called.
|
|
foreach (var scenario in scenarios)
|
|
scenario.Value.Initialize(ProbeReferenceVolume.instance.shBands);
|
|
|
|
if (!useStreamingAsset)
|
|
{
|
|
m_UseStreamingAsset = false;
|
|
m_TotalIndexList.Clear();
|
|
foreach (var index in cellDescs.Keys)
|
|
m_TotalIndexList.Add(index);
|
|
|
|
ResolveAllCellData();
|
|
}
|
|
|
|
// Reset blending.
|
|
if (ProbeReferenceVolume.instance.supportScenarioBlending)
|
|
BlendLightingScenario(null, 0.0f);
|
|
}
|
|
|
|
internal void Cleanup()
|
|
{
|
|
if (cellSharedDataAsset != null)
|
|
{
|
|
cellSharedDataAsset.Dispose();
|
|
foreach (var scenario in scenarios)
|
|
{
|
|
if (scenario.Value.IsValid())
|
|
{
|
|
scenario.Value.cellDataAsset.Dispose();
|
|
scenario.Value.cellOptionalDataAsset.Dispose();
|
|
scenario.Value.cellProbeOcclusionDataAsset.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_ReadCommandBuffer.IsCreated)
|
|
m_ReadCommandBuffer.Dispose();
|
|
|
|
foreach(var buffer in m_ReadOperationScratchBuffers)
|
|
buffer.Dispose();
|
|
|
|
m_ReadOperationScratchBuffers.Clear();
|
|
}
|
|
|
|
internal void SetActiveScenario(string scenario, bool verbose = true)
|
|
{
|
|
if (lightingScenario == scenario)
|
|
return;
|
|
|
|
if (!m_LightingScenarios.Contains(scenario))
|
|
{
|
|
if (verbose)
|
|
Debug.LogError($"Scenario '{scenario}' does not exist.");
|
|
return;
|
|
}
|
|
|
|
if (!scenarios.ContainsKey(scenario))
|
|
{
|
|
// We don't return here as it's still valid to enable a scenario that wasn't baked in the editor.
|
|
if (verbose)
|
|
Debug.LogError($"Scenario '{scenario}' has not been baked.");
|
|
}
|
|
|
|
lightingScenario = scenario;
|
|
m_ScenarioBlendingFactor = 0.0f;
|
|
|
|
if (ProbeReferenceVolume.instance.supportScenarioBlending)
|
|
{
|
|
// Trigger blending system to replace old cells with the one from the new active scenario.
|
|
// Although we technically don't need blending for that, it is better than unloading all cells
|
|
// because it will replace them progressively. There is no real performance cost to using blending
|
|
// rather than regular load thanks to the bypassBlending branch in AddBlendingBricks.
|
|
ProbeReferenceVolume.instance.ScenarioBlendingChanged(true);
|
|
}
|
|
else
|
|
ProbeReferenceVolume.instance.UnloadAllCells();
|
|
|
|
#if UNITY_EDITOR
|
|
UnityEditor.EditorUtility.SetDirty(this);
|
|
#endif
|
|
}
|
|
|
|
internal void BlendLightingScenario(string otherScenario, float blendingFactor)
|
|
{
|
|
if (!string.IsNullOrEmpty(otherScenario) && !ProbeReferenceVolume.instance.supportScenarioBlending)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// null scenario is valid in order to reset blending.
|
|
if (otherScenario != null && !m_LightingScenarios.Contains(otherScenario))
|
|
{
|
|
Debug.LogError($"Scenario '{otherScenario}' does not exist.");
|
|
return;
|
|
}
|
|
|
|
if (otherScenario != null && !scenarios.ContainsKey(otherScenario))
|
|
{
|
|
Debug.LogError($"Scenario '{otherScenario}' has not been baked.");
|
|
return;
|
|
}
|
|
|
|
blendingFactor = Mathf.Clamp01(blendingFactor);
|
|
|
|
if (otherScenario == lightingScenario || string.IsNullOrEmpty(otherScenario))
|
|
otherScenario = null;
|
|
if (otherScenario == null)
|
|
blendingFactor = 0.0f;
|
|
if (otherScenario == m_OtherScenario && Mathf.Approximately(blendingFactor, m_ScenarioBlendingFactor))
|
|
return;
|
|
|
|
bool scenarioChanged = otherScenario != m_OtherScenario;
|
|
m_OtherScenario = otherScenario;
|
|
m_ScenarioBlendingFactor = blendingFactor;
|
|
|
|
ProbeReferenceVolume.instance.ScenarioBlendingChanged(scenarioChanged);
|
|
}
|
|
|
|
internal int GetBakingHashCode()
|
|
{
|
|
int hash = maxCellPosition.GetHashCode();
|
|
hash = hash * 23 + minCellPosition.GetHashCode();
|
|
hash = hash * 23 + globalBounds.GetHashCode();
|
|
hash = hash * 23 + cellSizeInBricks.GetHashCode();
|
|
hash = hash * 23 + simplificationLevels.GetHashCode();
|
|
hash = hash * 23 + minDistanceBetweenProbes.GetHashCode();
|
|
|
|
return hash;
|
|
}
|
|
|
|
|
|
static int AlignUp16(int count)
|
|
{
|
|
var alignment = 16;
|
|
var remainder = count % alignment;
|
|
return count + (remainder == 0 ? 0 : alignment - remainder);
|
|
}
|
|
|
|
NativeArray<T> GetSubArray<T>(NativeArray<byte> input, int count, ref int offset) where T : struct
|
|
{
|
|
var size = count * UnsafeUtility.SizeOf<T>();
|
|
if (offset + size > input.Length)
|
|
return default;
|
|
|
|
var result = input.GetSubArray(offset, size).Reinterpret<T>(1);
|
|
offset = AlignUp16(offset + size);
|
|
return result;
|
|
}
|
|
|
|
NativeArray<byte> RequestScratchBuffer(int size)
|
|
{
|
|
if (m_ReadOperationScratchBuffers.Count == 0)
|
|
{
|
|
return new NativeArray<byte>(size, Allocator.Persistent);
|
|
}
|
|
else
|
|
{
|
|
var buffer = m_ReadOperationScratchBuffers.Pop();
|
|
Debug.Assert(buffer.IsCreated);
|
|
if (buffer.Length < size)
|
|
{
|
|
buffer.Dispose();
|
|
return new NativeArray<byte>(size, Allocator.Persistent);
|
|
}
|
|
else
|
|
{
|
|
return buffer;
|
|
}
|
|
}
|
|
}
|
|
|
|
private unsafe bool FileExists(string path)
|
|
{
|
|
// Can't use System.IO.File.Exists as it doesn't work with compressed streaming assets folder (iOS, Android)
|
|
FileInfoResult infoResult;
|
|
ReadHandle h = AsyncReadManager.GetFileInfo(path, &infoResult);
|
|
h.JobHandle.Complete();
|
|
return infoResult.FileState == FileState.Exists;
|
|
}
|
|
|
|
// Load from disk all data related to the required cells only.
|
|
// This allows us to avoid loading the whole file in memory which could be a huge spike for multi scene setups.
|
|
unsafe NativeArray<T> LoadStreambleAssetData<T>(ProbeVolumeStreamableAsset asset, List<int> cellIndices) where T : struct
|
|
{
|
|
if (!m_UseStreamingAsset)
|
|
{
|
|
Debug.Assert(asset.asset != null);
|
|
return asset.asset.GetData<byte>().Reinterpret<T>(1);
|
|
}
|
|
else
|
|
{
|
|
if (!FileExists(asset.GetAssetPath()))
|
|
{
|
|
asset.RefreshAssetPath();
|
|
if (!FileExists(asset.GetAssetPath()))
|
|
{
|
|
// If we can't load from StreamingAssets, but we have a valid asset reference, try to use the asset reference instead.
|
|
if (asset.HasValidAssetReference())
|
|
{
|
|
return asset.asset.GetData<byte>().Reinterpret<T>(1);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogAssertion($"File {asset.GetAssetPath()} does not exist on disk. If you are trying to load Adaptive Probe Volumes from AssetBundles or Addressables, " +
|
|
"tick the \"Probe Volume Disable Streaming Assets\" project setting in the Graphics tab when building the Player.");
|
|
return default;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prepare read commands.
|
|
// Reallocate read commands buffer if needed.
|
|
if (!m_ReadCommandBuffer.IsCreated || m_ReadCommandBuffer.Length < cellIndices.Count)
|
|
{
|
|
if (m_ReadCommandBuffer.IsCreated)
|
|
m_ReadCommandBuffer.Dispose();
|
|
m_ReadCommandBuffer = new NativeArray<ReadCommand>(cellIndices.Count, Allocator.Persistent);
|
|
}
|
|
|
|
// Compute total size and fill read command offsets/sizes
|
|
int totalSize = 0;
|
|
int commandIndex = 0;
|
|
foreach (int cellIndex in cellIndices)
|
|
{
|
|
var cell = cellDescs[cellIndex];
|
|
var streamableCellDesc = asset.streamableCellDescs[cellIndex];
|
|
ReadCommand command = new ReadCommand();
|
|
command.Offset = streamableCellDesc.offset;
|
|
command.Size = streamableCellDesc.elementCount * asset.elementSize;
|
|
command.Buffer = null;
|
|
m_ReadCommandBuffer[commandIndex++] = command;
|
|
|
|
totalSize += (int)command.Size;
|
|
}
|
|
|
|
var scratchBuffer = RequestScratchBuffer(totalSize);
|
|
|
|
// Update output buffer pointers
|
|
commandIndex = 0;
|
|
long outputOffset = 0;
|
|
byte* scratchPtr = (byte*)scratchBuffer.GetUnsafePtr();
|
|
foreach (int cellIndex in cellIndices)
|
|
{
|
|
// Stupid C# and no ref returns by default...
|
|
var command = m_ReadCommandBuffer[commandIndex];
|
|
command.Buffer = scratchPtr + outputOffset;
|
|
outputOffset += command.Size;
|
|
m_ReadCommandBuffer[commandIndex++] = command;
|
|
}
|
|
|
|
m_ReadCommandArray.CommandCount = cellIndices.Count;
|
|
m_ReadCommandArray.ReadCommands = (ReadCommand*)m_ReadCommandBuffer.GetUnsafePtr();
|
|
|
|
// We don't need async read here but only partial read to avoid loading up the whole file for ever.
|
|
// So we just wait for the result.
|
|
var readHandle = AsyncReadManager.Read(asset.OpenFile(), m_ReadCommandArray);
|
|
readHandle.JobHandle.Complete();
|
|
Debug.Assert(readHandle.Status == ReadStatus.Complete);
|
|
asset.CloseFile();
|
|
readHandle.Dispose();
|
|
|
|
return scratchBuffer.Reinterpret<T>(1);
|
|
}
|
|
}
|
|
|
|
void ReleaseStreamableAssetData<T>(NativeArray<T> buffer) where T : struct
|
|
{
|
|
if (m_UseStreamingAsset)
|
|
m_ReadOperationScratchBuffers.Push(buffer.Reinterpret<byte>(UnsafeUtility.SizeOf<T>()));
|
|
}
|
|
|
|
void PruneCellIndexList(List<int> cellIndices, List<int> prunedIndexList)
|
|
{
|
|
prunedIndexList.Clear();
|
|
foreach (var cellIndex in cellIndices)
|
|
{
|
|
// When clearing data only partially (ie: not all scenes are loaded), there can be left over indices here but no cells in the set.
|
|
if (!cellDataMap.ContainsKey(cellIndex))
|
|
{
|
|
prunedIndexList.Add(cellIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PruneCellIndexListForScenario(List<int> cellIndices, PerScenarioDataInfo scenarioData, List<int> prunedIndexList)
|
|
{
|
|
prunedIndexList.Clear();
|
|
foreach (var cellIndex in cellIndices)
|
|
{
|
|
// When partially (in terms of scenes) baking for different scenarios with different list of scenes on after the other,
|
|
// not all scenarios contains all cells, so we need to remove the cells that are not available.
|
|
if (scenarioData.cellDataAsset.streamableCellDescs.ContainsKey(cellIndex))
|
|
{
|
|
prunedIndexList.Add(cellIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal List<int> GetSceneCellIndexList(string sceneGUID)
|
|
{
|
|
if (perSceneCellLists.TryGetValue(sceneGUID, out var indexList))
|
|
return indexList;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
bool ResolveAllCellData()
|
|
{
|
|
Debug.Assert(!m_UseStreamingAsset);
|
|
Debug.Assert(m_TotalIndexList.Count != 0);
|
|
|
|
if (ResolveSharedCellData(m_TotalIndexList))
|
|
return ResolvePerScenarioCellData(m_TotalIndexList);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
internal bool ResolveCellData(List<int> cellIndices)
|
|
{
|
|
// All cells should already be resolved in CPU memory.
|
|
if (!m_UseStreamingAsset)
|
|
return true;
|
|
|
|
if (cellIndices == null)
|
|
return false;
|
|
|
|
// Prune index list (some cells might already be resolved from another scene).
|
|
PruneCellIndexList(cellIndices, m_PrunedIndexList);
|
|
|
|
// When disk streaming is enabled, we should never resolve Cell data in CPU memory.
|
|
// The streaming system will upload them directly to the GPU.
|
|
if (ProbeReferenceVolume.instance.diskStreamingEnabled)
|
|
{
|
|
// Prepare data structures.
|
|
// GPU data will stay empty but CPU data (bricks, support) will be streamed here.
|
|
foreach (var cell in m_PrunedIndexList)
|
|
{
|
|
Debug.Assert(!cellDataMap.ContainsKey(cell));
|
|
// Not ideal.
|
|
// When streaming and blending, we still need to have a valid list of scenario per CellData.
|
|
var newCellData = new CellData();
|
|
foreach (var scenario in scenarios)
|
|
newCellData.scenarios.Add(scenario.Key, default);
|
|
|
|
cellDataMap.Add(cell, newCellData);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (ResolveSharedCellData(m_PrunedIndexList))
|
|
{
|
|
return ResolvePerScenarioCellData(m_PrunedIndexList);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ResolveSharedCellData(List<int> cellIndices, NativeArray<ProbeBrickIndex.Brick> bricksData, NativeArray<byte> cellSharedData, NativeArray<byte> cellSupportData)
|
|
{
|
|
var prv = ProbeReferenceVolume.instance;
|
|
bool hasSupportData = cellSupportData.Length != 0;
|
|
|
|
// Resolve per cell
|
|
var sharedDataChunkOffset = 0;
|
|
var supportDataChunkOffset = 0;
|
|
int totalBricksCount = 0;
|
|
int totalSHChunkCount = 0;
|
|
for (var i = 0; i < cellIndices.Count; ++i)
|
|
{
|
|
int cellIndex = cellIndices[i];
|
|
var cellData = new CellData();
|
|
var cellDesc = cellDescs[cellIndex];
|
|
int bricksCount = cellDesc.bricksCount;
|
|
int shChunkCount = cellDesc.shChunkCount;
|
|
|
|
Debug.Assert(!cellDataMap.ContainsKey(cellIndex)); // Don't resolve the same cell twice.
|
|
|
|
// When we use Streaming Assets, we can't keep a reference to the source file data so we create a copy of the native array.
|
|
// When not using Streaming Assets, the file will always be alive so we can keep the reference on the file data.
|
|
var sourceBricks = bricksData.GetSubArray(totalBricksCount, bricksCount);
|
|
var sourceValidityNeightMaskData = cellSharedData.GetSubArray(sharedDataChunkOffset, sharedValidityMaskChunkSize * shChunkCount);
|
|
sharedDataChunkOffset += sharedValidityMaskChunkSize * shChunkCount;
|
|
|
|
cellData.bricks = m_UseStreamingAsset ? new NativeArray<ProbeBrickIndex.Brick>(sourceBricks, Allocator.Persistent) : sourceBricks;
|
|
cellData.validityNeighMaskData = m_UseStreamingAsset ? new NativeArray<byte>(sourceValidityNeightMaskData, Allocator.Persistent) : sourceValidityNeightMaskData;
|
|
|
|
// TODO save sky occlusion in a separate asset (see AdaptiveProbeVolumes WriteBakingCells)
|
|
// And load it depending on ProbeReferenceVolume.instance.skyOcclusion
|
|
if (bakedSkyOcclusion)
|
|
{
|
|
if (prv.skyOcclusion)
|
|
{
|
|
var sourceSkyOcclusionDataL0L1 = cellSharedData.GetSubArray(sharedDataChunkOffset, sharedSkyOcclusionL0L1ChunkSize * shChunkCount).Reinterpret<ushort>(1);
|
|
cellData.skyOcclusionDataL0L1 = m_UseStreamingAsset ? new NativeArray<ushort>(sourceSkyOcclusionDataL0L1, Allocator.Persistent) : sourceSkyOcclusionDataL0L1;
|
|
}
|
|
sharedDataChunkOffset += sharedSkyOcclusionL0L1ChunkSize * shChunkCount;
|
|
if (bakedSkyShadingDirection)
|
|
{
|
|
if (prv.skyOcclusion && prv.skyOcclusionShadingDirection)
|
|
{
|
|
var sourceSkyShadingDirectionIndices = cellSharedData.GetSubArray(sharedDataChunkOffset, sharedSkyShadingDirectionIndicesChunkSize * shChunkCount);
|
|
cellData.skyShadingDirectionIndices = m_UseStreamingAsset ? new NativeArray<byte>(sourceSkyShadingDirectionIndices, Allocator.Persistent) : sourceSkyShadingDirectionIndices;
|
|
}
|
|
sharedDataChunkOffset += sharedSkyShadingDirectionIndicesChunkSize * shChunkCount;
|
|
}
|
|
}
|
|
|
|
if (hasSupportData)
|
|
{
|
|
var sourcePositions = cellSupportData.GetSubArray(supportDataChunkOffset, shChunkCount * supportPositionChunkSize).Reinterpret<Vector3>(1);
|
|
supportDataChunkOffset += shChunkCount * supportPositionChunkSize;
|
|
cellData.probePositions = m_UseStreamingAsset ? new NativeArray<Vector3>(sourcePositions, Allocator.Persistent) : sourcePositions;
|
|
|
|
var sourceValidity = cellSupportData.GetSubArray(supportDataChunkOffset, shChunkCount * supportValidityChunkSize).Reinterpret<float>(1);
|
|
supportDataChunkOffset += shChunkCount * supportValidityChunkSize;
|
|
cellData.validity = m_UseStreamingAsset ? new NativeArray<float>(sourceValidity, Allocator.Persistent) : sourceValidity;
|
|
|
|
var sourceTouchup = cellSupportData.GetSubArray(supportDataChunkOffset, shChunkCount * supportTouchupChunkSize).Reinterpret<float>(1);
|
|
supportDataChunkOffset += shChunkCount * supportTouchupChunkSize;
|
|
cellData.touchupVolumeInteraction = m_UseStreamingAsset ? new NativeArray<float>(sourceTouchup, Allocator.Persistent) : sourceTouchup;
|
|
|
|
if (supportLayerMaskChunkSize != 0)
|
|
{
|
|
var sourceLayer = cellSupportData.GetSubArray(supportDataChunkOffset, shChunkCount * supportLayerMaskChunkSize).Reinterpret<byte>(1);
|
|
supportDataChunkOffset += shChunkCount * supportLayerMaskChunkSize;
|
|
cellData.layer = m_UseStreamingAsset ? new NativeArray<byte>(sourceLayer, Allocator.Persistent) : sourceLayer;
|
|
}
|
|
|
|
if (supportOffsetsChunkSize != 0)
|
|
{
|
|
var sourceOffsetVectors = cellSupportData.GetSubArray(supportDataChunkOffset, shChunkCount * supportOffsetsChunkSize).Reinterpret<Vector3>(1);
|
|
supportDataChunkOffset += shChunkCount * supportOffsetsChunkSize;
|
|
cellData.offsetVectors = m_UseStreamingAsset ? new NativeArray<Vector3>(sourceOffsetVectors, Allocator.Persistent) : sourceOffsetVectors;
|
|
}
|
|
}
|
|
|
|
cellDataMap.Add(cellIndex, cellData);
|
|
totalBricksCount += bricksCount;
|
|
totalSHChunkCount += shChunkCount;
|
|
}
|
|
}
|
|
|
|
|
|
internal bool ResolveSharedCellData(List<int> cellIndices)
|
|
{
|
|
Debug.Assert(!ProbeReferenceVolume.instance.diskStreamingEnabled);
|
|
|
|
// Set not baked
|
|
if (cellSharedDataAsset == null || !cellSharedDataAsset.IsValid())
|
|
return false;
|
|
|
|
if (!HasValidSharedData())
|
|
{
|
|
Debug.LogError($"One or more data file missing for baking set {name}. Cannot load shared data.");
|
|
return false;
|
|
}
|
|
|
|
// Load needed cells from disk.
|
|
var cellSharedData = LoadStreambleAssetData<byte>(cellSharedDataAsset, cellIndices);
|
|
var bricksData = LoadStreambleAssetData<ProbeBrickIndex.Brick>(cellBricksDataAsset, cellIndices);
|
|
|
|
bool hasSupportData = HasSupportData();
|
|
var cellSupportData = hasSupportData ? LoadStreambleAssetData<byte>(cellSupportDataAsset, cellIndices) : default;
|
|
|
|
ResolveSharedCellData(cellIndices, bricksData, cellSharedData, cellSupportData);
|
|
|
|
ReleaseStreamableAssetData(cellSharedData);
|
|
ReleaseStreamableAssetData(bricksData);
|
|
if (hasSupportData)
|
|
ReleaseStreamableAssetData(cellSupportData);
|
|
|
|
return true;
|
|
}
|
|
|
|
internal bool ResolvePerScenarioCellData(List<int> cellIndices)
|
|
{
|
|
Debug.Assert(!ProbeReferenceVolume.instance.diskStreamingEnabled);
|
|
|
|
bool shUseL2 = ProbeReferenceVolume.instance.shBands == ProbeVolumeSHBands.SphericalHarmonicsL2;
|
|
|
|
foreach (var scenario in scenarios)
|
|
{
|
|
var name = scenario.Key;
|
|
var data = scenario.Value;
|
|
|
|
PruneCellIndexListForScenario(cellIndices, data, m_PrunedScenarioIndexList);
|
|
|
|
if (!data.HasValidData(ProbeReferenceVolume.instance.shBands))
|
|
{
|
|
Debug.LogError($"One or more data file missing for baking set {name} scenario {lightingScenario}. Cannot load scenario data.");
|
|
return false;
|
|
}
|
|
|
|
var cellData = LoadStreambleAssetData<byte>(data.cellDataAsset, m_PrunedScenarioIndexList);
|
|
var cellOptionalData = shUseL2 ? LoadStreambleAssetData<byte>(data.cellOptionalDataAsset, m_PrunedScenarioIndexList) : default;
|
|
var cellProbeOcclusionData = bakedProbeOcclusion ? LoadStreambleAssetData<byte>(data.cellProbeOcclusionDataAsset, m_PrunedScenarioIndexList) : default;
|
|
if (!ResolvePerScenarioCellData(cellData, cellOptionalData, cellProbeOcclusionData, name, m_PrunedScenarioIndexList))
|
|
{
|
|
Debug.LogError($"Baked data for scenario '{name}' cannot be loaded.");
|
|
return false;
|
|
}
|
|
|
|
ReleaseStreamableAssetData(cellData);
|
|
if (shUseL2)
|
|
ReleaseStreamableAssetData(cellOptionalData);
|
|
if (bakedProbeOcclusion)
|
|
ReleaseStreamableAssetData(cellProbeOcclusionData);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal bool ResolvePerScenarioCellData(NativeArray<byte> cellData, NativeArray<byte> cellOptionalData, NativeArray<byte> cellProbeOcclusionData, string scenario, List<int> cellIndices)
|
|
{
|
|
Debug.Assert(!ProbeReferenceVolume.instance.diskStreamingEnabled);
|
|
|
|
if (!cellData.IsCreated)
|
|
return false;
|
|
|
|
// Optional L2 data
|
|
var hasOptionalData = cellOptionalData.IsCreated;
|
|
|
|
var hasProbeOcclusionData = cellProbeOcclusionData.IsCreated && cellProbeOcclusionData.Length > 0;
|
|
|
|
var chunkOffsetL0L1 = 0;
|
|
var chunkOffsetL2 = 0;
|
|
var chunkOffsetProbeOcclusion = 0;
|
|
|
|
for (var i = 0; i < cellIndices.Count; ++i)
|
|
{
|
|
var cellIndex = cellIndices[i];
|
|
var cell = cellDataMap[cellIndex];
|
|
var cellDesc = cellDescs[cellIndex];
|
|
var cellState = new CellData.PerScenarioData();
|
|
|
|
var shChunkCount = cellDesc.shChunkCount;
|
|
|
|
var sourceShL0L1RxDataSource = cellData.GetSubArray(chunkOffsetL0L1, L0ChunkSize * shChunkCount).Reinterpret<ushort>(1);
|
|
var sourceShL1GL1RyDataSource = cellData.GetSubArray(chunkOffsetL0L1 + L0ChunkSize * shChunkCount, L1ChunkSize * shChunkCount);
|
|
var sourceShL1BL1RzDataSource = cellData.GetSubArray(chunkOffsetL0L1 + (L0ChunkSize + L1ChunkSize) * shChunkCount, L1ChunkSize * shChunkCount);
|
|
|
|
cellState.shL0L1RxData = m_UseStreamingAsset ? new NativeArray<ushort>(sourceShL0L1RxDataSource, Allocator.Persistent) : sourceShL0L1RxDataSource;
|
|
cellState.shL1GL1RyData = m_UseStreamingAsset ?new NativeArray<byte>(sourceShL1GL1RyDataSource, Allocator.Persistent) : sourceShL1GL1RyDataSource;
|
|
cellState.shL1BL1RzData = m_UseStreamingAsset ? new NativeArray<byte>(sourceShL1BL1RzDataSource, Allocator.Persistent) : sourceShL1BL1RzDataSource;
|
|
|
|
if (hasOptionalData)
|
|
{
|
|
var L2DataSize = shChunkCount * L2TextureChunkSize;
|
|
|
|
var sourceShL2Data_0 = cellOptionalData.GetSubArray(chunkOffsetL2 + L2DataSize * 0, L2DataSize);
|
|
var sourceShL2Data_1 = cellOptionalData.GetSubArray(chunkOffsetL2 + L2DataSize * 1, L2DataSize);
|
|
var sourceShL2Data_2 = cellOptionalData.GetSubArray(chunkOffsetL2 + L2DataSize * 2, L2DataSize);
|
|
var sourceShL2Data_3 = cellOptionalData.GetSubArray(chunkOffsetL2 + L2DataSize * 3, L2DataSize);
|
|
|
|
cellState.shL2Data_0 = m_UseStreamingAsset ? new NativeArray<byte>(sourceShL2Data_0, Allocator.Persistent) : sourceShL2Data_0;
|
|
cellState.shL2Data_1 = m_UseStreamingAsset ? new NativeArray<byte>(sourceShL2Data_1, Allocator.Persistent) : sourceShL2Data_1;
|
|
cellState.shL2Data_2 = m_UseStreamingAsset ? new NativeArray<byte>(sourceShL2Data_2, Allocator.Persistent) : sourceShL2Data_2;
|
|
cellState.shL2Data_3 = m_UseStreamingAsset ? new NativeArray<byte>(sourceShL2Data_3, Allocator.Persistent) : sourceShL2Data_3;
|
|
}
|
|
|
|
if (hasProbeOcclusionData)
|
|
{
|
|
var sourceProbeOcclusionDataSource = cellProbeOcclusionData.GetSubArray(chunkOffsetProbeOcclusion, ProbeOcclusionChunkSize * shChunkCount);
|
|
|
|
cellState.probeOcclusion = m_UseStreamingAsset ? new NativeArray<byte>(sourceProbeOcclusionDataSource, Allocator.Persistent) : sourceProbeOcclusionDataSource;
|
|
}
|
|
|
|
chunkOffsetL0L1 += (L0ChunkSize + 2 * L1ChunkSize) * shChunkCount;
|
|
chunkOffsetL2 += (L2TextureChunkSize * 4) * shChunkCount;
|
|
chunkOffsetProbeOcclusion += ProbeOcclusionChunkSize * shChunkCount;
|
|
|
|
cell.scenarios.Add(scenario, cellState);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal void ReleaseCell(int cellIndex)
|
|
{
|
|
var cellData = cellDataMap[cellIndex];
|
|
cellData.Cleanup(true);
|
|
cellDataMap.Remove(cellIndex);
|
|
}
|
|
|
|
internal CellDesc GetCellDesc(int cellIndex)
|
|
{
|
|
if (cellDescs.TryGetValue(cellIndex, out var cellDesc))
|
|
return cellDesc;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
internal CellData GetCellData(int cellIndex)
|
|
{
|
|
if (cellDataMap.TryGetValue(cellIndex, out var cellData))
|
|
return cellData;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
internal int GetChunkGPUMemory(ProbeVolumeSHBands shBands)
|
|
{
|
|
// One L0 Chunk, Two L1 Chunks, 1 shared chunk which may contain sky occlusion
|
|
int size = L0ChunkSize + 2 * L1ChunkSize + sharedDataChunkSize;
|
|
|
|
// 4 Optional L2 Chunks
|
|
if (shBands == ProbeVolumeSHBands.SphericalHarmonicsL2)
|
|
size += 4 * L2TextureChunkSize;
|
|
|
|
// Optional probe occlusion
|
|
if (bakedProbeOcclusion)
|
|
size += ProbeOcclusionChunkSize;
|
|
|
|
return size;
|
|
}
|
|
|
|
internal bool HasSameSceneGUIDs(ProbeVolumeBakingSet other)
|
|
{
|
|
var otherSceneGUIDs = other.sceneGUIDs;
|
|
if (m_SceneGUIDs.Count != otherSceneGUIDs.Count)
|
|
return false;
|
|
|
|
for (var i = 0; i < m_SceneGUIDs.Count; ++i)
|
|
{
|
|
if (m_SceneGUIDs[i] != otherSceneGUIDs[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|