Skip to content

Commit 6a30cee

Browse files
DGI initial state from baked (#99)
* A prototype of DGI radiance initialization from baked SH. * Disable ZHWindow in ProbePropagationInitialize for now as it gives too smooth and dark radiance to axes. * Minor fixes in propagation and a cleanup. * Partner/crazyhunter 10.7.0 custom.1 dgi initial state from baked sh to from basis improvement (#107) Improve ambient dice basis coefficients, improve basis normalization, and improve methods for projection from and to SH, choosing the right method for the context. Dynamic GI: Ambient Dice to ZH: Switched over to using a raw ZH fit instead of deringed zh fit. It results in significantly lower error when compared to a ground truth monte carlo projection, which means slightly punchier + higher contrast + less washed out GI. And the ringing concern seems to not be an issue due to the way light is propagated. Across a number of test scenes no ringing is present. Co-authored-by: Nicholas Brancaccio <[email protected]>
1 parent 6ddd5a6 commit 6a30cee

File tree

11 files changed

+767
-28
lines changed

11 files changed

+767
-28
lines changed

com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/DynamicGI/AmbientDice.hlsl

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,56 @@ float AmbientDiceAndClampedCosineProductIntegral(const in AmbientDice a, const i
5454
return res;
5555
}
5656

57+
// Intentionally disabled deringing for zonal harmonics. In this context, the deringing results in loss of sharpness (over convolution)
58+
// without much gain - from our dynamic GI data, ringing does not occur in my tests, so does not need to be protected against after all.
59+
// #define ZONAL_HARMONICS_FROM_AMBIENT_DICE_DERING_ENABLED
60+
61+
#if !defined(ZONAL_HARMONICS_FROM_AMBIENT_DICE_DERING_ENABLED)
62+
// https://www.desmos.com/calculator/abirkxdgko
63+
// Simple Least Squares Fit - raw data generated by directly projecting the ambient dice lobe to a zonal harmonic via integration.
64+
// Likely contains ringing for some sharpness levels.
65+
float ComputeZonalHarmonicC0FromAmbientDiceSharpness(float sharpness)
66+
{
67+
// sharpness abs is to simply make the compiler happy.
68+
return pow(abs(sharpness) * 1.62301 + 1.59682, -0.993255) * 2.83522 + -0.001;
69+
}
70+
71+
float ComputeZonalHarmonicC1FromAmbientDiceSharpness(float sharpness)
72+
{
73+
return exp2(-3.56165 * pow(abs(sharpness * -7.42401 + -13.5339), -0.998517)) * -9.1717 + 9.1715;
74+
}
75+
76+
float ComputeZonalHarmonicC2FromAmbientDiceSharpness(float sharpness)
77+
{
78+
float lhs = 0.239989 + 0.42846 * sharpness + -0.202951 * sharpness * sharpness + 0.0303908 * sharpness * sharpness * sharpness;
79+
float rhs = exp2(-7.07205 * pow(abs(sharpness * 0.852362 + -0.469403), -0.898339)) * -0.557094 + 0.539681;
80+
return sharpness > 2.33 ? rhs : lhs;
81+
}
82+
83+
84+
// https://www.desmos.com/calculator/1ajnhdbg6j
85+
// Simple Least Squares Fit - raw data generated by directly projecting the ambient dice lobe to a zonal harmonic via integration.
86+
// Likely contains ringing for some sharpness levels.
87+
float ComputeZonalHarmonicC0FromAmbientDiceWrappedSharpness(float sharpness)
88+
{
89+
// sharpness abs is to simply make the compiler happy.
90+
return pow(abs(sharpness) * 0.816074 + 0.809515, -0.99663) * 2.87866 + -0.001;
91+
}
92+
93+
float ComputeZonalHarmonicC1FromAmbientDiceWrappedSharpness(float sharpness)
94+
{
95+
return exp2(-7.01231 * pow(abs(sharpness * 1.36435 + -1.3829), -0.786179)) * -1.14392 + 1.04683;
96+
}
97+
98+
float ComputeZonalHarmonicC2FromAmbientDiceWrappedSharpness(float sharpness)
99+
{
100+
float lhs = -0.438588 + 0.542959 * sharpness + -0.112098 * sharpness * sharpness + 0.00800693 * sharpness * sharpness * sharpness;
101+
float rhs = exp2(-6.39474 * pow(abs(sharpness * 0.488674 + -2.37692), -0.559034)) * -0.816382 + 0.473311;
102+
return sharpness > 5.0 ? rhs : lhs;
103+
}
104+
105+
106+
#else
57107
// https://www.desmos.com/calculator/umjtgtzmk8
58108
// Fit to pre-deringed data.
59109
// The dering constraint is evaluated post diffuse brdf convolution.
@@ -75,6 +125,7 @@ float ComputeZonalHarmonicC2FromAmbientDiceSharpness(float sharpness)
75125
float rhs = exp2(-1.44747 * pow(abs(sharpness * 0.644014 + -0.188877), -0.94422)) * -0.970862 + 0.967661;
76126
return sharpness > 2.33 ? rhs : lhs;
77127
}
128+
#endif
78129

79130
float3 ComputeZonalHarmonicFromAmbientDiceSharpness(float sharpness)
80131
{
@@ -85,4 +136,13 @@ float3 ComputeZonalHarmonicFromAmbientDiceSharpness(float sharpness)
85136
);
86137
}
87138

139+
float3 ComputeZonalHarmonicFromAmbientDiceWrappedSharpness(float sharpness)
140+
{
141+
return float3(
142+
ComputeZonalHarmonicC0FromAmbientDiceWrappedSharpness(sharpness),
143+
ComputeZonalHarmonicC1FromAmbientDiceWrappedSharpness(sharpness),
144+
ComputeZonalHarmonicC2FromAmbientDiceWrappedSharpness(sharpness)
145+
);
146+
}
147+
88148
#endif

com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/DynamicGI/ProbePropagationAxes.compute

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ int ProbeCoordinateToIndex(uint3 probeCoordinate)
8787

8888
float3 ProbeCoordinatesToWorldPosition(float3 probeCoordinates, float3x3 probeVolumeLtw)
8989
{
90-
float3 localPosition = ((probeCoordinates * _ProbeVolumeDGIResolutionInverse) * 2.0 - 1.0) * _ProbeVolumeDGIBoundsExtents;
90+
float3 localPosition = (((probeCoordinates + 0.5) * _ProbeVolumeDGIResolutionInverse) * 2.0 - 1.0) * _ProbeVolumeDGIBoundsExtents;
9191
return mul(localPosition, probeVolumeLtw) + GetAbsolutePositionWS(_ProbeVolumeDGIBoundsCenter);
9292
}
9393

@@ -221,6 +221,77 @@ float3 ComputeNeighborBoundaryEdgeWorldPosition(float3 probeWorldPosition, int a
221221
return probeWorldPosition + neighborDirectionWS * neighborAxisLength * _ProbeVolumeDGIMaxNeighborDistance;
222222
}
223223

224+
float3 PropagateLightFromNeighborSHWithSamplePeaks(SHIncomingIrradiance shIncomingIrradiance, NeighborAxisLookup neighborAxisLookup, float3x3 probeVolumeLtw, float hitWeight)
225+
{
226+
float3 basisAxisHitDirectionWS = mul(neighborAxisLookup.neighborDirection, probeVolumeLtw);
227+
return ProbeVolumeEvaluateIncomingRadiance(shIncomingIrradiance, basisAxisHitDirectionWS) * hitWeight;
228+
}
229+
230+
float3 PropagateLightFromNeighborSHWithZHFit(SHIncomingIrradiance shIncomingIrradiance, NeighborAxisLookup neighborAxisLookup, float3x3 probeVolumeLtw, float hitWeight, BasisAxisHit basisAxisNeighborHit)
231+
{
232+
float3 basisAxisHitDirectionWS = mul(neighborAxisLookup.neighborDirection, probeVolumeLtw);
233+
ZHWindow basisAxisHitZHWindow = ComputeZHWindowFromBasisAxisHit(basisAxisNeighborHit);
234+
SHIncomingIrradianceConvolveZHWindowWithoutDeltaFunction(shIncomingIrradiance, basisAxisHitZHWindow);
235+
return ProbeVolumeEvaluateIncomingRadiance(shIncomingIrradiance, basisAxisHitDirectionWS) * hitWeight;
236+
}
237+
238+
float3 PropagateLightFromNeighborSHWithMonteCarloNaiveProjection(SHIncomingIrradiance shIncomingIrradiance, NeighborAxisLookup neighborAxisLookup, float3x3 probeVolumeLtw, float hitWeight, BasisAxisHit basisAxisHit, BasisAxisHit basisAxisNeighborHit)
239+
{
240+
BasisAxisHit basisAxisNeighborHitWS = basisAxisNeighborHit;
241+
basisAxisNeighborHitWS.mean = mul(basisAxisNeighborHitWS.mean, probeVolumeLtw);
242+
243+
const float SOLID_ANGLE_SPHERE = 4.0 * PI;
244+
const float GOLDEN_ANGLE = PI * (3.0 - sqrt(5.0));
245+
const float GOLDEN_ANGLE_HALF = GOLDEN_ANGLE * 0.5;
246+
const int SAMPLE_COUNT = 512;
247+
const float SAMPLE_COUNT_INVERSE = 1.0 / (float)SAMPLE_COUNT;
248+
249+
float3 incomingHitRadiance = 0.0;
250+
for (int sampleIndex = 0; sampleIndex < SAMPLE_COUNT; ++sampleIndex)
251+
{
252+
float3 sampleDirectionWS;
253+
{
254+
float offset = (float)sampleIndex + 0.5;
255+
float theta = offset * GOLDEN_ANGLE + GOLDEN_ANGLE_HALF;
256+
float z = -(offset * SAMPLE_COUNT_INVERSE * 2.0 - 1.0);
257+
float r = sqrt(1.0 - z * z);
258+
259+
float3 sampleDirectionBasisOS = float3(
260+
r * cos(theta),
261+
r * sin(theta),
262+
z
263+
);
264+
265+
sampleDirectionWS = sampleDirectionBasisOS; // No rotation necessary, we are sampling the whole sphere.
266+
}
267+
268+
const float DIFFERENTIAL_AREA = SOLID_ANGLE_SPHERE * SAMPLE_COUNT_INVERSE;
269+
270+
float3 sampleIncomingRadiance = ProbeVolumeEvaluateIncomingRadiance(shIncomingIrradiance, sampleDirectionWS);
271+
272+
float3 basisAxisHitDirectionWS = mul(basisAxisHit.mean, probeVolumeLtw);
273+
float3 basisAxisHitDirectionOS = basisAxisHit.mean;
274+
basisAxisHit.mean = basisAxisHitDirectionWS;
275+
276+
incomingHitRadiance += sampleIncomingRadiance
277+
* DIFFERENTIAL_AREA
278+
* ComputeBasisAxisHitEvaluateFromDirection(basisAxisHit, sampleDirectionWS)
279+
* ComputeBasisAxisHitEvaluateFromDirection(basisAxisNeighborHitWS, sampleDirectionWS);
280+
281+
basisAxisHit.mean = basisAxisHitDirectionOS;
282+
}
283+
284+
return incomingHitRadiance;
285+
}
286+
287+
#define BASIS_FROM_SH_MODE_MONTE_CARLO_NAIVE_PROJECTION (0)
288+
#define BASIS_FROM_SH_MODE_SAMPLE_PEAKS (1)
289+
#define BASIS_FROM_SH_MODE_ZH_FIT (2)
290+
291+
// See note in ProbePropagationInitialize.compute.
292+
// Sample Peaks turns out to be better than ZH Fit in practice for our combination of basis and SH order.
293+
#define BASIS_FROM_SH_MODE BASIS_FROM_SH_MODE_SAMPLE_PEAKS
294+
224295
[numthreads(GROUP_SIZE, 1, 1)]
225296
void PropagateLight(uint3 id : SV_DispatchThreadID)
226297
{
@@ -286,8 +357,8 @@ void PropagateLight(uint3 id : SV_DispatchThreadID)
286357
if(hitIndex < (uint)_HitRadianceCacheAxisCount)
287358
{
288359
// Hit
289-
float weight = neighborAxisLookup.hitWeight * basisAxisNeighborHitIntegral;
290-
incomingHitRadiance += DecodeRadiance(_HitRadianceCacheAxis[hitIndex]) * weight;
360+
float hitWeight = neighborAxisLookup.hitWeight * basisAxisNeighborHitIntegral;
361+
incomingHitRadiance += DecodeRadiance(_HitRadianceCacheAxis[hitIndex]) * hitWeight;
291362
}
292363
else
293364
{
@@ -304,8 +375,7 @@ void PropagateLight(uint3 id : SV_DispatchThreadID)
304375
uint neighborProbeIndex = ProbeCoordinateToIndex(neighborProbeCoordinate);
305376
float3 prevAxisRadiance = ReadPreviousPropagationAxis(neighborProbeIndex, axisIndex);
306377

307-
float weight = neighborAxisLookup.propagationWeight;
308-
incomingMissRadiance += prevAxisRadiance * weight * InvalidScale(probeAxisValidity);
378+
incomingMissRadiance += prevAxisRadiance * neighborAxisLookup.propagationWeight * InvalidScale(probeAxisValidity);
309379
#endif
310380
}
311381
#if defined(SAMPLE_NEIGHBORS_DIRECTION_ONLY) || defined(SAMPLE_NEIGHBORS_POSITION_AND_DIRECTION)
@@ -320,17 +390,17 @@ void PropagateLight(uint3 id : SV_DispatchThreadID)
320390
SHIncomingIrradiance shIncomingIrradiance = shIncomingIrradianceProbeCurrent;
321391
#endif
322392

323-
float weight = neighborAxisLookup.hitWeight * basisAxisNeighborHitIntegral;
324-
325-
// Because convolution in SH space is cheap (zonal harmonic window component-wise multiplication), we are able to
326-
// more accurately capture the integral of incoming irradiance over the hit basis solid angle + weight.
327-
// It's important to note that this is an approximation - because our hit axis lobe is not a zonal harmonic - it is an SG or AmbientDice lobe,
328-
// but it can be approximately represented as an ZH.
329-
// This has been validated against a ground truth monte carlo sampling of the basis over the hemisphere and the results are almost completely indistinguishable.
330-
float3 basisAxisHitDirectionWS = mul(basisAxisHit.mean, probeVolumeLtw);
331-
ZHWindow basisAxisHitZHWindow = ComputeZHWindowFromBasisAxisHit(basisAxisHit);
332-
SHIncomingIrradianceConvolveZHWindowWithoutDeltaFunction(shIncomingIrradiance, basisAxisHitZHWindow);
333-
incomingHitRadiance += ProbeVolumeEvaluateIncomingRadiance(shIncomingIrradiance, basisAxisHitDirectionWS) * weight;
393+
float hitWeight = neighborAxisLookup.hitWeight * basisAxisNeighborHitIntegral;
394+
395+
#if BASIS_FROM_SH_MODE == BASIS_FROM_SH_MODE_MONTE_CARLO_NAIVE_PROJECTION
396+
incomingHitRadiance += PropagateLightFromNeighborSHWithMonteCarloNaiveProjection(shIncomingIrradiance, neighborAxisLookup, probeVolumeLtw, hitWeight, basisAxisHit, basisAxisNeighborHit);
397+
398+
#elif BASIS_FROM_SH_MODE == BASIS_FROM_SH_MODE_ZH_FIT
399+
incomingHitRadiance += PropagateLightFromNeighborSHWithZHFit(shIncomingIrradiance, neighborAxisLookup, probeVolumeLtw, hitWeight, basisAxisNeighborHit);
400+
401+
#elif BASIS_FROM_SH_MODE == BASIS_FROM_SH_MODE_SAMPLE_PEAKS
402+
incomingHitRadiance += PropagateLightFromNeighborSHWithSamplePeaks(shIncomingIrradiance, neighborAxisLookup, probeVolumeLtw, hitWeight);
403+
#endif
334404
}
335405
#endif
336406
}

com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/DynamicGI/ProbePropagationBasis.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,22 @@ public static void ComputeAmbientDiceSharpAmplitudeAndSharpnessFromAxisDirection
7373
componentNonZeroCount += Mathf.Abs(axisDirection.y) > 1e-3 ? 1 : 0;
7474
componentNonZeroCount += Mathf.Abs(axisDirection.z) > 1e-3 ? 1 : 0;
7575

76+
#if true
77+
// New coefficients tuned to to maximize unity across the sphere when integrating incoming radiance from monte carlo directions (i.e in a white furnace test).
78+
float amplitudeEdge = 0.76f;
79+
float amplitudeCorner = 0.52f;
80+
float amplitudeOther = 0.73f;
81+
#else
82+
float amplitudeEdge = 0.693f;
83+
float amplitudeCorner = 0.3087f;
84+
float amplitudeOther = 0.64575f;
85+
#endif
7686
amplitude = (componentNonZeroCount == 3)
77-
? 0.3087f // diagonal
87+
? amplitudeCorner // diagonal
7888
: ((componentNonZeroCount == 2)
79-
? 0.693f // edge
80-
: 0.64575f); // center
89+
? amplitudeEdge // edge
90+
: amplitudeOther); // center
91+
8192
sharpness = (componentNonZeroCount == 3)
8293
? 9f // diagonal
8394
: 6f; // center

com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/DynamicGI/ProbePropagationBasis.hlsl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,24 @@ ZHWindow ComputeZHWindowFromBasisAxisHit(BasisAxisHit basisAxisHit)
213213
return zhWindow;
214214
}
215215

216+
ZHWindow ComputeZHWindowFromBasisAxisMiss(BasisAxisMiss basisAxisMiss)
217+
{
218+
ZHWindow zhWindow;
219+
ZERO_INITIALIZE(ZHWindow, zhWindow);
220+
221+
#if (defined(BASIS_PROPAGATION_OVERRIDE_NONE) && defined(BASIS_SPHERICAL_GAUSSIAN)) || defined(BASIS_PROPAGATION_OVERRIDE_SPHERICAL_GAUSSIAN)
222+
zhWindow = ZHWindowComputeFromSphericalGaussian(basisAxisMiss.sharpness);
223+
#elif defined(BASIS_PROPAGATION_OVERRIDE_NONE) && defined(BASIS_SPHERICAL_GAUSSIAN_WINDOWED)
224+
zhWindow = ZHWindowComputeFromSphericalGaussianCosineWindow(basisAxisMiss.sharpness);
225+
#elif defined(BASIS_PROPAGATION_OVERRIDE_NONE) && defined(BASIS_AMBIENT_DICE)
226+
zhWindow = ZHWindowComputeFromAmbientDiceSharpness(basisAxisMiss.sharpness);
227+
#elif defined(BASIS_PROPAGATION_OVERRIDE_AMBIENT_DICE_WRAPPED_SOFTER) || defined(BASIS_PROPAGATION_OVERRIDE_AMBIENT_DICE_WRAPPED_SUPER_SOFT) || defined(BASIS_PROPAGATION_OVERRIDE_AMBIENT_DICE_WRAPPED_ULTRA_SOFT)
228+
zhWindow = ZHWindowComputeFromAmbientDiceWrappedSharpness(basisAxisMiss.sharpness);
229+
#else
230+
#error "Undefined Probe Propagation Basis"
231+
#endif
232+
233+
return zhWindow;
234+
}
235+
216236
#endif // endof PROBE_PROPAGATION_BASIS

com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/DynamicGI/ProbePropagationBasisInternal.hlsl

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,23 @@ void ComputeAmbientDiceSharpAmplitudeAndSharpnessFromAxisDirectionBasis26Fit(out
4444
componentNonZeroCount += abs(axisDirection.y) > 1e-3 ? 1 : 0;
4545
componentNonZeroCount += abs(axisDirection.z) > 1e-3 ? 1 : 0;
4646

47+
#if 1
48+
// New coefficients tuned to to maximize unity across the sphere when integrating incoming radiance from monte carlo directions (i.e in a white furnace test).
49+
float amplitudeEdge = 0.76;
50+
float amplitudeDiagonal = 0.52;
51+
float amplitudeCenter = 0.73;
52+
#else
53+
float amplitudeEdge = 0.693;
54+
float amplitudeDiagonal = 0.3087;
55+
float amplitudeCenter = 0.64575;
56+
#endif
57+
4758
amplitude = (componentNonZeroCount == 3)
48-
? 0.3087 // diagonal
59+
? amplitudeDiagonal // diagonal
4960
: ((componentNonZeroCount == 2)
50-
? 0.693 // edge
51-
: 0.64575); // center
61+
? amplitudeEdge // edge
62+
: amplitudeCenter); // center
63+
5264
sharpness = (componentNonZeroCount == 3)
5365
? 9.0 // diagonal
5466
: ((componentNonZeroCount == 2)
@@ -183,4 +195,16 @@ ZHWindow ZHWindowComputeFromAmbientDiceSharpness(float sharpness)
183195
return zhWindow;
184196
}
185197

198+
ZHWindow ZHWindowComputeFromAmbientDiceWrappedSharpness(float sharpness)
199+
{
200+
ZHWindow zhWindow;
201+
202+
float3 coefficients = ComputeZonalHarmonicFromAmbientDiceWrappedSharpness(sharpness);
203+
zhWindow.data[0] = coefficients.x;
204+
zhWindow.data[1] = coefficients.y;
205+
zhWindow.data[2] = coefficients.z;
206+
207+
return zhWindow;
208+
}
209+
186210
#endif

0 commit comments

Comments
 (0)