Skip to content

Commit

Permalink
Fix recursive mutation bug in ZH Fit version of ProbePropagationIniti…
Browse files Browse the repository at this point in the history
…alize projection.

Dynamic GI Propagation: Sample Neighbors: Use ZH-Fit to convert from SH to our 26-axis basis - it produces results that are a bit closer to our monte carlo naive projection ground truth

Dynamic GI: Fix Infinite Bounce calculation after we changed our basis coefficients. We were missing scaling each lobe contribution by that lobes normalized integral. Also delete some dead code that was hanging around in the dynamic GI area for too long, and fix some code erroneously included inside of an ifdef (#109)

Dynamic GI probe dirty flag (#111)

* Store a dirty flag for each light probe in a volume, update only the ones that had reasons to change since the last update.
* Added a setting to Frame Setting to be able to disable dirty flags.
* Added debug visualisation of dirty probes of all active volumes.

DGI propagation fixes (#112)

* Fixed fallback radiance baking when dirty flags are enabled.
* Fixed noticeable intensity loss for lower propagation quality due to axis weights renormalization error.
* Separate radiance comparison for LogLUV encoding that avoids many false positives in dirty flags.
* Fixed propagation data not being cleared when Frame Settings are changed via the settings window causing the pipeline to be destroyed.

critical exception fix that was firing on domain reloads & enter play mode transitions
  • Loading branch information
pastasfuture committed Oct 19, 2022
1 parent 1bdf5e9 commit ee96637
Show file tree
Hide file tree
Showing 25 changed files with 650 additions and 253 deletions.
Expand Up @@ -349,6 +349,7 @@ static void Drawer_SectionLightingSettings(SerializedFrameSettings serialized, E
);
area.AmmendInfo(FrameSettingsField.ProbeVolumeDynamicGI, overrideable: () => hdrpSettings.supportProbeVolume && hdrpSettings.supportProbeVolumeDynamicGI);
area.AmmendInfo(FrameSettingsField.ProbeVolumeDynamicGIInfiniteBounces, overrideable: () => hdrpSettings.supportProbeVolume && hdrpSettings.supportProbeVolumeDynamicGI);
area.AmmendInfo(FrameSettingsField.ProbeVolumeDynamicGIDirtyFlagsDisabled, overrideable: () => hdrpSettings.supportProbeVolume && hdrpSettings.supportProbeVolumeDynamicGI);
area.AmmendInfo(FrameSettingsField.ProbeVolumeDynamicGIPropagationQuality,
customGetter: () => (ScalableLevel3ForFrameSettingsUIOnly)serialized.probeVolumeDynamicGIPropagationQuality.intValue, // 3 levels
customSetter: v => serialized.probeVolumeDynamicGIPropagationQuality.intValue = Math.Max(0, Math.Min((int)v, 2)), // Levels 0-2
Expand Down
Expand Up @@ -201,7 +201,8 @@ internal enum ProbeVolumeDebugMode
None,
VisualizeAtlas,
VisualizeDebugColors,
VisualizeValidity
VisualizeValidity,
VisualizeDynamicGIDirtyFlags
}

/// <summary>
Expand Down
Expand Up @@ -80,29 +80,6 @@ float ComputeZonalHarmonicC2FromAmbientDiceSharpness(float sharpness)
return sharpness > 2.33 ? rhs : lhs;
}


// https://www.desmos.com/calculator/1ajnhdbg6j
// Simple Least Squares Fit - raw data generated by directly projecting the ambient dice lobe to a zonal harmonic via integration.
// Likely contains ringing for some sharpness levels.
float ComputeZonalHarmonicC0FromAmbientDiceWrappedSharpness(float sharpness)
{
// sharpness abs is to simply make the compiler happy.
return pow(abs(sharpness) * 0.816074 + 0.809515, -0.99663) * 2.87866 + -0.001;
}

float ComputeZonalHarmonicC1FromAmbientDiceWrappedSharpness(float sharpness)
{
return exp2(-7.01231 * pow(abs(sharpness * 1.36435 + -1.3829), -0.786179)) * -1.14392 + 1.04683;
}

float ComputeZonalHarmonicC2FromAmbientDiceWrappedSharpness(float sharpness)
{
float lhs = -0.438588 + 0.542959 * sharpness + -0.112098 * sharpness * sharpness + 0.00800693 * sharpness * sharpness * sharpness;
float rhs = exp2(-6.39474 * pow(abs(sharpness * 0.488674 + -2.37692), -0.559034)) * -0.816382 + 0.473311;
return sharpness > 5.0 ? rhs : lhs;
}


#else
// https://www.desmos.com/calculator/umjtgtzmk8
// Fit to pre-deringed data.
Expand All @@ -127,6 +104,27 @@ float ComputeZonalHarmonicC2FromAmbientDiceSharpness(float sharpness)
}
#endif

// https://www.desmos.com/calculator/1ajnhdbg6j
// Simple Least Squares Fit - raw data generated by directly projecting the ambient dice lobe to a zonal harmonic via integration.
// Likely contains ringing for some sharpness levels.
float ComputeZonalHarmonicC0FromAmbientDiceWrappedSharpness(float sharpness)
{
// sharpness abs is to simply make the compiler happy.
return pow(abs(sharpness) * 0.816074 + 0.809515, -0.99663) * 2.87866 + -0.001;
}

float ComputeZonalHarmonicC1FromAmbientDiceWrappedSharpness(float sharpness)
{
return exp2(-7.01231 * pow(abs(sharpness * 1.36435 + -1.3829), -0.786179)) * -1.14392 + 1.04683;
}

float ComputeZonalHarmonicC2FromAmbientDiceWrappedSharpness(float sharpness)
{
float lhs = -0.438588 + 0.542959 * sharpness + -0.112098 * sharpness * sharpness + 0.00800693 * sharpness * sharpness * sharpness;
float rhs = exp2(-6.39474 * pow(abs(sharpness * 0.488674 + -2.37692), -0.559034)) * -0.816382 + 0.473311;
return sharpness > 5.0 ? rhs : lhs;
}

float3 ComputeZonalHarmonicFromAmbientDiceSharpness(float sharpness)
{
return float3(
Expand Down
@@ -0,0 +1,109 @@
Shader "Hidden/Debug/DebugDirtyFlags"
{
Properties
{
_ProbeVolumeResolution("_ProbeVolumeResolution", Vector) = (0, 0, 0, 0)
_ProbeVolumeProbeDisplayRadiusWS("_ProbeVolumeProbeDisplayRadiusWS", Float) = 1.0
}

SubShader
{
Tags{ "RenderPipeline" = "HDRenderPipeline" "RenderType" = "Opaque" "Queue" = "Transparent" }
ZWrite On
Cull Front

Pass
{
Name "ForwardUnlit"
Tags{ "LightMode" = "Forward" }

HLSLPROGRAM

#pragma editor_sync_compilation

#pragma vertex vert
#pragma fragment frag

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/EntityLighting.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/DynamicGI/ProbePropagationGlobals.hlsl"

struct appdata
{
uint vertexID : SV_VertexID;
};

struct v2f
{
float4 positionCS : SV_POSITION;
float3 color : COLOR;
float2 uv : TEXCOORD0;
};

float3 _ProbeVolumeResolution;
float4x4 _ProbeIndex3DToPositionWSMatrix;
float _ProbeVolumeProbeDisplayRadiusWS;
StructuredBuffer<int> _ProbeVolumeDirtyFlags;

uint3 ComputeWriteIndexFromReadIndex(uint readIndex, float3 resolution)
{
// _ProbeVolumeAtlasReadBuffer[z * resolutionY * resolutionX + y * resolutionX + x]
// TODO: Could implement as floating point operations, which is likely faster.
// Would need to verify precision.
uint x = readIndex % (uint)resolution.x;
uint y = (readIndex / (uint)resolution.x) % (uint)resolution.y;
uint z = readIndex / ((uint)resolution.y * (uint)resolution.x);

return uint3(x, y, z);
}

v2f vert(appdata v)
{
v2f o;

uint probeIndex1D = v.vertexID / 6u;
uint probeTriangleIndex = (v.vertexID / 3u) & 1u;
uint probeVertexIndex = v.vertexID - probeIndex1D * 6u - probeTriangleIndex * 3u;

bool dirty = IsProbeDirty(_ProbeVolumeDirtyFlags, probeIndex1D);

float2 vertexPositionOS = (probeTriangleIndex == 1u)
? float2((probeVertexIndex & 1u), saturate(probeVertexIndex))
: float2(saturate(probeVertexIndex), saturate((float)probeVertexIndex - 1.0));
o.uv = vertexPositionOS;
vertexPositionOS = vertexPositionOS * 2.0 - 1.0;
vertexPositionOS *= _ProbeVolumeProbeDisplayRadiusWS;

float3 probeIndex3D = ComputeWriteIndexFromReadIndex(probeIndex1D, _ProbeVolumeResolution);
float3 probeOriginWS = mul(_ProbeIndex3DToPositionWSMatrix, float4(probeIndex3D, 1.0)).xyz;
float3 probeOriginRWS = GetCameraRelativePositionWS(probeOriginWS);

float3 cameraRightWS = mul(float4(1.0, 0.0, 0.0, 0.0), UNITY_MATRIX_V).xyz;
float3 cameraUpWS = mul(float4(0.0, 1.0, 0.0, 0.0), UNITY_MATRIX_V).xyz;

float3 positionRWS = (cameraRightWS * vertexPositionOS.x + cameraUpWS * vertexPositionOS.y) + probeOriginRWS;

o.color = dirty ? float3(1, 0.5, 0) : float3(0.5, 0, 0);
o.positionCS = TransformWorldToHClip(positionRWS);

return o;
}

void ClipProbeSphere(float2 uv)
{
float2 positionProbeCard = uv * 2.0 - 1.0;
clip(dot(positionProbeCard, positionProbeCard) < 1.0 ? 1.0 : -1.0);
}

float4 frag(v2f i) : SV_Target
{
ClipProbeSphere(i.uv);
return float4(i.color, 1.0);
}

ENDHLSL
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -49,7 +49,6 @@ public class ProbeDynamicGI : VolumeComponent
[Tooltip("Advanced control to clear all dynamic GI buffers in the event lighting blows up when tuning")]
public BoolParameter clear = new BoolParameter(false);


[Serializable]
public sealed class ProbeVolumeDynamicGIBasisParameter : VolumeParameter<ProbeVolumeDynamicGI.ProbeVolumeDynamicGIBasis>
{
Expand Down
Expand Up @@ -10,6 +10,7 @@
#pragma multi_compile BASIS_PROPAGATION_OVERRIDE_NONE BASIS_PROPAGATION_OVERRIDE_SPHERICAL_GAUSSIAN BASIS_PROPAGATION_OVERRIDE_AMBIENT_DICE_WRAPPED_SOFTER BASIS_PROPAGATION_OVERRIDE_AMBIENT_DICE_WRAPPED_SUPER_SOFT BASIS_PROPAGATION_OVERRIDE_AMBIENT_DICE_WRAPPED_ULTRA_SOFT
#pragma multi_compile _ RADIANCE_ENCODING_LOGLUV RADIANCE_ENCODING_HALFLUV RADIANCE_ENCODING_R11G11B10
#pragma multi_compile PROBE_VOLUMES_ENCODING_SPHERICAL_HARMONICS_L1 PROBE_VOLUMES_ENCODING_SPHERICAL_HARMONICS_L2
#pragma multi_compile _ DIRTY_FLAGS_DISABLED

#include "Packages/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
Expand Down Expand Up @@ -43,6 +44,11 @@ StructuredBuffer<NeighborAxisLookup> _SortedNeighborAxisLookups;
StructuredBuffer<NeighborAxis> _ProbeVolumeNeighbors;
int _ProbeVolumeNeighborsCount;

#ifndef DIRTY_FLAGS_DISABLED
StructuredBuffer<int> _ProbeVolumeDirtyFlags;
RWStructuredBuffer<int> _ProbeVolumeNextDirtyFlags;
#endif

StructuredBuffer<RADIANCE> _HitRadianceCacheAxis;
int _HitRadianceCacheAxisCount;

Expand Down Expand Up @@ -231,7 +237,7 @@ float3 PropagateLightFromNeighborSHWithZHFit(SHIncomingIrradiance shIncomingIrra
{
float3 basisAxisHitDirectionWS = mul(neighborAxisLookup.neighborDirection, probeVolumeLtw);
ZHWindow basisAxisHitZHWindow = ComputeZHWindowFromBasisAxisHit(basisAxisNeighborHit);
SHIncomingIrradianceConvolveZHWindowWithoutDeltaFunction(shIncomingIrradiance, basisAxisHitZHWindow);
SHIncomingIrradianceConvolveZHWindowWithoutDeltaFunctionInPlace(shIncomingIrradiance, basisAxisHitZHWindow);
return ProbeVolumeEvaluateIncomingRadiance(shIncomingIrradiance, basisAxisHitDirectionWS) * hitWeight;
}

Expand Down Expand Up @@ -284,13 +290,21 @@ float3 PropagateLightFromNeighborSHWithMonteCarloNaiveProjection(SHIncomingIrrad
return incomingHitRadiance;
}

RADIANCE GetPreviousAxisRadiance(int index)
{
#if defined(PREVIOUS_RADIANCE_CACHE_INVALID)
return ZeroRadiance();
#else
return _PreviousRadianceCacheAxis[index];
#endif
}

#define BASIS_FROM_SH_MODE_MONTE_CARLO_NAIVE_PROJECTION (0)
#define BASIS_FROM_SH_MODE_SAMPLE_PEAKS (1)
#define BASIS_FROM_SH_MODE_ZH_FIT (2)

// See note in ProbePropagationInitialize.compute.
// Sample Peaks turns out to be better than ZH Fit in practice for our combination of basis and SH order.
#define BASIS_FROM_SH_MODE BASIS_FROM_SH_MODE_SAMPLE_PEAKS
// Our ZH Fit produces results that are closer to the monte carlo naive projection ground turth than the sample peaks approach does. Both approaches are reasonable however.
#define BASIS_FROM_SH_MODE BASIS_FROM_SH_MODE_ZH_FIT

[numthreads(GROUP_SIZE, 1, 1)]
void PropagateLight(uint3 id : SV_DispatchThreadID)
Expand All @@ -303,18 +317,21 @@ void PropagateLight(uint3 id : SV_DispatchThreadID)

uint3 probeCoordinate = ProbeIndexToProbeCoordinatesUint(probeIndex);

#ifndef DIRTY_FLAGS_DISABLED
if (!IsProbeDirty(_ProbeVolumeDirtyFlags, probeIndex))
{
_RadianceCacheAxis[index] = GetPreviousAxisRadiance(index);
return;
}
#endif

const float3x3 probeVolumeLtw = float3x3(_ProbeVolumeDGIBoundsRight, _ProbeVolumeDGIBoundsUp, cross(_ProbeVolumeDGIBoundsRight, _ProbeVolumeDGIBoundsUp));
const float3 probePositionWS = ProbeCoordinatesToWorldPosition(probeCoordinate, probeVolumeLtw);

// Early out at far distances
if (IsFarFromCamera(probePositionWS, _RangeInFrontOfCamera, _RangeBehindCamera))
{
#if defined(PREVIOUS_RADIANCE_CACHE_INVALID)
_RadianceCacheAxis[index] = ZeroRadiance();
#else
_RadianceCacheAxis[index] = _PreviousRadianceCacheAxis[index];
#endif

_RadianceCacheAxis[index] = GetPreviousAxisRadiance(index);
return;
}

Expand Down Expand Up @@ -406,6 +423,32 @@ void PropagateLight(uint3 id : SV_DispatchThreadID)
}
}

_RadianceCacheAxis[index] = EncodeRadiance((incomingHitRadiance + incomingMissRadiance) * _PropagationContribution);
const float3 radiance = (incomingHitRadiance + incomingMissRadiance) * _PropagationContribution;
_RadianceCacheAxis[index] = EncodeRadiance(radiance);

#ifndef DIRTY_FLAGS_DISABLED
if (!IsSimilarEqual(GetPreviousAxisRadiance(index), radiance))
{
for (int l = 0; l < PROPAGATION_AXIS_AMOUNT; ++l)
{
NeighborAxisLookup neighborAxisLookup = _SortedNeighborAxisLookups[neighborAxisIndexOffset + l];
int i = neighborAxisLookup.index;

int3 offset = GetNeighborAxisOffset(i);
// When we gathered radiance from neighbors in miss directions we were adding the offset,
// but here we try to predict which probes will gather changes from us so we subtract.
int3 neighborProbeCoordinate = probeCoordinate - offset;

if(neighborProbeCoordinate.x >= 0 && neighborProbeCoordinate.x < (int)_ProbeVolumeDGIResolutionX &&
neighborProbeCoordinate.y >= 0 && neighborProbeCoordinate.y < (int)_ProbeVolumeDGIResolutionY &&
neighborProbeCoordinate.z >= 0 && neighborProbeCoordinate.z < (int)_ProbeVolumeDGIResolutionZ)
{
uint neighborProbeIndex = ProbeCoordinateToIndex(neighborProbeCoordinate);
SetProbeDirty(_ProbeVolumeNextDirtyFlags, neighborProbeIndex);
}
}
}
#endif

}
}
Expand Up @@ -67,6 +67,23 @@ float ComputeBasisAxisHitIntegral(BasisAxisHit basisAxisHit)
return integral;
}

float ComputeBasisAxisHitIntegralFromSharpness(BasisAxisHit basisAxisHit)
{
float integral = 0.0;

#if defined(BASIS_SPHERICAL_GAUSSIAN)
integral = SGIntegralFromSharpness(basisAxisHit.sharpness);
#elif defined(BASIS_SPHERICAL_GAUSSIAN_WINDOWED)
integral = SGClampedCosineWindowIntegralFromSharpness(basisAxisHit.sharpness);
#elif defined(BASIS_AMBIENT_DICE)
integral = AmbientDiceIntegralFromSharpness(basisAxisHit.sharpness);
#else
#error "Undefined Probe Propagation Basis"
#endif

return integral;
}

BasisAxisHit ComputeBasisAxisHit(float3 axis, float sharpnessIn)
{
BasisAxisHit basisAxis;
Expand Down
Expand Up @@ -6,6 +6,7 @@
#pragma multi_compile BASIS_PROPAGATION_OVERRIDE_NONE BASIS_PROPAGATION_OVERRIDE_SPHERICAL_GAUSSIAN BASIS_PROPAGATION_OVERRIDE_AMBIENT_DICE_WRAPPED_SOFTER BASIS_PROPAGATION_OVERRIDE_AMBIENT_DICE_WRAPPED_SUPER_SOFT BASIS_PROPAGATION_OVERRIDE_AMBIENT_DICE_WRAPPED_ULTRA_SOFT
#pragma multi_compile _ RADIANCE_ENCODING_LOGLUV RADIANCE_ENCODING_HALFLUV RADIANCE_ENCODING_R11G11B10
#pragma multi_compile PROBE_VOLUMES_ENCODING_SPHERICAL_HARMONICS_L1 PROBE_VOLUMES_ENCODING_SPHERICAL_HARMONICS_L2
#pragma multi_compile _ DIRTY_FLAGS_DISABLED

#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/DynamicGI/ProbeVolumeDynamicGI.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs.hlsl"
Expand Down Expand Up @@ -34,6 +35,10 @@ float3 _ProbeVolumeAtlasSHRotateForward;
float3 _ProbeVolumeDGIBoundsRight;
float3 _ProbeVolumeDGIBoundsUp;

#ifndef DIRTY_FLAGS_DISABLED
RWStructuredBuffer<int> _ProbeVolumeDirtyFlags;
#endif

StructuredBuffer<RADIANCE> _RadianceCacheAxis;
int _RadianceCacheAxisCount;
float _BakedLightingContribution;
Expand Down Expand Up @@ -320,6 +325,11 @@ void CombinePropagationAxis(uint3 id : SV_DispatchThreadID)
const uint readIndex = id.x;
if (readIndex < _ProbeVolumeAtlasReadBufferCount)
{
#ifndef DIRTY_FLAGS_DISABLED
if (!IsProbeDirty(_ProbeVolumeDirtyFlags, readIndex))
return;
#endif

uint3 writeIndex = ComputeWriteIndexFromReadIndex(readIndex, _ProbeVolumeResolution);
const float validity = ReadValidity(readIndex);

Expand Down

0 comments on commit ee96637

Please sign in to comment.