diff --git a/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeGIBaking.VirtualOffset.cs b/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeGIBaking.VirtualOffset.cs index 3b425855d55..99ff4f86219 100644 --- a/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeGIBaking.VirtualOffset.cs +++ b/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeGIBaking.VirtualOffset.cs @@ -1,16 +1,30 @@ -using System; -using UnityEngine.Profiling; -using UnityEngine; -using UnityEngine.Experimental.Rendering; +#define USE_JOBS +#if HAS_BURST +#define USE_BURST +#endif +//#define VERBOSE + using System.Collections.Generic; -using System.Runtime.InteropServices; using UnityEditor; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +#if USE_BURST + using Unity.Burst; +#endif + namespace UnityEngine.Experimental.Rendering { partial class ProbeGIBaking { - static List addedOccluders; + // limit memory usage to ~200 MB (run multiple batches to keep below limit) + const int kMaxMemoryUsage = 200 * 1024 * 1024; + + const int kMinCommandsPerJob = 512; + const int kRayDirectionsPerPosition = 3 * 3 * 3 - 1; + + static List ms_AddedOccluders; static void ApplyVirtualOffsets(Vector3[] positions, out Vector3[] offsets) { @@ -24,39 +38,25 @@ static void ApplyVirtualOffsets(Vector3[] positions, out Vector3[] offsets) var queriesHitBackBefore = Physics.queriesHitBackfaces; try { - Physics.queriesHitBackfaces = true; + if (!queriesHitBackBefore) + Physics.queriesHitBackfaces = true; AddOccluders(); - ApplyVirtualOffsetsSingleThreaded(positions, out offsets, voSettings); + DoApplyVirtualOffsets(positions, out offsets, voSettings); } finally { - Physics.queriesHitBackfaces = queriesHitBackBefore; - CleanupOccluders(); - } - } + if (!queriesHitBackBefore) + Physics.queriesHitBackfaces = false; - static void ApplyVirtualOffsetsSingleThreaded(Vector3[] positions, out Vector3[] offsets, VirtualOffsetSettings voSettings) - { - offsets = new Vector3[positions.Length]; - for (int i = 0; i < positions.Length; ++i) - { - int subdivLevel = 0; - m_BakingBatch.uniqueBrickSubdiv.TryGetValue(positions[i], out subdivLevel); - float brickSize = ProbeReferenceVolume.CellSize(subdivLevel); - float searchDistance = (brickSize * m_BakingProfile.minBrickSize) / ProbeBrickPool.kBrickCellCount; - - float scaleForSearchDist = voSettings.searchMultiplier; - Vector3 pushedPosition = PushPositionOutOfGeometry(positions[i], scaleForSearchDist * searchDistance, voSettings.outOfGeoOffset); - - offsets[i] = pushedPosition - positions[i]; - positions[i] = pushedPosition; + CleanupOccluders(); } } static void AddOccluders() { - addedOccluders = new List(); + ms_AddedOccluders = new List(); + for (int sceneIndex = 0; sceneIndex < SceneManagement.SceneManager.sceneCount; ++sceneIndex) { SceneManagement.Scene scene = SceneManagement.SceneManager.GetSceneAt(sceneIndex); @@ -73,7 +73,8 @@ static void AddOccluders() { var meshCollider = mr.gameObject.AddComponent(); meshCollider.hideFlags |= HideFlags.DontSaveInEditor | HideFlags.DontSaveInBuild; - addedOccluders.Add(mr); + + ms_AddedOccluders.Add(meshCollider); } } } @@ -82,100 +83,369 @@ static void AddOccluders() var autoSimState = Physics.autoSimulation; try { - Physics.autoSimulation = false; + if (autoSimState) + Physics.autoSimulation = false; + Physics.Simulate(0.1f); } finally { - Physics.autoSimulation = autoSimState; + if (autoSimState) + Physics.autoSimulation = true; } } private static void CleanupOccluders() { - foreach (MeshRenderer meshRenderer in addedOccluders) - { - MeshCollider collider = meshRenderer.gameObject.GetComponent(); - UnityEngine.Object.DestroyImmediate(collider); - } + ms_AddedOccluders.ForEach(Object.DestroyImmediate); } - private static bool HasMeshColliderHits(RaycastHit[] outBoundHits, RaycastHit[] inBoundHits, Vector3 outRay, Vector3 inRay, float rayEnd, out float distance) + static void DoApplyVirtualOffsets(Vector3[] probePositions, out Vector3[] probeOffsets, VirtualOffsetSettings voSettings) { - distance = float.MaxValue; - bool hasHit = false; + // Limit memory usage based on ray cast / hit structures (of which there are lots per position) + int maxPositionsPerBatch; + { + var rayCastBytesPerPosition = UnsafeUtility.SizeOf() * kRayDirectionsPerPosition; + var rayHitBytesPerPosition = UnsafeUtility.SizeOf() * kRayDirectionsPerPosition * voSettings.maxHitsPerRay; + var rayDataBytesPerPosition = rayCastBytesPerPosition + rayHitBytesPerPosition; + maxPositionsPerBatch = (kMaxMemoryUsage / 2) / rayDataBytesPerPosition; + +#if VERBOSE + Debug.Log($"Running virtual offset over {(probePositions.Length + maxPositionsPerBatch - 1)/maxPositionsPerBatch} batches."); +#endif + } + + // This data is shared across all jobs + var positions = new NativeArray(probePositions, Allocator.TempJob); + var offsets = new NativeArray(probePositions.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var searchDistanceForPosition = new NativeArray(positions.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + var positionHasColliders = new NativeArray(positions.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + + // Allocate ray cast/hit data + var raycastCommands = new[] + { + new NativeArray(maxPositionsPerBatch * kRayDirectionsPerPosition, Allocator.TempJob, NativeArrayOptions.UninitializedMemory), + new NativeArray( maxPositionsPerBatch * kRayDirectionsPerPosition, Allocator.TempJob, NativeArrayOptions.UninitializedMemory) + }; + { + // We need to set a known per-ray maxHits up-front since raycast command schedule reads this at schedule time. This is a bit annoying but it's a + // price we'll have to pay right now to be able to create commands from a job. + var defaultRaycastCommand = new RaycastCommand(Vector3.zero, Vector3.zero, 0f, 0, voSettings.maxHitsPerRay); + for (var i = 0; i < maxPositionsPerBatch * kRayDirectionsPerPosition; ++i) + raycastCommands[0][i] = raycastCommands[1][i] = defaultRaycastCommand; + } + var raycastHits = new[] + { + new NativeArray(maxPositionsPerBatch * kRayDirectionsPerPosition * voSettings.maxHitsPerRay, Allocator.TempJob, NativeArrayOptions.UninitializedMemory), + new NativeArray(maxPositionsPerBatch * kRayDirectionsPerPosition * voSettings.maxHitsPerRay,Allocator.TempJob, NativeArrayOptions.UninitializedMemory) + }; - foreach (var hit in outBoundHits) + // Create job data + var createRayCastCommandsJob = new CreateRayCastCommandsJob + { + voSettings = voSettings, + positions = positions, + positionHasColliders = positionHasColliders, + searchDistanceForPosition = searchDistanceForPosition + }; + var pushOutGeometryJob = new PushOutGeometryJob { - if (hit.collider is MeshCollider && Vector3.Dot(outRay, hit.normal) > 0) + voSettings = voSettings, + positions = positions, + offsets = offsets, + positionHasColliders = positionHasColliders, + }; + var jobHandles = new JobHandle[2]; + + try + { +#if VERBOSE + var positionsWithColliders = 0; +#endif + + for (int globalPosIdx = 0, nextBatchIdx = -1; globalPosIdx < positions.Length; globalPosIdx += maxPositionsPerBatch) { - if (hit.distance < distance) + // Run a quick overlap check for each search box before setting up rays for the position + var batchPosStart = globalPosIdx; + var batchPosEnd = Mathf.Min(positions.Length, batchPosStart + maxPositionsPerBatch); + for (var batchPosIdx = batchPosStart; batchPosIdx < batchPosEnd; ++batchPosIdx) { - distance = hit.distance; - hasHit = true; + m_BakingBatch.uniqueBrickSubdiv.TryGetValue(positions[batchPosIdx], out var subdivLevel); + var brickSize = ProbeReferenceVolume.CellSize(subdivLevel); + var searchDistance = (brickSize * m_BakingProfile.minBrickSize) / ProbeBrickPool.kBrickCellCount; + + var scaleForSearchDist = voSettings.searchMultiplier; + var distanceSearch = scaleForSearchDist * searchDistance; + + var positionHasCollider = Physics.CheckBox(positions[batchPosIdx], new Vector3(distanceSearch, distanceSearch, distanceSearch), Quaternion.identity, voSettings.collisionMask); + +#if VERBOSE + if (positionHasCollider) + ++positionsWithColliders; +#endif + + searchDistanceForPosition[batchPosIdx] = distanceSearch; + positionHasColliders[batchPosIdx] = positionHasCollider; } + + // Swap buffers and sync any already running job at that slot + nextBatchIdx = (nextBatchIdx + 1) % 2; + jobHandles[nextBatchIdx].Complete(); + + // Assign ranges and ray/hit arrays + createRayCastCommandsJob.startIdx = batchPosStart; + createRayCastCommandsJob.endIdx = batchPosEnd; + createRayCastCommandsJob.raycastCommands = raycastCommands[nextBatchIdx]; + pushOutGeometryJob.startIdx = batchPosStart; + pushOutGeometryJob.endIdx = batchPosEnd; + pushOutGeometryJob.raycastCommands = raycastCommands[nextBatchIdx]; + pushOutGeometryJob.raycastHits = raycastHits[nextBatchIdx]; + +#if VERBOSE + Debug.Log($"Dispatching batch {batchPosStart/maxPositionsPerBatch} {batchPosStart} - {batchPosEnd} using index {nextBatchIdx} (accumulated colliders {positionsWithColliders}"); +#endif + +#if USE_JOBS + // Kick off jobs immediately + var createRayCastCommandsJobHandle = createRayCastCommandsJob.Schedule(); + var raycastCommandsJobHandle = RaycastCommand.ScheduleBatch(raycastCommands[nextBatchIdx], raycastHits[nextBatchIdx], kMinCommandsPerJob, createRayCastCommandsJobHandle); + jobHandles[nextBatchIdx] = pushOutGeometryJob.Schedule(raycastCommandsJobHandle); + JobHandle.ScheduleBatchedJobs(); +#else + // Run jobs in-place for easier debugging + createRayCastCommandsJob.Run(); + RaycastCommand.ScheduleBatch(raycastCommands[nextBatchIdx], raycastHits[nextBatchIdx], kMinCommandsPerJob).Complete(); + pushOutGeometryJob.Run(); +#endif } + + // Sync any in-flight jobs (order doesn't matter) + JobHandle.CompleteAll(ref jobHandles[0], ref jobHandles[1]); + + // Copy out result data + positions.CopyTo(probePositions); + probeOffsets = offsets.ToArray(); + +#if VERBOSE + Debug.Log($"Earlied out {positions.Length - positionsWithColliders}/{positions.Length} probe positions from virtual offset."); + Debug.Log($"Working memory used: {(raycastCommands[0].Length * UnsafeUtility.SizeOf() * 2 + raycastHits[0].Length * UnsafeUtility.SizeOf() * 2) / 1024 / 1024} MB"); +#endif + } + catch (System.Exception e) + { + Debug.LogException(e); + JobHandle.CompleteAll(ref jobHandles[0], ref jobHandles[1]); + probeOffsets = null; + } + finally + { + positions.Dispose(); + offsets.Dispose(); + searchDistanceForPosition.Dispose(); + positionHasColliders.Dispose(); + + raycastCommands[0].Dispose(); + raycastCommands[1].Dispose(); + raycastHits[0].Dispose(); + raycastHits[1].Dispose(); } + } - foreach (var hit in inBoundHits) + // A job that creates raycast commands for any probe position that has passed the initial + // overlap culling test. Rays are created in the directions of a 3d grid around the center + // position. (3^3-1 rays per position) +#if USE_BURST + [BurstCompile] +#endif + struct CreateRayCastCommandsJob : IJob + { + [ReadOnly] public VirtualOffsetSettings voSettings; + + [NativeDisableContainerSafetyRestriction] + [ReadOnly] public NativeArray positions; + + [NativeDisableContainerSafetyRestriction] + [ReadOnly] public NativeArray positionHasColliders; + + [NativeDisableContainerSafetyRestriction] + [ReadOnly] public NativeArray searchDistanceForPosition; + + [ReadOnly] public int startIdx; + [ReadOnly] public int endIdx; + + [WriteOnly] public NativeArray raycastCommands; + + public void Execute() { - if (hit.collider is MeshCollider && Vector3.Dot(inRay, hit.normal) > 0) + var cmdIdx = 0; + for (var i = startIdx; i < endIdx; ++i) { - if ((rayEnd - hit.distance) < distance) + if (positionHasColliders[i]) { - distance = hit.distance; - hasHit = true; + var position = positions[i]; + var searchDistance = searchDistanceForPosition[i]; + + for (var j = 0; j < kRayDirectionsPerPosition; ++j) + { + var direction = kRayDirections[j]; + var origin = position + direction * voSettings.rayOriginBias; + raycastCommands[cmdIdx++] = new RaycastCommand(origin, direction, searchDistance, voSettings.collisionMask, voSettings.maxHitsPerRay); + } + } + else + { + // Since there's no option to dispatch commands with a subset of an array, we fill up the commands buffer with no-op raycasts. + for (var j = 0; j < kRayDirectionsPerPosition; ++j) + raycastCommands[cmdIdx++] = new RaycastCommand(Vector3.zero, Vector3.zero, 0f, 0, voSettings.maxHitsPerRay); } } + + // Zero out any remainder of the raycast array + for (; cmdIdx < raycastCommands.Length;) + raycastCommands[cmdIdx++] = new RaycastCommand(Vector3.zero, Vector3.zero, 0f, 0, voSettings.maxHitsPerRay); } - return hasHit; + // Typed out in a way Burst understands. + const float k0 = 0, k1 = 1, k2 = (float)0.70710678118654752440084436210485, k3 = (float)0.57735026918962576450914878050196; + static readonly Vector3[] kRayDirections = + { + new(-k3, +k3, -k3), + new( k0, +k2, -k2), + new(+k3, +k3, -k3), + new(-k2, +k2, k0), + new( k0, +k1, k0), + new(+k2, +k2, k0), + new(-k3, +k3, +k3), + new( k0, +k2, +k2), + new(+k3, +k3, +k3), + + new(-k2, k0, -k2), + new( k0, k0, -k1), + new(+k2, k0, -k2), + new(-k1, k0, k0), + // k0, k0, k0 - skip center position (which would be a zero-length ray) + new(+k1, k0, k0), + new(-k2, k0, +k2), + new( k0, k0, +k1), + new(+k2, k0, +k2), + + new(-k3, -k3, -k3), + new( k0, -k2, -k2), + new(+k3, -k3, -k3), + new(-k2, -k2, k0), + new( k0, -k1, k0), + new(+k2, -k2, k0), + new(-k3, -k3, +k3), + new( k0, -k2, +k2), + new(+k3, -k3, +k3), + }; } - private static Vector3 PushPositionOutOfGeometry(Vector3 worldPosition, float distanceSearch, float biasOutGeo) + // A job that pushes probe positions out of geometry based on raycast results. +#if USE_BURST + [BurstCompile] +#endif + struct PushOutGeometryJob : IJob { - bool queriesHitBackBefore = Physics.queriesHitBackfaces; - Physics.queriesHitBackfaces = true; + [ReadOnly] public VirtualOffsetSettings voSettings; + + [NativeDisableContainerSafetyRestriction] + [ReadOnly] public NativeArray positionHasColliders; + + [ReadOnly] public int startIdx; + [ReadOnly] public int endIdx; - float minDist = float.MaxValue; - bool hitFound = false; - Vector3 outDirection = Vector3.zero; - for (int x = -1; x <= 1; ++x) + [ReadOnly] public NativeArray raycastCommands; + [ReadOnly] public NativeArray raycastHits; + + [NativeDisableContainerSafetyRestriction] + public NativeArray positions; + + [NativeDisableContainerSafetyRestriction] + [WriteOnly] public NativeArray offsets; + + public void Execute() { - for (int y = -1; y <= 1; ++y) + for (int i = startIdx, cmdIdx = 0; i < endIdx; ++i) { - for (int z = -1; z <= 1; ++z) + if (!positionHasColliders[i]) { - Vector3 searchDir = new Vector3(x, y, z); - Vector3 normDir = searchDir.normalized; - Vector3 ray = normDir * distanceSearch; - var collisionLayerMask = ~0; - RaycastHit[] outBoundHits = Physics.RaycastAll(worldPosition, normDir, distanceSearch, collisionLayerMask); - RaycastHit[] inBoundHits = Physics.RaycastAll(worldPosition + ray, -1.0f * normDir, distanceSearch, collisionLayerMask); - - float distanceForDir = 0; - bool hasMeshColliderHits = HasMeshColliderHits(outBoundHits, inBoundHits, normDir, -normDir, distanceSearch, out distanceForDir); - if (hasMeshColliderHits) - { - hitFound = true; - if (distanceForDir < minDist) - { - outDirection = searchDir; - minDist = distanceForDir; - } - } + offsets[i] = Vector3.zero; // We need to write valid data to the entire offset array + cmdIdx += kRayDirectionsPerPosition; // Need to maintain cmd<->hit index mapping past noop casts + continue; } + + var position = positions[i]; + var pushedPosition = PushOutOfGeometry(ref cmdIdx, position, voSettings.outOfGeoOffset, voSettings.maxHitsPerRay); + positions[i] = pushedPosition; + offsets[i] = pushedPosition - position; } } - if (hitFound) + static bool IsNewBestHit(float newDistance, float oldDistance, float newDot, float oldDot) { - worldPosition = worldPosition + outDirection.normalized * (minDist * 1.05f + biasOutGeo); + const float kDistanceThreshold = 5e-5f; + const float kDotThreshold = 1e-2f; + + var distanceDiff = newDistance - oldDistance; + + // If new distance is smaller by at least kDistanceThreshold, we accept it as our new best ray. + var newBestHit = distanceDiff < -kDistanceThreshold; + + // If new distance is larger but by no more than kDistanceThreshold, and ray is at least kDotThreshold more colinear with normal, accept it as new best ray + if (!newBestHit && distanceDiff < kDistanceThreshold && newDot - oldDot > kDotThreshold) + newBestHit = true; + + return newBestHit; } - Physics.queriesHitBackfaces = queriesHitBackBefore; + void GetClosestColliderHit(int hitIdx, Vector3 outRay, int maxHitsPerRay, out float distance, out float dotSurface) + { + distance = float.MaxValue; + dotSurface = -1f; + + for (var n = hitIdx + maxHitsPerRay; hitIdx < n; ++hitIdx) + { + var hit = raycastHits[hitIdx]; + if (hit.colliderInstanceID == 0) + break; + + var dotRaySurface = Vector3.Dot(outRay, hit.normal); + if (dotRaySurface > 0f && IsNewBestHit(hit.distance, distance, dotRaySurface, dotSurface)) + { + distance = hit.distance; + dotSurface = dotRaySurface; + } + } + } - return worldPosition; + Vector3 PushOutOfGeometry(ref int cmdIdx, Vector3 worldPosition, float biasOutGeo, int maxHitsPerRay) + { + var minDist = float.MaxValue; + var maxDotSurface = -1f; + var outDirection = Vector3.zero; + + var hitIdx = cmdIdx * maxHitsPerRay; + for (var i = 0; i < kRayDirectionsPerPosition; ++i, hitIdx += maxHitsPerRay) + { + var outBoundRay = raycastCommands[cmdIdx++]; + GetClosestColliderHit(hitIdx, outBoundRay.direction, maxHitsPerRay, out var distanceForDir, out var dotSurface); + + if (IsNewBestHit(distanceForDir, minDist, dotSurface, maxDotSurface)) + { + outDirection = outBoundRay.direction; + minDist = distanceForDir; + maxDotSurface = dotSurface; + } + } + + if (minDist < float.MaxValue) + { + worldPosition += outDirection * (minDist * 1.05f + biasOutGeo); + } + + return worldPosition; + } } } } diff --git a/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettingsDrawer.cs b/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettingsDrawer.cs index bbec5159ec1..0502ce98d30 100644 --- a/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettingsDrawer.cs +++ b/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettingsDrawer.cs @@ -16,12 +16,18 @@ static class Styles public static readonly GUIContent useVirtualOffset = EditorGUIUtility.TrTextContent("Use Virtual Offset", "Push invalid probes out of geometry. Please note, this feature is currently a proof of concept, it is fairly slow and not optimal in quality."); public static readonly GUIContent virtualOffsetSearchMultiplier = EditorGUIUtility.TrTextContent("Search multiplier", "A multiplier to be applied on the distance between two probes to derive the search distance out of geometry."); public static readonly GUIContent virtualOffsetBiasOutGeometry = EditorGUIUtility.TrTextContent("Bias out geometry", "Determines how much a probe is pushed out of the geometry on top of the distance to closest hit."); + public static readonly GUIContent virtualOffsetRayOriginBias = EditorGUIUtility.TrTextContent("Ray origin bias", "The distance with which to bias each ray direction away from the probe position."); + public static readonly GUIContent virtualOffsetMaxHitsPerRay = EditorGUIUtility.TrTextContent("Max hits per ray", "Determines how many colliders intersecting each ray are included in calculations."); + public static readonly GUIContent virtualOffsetCollisionMask = EditorGUIUtility.TrTextContent("Collision mask", "The collision layer mask to cast rays against."); + + public static readonly GUIContent advanced = EditorGUIUtility.TrTextContent("Advanced"); public static readonly string dilationSettingsTitle = "Dilation Settings"; - public static readonly string advancedTitle = "Advanced"; public static readonly string virtualOffsetSettingsTitle = "Virtual Offset Settings"; } + bool m_VirtualOffsetShowAdvanced; + // Draw the property inside the given rect public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { @@ -76,20 +82,33 @@ void DrawDilationSettings(SerializedProperty dilationSettings) void DrawVirtualOffsetSettings(SerializedProperty virtualOffsetSettings) { - - var m_EnableVirtualOffset = virtualOffsetSettings.FindPropertyRelative("useVirtualOffset"); - var m_VirtualOffsetGeometrySearchMultiplier = virtualOffsetSettings.FindPropertyRelative("searchMultiplier"); - var m_VirtualOffsetBiasOutOfGeometry = virtualOffsetSettings.FindPropertyRelative("outOfGeoOffset"); - EditorGUILayout.LabelField(Styles.virtualOffsetSettingsTitle, EditorStyles.boldLabel); - EditorGUI.indentLevel++; - m_EnableVirtualOffset.boolValue = EditorGUILayout.Toggle(Styles.useVirtualOffset, m_EnableVirtualOffset.boolValue); - EditorGUI.BeginDisabledGroup(!m_EnableVirtualOffset.boolValue); - m_VirtualOffsetGeometrySearchMultiplier.floatValue = Mathf.Clamp01(EditorGUILayout.FloatField(Styles.virtualOffsetSearchMultiplier, m_VirtualOffsetGeometrySearchMultiplier.floatValue)); - m_VirtualOffsetBiasOutOfGeometry.floatValue = EditorGUILayout.FloatField(Styles.virtualOffsetBiasOutGeometry, m_VirtualOffsetBiasOutOfGeometry.floatValue); - EditorGUI.indentLevel--; - EditorGUI.EndDisabledGroup(); + using (new EditorGUI.IndentLevelScope()) + { + var enableVirtualOffset = virtualOffsetSettings.FindPropertyRelative("useVirtualOffset"); + EditorGUILayout.PropertyField(enableVirtualOffset, Styles.useVirtualOffset); + + using (new EditorGUI.DisabledScope(!enableVirtualOffset.boolValue)) + { + if (m_VirtualOffsetShowAdvanced = EditorGUILayout.Foldout(m_VirtualOffsetShowAdvanced, Styles.advanced)) + { + using (new EditorGUI.IndentLevelScope()) + { + var virtualOffsetGeometrySearchMultiplier = virtualOffsetSettings.FindPropertyRelative("searchMultiplier"); + var virtualOffsetBiasOutOfGeometry = virtualOffsetSettings.FindPropertyRelative("outOfGeoOffset"); + var virtualOffsetRayOriginBias = virtualOffsetSettings.FindPropertyRelative("rayOriginBias"); + var virtualOffsetMaxHitsPerRay = virtualOffsetSettings.FindPropertyRelative("maxHitsPerRay"); + var virtualOffsetCollisionMask = virtualOffsetSettings.FindPropertyRelative("collisionMask"); + EditorGUILayout.PropertyField(virtualOffsetGeometrySearchMultiplier, Styles.virtualOffsetSearchMultiplier); + EditorGUILayout.PropertyField(virtualOffsetBiasOutOfGeometry, Styles.virtualOffsetBiasOutGeometry); + EditorGUILayout.PropertyField(virtualOffsetRayOriginBias, Styles.virtualOffsetRayOriginBias); + EditorGUILayout.PropertyField(virtualOffsetMaxHitsPerRay, Styles.virtualOffsetMaxHitsPerRay); + EditorGUILayout.PropertyField(virtualOffsetCollisionMask, Styles.virtualOffsetCollisionMask); + } + } + } + } } } } diff --git a/com.unity.render-pipelines.core/Editor/Unity.RenderPipelines.Core.Editor.asmdef b/com.unity.render-pipelines.core/Editor/Unity.RenderPipelines.Core.Editor.asmdef index f4e5aa37f44..1615ed4501f 100644 --- a/com.unity.render-pipelines.core/Editor/Unity.RenderPipelines.Core.Editor.asmdef +++ b/com.unity.render-pipelines.core/Editor/Unity.RenderPipelines.Core.Editor.asmdef @@ -1,7 +1,9 @@ { "name": "Unity.RenderPipelines.Core.Editor", + "rootNamespace": "", "references": [ - "GUID:df380645f10b7bc4b97d4f5eb6303d95" + "GUID:df380645f10b7bc4b97d4f5eb6303d95", + "GUID:2665a8d13d1b3f18800f46e256720795" ], "includePlatforms": [ "Editor" @@ -12,5 +14,12 @@ "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [], - "versionDefines": [] + "versionDefines": [ + { + "name": "com.unity.burst", + "expression": "1.5.0", + "define": "HAS_BURST" + } + ], + "noEngineReferences": false } \ No newline at end of file diff --git a/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettings.cs b/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettings.cs index fdffa9fefd1..b4ff8dba178 100644 --- a/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettings.cs +++ b/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeBakingProcessSettings.cs @@ -1,8 +1,3 @@ -using UnityEngine.Rendering; -#if UNITY_EDITOR -using UnityEditor; -#endif - namespace UnityEngine.Experimental.Rendering { [System.Serializable] @@ -13,20 +8,91 @@ internal struct ProbeDilationSettings public float dilationValidityThreshold; public int dilationIterations; public bool squaredDistWeighting; + + internal void SetDefaults() + { + enableDilation = true; + dilationDistance = 1; + dilationValidityThreshold = 0.25f; + dilationIterations = 1; + squaredDistWeighting = true; + } + + internal void UpgradeFromTo(ProbeVolumeBakingProcessSettings.SettingsVersion from, ProbeVolumeBakingProcessSettings.SettingsVersion to) { } } [System.Serializable] internal struct VirtualOffsetSettings { public bool useVirtualOffset; - public float outOfGeoOffset; - public float searchMultiplier; + [Range(0f, 1f)] public float outOfGeoOffset; + [Range(0f, 2f)] public float searchMultiplier; + [Range(-0.05f, 0f)] public float rayOriginBias; + [Range(4, 24)] public int maxHitsPerRay; + public LayerMask collisionMask; + + internal void SetDefaults() + { + useVirtualOffset = true; + outOfGeoOffset = 0.01f; + searchMultiplier = 0.2f; + UpgradeFromTo(ProbeVolumeBakingProcessSettings.SettingsVersion.Initial, ProbeVolumeBakingProcessSettings.SettingsVersion.ThreadedVirtualOffset); + } + + internal void UpgradeFromTo(ProbeVolumeBakingProcessSettings.SettingsVersion from, ProbeVolumeBakingProcessSettings.SettingsVersion to) + { + if (from < ProbeVolumeBakingProcessSettings.SettingsVersion.ThreadedVirtualOffset && to >= ProbeVolumeBakingProcessSettings.SettingsVersion.ThreadedVirtualOffset) + { + rayOriginBias = -0.001f; + maxHitsPerRay = 10; + collisionMask = Physics.DefaultRaycastLayers; + } + } } // TODO: Use this structure in the actual authoring component rather than just a mean to group output parameters. [System.Serializable] internal struct ProbeVolumeBakingProcessSettings { + internal static ProbeVolumeBakingProcessSettings Default { get { var s = new ProbeVolumeBakingProcessSettings(); s.SetDefaults(); return s; } } + + internal enum SettingsVersion + { + Initial, + ThreadedVirtualOffset, + + Max, + Current = Max - 1 + } + + internal ProbeVolumeBakingProcessSettings(ProbeDilationSettings dilationSettings, VirtualOffsetSettings virtualOffsetSettings) + { + m_Version = SettingsVersion.Current; + this.dilationSettings = dilationSettings; + this.virtualOffsetSettings = virtualOffsetSettings; + } + + internal void SetDefaults() + { + m_Version = SettingsVersion.Current; + dilationSettings.SetDefaults(); + virtualOffsetSettings.SetDefaults(); + } + + internal void Upgrade() + { + if (m_Version != SettingsVersion.Current) + { + // Debug.Log($"Upgrading probe volume baking process settings from '{m_Version}' to '{SettingsVersion.Current}'."); + + dilationSettings.UpgradeFromTo(m_Version, SettingsVersion.Current); + virtualOffsetSettings.UpgradeFromTo(m_Version, SettingsVersion.Current); + m_Version = SettingsVersion.Current; + } + } + + [SerializeField] SettingsVersion m_Version; + public ProbeDilationSettings dilationSettings; public VirtualOffsetSettings virtualOffsetSettings; } diff --git a/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeSceneData.cs b/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeSceneData.cs index 0a2e538b512..eb33c1120e6 100644 --- a/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeSceneData.cs +++ b/com.unity.render-pipelines.core/Runtime/Lighting/ProbeVolume/ProbeVolumeSceneData.cs @@ -183,7 +183,12 @@ public void OnAfterDeserialize() m_BakingState = ProbeReferenceVolume.defaultBakingState; foreach (var set in serializedBakingSets) + { + // Ensure baking set settings are up to date + set.settings.Upgrade(); + bakingSets.Add(set); + } } // This function must not be called during the serialization (because of asset creation) @@ -280,23 +285,7 @@ void InitializeBakingSet(BakingSet set, string name) set.name = name; set.profile = newProfile; - set.settings = new ProbeVolumeBakingProcessSettings - { - dilationSettings = new ProbeDilationSettings - { - enableDilation = true, - dilationDistance = 1, - dilationValidityThreshold = 0.25f, - dilationIterations = 1, - squaredDistWeighting = true, - }, - virtualOffsetSettings = new VirtualOffsetSettings - { - useVirtualOffset = true, - outOfGeoOffset = 0.01f, - searchMultiplier = 0.2f, - } - }; + set.settings = ProbeVolumeBakingProcessSettings.Default; InitializeBakingStates(set); } @@ -549,11 +538,7 @@ internal void SetBakeSettingsForScene(Scene scene, ProbeDilationSettings dilatio if (sceneBakingSettings == null) sceneBakingSettings = new Dictionary(); var sceneGUID = GetSceneGUID(scene); - - ProbeVolumeBakingProcessSettings settings = new ProbeVolumeBakingProcessSettings(); - settings.dilationSettings = dilationSettings; - settings.virtualOffsetSettings = virtualOffsetSettings; - sceneBakingSettings[sceneGUID] = settings; + sceneBakingSettings[sceneGUID] = new ProbeVolumeBakingProcessSettings(dilationSettings, virtualOffsetSettings); } internal ProbeReferenceVolumeProfile GetProfileForScene(Scene scene) @@ -577,7 +562,7 @@ internal ProbeVolumeBakingProcessSettings GetBakeSettingsForScene(Scene scene) if (sceneBakingSettings != null && sceneBakingSettings.ContainsKey(sceneGUID)) return sceneBakingSettings[sceneGUID]; - return new ProbeVolumeBakingProcessSettings(); + return ProbeVolumeBakingProcessSettings.Default; } // This is sub-optimal, but because is called once when kicking off a bake