Skip to content

Commit 29458ad

Browse files
adrien-de-tocquevilleEvergreen
authored andcommitted
[APV] Offset on probe positions at bake time
On very simple scenes with grid aligned objects (eg. see this [forum post](https://forum.unity.com/threads/adaptive-probe-volumes-apvs-experimental-release-for-hdrp-in-2021-2.1238824/page-16#post-9620177)), probe placement can be very suboptimal. For example on this screenshot, a total of 8 cells are generated, and most of the probes are placed inside geometry ![image](https://media.github.cds.internal.unity3d.com/user/2154/files/45038237-d2a0-4e5f-94a0-02dc7f608d75) Simply offsetting the cell placement without moving any geometry helps a lot ![image](https://media.github.cds.internal.unity3d.com/user/2154/files/3a420e12-bdd3-46a8-9e18-71b7ea5f1e81) Only one cell gets generated and all probes are not inside geometry anymore ![image](https://media.github.cds.internal.unity3d.com/user/2154/files/50da4dde-4cc5-40eb-bd46-15f307113019)
1 parent a7cc9a1 commit 29458ad

20 files changed

+232
-127
lines changed

Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeGIBaking.VirtualOffset.cs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,6 @@ public bool RunVirtualOffsetStep()
139139
if (currentStep >= stepCount)
140140
return true;
141141

142-
float cellSize = m_ProfileInfo.cellSizeInMeters;
143142
float minBrickSize = m_ProfileInfo.minBrickSize;
144143

145144
// Prepare batch
@@ -151,7 +150,7 @@ public bool RunVirtualOffsetStep()
151150
var searchDistance = (brickSize * minBrickSize) / ProbeBrickPool.kBrickCellCount;
152151
var distanceSearch = scaleForSearchDist * searchDistance;
153152

154-
int cellIndex = PosToIndex(Vector3Int.FloorToInt(positions[batchPosIdx] / cellSize));
153+
int cellIndex = PosToIndex(m_ProfileInfo.PositionToCell(positions[batchPosIdx]));
155154
if (cellToVolumes.TryGetValue(cellIndex, out var volumes))
156155
{
157156
bool adjusted = false;
@@ -243,7 +242,7 @@ static internal void RecomputeVOForDebugOnly()
243242
return;
244243

245244
globalBounds = prv.globalBounds;
246-
CellCountInDirections(out minCellPosition, out maxCellPosition, prv.MaxBrickSize());
245+
CellCountInDirections(out minCellPosition, out maxCellPosition, prv.MaxBrickSize(), prv.ProbeOffset());
247246
cellCount = maxCellPosition + Vector3Int.one - minCellPosition;
248247

249248
m_BakingBatch = new BakingBatch(cellCount);
@@ -346,9 +345,7 @@ struct TouchupsPerCell
346345

347346
static Dictionary<int, TouchupsPerCell> GetTouchupsPerCell(out bool hasAppliers)
348347
{
349-
float cellSize = m_ProfileInfo.cellSizeInMeters;
350348
hasAppliers = false;
351-
352349
var adjustmentVolumes = s_AdjustmentVolumes != null ? s_AdjustmentVolumes : GetAdjustementVolumes();
353350

354351
Dictionary<int, TouchupsPerCell> cellToVolumes = new();
@@ -361,8 +358,8 @@ static Dictionary<int, TouchupsPerCell> GetTouchupsPerCell(out bool hasAppliers)
361358

362359
hasAppliers |= mode == ProbeAdjustmentVolume.Mode.ApplyVirtualOffset;
363360

364-
Vector3Int min = Vector3Int.FloorToInt(adjustment.aabb.min / cellSize);
365-
Vector3Int max = Vector3Int.FloorToInt(adjustment.aabb.max / cellSize);
361+
Vector3Int min = m_ProfileInfo.PositionToCell(adjustment.aabb.min);
362+
Vector3Int max = m_ProfileInfo.PositionToCell(adjustment.aabb.max);
366363

367364
for (int x = min.x; x <= max.x; x++)
368365
{
@@ -389,10 +386,9 @@ static Dictionary<int, TouchupsPerCell> GetTouchupsPerCell(out bool hasAppliers)
389386

390387
static Vector3[] DoApplyVirtualOffsetsFromAdjustmentVolumes(NativeList<Vector3> positions, Vector3[] offsets, Dictionary<int, TouchupsPerCell> cellToVolumes)
391388
{
392-
float cellSize = m_ProfileInfo.cellSizeInMeters;
393389
for (int i = 0; i < positions.Length; i++)
394390
{
395-
int cellIndex = PosToIndex(Vector3Int.FloorToInt(positions[i] / cellSize));
391+
int cellIndex = PosToIndex(m_ProfileInfo.PositionToCell(positions[i]));
396392
if (cellToVolumes.TryGetValue(cellIndex, out var volumes))
397393
{
398394
foreach (var (touchup, obb, center, offset) in volumes.appliers)

Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeGIBaking.cs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ class BakingBatch
234234
// Utilities to compute unique probe position hash
235235
Vector3Int maxBrickCount;
236236
float inverseScale;
237+
Vector3 offset;
237238

238239
public Dictionary<(int, int), float> customDilationThresh = new Dictionary<(int, int), float>();
239240
public Dictionary<Vector3, Bounds> forceInvalidatedProbesAndTouchupVols = new Dictionary<Vector3, Bounds>();
@@ -255,11 +256,12 @@ public BakingBatch(Vector3Int cellCount)
255256
{
256257
maxBrickCount = cellCount * ProbeReferenceVolume.CellSize(ProbeReferenceVolume.instance.GetMaxSubdivision());
257258
inverseScale = ProbeBrickPool.kBrickCellCount / ProbeReferenceVolume.instance.MinBrickSize();
259+
offset = ProbeReferenceVolume.instance.ProbeOffset();
258260
}
259261

260262
public int GetProbePositionHash(Vector3 position)
261263
{
262-
var brickPosition = Vector3Int.RoundToInt(position * inverseScale);
264+
var brickPosition = Vector3Int.RoundToInt((position - offset) * inverseScale); // Inverse of op in ConvertBricksToPositions()
263265
return brickPosition.x + brickPosition.y * maxBrickCount.x + brickPosition.z * maxBrickCount.x * maxBrickCount.y;
264266
}
265267

@@ -270,11 +272,14 @@ class ProbeVolumeProfileInfo
270272
{
271273
public int simplificationLevels;
272274
public float minDistanceBetweenProbes;
275+
public Vector3 probeOffset;
273276

274277
public int maxSubdivision => ProbeVolumeBakingSet.GetMaxSubdivision(simplificationLevels);
275278
public float minBrickSize => ProbeVolumeBakingSet.GetMinBrickSize(minDistanceBetweenProbes);
276279
public int cellSizeInBricks => ProbeVolumeBakingSet.GetCellSizeInBricks(simplificationLevels);
277280
public float cellSizeInMeters => (float)cellSizeInBricks * minBrickSize;
281+
282+
public Vector3Int PositionToCell(Vector3 position) => Vector3Int.FloorToInt((position - probeOffset) / cellSizeInMeters);
278283
}
279284

280285
/// <summary>
@@ -712,6 +717,7 @@ static ProbeVolumeProfileInfo GetProfileInfoFromBakingSet(ProbeVolumeBakingSet s
712717
var result = new ProbeVolumeProfileInfo();
713718
result.minDistanceBetweenProbes = set.minDistanceBetweenProbes;
714719
result.simplificationLevels = set.simplificationLevels;
720+
result.probeOffset = set.probeOffset;
715721
return result;
716722
}
717723

@@ -756,7 +762,7 @@ static bool InitializeBake()
756762
}
757763

758764
// Get min/max
759-
CellCountInDirections(out minCellPosition, out maxCellPosition, m_ProfileInfo.cellSizeInMeters);
765+
CellCountInDirections(out minCellPosition, out maxCellPosition, m_ProfileInfo.cellSizeInMeters, m_ProfileInfo.probeOffset);
760766
cellCount = maxCellPosition + Vector3Int.one - minCellPosition;
761767

762768
ProbeReferenceVolume.instance.EnsureCurrentBakingSet(m_BakingSet);
@@ -789,14 +795,13 @@ static void OnBakeStarted()
789795
}
790796
}
791797

792-
static void CellCountInDirections(out Vector3Int minCellPositionXYZ, out Vector3Int maxCellPositionXYZ, float cellSizeInMeters)
798+
static void CellCountInDirections(out Vector3Int minCellPositionXYZ, out Vector3Int maxCellPositionXYZ, float cellSizeInMeters, Vector3 worldOffset)
793799
{
794800
minCellPositionXYZ = Vector3Int.zero;
795801
maxCellPositionXYZ = Vector3Int.zero;
796802

797-
Vector3 center = Vector3.zero;
798-
var centeredMin = globalBounds.min - center;
799-
var centeredMax = globalBounds.max - center;
803+
var centeredMin = globalBounds.min - worldOffset;
804+
var centeredMax = globalBounds.max - worldOffset;
800805

801806
minCellPositionXYZ.x = Mathf.FloorToInt(centeredMin.x / cellSizeInMeters);
802807
minCellPositionXYZ.y = Mathf.FloorToInt(centeredMin.y / cellSizeInMeters);
@@ -1209,7 +1214,7 @@ static void ApplyPostBakeOperations(NativeArray<SphericalHarmonicsL2> sh, Native
12091214

12101215
// Make sure all pending operations are done (needs to be after the Clear to unload all previous scenes)
12111216
probeRefVolume.PerformPendingOperations();
1212-
probeRefVolume.SetMinBrickAndMaxSubdiv(m_ProfileInfo.minBrickSize, m_ProfileInfo.maxSubdivision);
1217+
probeRefVolume.SetSubdivisionDimensions(m_ProfileInfo.minBrickSize, m_ProfileInfo.maxSubdivision, m_ProfileInfo.probeOffset);
12131218

12141219
// Use the globalBounds we just computed, as the one in probeRefVolume doesn't include scenes that have never been baked
12151220
probeRefVolume.globalBounds = globalBounds;
@@ -1702,6 +1707,7 @@ unsafe static void WriteBakingCells(BakingCell[] bakingCells)
17021707
m_BakingSet.cellDescs = new SerializedDictionary<int, CellDesc>();
17031708
m_BakingSet.bakedMinDistanceBetweenProbes = m_ProfileInfo.minDistanceBetweenProbes;
17041709
m_BakingSet.bakedSimplificationLevels = m_ProfileInfo.simplificationLevels;
1710+
m_BakingSet.bakedProbeOffset = m_ProfileInfo.probeOffset;
17051711
m_BakingSet.bakedSkyOcclusion = m_BakingSet.skyOcclusion;
17061712
m_BakingSet.bakedSkyShadingDirection = m_BakingSet.bakedSkyOcclusion && m_BakingSet.skyOcclusionShadingDirection;
17071713

@@ -2133,7 +2139,8 @@ static NativeList<Vector3> RunPlacement(Span<BakeJob> jobs)
21332139
// Overwrite loaded settings with data from profile. Note that the m_BakingSet.profile is already patched up if isFreezingPlacement
21342140
float prevBrickSize = ProbeReferenceVolume.instance.MinBrickSize();
21352141
int prevMaxSubdiv = ProbeReferenceVolume.instance.GetMaxSubdivision();
2136-
ProbeReferenceVolume.instance.SetMinBrickAndMaxSubdiv(m_ProfileInfo.minBrickSize, m_ProfileInfo.maxSubdivision);
2142+
Vector3 prevOffset = ProbeReferenceVolume.instance.ProbeOffset();
2143+
ProbeReferenceVolume.instance.SetSubdivisionDimensions(m_ProfileInfo.minBrickSize, m_ProfileInfo.maxSubdivision, m_ProfileInfo.probeOffset);
21372144

21382145
// All probes need to be baked only once for the whole batch and not once per cell
21392146
// The reason is that the baker is not deterministic so the same probe position baked in two different cells may have different values causing seams artefacts.
@@ -2150,7 +2157,7 @@ static NativeList<Vector3> RunPlacement(Span<BakeJob> jobs)
21502157
positions = ApplySubdivisionResults(result, jobs);
21512158

21522159
// Restore loaded asset settings
2153-
ProbeReferenceVolume.instance.SetMinBrickAndMaxSubdiv(prevBrickSize, prevMaxSubdiv);
2160+
ProbeReferenceVolume.instance.SetSubdivisionDimensions(prevBrickSize, prevMaxSubdiv, prevOffset);
21542161

21552162
return positions;
21562163
}
@@ -2271,6 +2278,7 @@ static void ModifyProfileFromLoadedData(ProbeVolumeBakingSet bakingSet)
22712278
{
22722279
m_ProfileInfo.simplificationLevels = bakingSet.bakedSimplificationLevels;
22732280
m_ProfileInfo.minDistanceBetweenProbes = bakingSet.bakedMinDistanceBetweenProbes;
2281+
m_ProfileInfo.probeOffset = bakingSet.bakedProbeOffset;
22742282
globalBounds = bakingSet.globalBounds;
22752283
}
22762284

@@ -2279,6 +2287,7 @@ internal static void ConvertBricksToPositions(Brick[] bricks, out Vector3[] outP
22792287
{
22802288
int posIdx = 0;
22812289
float scale = ProbeReferenceVolume.instance.MinBrickSize() / ProbeBrickPool.kBrickCellCount;
2290+
Vector3 offset = ProbeReferenceVolume.instance.ProbeOffset();
22822291

22832292
outProbePositions = new Vector3[bricks.Length * ProbeBrickPool.kBrickProbeCountTotal];
22842293
outBrickSubdiv = new int[bricks.Length * ProbeBrickPool.kBrickProbeCountTotal];
@@ -2296,7 +2305,7 @@ internal static void ConvertBricksToPositions(Brick[] bricks, out Vector3[] outP
22962305
{
22972306
var probeOffset = brickOffset + new Vector3Int(x, y, z) * brickSize;
22982307

2299-
outProbePositions[posIdx] = (Vector3)probeOffset * scale;
2308+
outProbePositions[posIdx] = offset + (Vector3)probeOffset * scale;
23002309
outBrickSubdiv[posIdx] = b.subdivisionLevel;
23012310

23022311
posIdx++;

Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbePlacement.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ static void SubdivideSubCell(Bounds cellAABB, ProbeSubdivisionContext subdivisio
331331
}
332332

333333
float minBrickSize = subdivisionCtx.profile.minBrickSize;
334+
var cellOffset = subdivisionCtx.profile.probeOffset;
334335

335336
var cmd = CommandBufferPool.Get($"Subdivide (Sub)Cell {cellAABB.center}");
336337

@@ -373,7 +374,7 @@ static void SubdivideSubCell(Bounds cellAABB, ProbeSubdivisionContext subdivisio
373374
}
374375

375376
// Generate the list of bricks on the GPU
376-
SubdivideFromDistanceField(cmd, cellAABB, ctx, probeSubdivisionData, bricksBuffer, brickCountPerAxis, subdivisionLevel, minBrickSize);
377+
SubdivideFromDistanceField(cmd, cellAABB, ctx, probeSubdivisionData, bricksBuffer, brickCountPerAxis, subdivisionLevel, minBrickSize, cellOffset);
377378

378379
cmd.CopyCounterValue(bricksBuffer, brickCountReadbackBuffer, 0);
379380
// Capture locally the subdivision level to use it inside the lambda
@@ -632,13 +633,13 @@ static void VoxelizeProbeVolumeData(CommandBuffer cmd, Bounds cellAABB,
632633

633634
static void SubdivideFromDistanceField(
634635
CommandBuffer cmd, Bounds volume, GPUSubdivisionContext ctx, RenderTexture probeVolumeData,
635-
ComputeBuffer buffer, int brickCount, int subdivisionLevel, float minBrickSize)
636+
ComputeBuffer buffer, int brickCount, int subdivisionLevel, float minBrickSize, Vector3 cellOffset)
636637
{
637638
using (new ProfilingScope(cmd, new ProfilingSampler($"Subdivide Bricks at level {Mathf.Log(brickCount, 3)}")))
638639
{
639640
// We convert the world space volume position (of a corner) in bricks.
640641
// This is necessary to have correct brick position (the position calculated in the compute shader needs to be in number of bricks from the reference volume (origin)).
641-
Vector3 volumeBrickPosition = (volume.center - volume.extents) / minBrickSize;
642+
Vector3 volumeBrickPosition = (volume.center - volume.extents - cellOffset) / minBrickSize;
642643
cmd.SetComputeVectorParam(subdivideSceneCS, _VolumeOffsetInBricks, volumeBrickPosition);
643644
cmd.SetComputeBufferParam(subdivideSceneCS, s_SubdivideKernel, _Bricks, buffer);
644645
cmd.SetComputeVectorParam(subdivideSceneCS, _MaxBrickSize, Vector3.one * brickCount);

Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeSubdivisionContext.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -159,24 +159,23 @@ public void Initialize(ProbeVolumeBakingSet bakingSet, ProbeVolumeProfileInfo pr
159159
foreach (var pv in probeVolumes)
160160
{
161161
// This method generates many cells outside of the probe volumes but it's ok because next step will do obb collision tests between each cell and each probe volumes so we will eliminate them.
162-
var minCellPosition = pv.bounds.min / cellSize;
163-
var maxCellPosition = pv.bounds.max / cellSize;
162+
var min = profileInfo.PositionToCell(pv.bounds.min);
163+
var max = profileInfo.PositionToCell(pv.bounds.max);
164164

165-
Vector3Int min = new Vector3Int(Mathf.FloorToInt(minCellPosition.x), Mathf.FloorToInt(minCellPosition.y), Mathf.FloorToInt(minCellPosition.z));
166-
Vector3Int max = new Vector3Int(Mathf.CeilToInt(maxCellPosition.x), Mathf.CeilToInt(maxCellPosition.y), Mathf.CeilToInt(maxCellPosition.z));
167-
168-
for (int x = min.x; x < max.x; x++)
165+
for (int x = min.x; x <= max.x; x++)
169166
{
170-
for (int y = min.y; y < max.y; y++)
171-
for (int z = min.z; z < max.z; z++)
167+
for (int y = min.y; y <= max.y; y++)
168+
{
169+
for (int z = min.z; z <= max.z; z++)
172170
{
173171
var cellPos = new Vector3Int(x, y, z);
174172
if (cellPositions.Add(cellPos))
175173
{
176-
var center = new Vector3((cellPos.x + 0.5f) * cellSize, (cellPos.y + 0.5f) * cellSize, (cellPos.z + 0.5f) * cellSize);
174+
var center = profileInfo.probeOffset + new Vector3((cellPos.x + 0.5f) * cellSize, (cellPos.y + 0.5f) * cellSize, (cellPos.z + 0.5f) * cellSize);
177175
cells.Add((cellPos, new Bounds(center, cellDimensions)));
178176
}
179177
}
178+
}
180179
}
181180
}
182181

Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeVolumeBakingSetEditor.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ internal class ProbeVolumeBakingSetEditor : Editor
3838
SerializedProperty m_MinRendererVolumeSize;
3939
SerializedProperty m_RenderersLayerMask;
4040
SerializedProperty m_FreezePlacement;
41+
SerializedProperty m_ProbeOffset;
4142
SerializedProperty m_ProbeVolumeBakingSettings;
4243
SerializedProperty m_LightingScenarios;
4344
SerializedProperty m_SkyOcclusion;
@@ -63,6 +64,7 @@ static class Styles
6364

6465
// Probe Placement section
6566
public static readonly string msgProbeFreeze = "Some scene(s) in this Baking Set are not currently loaded in the Hierarchy. Set Probe Positions to Don't Recalculate to not break compatibility with already baked scenarios.";
67+
public static readonly GUIContent probeOffset = new GUIContent("Probe Offset", "Offset on world origin used during baking. Can be used to have cells on positions that are not multiples of the probe spacing.");
6668
public static readonly GUIContent maxDistanceBetweenProbes = new GUIContent("Max Probe Spacing", "Maximum distance between probes, in meters. Determines the number of Bricks in a streamable unit.");
6769
public static readonly GUIContent minDistanceBetweenProbes = new GUIContent("Min Probe Spacing", "Minimum distance between probes, in meters.");
6870
public static readonly string simplificationLevelsHighWarning = " Using this many brick sizes will result in high memory usage and can cause instabilities.";
@@ -94,6 +96,7 @@ void OnEnable()
9496
m_MinRendererVolumeSize = serializedObject.FindProperty(nameof(ProbeVolumeBakingSet.minRendererVolumeSize));
9597
m_RenderersLayerMask = serializedObject.FindProperty(nameof(ProbeVolumeBakingSet.renderersLayerMask));
9698
m_FreezePlacement = serializedObject.FindProperty(nameof(ProbeVolumeBakingSet.freezePlacement));
99+
m_ProbeOffset = serializedObject.FindProperty(nameof(ProbeVolumeBakingSet.probeOffset));
97100
m_ProbeVolumeBakingSettings = serializedObject.FindProperty(nameof(ProbeVolumeBakingSet.settings));
98101
m_LightingScenarios = serializedObject.FindProperty(nameof(ProbeVolumeBakingSet.m_LightingScenarios));
99102
m_SkyOcclusion = serializedObject.FindProperty(nameof(ProbeVolumeBakingSet.skyOcclusion));
@@ -178,6 +181,22 @@ void ProbePlacementGUI()
178181

179182
using (new EditorGUI.DisabledScope(Lightmapping.isRunning || (canFreezePlacement && ProbeGIBaking.isFreezingPlacement)))
180183
{
184+
// Display vector3 ourselves otherwise display is messed up
185+
{
186+
var rect = EditorGUILayout.GetControlRect();
187+
EditorGUI.BeginProperty(rect, Styles.probeOffset, m_ProbeOffset);
188+
189+
rect = EditorGUI.PrefixLabel(rect, Styles.probeOffset);
190+
rect.xMin -= 10 * EditorGUIUtility.pixelsPerPoint;
191+
192+
EditorGUI.BeginChangeCheck();
193+
var value = EditorGUI.Vector3Field(rect, GUIContent.none, m_ProbeOffset.vector3Value);
194+
if (EditorGUI.EndChangeCheck())
195+
m_ProbeOffset.vector3Value = value;
196+
197+
EditorGUI.EndProperty();
198+
}
199+
181200
EditorGUI.BeginChangeCheck();
182201
EditorGUILayout.PropertyField(m_MinDistanceBetweenProbes, Styles.minDistanceBetweenProbes);
183202
if (EditorGUI.EndChangeCheck())

Packages/com.unity.render-pipelines.core/Editor/Lighting/ProbeVolume/ProbeVolumeCellDilation.compute

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ void DilateCell(uint3 id : SV_DispatchThreadID)
103103

104104
if (_NeedDilating[probeIdx] > 0)
105105
{
106-
float3 centralPosition = _ProbePositionsBuffer[probeIdx];
106+
float3 centralPosition = _ProbePositionsBuffer[probeIdx] - _WorldOffset;
107107
DilatedProbe probe = (DilatedProbe)0;
108108

109109
float3 uvw;

0 commit comments

Comments
 (0)