Skip to content

Commit

Permalink
Dynamic GI sky lighting (#53)
Browse files Browse the repository at this point in the history
* Sample sky lighting on the edges of Probe Volumes for dynamic GI propagation.

* Added Sky Contribution setting to Probe Dynamic GI volume component.

* Renamed "Sky Contribution" to "Sky Multiplier" to fit better with "Baked Emission Multiplier".

* Removed the Direct Contribution setting and normalization that led to all bounces being effectively multiplied by 0.5 with default settings.

* Rotate axes in ProbePropagationCombine in the way consistent with other stages.

* Added Max Albedo setting for dynamic GI to prevent infinite bounces from blowing up the lighting.

* Apply validity to emission. Cleanup.

* Changed default Max Albedo to 0.9.

* Divide dynamic GI direct lighting by Pi to fix the look.

* Experimental changes to skylight and direct hit injection before looking deeper into our SG-to-SH conversion.

* Replaced weight normalization in hit and miss passes with prenormalized amplitudes. Experimental.

* Added the inv-pi normalization to infinite bounces.

* Finish fixing up normalization of hits, misses, and sky. Additionally, add amplitude adjustment for edge and diagonal SGs

* Fixed StackLit shader graphs compilation by adding missing ComputeReflectionProbeNormalizationDirection function, expected by the LightLoop.

* WIP Dynamic GI bases: Windowed Spherical Gaussian and Ambient Dice

* Turn back on sky contribution

* More iteration

* Latest

* Cleanup dynamic gi propagation bases defines into their own header that exposes an api so that callers do not have to know the specifics of the basis. Expose all bases as options on the Probe Dynamic GI volume override

* Miss integral

* Fix hit and miss lobe creation to the more subtle, but correct implementation that got lost during the cleanup.

* Formalize the logic for injecting light from neighboring probe volumes / sky into a basis axis. Convolve the incoming irradiance with the zonal harmonic representation of the neighbor basis lobe in order to analytically evaluate the incoming irradiance over the entire lobe, rather than just the point sample. I compared this against a brute force monte carlo sampling of the SH over the neighbor axis lobe and the results are almost completely indestringuishable. The accuracy is based on how accurate the ZH fit of the neighbor axis lobe is.

* remove some dead code

* delete more dead code

* Add back a line of code I accidentally deleted in the cleanup

* Split up the basis options into a basis dropdown and a basis propagation override dropdown. Removed the not useful no diagonal basis options. Added ambient dice wrapped options to the propagation override modes

* Remove old option for SH from SG Mode: We now always use the correct SH From SG via zonal harmonic fit conversion. Changed the default propagation override to Basis Ambient Dice Wrapped Super Soft. Fixed a subtle bug with skylight propagation - use the current axis lobe direction for sampling the sky, not the neighbor direction. This makes it consistent with how hits are propagated (and cleans up a little code in the process)

Co-authored-by: pastasfuture <pastasfuture@gmail.com>
  • Loading branch information
AndrewSaraevUnity and pastasfuture committed Feb 3, 2022
1 parent 30c2441 commit 3db70c6
Show file tree
Hide file tree
Showing 18 changed files with 953 additions and 282 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#ifndef AMBIENT_DICE
#define AMBIENT_DICE

struct AmbientDice
{
float amplitude;
float sharpness;
float3 mean;
};

float AmbientDiceEvaluateFromDirection(in AmbientDice a, const in float3 direction)
{
return a.amplitude * pow(saturate(dot(a.mean, direction)), a.sharpness);
}

struct AmbientDiceWrapped
{
float amplitude;
float sharpness;
float3 mean;
};

float AmbientDiceWrappedEvaluateFromDirection(in AmbientDiceWrapped a, const in float3 direction)
{
return a.amplitude * pow(saturate(dot(a.mean, direction) * 0.5 + 0.5), a.sharpness);
}

// Analytic function fit over the sharpness range of [2, 32]
// https://www.desmos.com/calculator/gl9lomqucs
float AmbientDiceIntegralFromSharpness(const in float sharpness)
{
return exp2(6.14741 * pow(abs(sharpness * 12.5654 + 14.6469), -1.0)) * 18.5256 + -18.5238;
}

float AmbientDiceIntegral(const in AmbientDice a)
{
return a.amplitude * AmbientDiceIntegralFromSharpness(a.sharpness);
}

// Does not include divide by PI required to normalize the clamped cosine diffuse BRDF.
// This was done to match the format of SGIrradianceFitted() which also does not include the divide by PI.
// Post dividing by PI is required.
float AmbientDiceAndClampedCosineProductIntegral(const in AmbientDice a, const in float3 clampedCosineNormal)
{
float mDotN = dot(a.mean, clampedCosineNormal);

float sharpnessScale = pow(abs(a.sharpness), -0.121796) * -2.18362 + 2.7562;
float sharpnessBias = pow(abs(a.sharpness), 0.137288) * -0.555517 + 0.711175;

mDotN = mDotN * sharpnessScale + sharpnessBias;

float res = max(0.0, exp2(-9.45649 * pow(abs(mDotN * 0.240416 + 1.16513), -5.16291)) * 2.71745 + -0.00193676);
res *= AmbientDiceIntegral(a);
return res;
}

// https://www.desmos.com/calculator/umjtgtzmk8
// Fit to pre-deringed data.
// The dering constraint is evaluated post diffuse brdf convolution.
// The signal can still ring in raw irradiance space.
float ComputeZonalHarmonicC0FromAmbientDiceSharpness(float sharpness)
{
return pow(abs(sharpness * 1.62301 + 1.59682), -0.993255) * 2.83522 + -0.001;
}

float ComputeZonalHarmonicC1FromAmbientDiceSharpness(float sharpness)
{
return exp2(3.37607 * pow(abs(sharpness * 1.45269 + 6.46623), -1.88874)) * 20.0 + -19.9337;
}


float ComputeZonalHarmonicC2FromAmbientDiceSharpness(float sharpness)
{
float lhs = 0.239989 + 0.42846 * sharpness + -0.202951 * sharpness * sharpness + 0.0303908 * sharpness * sharpness * sharpness;
float rhs = exp2(-1.44747 * pow(abs(sharpness * 0.644014 + -0.188877), -0.94422)) * -0.970862 + 0.967661;
return sharpness > 2.33 ? rhs : lhs;
}

float3 ComputeZonalHarmonicFromAmbientDiceSharpness(float sharpness)
{
return float3(
ComputeZonalHarmonicC0FromAmbientDiceSharpness(sharpness),
ComputeZonalHarmonicC1FromAmbientDiceSharpness(sharpness),
ComputeZonalHarmonicC2FromAmbientDiceSharpness(sharpness)
);
}

#endif

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

Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,28 @@ public class ProbeDynamicGI : VolumeComponent
public ClampedFloatParameter indirectMultiplier = new ClampedFloatParameter(1f, 0.0f, 2f);
[Tooltip("Multiplier for material emissive colors that affect Dynamic GI")]
public ClampedFloatParameter bakedEmissionMultiplier = new ClampedFloatParameter(0f, 0f, 2f);
[Tooltip("Multiplier for sky lighting that affect Dynamic GI of")]
public ClampedFloatParameter skyMultiplier = new ClampedFloatParameter(0f, 0f, 2f);
[Tooltip("A control for blending in more influence of infinite bounce light near surfaces")]
public ClampedFloatParameter infiniteBounce = new ClampedFloatParameter(1f, 0.0f, 2f);
[Tooltip("Limits albedo value used for infinite bounces to prevent lighting from blowing up")]
public ClampedFloatParameter maxAlbedo = new ClampedFloatParameter(0.9f, 0f, 1f);
[Tooltip("Advanced control for the contribution amount of secondary propagation indirect light")]
public ClampedFloatParameter propagationContribution = new ClampedFloatParameter(1f, 0f, 2f);

[Tooltip("Max range to perform dynamic GI operation on an individual probe")]
public ClampedFloatParameter rangeInFrontOfCamera = new ClampedFloatParameter(50.0f, 0.0f, 100.0f);
[Tooltip("Max range to perform dynamic GI operation on an individual probe")]
public ClampedFloatParameter rangeBehindCamera = new ClampedFloatParameter(25.0f, 0.0f, 100.0f);

[Tooltip("Advanced control for the contribution amount of direct light")]
public ClampedFloatParameter directContribution = new ClampedFloatParameter(1f, 0.5f, 2);
[Tooltip("Advanced control for the contribution amount of secondary propagation indirect light")]
public ClampedFloatParameter propagationContribution = new ClampedFloatParameter(1f, 0.5f, 2);
[Tooltip("Advanced control selecting the how light is represented and propagated along each axis")]
public ProbeVolumeDynamicGIBasisParameter basis = new ProbeVolumeDynamicGIBasisParameter(ProbeVolumeDynamicGI.ProbeVolumeDynamicGIBasis.BasisAmbientDiceSharp);
[Tooltip("Advanced control selecting an alternative basis for use during propagation. None uses the same basis for propagation as hits.")]
public ProbeVolumeDynamicGIBasisPropagationOverrideParameter basisPropagationOverride = new ProbeVolumeDynamicGIBasisPropagationOverrideParameter(ProbeVolumeDynamicGI.ProbeVolumeDynamicGIBasisPropagationOverride.BasisAmbientDiceWrappedSuperSoft);
[Tooltip("Advanced control for the SG sharpness used when evaluating the influence of infinite bounce light near surfaces")]
public ClampedFloatParameter sharpness = new ClampedFloatParameter(6.0f, 0.0f, 16.0f);
[Tooltip("Advanced control for the SG sharpness used when propagating light")]
public ClampedFloatParameter propagationSharpness = new ClampedFloatParameter(2.0f, 0.0f, 16.0f);
[Tooltip("Advanced control for the SG sharpness used when evaluating the influence of infinite bounce light near surfaces")]
public ClampedFloatParameter infiniteBounceSharpness = new ClampedFloatParameter(2.0f, 0.0f, 16.0f);
[Tooltip("Advanced control for probe propagation combine pass.\nSamplePeakAndProject: Spherical gaussians will simply be evaluated at their peak and projected to convert to spherical harmonics.\nSHFromSGFit: A spherical gaussian to spherical harmonic function fit is used, which is physically plausible.\nSHFromSGFitWithCosineWindow: A spherical gaussian with an additional cosine window to spherical harmonic function fit is used, which is physically plausible. Less directional blur than SHFromSGFit.")]
public SHFromSGModeParameter shFromSGMode = new SHFromSGModeParameter(SHFromSGMode.SamplePeakAndProject);
[Tooltip("Advanced control for darkening down the indirect light on invalid probes")]
public ClampedFloatParameter leakMultiplier = new ClampedFloatParameter(0.0f, 0.0f, 1.0f);
[Tooltip("Advanced control to bias the distance from the normal of the hit surface to perform direct lighting evaluation on")]
Expand All @@ -54,18 +58,18 @@ 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 enum SHFromSGMode
public sealed class ProbeVolumeDynamicGIBasisParameter : VolumeParameter<ProbeVolumeDynamicGI.ProbeVolumeDynamicGIBasis>
{
SamplePeakAndProject = 0,
SHFromSGFit,
SHFromSGFitWithCosineWindow
};
public ProbeVolumeDynamicGIBasisParameter(ProbeVolumeDynamicGI.ProbeVolumeDynamicGIBasis value, bool overrideState = false)
: base(value, overrideState) {}
}

[Serializable]
public sealed class SHFromSGModeParameter : VolumeParameter<SHFromSGMode>
public sealed class ProbeVolumeDynamicGIBasisPropagationOverrideParameter : VolumeParameter<ProbeVolumeDynamicGI.ProbeVolumeDynamicGIBasisPropagationOverride>
{
public SHFromSGModeParameter(SHFromSGMode value, bool overrideState = false)
public ProbeVolumeDynamicGIBasisPropagationOverrideParameter(ProbeVolumeDynamicGI.ProbeVolumeDynamicGIBasisPropagationOverride value, bool overrideState = false)
: base(value, overrideState) {}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,19 @@ float3 ReadPreviousPropagationAxis(uint probeIndex, uint axisIndex)
return _PreviousRadianceCacheAxis[index];
}

float3 NormalizeOutputRadiance(float4 lightingAndWeight, float probeValidity)
float3 NormalizeOutputRadiance(float3 lighting, float probeValidity)
{
float validity = pow(1.0 - probeValidity, 8.0);
const float invalidScale = (1.0f - lerp(_LeakMultiplier, 0.0f, validity));

float3 radiance = lightingAndWeight.xyz * invalidScale;
radiance *= rcp(lightingAndWeight.w);
float3 radiance = lighting * invalidScale;

return radiance;
}

void WritePropagationOutput(uint index, float4 lightingAndWeight, float probeValidity)
void WritePropagationOutput(uint index, float3 lighting, float probeValidity)
{
const float3 finalRadiance = NormalizeOutputRadiance(lightingAndWeight, probeValidity);
const float3 finalRadiance = NormalizeOutputRadiance(lighting, probeValidity);
_RadianceCacheAxis[index] = finalRadiance;
}

Expand Down

0 comments on commit 3db70c6

Please sign in to comment.