2828 #define VBUFFER_VOXEL_SIZE 8
2929#endif
3030
31- #define GROUP_SIZE_1D 8
32- #define SUPPORT_LOCAL_LIGHTS 1 // Local lights contribute to fog lighting
33- #define SHADOW_USE_DEPTH_BIAS 0
34- #define PREFER_HALF 0
31+ #define PREFER_HALF 0
32+ #define GROUP_SIZE_1D 8
33+ #define SUPPORT_LOCAL_LIGHTS 1 // Local lights contribute to fog lighting
34+ #define SHADOW_USE_DEPTH_BIAS 0 // Too expensive, not particularly effective
35+ #define SHADOW_LOW // Too expensive
36+ #define SHADOW_AUTO_FLIP_NORMAL 0 // No normal information, so no need to flip
37+ #define SHADOW_VIEW_BIAS 1 // Prevents light leaking through thin geometry. Not as good as normal bias at grazing angles, but cheaper and independent from the geometry.
38+ #define USE_DEPTH_BUFFER 1 // Accounts for opaque geometry along the camera ray
3539
3640//--------------------------------------------------------------------------------------------------
3741// Included headers
5660
5761#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Sky/PhysicallyBasedSky/PhysicallyBasedSkyCommon.hlsl"
5862
59- // Use low quality shadow when doing volumetric
60- #define SHADOW_LOW
6163#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/Lighting.hlsl"
6264#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoopDef.hlsl"
6365#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightEvaluation.hlsl"
@@ -83,6 +85,7 @@ struct JitteredRay
8385 float3 jitterDirWS;
8486 float3 xDirDerivWS;
8587 float3 yDirDerivWS;
88+ float geomDist;
8689};
8790
8891struct VoxelLighting
@@ -91,6 +94,11 @@ struct VoxelLighting
9194 float3 radianceNoPhase;
9295};
9396
97+ bool IsInRange(float x, float2 range)
98+ {
99+ return clamp(x, range.x, range.y) == x;
100+ }
101+
94102float ComputeHistoryWeight()
95103{
96104 // Compute the exponential moving average over 'n' frames:
@@ -149,9 +157,13 @@ VoxelLighting EvaluateVoxelLightingDirectional(LightLoopContext context, uint fe
149157 // Is it worth sampling the shadow map?
150158 if ((light.volumetricLightDimmer > 0) && (light.volumetricShadowDimmer > 0))
151159 {
152- // Pass the light direction as the normal to avoid issues with shadow biasing code.
153- // TODO: does it make sense?
154- float3 shadowN = L;
160+ #if SHADOW_VIEW_BIAS
161+ // Our shadows only support normal bias. Volumetrics has no access to the surface normal.
162+ // We fake view bias by invoking the normal bias code with the view direction.
163+ float3 shadowN = -ray.jitterDirWS;
164+ #else
165+ float3 shadowN = 0; // No bias
166+ #endif // SHADOW_VIEW_BIAS
155167
156168 context.shadowValue = GetDirectionalShadowAttenuation(context.shadowContext,
157169 posInput.positionSS, posInput.positionWS, shadowN,
@@ -182,9 +194,13 @@ VoxelLighting EvaluateVoxelLightingDirectional(LightLoopContext context, uint fe
182194 lightColor.a *= light.volumetricLightDimmer;
183195 lightColor.rgb *= lightColor.a; // Composite
184196
185- // Pass the light direction as the normal to avoid issues with shadow biasing code.
186- // TODO: does it make sense?
187- float3 shadowN = L;
197+ #if SHADOW_VIEW_BIAS
198+ // Our shadows only support normal bias. Volumetrics has no access to the surface normal.
199+ // We fake view bias by invoking the normal bias code with the view direction.
200+ float3 shadowN = -ray.jitterDirWS;
201+ #else
202+ float3 shadowN = 0; // No bias
203+ #endif // SHADOW_VIEW_BIAS
188204
189205 // This code works for both surface reflection and thin object transmission.
190206 DirectionalShadowType shadow = EvaluateShadow_Directional(context, posInput, light, unused, shadowN);
@@ -298,9 +314,13 @@ VoxelLighting EvaluateVoxelLightingLocal(LightLoopContext context, uint featureF
298314 lightColor.a *= light.volumetricLightDimmer;
299315 lightColor.rgb *= lightColor.a; // Composite
300316
301- // Pass the light direction as the normal to avoid issues with shadow biasing code.
302- // TODO: does it make sense?
303- float3 shadowN = L;
317+ #if SHADOW_VIEW_BIAS
318+ // Our shadows only support normal bias. Volumetrics has no access to the surface normal.
319+ // We fake view bias by invoking the normal bias code with the view direction.
320+ float3 shadowN = -ray.jitterDirWS;
321+ #else
322+ float3 shadowN = 0; // No bias
323+ #endif // SHADOW_VIEW_BIAS
304324
305325 float shadow = EvaluateShadow_Punctual(context, posInput, light, unused, shadowN, L, distances);
306326 lightColor.rgb *= ComputeShadowColor(shadow, light.shadowTint, light.penumbraTint);
@@ -379,9 +399,13 @@ VoxelLighting EvaluateVoxelLightingLocal(LightLoopContext context, uint featureF
379399 lightColor.a *= light.volumetricLightDimmer;
380400 lightColor.rgb *= lightColor.a; // Composite
381401
382- // Pass the light direction as the normal to avoid issues with shadow biasing code.
383- // TODO: does it make sense?
384- float3 shadowN = L;
402+ #if SHADOW_VIEW_BIAS
403+ // Our shadows only support normal bias. Volumetrics has no access to the surface normal.
404+ // We fake view bias by invoking the normal bias code with the view direction.
405+ float3 shadowN = -ray.jitterDirWS;
406+ #else
407+ float3 shadowN = 0; // No bias
408+ #endif // SHADOW_VIEW_BIAS
385409
386410 float shadow = EvaluateShadow_Punctual(context, posInput, light, unused, shadowN, L, distances);
387411 lightColor.rgb *= ComputeShadowColor(shadow, light.shadowTint, light.penumbraTint);
@@ -474,7 +498,22 @@ void FillVolumetricLightingBuffer(LightLoopContext context, uint featureFlags,
474498
475499 float e1 = slice * de + de; // (slice + 1) / sliceCount
476500 float t1 = DecodeLogarithmicDepthGeneralized(e1, _VBufferDistanceDecodingParams);
477- float dt = t1 - t0;
501+ float tNext = t1;
502+
503+ #if USE_DEPTH_BUFFER
504+ bool containsOpaqueGeometry = IsInRange(ray.geomDist, float2(t0, t1));
505+
506+ if (containsOpaqueGeometry)
507+ {
508+ // Only integrate up to the opaque surface (make the voxel shorter, but not completely flat).
509+ // Note that we can NOT completely stop integrating when the ray reaches geometry, since
510+ // otherwise we get flickering at geometric discontinuities if reprojection is enabled.
511+ // In this case, a temporally stable light leak is better than flickering.
512+ t1 = max(t0 * 1.0001, ray.geomDist);
513+ }
514+ #endif
515+
516+ float dt = t1 - t0; // Is geometry-aware
478517
479518 // Accurately compute the center of the voxel in the log space. It's important to perform
480519 // the inversion exactly, since the accumulated value of the integral is stored at the center.
@@ -553,7 +592,7 @@ void FillVolumetricLightingBuffer(LightLoopContext context, uint featureFlags,
553592 _VBufferHistoryViewportLimit.xyz,
554593 _VBufferPrevDistanceEncodingParams,
555594 _VBufferPrevDistanceDecodingParams,
556- false, true) * float4(GetInversePreviousExposureMultiplier().xxx, 1);
595+ false, false, true) * float4(GetInversePreviousExposureMultiplier().xxx, 1);
557596
558597 bool reprojSuccess = (_VBufferHistoryIsValid != 0) && (reprojValue.a != 0);
559598
@@ -592,7 +631,7 @@ void FillVolumetricLightingBuffer(LightLoopContext context, uint featureFlags,
592631 blendValue.rgb *= phaseCurrFrame;
593632 #endif // ENABLE_ANISOTROPY
594633
595- #else // ENABLE_REPROJECTION
634+ #else // NO REPROJECTION
596635
597636 #ifdef ENABLE_ANISOTROPY
598637 float4 blendValue = float4(aggregateLighting.radianceComplete, extinction * dt);
@@ -615,6 +654,7 @@ void FillVolumetricLightingBuffer(LightLoopContext context, uint featureFlags,
615654 // Integral{a, b}{Transmittance(0, t) * L_s(t) dt} = Transmittance(0, a) * Integral{a, b}{Transmittance(0, t - a) * L_s(t) dt}.
616655 float3 probeRadiance = probeInScatteredRadiance * TransmittanceIntegralHomogeneousMedium(extinction, dt);
617656
657+ // Accumulate radiance along the ray.
618658 totalRadiance += transmittance * scattering * (phase * blendValue.rgb + probeRadiance);
619659
620660 // Compute the optical depth up to the center of the interval.
@@ -630,7 +670,7 @@ void FillVolumetricLightingBuffer(LightLoopContext context, uint featureFlags,
630670 // Compute the optical depth up to the end of the interval.
631671 opticalDepth += 0.5 * blendValue.a;
632672
633- t0 = t1 ;
673+ t0 = tNext ;
634674 }
635675}
636676
@@ -643,17 +683,6 @@ void VolumetricLighting(uint3 dispatchThreadId : SV_DispatchThreadID,
643683
644684 uint2 groupOffset = groupId * GROUP_SIZE_1D;
645685 uint2 voxelCoord = groupOffset + groupThreadId;
646- #ifdef VL_PRESET_OPTIMAL
647- // The entire thread group is within the same light tile.
648- uint2 tileCoord = groupOffset * VBUFFER_VOXEL_SIZE / TILE_SIZE_BIG_TILE;
649- #else
650- // No compile-time optimizations, no scalarization.
651- // If _VBufferVoxelSize is not a power of 2 or > TILE_SIZE_BIG_TILE, a voxel may straddle
652- // a tile boundary. This means different voxel subsamples may belong to different tiles.
653- // We accept this error, and simply use the coordinates of the center of the voxel.
654- uint2 tileCoord = (uint2)((voxelCoord + 0.5) * _VBufferVoxelSize / TILE_SIZE_BIG_TILE);
655- #endif
656- uint tileIndex = tileCoord.x + _NumTileBigTileX * tileCoord.y;
657686
658687 // Reminder: our voxels are sphere-capped right frustums (truncated right pyramids).
659688 // The curvature of the front and back faces is quite gentle, so we can use
@@ -665,6 +694,7 @@ void VolumetricLighting(uint3 dispatchThreadId : SV_DispatchThreadID,
665694
666695 float3 F = GetViewForwardDir();
667696 float3 U = GetViewUpDir();
697+ float3 R = cross(F, U);
668698
669699 float2 centerCoord = voxelCoord + float2(0.5, 0.5);
670700
@@ -685,14 +715,45 @@ void VolumetricLighting(uint3 dispatchThreadId : SV_DispatchThreadID,
685715 ray.yDirDerivWS = cross(ray.xDirDerivWS, ray.centerDirWS); // Will have the length of 'unitDistFaceSize' by construction
686716
687717#ifdef ENABLE_REPROJECTION
688- ray.jitterDirWS = normalize(ray.centerDirWS + _VBufferSampleOffset.x * ray.xDirDerivWS
689- + _VBufferSampleOffset.y * ray.yDirDerivWS);
718+ float2 sampleOffset = _VBufferSampleOffset.xy;
690719#else
691- ray.jitterDirWS = ray.centerDirWS ;
720+ float2 sampleOffset = 0 ;
692721#endif
693722
723+ ray.jitterDirWS = normalize(ray.centerDirWS + sampleOffset.x * ray.xDirDerivWS
724+ + sampleOffset.y * ray.yDirDerivWS);
725+
726+ // We would like to determine the screen pixel (at the full resolution) which
727+ // the jittered ray corresponds to. The exact solution can be obtained by intersecting
728+ // the ray with the screen plane, e.i. (ViewSpace(jitterDirWS).z = 1). That's a little expensive.
729+ // So, as an approximation, we ignore the curvature of the frustum.
730+ uint2 pixelCoord = (uint2)((voxelCoord + 0.5 + sampleOffset) * _VBufferVoxelSize);
731+
732+ #ifdef VL_PRESET_OPTIMAL
733+ // The entire thread group is within the same light tile.
734+ uint2 tileCoord = groupOffset * VBUFFER_VOXEL_SIZE / TILE_SIZE_BIG_TILE;
735+ #else
736+ // No compile-time optimizations, no scalarization.
737+ uint2 tileCoord = pixelCoord / TILE_SIZE_BIG_TILE;
738+ #endif
739+ uint tileIndex = tileCoord.x + _NumTileBigTileX * tileCoord.y;
740+
741+ // Do not jitter 'voxelCoord' else. It's expected to correspond to the center of the voxel.
694742 PositionInputs posInput = GetPositionInput(voxelCoord, _VBufferViewportSize.zw, tileCoord);
695743
744+ ray.geomDist = FLT_INF;
745+
746+ #if USE_DEPTH_BUFFER
747+ float deviceDepth = LoadCameraDepth(pixelCoord);
748+
749+ if (deviceDepth > 0) // Skip the skybox
750+ {
751+ // Convert it to distance along the ray. Doesn't work with tilt shift, etc.
752+ float linearDepth = LinearEyeDepth(deviceDepth, _ZBufferParams);
753+ ray.geomDist = linearDepth * rcp(dot(ray.jitterDirWS, F));
754+ }
755+ #endif
756+
696757 // TODO
697758 LightLoopContext context;
698759 context.shadowContext = InitShadowContext();
0 commit comments