Skip to content

Commit

Permalink
feat: complex skylighting (doodlum#348)
Browse files Browse the repository at this point in the history
* feat: basic diffuse skylighting with probe clipmap

* style: 🎨 apply clang-format changes

* feat: proper clipmap update and fadeout

clipmap isn't really necessary tho

* fix: fix out-of-camera fadeout

* feat: faux specular skylighting

* feat: added skylighting visual settings

* feat: add rain occlusion

* fix: fix VR address

* refactor: `updateProbes` uses the header

* feat: skylight vl ig

* feat: true average in place of exponential average

Also added doodlum's optimisation hook, but disabled.

Co-Authored-By: doodlum <[email protected]>

* fix: cell invalidation fix, enabled optimisation hook

* fix: fixed cell invalidation...again

* chore: update with `dev` changes

* feat: add DirectionalDiffuse option

* feat: potential VR support for skylighting

* fix: toggle skylighting VR

* fix: skylighting forward rendering

* chore: update to `dev` standards

* chore: remove `SkylightingBlurCS.hlsl`

* chore: replace sobol with r2 due to licensing concerns

* chore: add `RestoreDefaultSettings`

* chore: remove test lines

* chore: changed AO blending to prevent over darkening

---------

Co-authored-by: Pentalimbed <[email protected]>
Co-authored-by: doodlum <[email protected]>
  • Loading branch information
3 people authored Jul 24, 2024
1 parent d7cd1d0 commit a383833
Show file tree
Hide file tree
Showing 16 changed files with 633 additions and 889 deletions.
16 changes: 12 additions & 4 deletions features/Grass Lighting/Shaders/RunGrass.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ float3x3 CalculateTBN(float3 N, float3 p, float2 uv)
# endif

# if defined(SKYLIGHTING)
# define LinearSampler SampBaseSampler
# define SL_INCL_METHODS
# include "Skylighting/Skylighting.hlsli"
# endif

Expand Down Expand Up @@ -459,9 +459,17 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace
float3 directionalAmbientColor = mul(DirectionalAmbientShared, float4(normal, 1.0));

# if defined(SKYLIGHTING)
float4 skylightingSH = SkylightingTexture.Load(int3(input.HPosition.xy, 0));
float skylighting = saturate(shUnproject(skylightingSH, normal));
directionalAmbientColor *= lerp(1.0 / 3.0, 1.0, skylighting);
# if defined(VR)
float3 positionMSSkylight = input.WorldPosition.xyz + (eyeIndex == 1 ? CameraPosAdjust[1] - CameraPosAdjust[0] : 0);
# else
float3 positionMSSkylight = input.WorldPosition.xyz;
# endif

sh2 skylightingSH = sampleSkylighting(skylightingSettings, SkylightingProbeArray, positionMSSkylight, worldSpaceNormal);
float skylighting = shHallucinateZH3Irradiance(skylightingSH, normalWS);
skylighting = lerp(skylightingSettings.MixParams.x, 1, saturate(skylighting * skylightingSettings.MixParams.y));
skylighting = applySkylightingFadeout(skylighting, length(positionMSSkylight));
directionalAmbientColor *= skylighting;
# endif

diffuseColor += directionalAmbientColor;
Expand Down
197 changes: 110 additions & 87 deletions features/Skylighting/Shaders/Skylighting/Skylighting.hlsli
Original file line number Diff line number Diff line change
@@ -1,129 +1,152 @@
#include "Common/Spherical Harmonics/SphericalHarmonics.hlsli"
// Define SL_INCL_STRUCT and SL_INCL_METHODS to include different parts
// Because this file is included by both forward and deferred shaders

Texture2D<unorm float> OcclusionMapSampler : register(t29);
Texture2D<float4> SkylightingTexture : register(t30);

cbuffer SkylightingData : register(b8)
#ifdef SL_INCL_STRUCT
struct SkylightingSettings
{
row_major float4x4 OcclusionViewProj;
float4 EyePosition;
float4 ShadowDirection;
float4 OcclusionDir;

float4 PosOffset;
uint4 ArrayOrigin;
int4 ValidMargin;

float4 MixParams; // x: min diffuse visibility, y: diffuse mult, z: min specular visibility, w: specular mult

uint DirectionalDiffuse;
float3 _pad1;
};

float GetVLSkylighting(float3 startPosWS, float3 endPosWS, float2 screenPosition)
{
const static uint nSteps = 16;
const static float step = 1.0 / float(nSteps);
#endif

float3 worldDir = endPosWS - startPosWS;
#ifdef SL_INCL_METHODS

float noise = InterleavedGradientNoise(screenPosition);
# include "Common/Spherical Harmonics/SphericalHarmonics.hlsli"

startPosWS += worldDir * step * noise;
# ifdef PSHADER
Texture3D<sh2> SkylightingProbeArray : register(t29);
# endif

float noise2 = noise * 2.0 * M_PI;
const static uint3 SL_ARRAY_DIM = uint3(128, 128, 64);
const static float3 SL_ARRAY_SIZE = float3(8192, 8192, 8192 * 0.5);
const static float3 SL_CELL_SIZE = SL_ARRAY_SIZE / SL_ARRAY_DIM;

half2x2 rotationMatrix = half2x2(cos(noise2), sin(noise2), -sin(noise2), cos(noise2));
sh2 sampleSkylighting(SkylightingSettings params, Texture3D<sh2> probeArray, float3 positionMS, float3 normalWS)
{
float3 positionMSAdjusted = positionMS - params.PosOffset;
float3 uvw = positionMSAdjusted / SL_ARRAY_SIZE + .5;

float vl = 0;
if (any(uvw < 0) || any(uvw > 1))
return float4(1, 0, 1, 0);

half2 PoissonDisk[16] = {
half2(-0.94201624, -0.39906216),
half2(0.94558609, -0.76890725),
half2(-0.094184101, -0.92938870),
half2(0.34495938, 0.29387760),
half2(-0.91588581, 0.45771432),
half2(-0.81544232, -0.87912464),
half2(-0.38277543, 0.27676845),
half2(0.97484398, 0.75648379),
half2(0.44323325, -0.97511554),
half2(0.53742981, -0.47373420),
half2(-0.26496911, -0.41893023),
half2(0.79197514, 0.19090188),
half2(-0.24188840, 0.99706507),
half2(-0.81409955, 0.91437590),
half2(0.19984126, 0.78641367),
half2(0.14383161, -0.14100790)
};
float3 cellVxCoord = uvw * SL_ARRAY_DIM;
int3 cell000 = floor(cellVxCoord - 0.5);
float3 trilinearPos = cellVxCoord - 0.5 - cell000;

for (uint i = 0; i < nSteps; ++i) {
float t = saturate(i * step);
sh2 sum = 0;
float wsum = 0;
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
for (int k = 0; k < 2; k++) {
int3 offset = int3(i, j, k);
int3 cellID = cell000 + offset;

float shadow = 0;
{
float3 samplePositionWS = startPosWS + worldDir * t;
if (any(cellID < 0) || any(cellID >= SL_ARRAY_DIM))
continue;

half2 offset = mul(PoissonDisk[(float(i) + noise) % 16].xy, rotationMatrix);
samplePositionWS.xy += offset * 128;
float3 cellCentreMS = cellID + 0.5 - SL_ARRAY_DIM / 2;
cellCentreMS = cellCentreMS * SL_CELL_SIZE;

half3 samplePositionLS = mul(OcclusionViewProj, float4(samplePositionWS.xyz, 1));
samplePositionLS.y = -samplePositionLS.y;
// https://handmade.network/p/75/monter/blog/p/7288-engine_work__global_illumination_with_irradiance_probes
// basic tangent checks
if (dot(cellCentreMS - positionMSAdjusted, normalWS) <= 0)
continue;

float deltaZ = samplePositionLS.z - (1e-2 * 0.05);
float3 trilinearWeights = 1 - abs(offset - trilinearPos);
float w = trilinearWeights.x * trilinearWeights.y * trilinearWeights.z;

half2 occlusionUV = samplePositionLS.xy * 0.5 + 0.5;
uint3 cellTexID = (cellID + params.ArrayOrigin.xyz) % SL_ARRAY_DIM;
sh2 probe = shScale(probeArray[cellTexID], w);

float4 depths = OcclusionMapSampler.GatherRed(LinearSampler, occlusionUV.xy, 0);
sum = shAdd(sum, probe);
wsum += w;
}

shadow = dot(depths > deltaZ, 0.25);
}
vl += shadow;
}
return vl * step;
sh2 result = shScale(sum, rcp(wsum + 1e-10));

return result;
}

float GetSkylightOcclusion(float3 worldPosition, float noise)
float getVLSkylighting(SkylightingSettings params, Texture3D<sh2> probeArray, float3 startPosWS, float3 endPosWS, float2 screenPosition)
{
const static uint nSteps = 16;
const static float step = 1.0 / float(nSteps);

float noise2 = noise * 2.0 * M_PI;
float3 worldDir = endPosWS - startPosWS;
float3 worldDirNormalised = normalize(worldDir);

half2x2 rotationMatrix = half2x2(cos(noise2), sin(noise2), -sin(noise2), cos(noise2));
float noise = InterleavedGradientNoise(screenPosition);

float vl = 0;

half2 PoissonDisk[16] = {
half2(-0.94201624, -0.39906216),
half2(0.94558609, -0.76890725),
half2(-0.094184101, -0.92938870),
half2(0.34495938, 0.29387760),
half2(-0.91588581, 0.45771432),
half2(-0.81544232, -0.87912464),
half2(-0.38277543, 0.27676845),
half2(0.97484398, 0.75648379),
half2(0.44323325, -0.97511554),
half2(0.53742981, -0.47373420),
half2(-0.26496911, -0.41893023),
half2(0.79197514, 0.19090188),
half2(-0.24188840, 0.99706507),
half2(-0.81409955, 0.91437590),
half2(0.19984126, 0.78641367),
half2(0.14383161, -0.14100790)
};

for (uint i = 0; i < nSteps; ++i) {
float t = saturate(i * step);

float shadow = 0;
{
float3 samplePositionWS = worldPosition;
float3 samplePositionWS = startPosWS + worldDir * t;

half2 offset = mul(PoissonDisk[i].xy, rotationMatrix);
samplePositionWS.xy += offset * 16;
sh2 skylighting = sampleSkylighting(params, probeArray, samplePositionWS, float3(0, 0, 1));

half3 samplePositionLS = mul(OcclusionViewProj, float4(samplePositionWS.xyz, 1));
samplePositionLS.y = -samplePositionLS.y;
shadow += lerp(params.MixParams.x, 1, saturate(shUnproject(skylighting, worldDirNormalised) * params.MixParams.y));
}
vl += shadow;
}
return vl * step;
}

float deltaZ = samplePositionLS.z - (1e-2 * 0.2);
// http://torust.me/ZH3.pdf
// ZH hallucination that makes skylighting more directional
// skipped luminance because it's single channel
float shHallucinateZH3Irradiance(sh2 sh, float3 normal)
{
const static float factor = sqrt(5.0f / (16.0f * 3.1415926f));

half2 occlusionUV = samplePositionLS.xy * 0.5 + 0.5;
float3 zonalAxis = normalize(sh.wyz);
float ratio = abs(dot(sh.wyz, zonalAxis)) / sh.x;
float3 zonalL2Coeff = sh.x * (0.08f * ratio + 0.6f * ratio * ratio);

float4 depths = OcclusionMapSampler.GatherRed(LinearSampler, occlusionUV.xy, 0);
float fZ = dot(zonalAxis, normal);
float zhDir = factor * (3.0f * fZ * fZ - 1.0f);

shadow = dot(depths > deltaZ, 0.25);
}
vl += shadow;
}
return sqrt(vl * step);
float result = shFuncProductIntegral(sh, shEvaluateCosineLobe(normal));

result += 0.25f * zonalL2Coeff * zhDir;

return saturate(result);
}

sh2 fauxSpecularLobeSH(float3 N, float3 V, float roughness)
{
// https://www.gdcvault.com/play/1026701/Fast-Denoising-With-Self-Stabilizing
// get dominant ggx reflection direction
float f = (1 - roughness) * (sqrt(1 - roughness) + roughness);
float3 R = reflect(-V, N);
float3 D = lerp(N, R, f);
float3 dominantDir = normalize(D);

sh2 directional = shEvaluate(dominantDir);
sh2 cosineLobe = shEvaluateCosineLobe(dominantDir);
sh2 result = shAdd(shScale(directional, f), shScale(cosineLobe, 1 - f));

return result;
}

float applySkylightingFadeout(float x, float dist)
{
const static float fadeDist = 0.9;
float fadeFactor = saturate((dist * 2 / SL_ARRAY_SIZE.x - fadeDist) / (1 - fadeDist));
return lerp(x, 1, fadeFactor);
}

#endif
73 changes: 0 additions & 73 deletions features/Skylighting/Shaders/Skylighting/SkylightingBlurCS.hlsl

This file was deleted.

Loading

0 comments on commit a383833

Please sign in to comment.