diff --git a/features/Exponential Height Fog/Shaders/ExponentialHeightFog/ExponentialHeightFog.hlsli b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/ExponentialHeightFog.hlsli index c2e6bba23f..c15a0280d3 100644 --- a/features/Exponential Height Fog/Shaders/ExponentialHeightFog/ExponentialHeightFog.hlsli +++ b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/ExponentialHeightFog.hlsli @@ -1,12 +1,16 @@ #ifndef __EXPONENTIAL_HEIGHT_FOG_HLSLI__ #define __EXPONENTIAL_HEIGHT_FOG_HLSLI__ +#include "Common/Random.hlsli" #include "Common/SharedData.hlsli" +#include "ExponentialHeightFog/VolumetricFogCommon.hlsli" #if defined(DYNAMIC_CUBEMAPS) # include "DynamicCubemaps/DynamicCubemaps.hlsli" #endif +Texture3D ExponentialHeightFogIntegratedLightScattering : register(t19); + namespace ExponentialHeightFog { float GetVanillaFogFade(float vanillaFogFade) @@ -19,32 +23,213 @@ namespace ExponentialHeightFog return SharedData::exponentialHeightFogSettings.enabled && SharedData::exponentialHeightFogSettings.disableVanillaFog != 0; } - // Henyey-Greenstein phase function for physically-based inscattering. - // g: asymmetry parameter [-1, 1]. Positive = forward scattering, 0 = isotropic. - float HenyeyGreenstein(float cosTheta, float g) + uint GetEyeIndexFromCameraWS(float3 cameraWS) { - float g2 = g * g; - float denom = 1.0f + g2 - 2.0f * g * cosTheta; - return (1.0f - g2) / (4.0f * Math::PI * pow(max(denom, 1e-5f), 1.5f)); +#if defined(VR) + return distance(cameraWS, FrameBuffer::CameraPosAdjust[1].xyz) < distance(cameraWS, FrameBuffer::CameraPosAdjust[0].xyz) ? 1u : 0u; +#else + return 0u; +#endif } - float4 GetExponentialHeightFog(float3 positionWS, float3 cameraWS, float3 fogColor) + bool ShouldApplyVolumetricFog() + { + return SharedData::exponentialHeightFogSettings.enabled != 0 && + SharedData::exponentialHeightFogSettings.volumetricFogEnabled != 0 && + SharedData::exponentialHeightFogSettings.volumetricFogDistance > SharedData::exponentialHeightFogSettings.volumetricFogStartDistance + 1.0f; + } + + float GetSceneDepthFromClip(float4 clipPosition) + { + return max(clipPosition.w, SharedData::CameraData.y); + } + + float GetSceneDepthForFog(float3 positionWS, uint eyeIndex, out float2 volumeUV, out float projectedDepth) + { + float4 clipPosition = mul(FrameBuffer::CameraViewProj[eyeIndex], float4(positionWS, 1.0f)); + [branch] if (clipPosition.w <= 0.0f) + { + volumeUV = 0.0f.xx; + projectedDepth = 0.0f; + return 0.0f; + } + + projectedDepth = GetSceneDepthFromClip(clipPosition); + volumeUV = clipPosition.xy / clipPosition.w * float2(0.5f, -0.5f) + 0.5f; + + volumeUV = saturate(volumeUV); + return projectedDepth; + } + + float4 SampleVolumetricFog(float3 positionWS, uint eyeIndex) + { + if (!ShouldApplyVolumetricFog()) + return float4(0.0f, 0.0f, 0.0f, 1.0f); + + uint volumeWidth; + uint volumeHeight; + uint volumeDepth; + ExponentialHeightFogIntegratedLightScattering.GetDimensions(volumeWidth, volumeHeight, volumeDepth); + if (volumeWidth == 0 || volumeHeight == 0 || volumeDepth == 0) + return float4(0.0f, 0.0f, 0.0f, 1.0f); + + float2 volumeUV; + float projectedDepth; + float sceneDepth = GetSceneDepthForFog(positionWS, eyeIndex, volumeUV, projectedDepth); + if (projectedDepth <= 0.0f) + return float4(0.0f, 0.0f, 0.0f, 1.0f); + +#if defined(VR) + volumeUV = Stereo::ConvertToStereoUV(volumeUV, eyeIndex); +#endif + + float volumeZ = saturate(ComputeVolumetricNormalizedSlice(sceneDepth, float(volumeDepth))); + + float3 volumeTexelCenter = 0.5f / float3(volumeWidth, volumeHeight, volumeDepth); + float2 volumeUVMin = volumeTexelCenter.xy; + float2 volumeUVMax = 1.0f.xx - volumeTexelCenter.xy; +#if defined(VR) + float eyeMinX = (eyeIndex == 0u ? 0.0f : 0.5f) + volumeTexelCenter.x; + float eyeMaxX = (eyeIndex == 0u ? 0.5f : 1.0f) - volumeTexelCenter.x; + volumeUVMin.x = eyeMinX; + volumeUVMax.x = eyeMaxX; +#endif + float3 volumeUVW = float3(clamp(volumeUV, volumeUVMin, volumeUVMax), clamp(volumeZ, volumeTexelCenter.z, 1.0f - volumeTexelCenter.z)); + float4 volumetricFog = ExponentialHeightFogIntegratedLightScattering.SampleLevel(SampColorSampler, volumeUVW, 0); + return lerp(float4(0.0f, 0.0f, 0.0f, 1.0f), volumetricFog, saturate((sceneDepth - GetVolumetricStartDistance()) * 100000000.0f)); + } + + float2 GetVolumetricFogUVMax(float2 volumeSize, float gridPixelSize) + { + float2 physicalSize = max(volumeSize * gridPixelSize, 1.0f.xx); + float2 viewSizeSafe = ceil(SharedData::BufferDim.xy / gridPixelSize) * gridPixelSize - (gridPixelSize * 0.5f + 1.0f); + return saturate(viewSizeSafe / physicalSize); + } + + float4 SampleVolumetricFog(float4 screenPosition, uint eyeIndex) + { + if (!ShouldApplyVolumetricFog()) + return float4(0.0f, 0.0f, 0.0f, 1.0f); + + uint volumeWidth; + uint volumeHeight; + uint volumeDepth; + ExponentialHeightFogIntegratedLightScattering.GetDimensions(volumeWidth, volumeHeight, volumeDepth); + if (volumeWidth == 0 || volumeHeight == 0 || volumeDepth == 0) + return float4(0.0f, 0.0f, 0.0f, 1.0f); + + float sceneDepth = SharedData::GetScreenDepth(screenPosition.z); + float volumeZ = saturate(ComputeVolumetricNormalizedSlice(sceneDepth, float(volumeDepth))); + + float2 volumeSize = float2(volumeWidth, volumeHeight); + float2 inferredGridPixelSize = ceil(SharedData::BufferDim.xy / max(volumeSize, 1.0f.xx)); + float gridPixelSize = max(max(inferredGridPixelSize.x, inferredGridPixelSize.y), 1.0f); + float2 jitter = 0.0f.xx; + [branch] if (SharedData::exponentialHeightFogSettings.volumetricUpsampleJitterMultiplier > 0.0f) + { + float2 noise = float2( + Random::InterleavedGradientNoise(screenPosition.xy, SharedData::FrameCount), + Random::InterleavedGradientNoise(screenPosition.yx + 19.19f, SharedData::FrameCount)); + jitter = (noise * 2.0f - 1.0f) * SharedData::exponentialHeightFogSettings.volumetricUpsampleJitterMultiplier * gridPixelSize; + } + + float2 volumeUV = (screenPosition.xy + jitter) / (volumeSize * gridPixelSize); + float3 volumeTexelCenter = 0.5f / float3(volumeWidth, volumeHeight, volumeDepth); + float2 volumeUVMin = volumeTexelCenter.xy; + float2 volumeUVMax = max(GetVolumetricFogUVMax(volumeSize, gridPixelSize), volumeUVMin); +#if defined(VR) + volumeUVMin.x = (eyeIndex == 0u ? 0.0f : 0.5f) + volumeTexelCenter.x; + volumeUVMax.x = max(volumeUVMin.x, min(volumeUVMax.x, (eyeIndex == 0u ? 0.5f : 1.0f) - volumeTexelCenter.x)); +#endif + float3 volumeUVW = float3(clamp(volumeUV, volumeUVMin, volumeUVMax), clamp(volumeZ, volumeTexelCenter.z, 1.0f - volumeTexelCenter.z)); + float4 volumetricFog = ExponentialHeightFogIntegratedLightScattering.SampleLevel(SampColorSampler, volumeUVW, 0); + return lerp(float4(0.0f, 0.0f, 0.0f, 1.0f), volumetricFog, saturate((sceneDepth - GetVolumetricStartDistance()) * 100000000.0f)); + } + + // Apply per-pixel directional light phase correction to volumetric fog. + // The volumetric compute stores directional scattering with isotropic phase (1/4PI) to + // avoid angular aliasing at coarse froxel XY resolution. Here we restore the correct + // per-pixel HG phase, weighted by the estimated directional light fraction. + float4 ApplyDirectionalPhaseCorrection(float4 volumetricFog, float3 viewDirection) + { + if (volumetricFog.r + volumetricFog.g + volumetricFog.b < 1e-7f) + return volumetricFog; + + float g = SharedData::exponentialHeightFogSettings.volumetricFogScatteringDistribution; + float cosTheta = dot(normalize(SharedData::DirLightDirection.xyz), viewDirection); + float perPixelPhase = HenyeyGreenstein(cosTheta, g); + float isotropicPhase = 1.0f / (4.0f * Math::PI); + + // Estimate directional light's fraction of total volumetric inscattering + float dirStrength = dot(SharedData::DirLightColor.xyz, float3(0.2126f, 0.7152f, 0.0722f)) * + SharedData::exponentialHeightFogSettings.volumetricDirectionalScatteringIntensity; + float skyStrength = SharedData::exponentialHeightFogSettings.volumetricSkyLightingIntensity; + float dirFraction = saturate(dirStrength / max(dirStrength + skyStrength, 1e-5f)); + + // Apply phase correction only to the estimated directional portion + float correction = lerp(1.0f, perPixelPhase / isotropicPhase, dirFraction); + volumetricFog.rgb *= correction; + return volumetricFog; + } + + float4 CombineVolumetricFog(float4 analyticalFog, float3 positionWS, uint eyeIndex, float3 viewDirection) + { + float4 volumetricFog = SampleVolumetricFog(positionWS, eyeIndex); + volumetricFog = ApplyDirectionalPhaseCorrection(volumetricFog, viewDirection); + float analyticalTransmittance = 1.0f - analyticalFog.w; + float combinedTransmittance = volumetricFog.a * analyticalTransmittance; + float combinedOpacity = saturate(1.0f - combinedTransmittance); + float3 analyticalPremultiplied = analyticalFog.rgb * analyticalFog.w; + float3 combinedPremultiplied = volumetricFog.rgb + volumetricFog.a * analyticalPremultiplied; + return float4(combinedOpacity > 1e-4f ? combinedPremultiplied / combinedOpacity : float3(0.0f, 0.0f, 0.0f), combinedOpacity); + } + + float4 CombineVolumetricFog(float4 analyticalFog, float4 screenPosition, uint eyeIndex, float3 viewDirection) + { + float4 volumetricFog = SampleVolumetricFog(screenPosition, eyeIndex); + volumetricFog = ApplyDirectionalPhaseCorrection(volumetricFog, viewDirection); + float analyticalTransmittance = 1.0f - analyticalFog.w; + float combinedTransmittance = volumetricFog.a * analyticalTransmittance; + float combinedOpacity = saturate(1.0f - combinedTransmittance); + float3 analyticalPremultiplied = analyticalFog.rgb * analyticalFog.w; + float3 combinedPremultiplied = volumetricFog.rgb + volumetricFog.a * analyticalPremultiplied; + return float4(combinedOpacity > 1e-4f ? combinedPremultiplied / combinedOpacity : float3(0.0f, 0.0f, 0.0f), combinedOpacity); + } + + float4 GetExponentialHeightFogInternal(float3 positionWS, float3 cameraWS, float3 fogColor, bool useScreenPosition, float4 screenPosition, bool applyVolumetricFog) { float fogHeightFalloff = SharedData::exponentialHeightFogSettings.fogHeightFalloff * 0.001f; float fogDensity = SharedData::exponentialHeightFogSettings.fogDensity * 0.001f; if (fogDensity <= 0.0f) { return 0.0f; } + uint eyeIndex = GetEyeIndexFromCameraWS(cameraWS); float3 viewToPos = positionWS; + float2 volumeUV; + float projectedDepth; + float sceneDepth = GetSceneDepthForFog(positionWS, eyeIndex, volumeUV, projectedDepth); + [branch] if (projectedDepth > 1e-4f && sceneDepth > projectedDepth) + { + viewToPos *= sceneDepth / projectedDepth; + } + float viewToPosLength = length(viewToPos); - float viewToPosLengthInv = rcp(viewToPosLength); + float viewToPosLengthInv = rcp(max(viewToPosLength, 1e-4f)); float rayOriginTerms = fogDensity * exp2(-fogHeightFalloff * max(cameraWS.z - SharedData::exponentialHeightFogSettings.fogHeight, 0)); float rayLength = viewToPosLength; float rayDirectionZ = viewToPos.z; - if (SharedData::exponentialHeightFogSettings.startDistance > 0) { - float excludeIntersectionTime = SharedData::exponentialHeightFogSettings.startDistance * viewToPosLengthInv; + float excludeDistance = SharedData::exponentialHeightFogSettings.startDistance; + if (applyVolumetricFog && ShouldApplyVolumetricFog()) { + float cosAngle = sceneDepth * viewToPosLengthInv; + float invCosAngle = cosAngle > 0.001f ? rcp(cosAngle) : 0.0f; + excludeDistance = max(excludeDistance, GetVolumetricEndDistance() * invCosAngle); + } + + if (excludeDistance > 0) { + excludeDistance = min(excludeDistance, viewToPosLength); + float excludeIntersectionTime = excludeDistance * viewToPosLengthInv; float cameraToExclusionIntersectionZ = excludeIntersectionTime * viewToPos.z; float exclusionIntersectionZ = cameraWS.z + cameraToExclusionIntersectionZ; rayLength = (1.0f - excludeIntersectionTime) * viewToPosLength; @@ -75,18 +260,43 @@ namespace ExponentialHeightFog float3 directionalInscattering = 0; + float3 viewDirection = viewToPos * viewToPosLengthInv; + // Calculate directional light inscattering using Henyey-Greenstein phase function if (SharedData::exponentialHeightFogSettings.directionalInscatteringMultiplier > 0) { - float cosTheta = dot(normalize(positionWS), SharedData::DirLightDirection.xyz); + float3 lightDirection = normalize(SharedData::DirLightDirection.xyz); + float cosTheta = dot(lightDirection, viewDirection); float phase = HenyeyGreenstein(cosTheta, SharedData::exponentialHeightFogSettings.directionalInscatteringAnisotropy); float3 directionalLightInscattering = SharedData::DirLightColor.xyz * phase; - float dirExponentialHeightLineIntegral = exponentialHeightLineIntegralCalc * max(rayLength - SharedData::exponentialHeightFogSettings.startDistance, 0); - float dirExpFogFactor = saturate(exp2(-dirExponentialHeightLineIntegral)); - directionalInscattering = directionalLightInscattering * (1 - dirExpFogFactor) * SharedData::exponentialHeightFogSettings.directionalInscatteringMultiplier; + directionalInscattering = directionalLightInscattering * (1.0f - expFogFactor) * SharedData::exponentialHeightFogSettings.directionalInscatteringMultiplier; } fogColor += directionalInscattering; - return float4(fogColor, 1.0f - expFogFactor); + float4 analyticalFog = float4(fogColor, 1.0f - expFogFactor); + if (!applyVolumetricFog) { + return analyticalFog; + } + return useScreenPosition ? CombineVolumetricFog(analyticalFog, screenPosition, eyeIndex, viewDirection) : CombineVolumetricFog(analyticalFog, positionWS, eyeIndex, viewDirection); + } + + float4 GetExponentialHeightFog(float3 positionWS, float3 cameraWS, float3 fogColor) + { + return GetExponentialHeightFogInternal(positionWS, cameraWS, fogColor, false, 0.0f.xxxx, true); + } + + float4 GetExponentialHeightFog(float3 positionWS, float3 cameraWS, float3 fogColor, float4 screenPosition) + { + return GetExponentialHeightFogInternal(positionWS, cameraWS, fogColor, true, screenPosition, true); + } + + float4 GetExponentialHeightFogNoVolumetric(float3 positionWS, float3 cameraWS, float3 fogColor) + { + return GetExponentialHeightFogInternal(positionWS, cameraWS, fogColor, false, 0.0f.xxxx, false); + } + + float4 GetExponentialHeightFogNoVolumetric(float3 positionWS, float3 cameraWS, float3 fogColor, float4 screenPosition) + { + return GetExponentialHeightFogInternal(positionWS, cameraWS, fogColor, true, screenPosition, false); } float GetSunlightFogAttenuation(float3 positionWS, float3 cameraWS) diff --git a/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogCSCommon.hlsli b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogCSCommon.hlsli new file mode 100644 index 0000000000..bdd69fb861 --- /dev/null +++ b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogCSCommon.hlsli @@ -0,0 +1,58 @@ +#ifndef __EXPONENTIAL_HEIGHT_FOG_VOLUMETRIC_CS_COMMON_HLSLI__ +#define __EXPONENTIAL_HEIGHT_FOG_VOLUMETRIC_CS_COMMON_HLSLI__ + +#include "Common/FrameBuffer.hlsli" +#include "Common/VR.hlsli" + +cbuffer VolumetricFogCB : register(b0) +{ + uint4 VolumetricFogGridSizeAndFlags; + float4 VolumetricFogInvGridSizeAndNearFade; + float4 VolumetricFogGridZParams; + row_major float4x4 VolumetricFogClipToWorld[2]; + float4 VolumetricFogFrameJitterOffsets[16]; + float4 VolumetricFogHistoryParameters; + float4 VolumetricFogJitterParameters; +}; + +#define VolumetricFogGridSize VolumetricFogGridSizeAndFlags.xyz +#define VolumetricFogHasDirectionalShadowMap ((VolumetricFogGridSizeAndFlags.w & 1u) != 0u) +#define VolumetricFogHasConservativeDepth ((VolumetricFogGridSizeAndFlags.w & 2u) != 0u) +#define VolumetricFogHasIBL ((VolumetricFogGridSizeAndFlags.w & 4u) != 0u) +#define VolumetricFogHasSkylighting ((VolumetricFogGridSizeAndFlags.w & 8u) != 0u) +#define VolumetricFogHasPrevConservativeDepth ((VolumetricFogGridSizeAndFlags.w & 16u) != 0u) +#define VolumetricFogHasLocalLights ((VolumetricFogGridSizeAndFlags.w & 32u) != 0u) +#define VolumetricFogInvGridSize VolumetricFogInvGridSizeAndNearFade.xyz +#define VolumetricFogNearFadeInDistanceInv VolumetricFogInvGridSizeAndNearFade.w +#define VolumetricFogHistoryWeight VolumetricFogHistoryParameters.x +#define VolumetricFogHistoryMissSampleCount max(1u, min(16u, (uint)(VolumetricFogHistoryParameters.y + 0.5f))) +#define VolumetricFogSampleJitterMultiplier VolumetricFogJitterParameters.x +#define VolumetricFogStateFrameIndexMod8 ((uint)(VolumetricFogJitterParameters.y + 0.5f)) + +#define EXP_HEIGHT_FOG_GRID_SIZE_Z VolumetricFogGridSizeAndFlags.z +#define EXP_HEIGHT_FOG_GRID_Z_PARAMS VolumetricFogGridZParams.xyz +#include "ExponentialHeightFog/VolumetricFogCommon.hlsli" + +namespace ExponentialHeightFog +{ + bool IsInsideVolumetricGrid(uint3 coord) + { + return all(coord < VolumetricFogGridSize); + } + + float3 ComputeCellWorldPosition(uint3 coord, float3 cellOffset, out uint eyeIndex, out float viewDepth) + { + float2 volumeUV = (float2(coord.xy) + cellOffset.xy) * VolumetricFogInvGridSize.xy; + eyeIndex = Stereo::GetEyeIndexFromTexCoord(volumeUV); + float2 eyeUV = Stereo::ConvertFromStereoUV(volumeUV, eyeIndex); + + viewDepth = ComputeVolumetricSliceDepth(max(float(coord.z) + cellOffset.z, 0.0f)); + + float2 ndc = eyeUV * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f); + float deviceZ = (SharedData::CameraData.x - SharedData::CameraData.w / viewDepth) / SharedData::CameraData.z; + float4 worldPosition = mul(VolumetricFogClipToWorld[eyeIndex], float4(ndc, deviceZ, 1.0f)); + return worldPosition.xyz / worldPosition.w; + } +} + +#endif diff --git a/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogCommon.hlsli b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogCommon.hlsli new file mode 100644 index 0000000000..c963a9a891 --- /dev/null +++ b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogCommon.hlsli @@ -0,0 +1,101 @@ +#ifndef __EXPONENTIAL_HEIGHT_FOG_VOLUMETRIC_COMMON_HLSLI__ +#define __EXPONENTIAL_HEIGHT_FOG_VOLUMETRIC_COMMON_HLSLI__ + +#include "Common/Math.hlsli" +#include "Common/SharedData.hlsli" + +namespace ExponentialHeightFog +{ + float HenyeyGreenstein(float cosTheta, float g) + { + float g2 = g * g; + float denom = 1.0f + g2 - 2.0f * g * cosTheta; + return (1.0f - g2) / (4.0f * Math::PI * pow(max(denom, 1e-5f), 1.5f)); + } + + float GetHeightFogFalloff() + { + return SharedData::exponentialHeightFogSettings.fogHeightFalloff * 0.001f; + } + + float GetHeightFogDensity() + { + return SharedData::exponentialHeightFogSettings.fogDensity * 0.001f; + } + + float GetVolumetricStartDistance() + { + return max(0.0f, SharedData::exponentialHeightFogSettings.volumetricFogStartDistance); + } + + float GetVolumetricEndDistance() + { + return max(GetVolumetricStartDistance() + 1.0f, SharedData::exponentialHeightFogSettings.volumetricFogDistance); + } + + float GetVolumetricGridSizeZ() + { +#if defined(EXP_HEIGHT_FOG_GRID_SIZE_Z) + return clamp(float(EXP_HEIGHT_FOG_GRID_SIZE_Z), 16.0f, 160.0f); +#else + return clamp(float(SharedData::exponentialHeightFogSettings.volumetricGridSizeZ), 16.0f, 160.0f); +#endif + } + + float GetVolumetricDepthDistributionScale() + { + return max(SharedData::exponentialHeightFogSettings.volumetricDepthDistributionScale, GetVolumetricGridSizeZ() / 120.0f); + } + + float3 GetVolumetricGridZParams(float gridSizeZ) + { +#if defined(EXP_HEIGHT_FOG_GRID_Z_PARAMS) + return EXP_HEIGHT_FOG_GRID_Z_PARAMS; +#else + gridSizeZ = clamp(gridSizeZ, 16.0f, 160.0f); + float nearPlane = max(SharedData::CameraData.y, GetVolumetricStartDistance()); + float farPlane = max(nearPlane + 1.0f, GetVolumetricEndDistance()); + float nearWithOffset = nearPlane + 0.095f * 100.0f; + float farExp = exp2(min(gridSizeZ / GetVolumetricDepthDistributionScale(), 120.0f)); + float gridZOffset = (farPlane - nearWithOffset * farExp) / (farPlane - nearWithOffset); + float gridZScale = (1.0f - gridZOffset) / nearWithOffset; + return float3(gridZScale, gridZOffset, GetVolumetricDepthDistributionScale()); +#endif + } + + float3 GetVolumetricGridZParams() + { + return GetVolumetricGridZParams(GetVolumetricGridSizeZ()); + } + + float ComputeVolumetricSliceDepth(float slice) + { + float3 gridZParams = GetVolumetricGridZParams(); + float sliceExp = exp2(min(slice / max(gridZParams.z, 1e-4f), 120.0f)); + return (sliceExp - gridZParams.y) / max(gridZParams.x, 1e-20f); + } + + float ComputeVolumetricNormalizedSlice(float viewDepth, float gridSizeZ) + { + gridSizeZ = clamp(gridSizeZ, 16.0f, 160.0f); + float3 gridZParams = GetVolumetricGridZParams(gridSizeZ); + return log2(max(viewDepth * gridZParams.x + gridZParams.y, 1e-6f)) * gridZParams.z / gridSizeZ; + } + + float ComputeVolumetricNormalizedSlice(float viewDepth) + { + return ComputeVolumetricNormalizedSlice(viewDepth, GetVolumetricGridSizeZ()); + } + + float EvaluateHeightFogExtinction(float3 positionWS, float3 cameraWS) + { + float fogDensity = GetHeightFogDensity(); + float fogHeightFalloff = GetHeightFogFalloff(); + float worldHeight = positionWS.z + cameraWS.z; + float exponent = fogHeightFalloff * max(worldHeight - SharedData::exponentialHeightFogSettings.fogHeight, 0.0f); + float localDensity = fogDensity * exp2(-exponent); + return max(localDensity * SharedData::exponentialHeightFogSettings.volumetricFogExtinctionScale * 0.5f, 0.0f); + } +} + +#endif diff --git a/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogConservativeDepthCS.hlsl b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogConservativeDepthCS.hlsl new file mode 100644 index 0000000000..7a87efa39e --- /dev/null +++ b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogConservativeDepthCS.hlsl @@ -0,0 +1,34 @@ +#include "ExponentialHeightFog/VolumetricFogCSCommon.hlsli" + +RWTexture2D ConservativeDepthTexture : register(u0); + +[numthreads(8, 8, 1)] void main(uint3 dispatchID : SV_DispatchThreadID) { + if (any(dispatchID.xy >= VolumetricFogGridSize.xy)) + return; + + float2 volumeUVMin = (float2(dispatchID.xy) - 0.5f.xx) * VolumetricFogInvGridSize.xy; + float2 volumeUVMax = (float2(dispatchID.xy + 1u) + 0.5f.xx) * VolumetricFogInvGridSize.xy; + float2 volumeUVCenter = (float2(dispatchID.xy) + 0.5f.xx) * VolumetricFogInvGridSize.xy; + + uint eyeIndex = Stereo::GetEyeIndexFromTexCoord(volumeUVCenter); + float2 eyeUVMin = saturate(Stereo::ConvertFromStereoUV(volumeUVMin, eyeIndex)); + float2 eyeUVMax = saturate(Stereo::ConvertFromStereoUV(volumeUVMax, eyeIndex)); + + int2 minCoord = SharedData::ConvertUVToSampleCoord(min(eyeUVMin, eyeUVMax), eyeIndex).xy; + int2 maxCoord = SharedData::ConvertUVToSampleCoord(max(eyeUVMin, eyeUVMax), eyeIndex).xy - 1; + maxCoord = max(maxCoord, minCoord); + + int2 bufferMax = int2(SharedData::BufferDim.xy) - 1; + minCoord = clamp(minCoord, int2(0, 0), bufferMax); + maxCoord = clamp(maxCoord, int2(0, 0), bufferMax); + + float conservativeDepth = 0.0f; + for (int y = minCoord.y; y <= maxCoord.y; y++) { + for (int x = minCoord.x; x <= maxCoord.x; x++) { + float rawDepth = SharedData::DepthTexture.Load(int3(x, y, 0)).x; + conservativeDepth = max(conservativeDepth, SharedData::GetScreenDepth(rawDepth)); + } + } + + ConservativeDepthTexture[dispatchID.xy] = conservativeDepth; +} diff --git a/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogIntegrationCS.hlsl b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogIntegrationCS.hlsl new file mode 100644 index 0000000000..e009d7885b --- /dev/null +++ b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogIntegrationCS.hlsl @@ -0,0 +1,42 @@ +Texture3D LightScattering : register(t0); +RWTexture3D IntegratedLightScattering : register(u0); + +#include "ExponentialHeightFog/VolumetricFogCSCommon.hlsli" + +[numthreads(8, 8, 1)] void main(uint3 dispatchID : SV_DispatchThreadID) { + if (any(dispatchID.xy >= VolumetricFogGridSize.xy)) + return; + + float3 accumulatedLighting = 0.0f.xxx; + float accumulatedTransmittance = 1.0f; + float accumulatedDepth = 0.0f; + + uint eyeIndex; + float previousDepth; + float3 previousPositionWS = ExponentialHeightFog::ComputeCellWorldPosition(uint3(dispatchID.xy, 0), float3(0.5f, 0.5f, 0.0f), eyeIndex, previousDepth); + + [loop] for (uint layerIndex = 0; layerIndex < VolumetricFogGridSize.z; layerIndex++) + { + uint3 layerCoordinate = uint3(dispatchID.xy, layerIndex); + float4 scatteringAndExtinction = LightScattering[layerCoordinate]; + + uint layerEyeIndex; + float layerDepth; + float3 layerPositionWS = ExponentialHeightFog::ComputeCellWorldPosition(layerCoordinate, 0.5f.xxx, layerEyeIndex, layerDepth); + float stepLength = length(layerPositionWS - previousPositionWS); + previousPositionWS = layerPositionWS; + + float extinction = max(scatteringAndExtinction.w, 0.0f); + float transmittance = exp(-extinction * stepLength); + + accumulatedDepth += stepLength; + float fadeIn = saturate(accumulatedDepth * VolumetricFogNearFadeInDistanceInv); + + float3 scatteringIntegratedOverSlice = + fadeIn * (scatteringAndExtinction.rgb - scatteringAndExtinction.rgb * transmittance) / max(extinction, 1e-5f); + accumulatedLighting += scatteringIntegratedOverSlice * accumulatedTransmittance; + accumulatedTransmittance *= lerp(1.0f, transmittance, fadeIn); + + IntegratedLightScattering[layerCoordinate] = float4(max(accumulatedLighting, 0.0f.xxx), saturate(accumulatedTransmittance)); + } +} diff --git a/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogLightScatteringCS.hlsl b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogLightScatteringCS.hlsl new file mode 100644 index 0000000000..9263c88ba8 --- /dev/null +++ b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogLightScatteringCS.hlsl @@ -0,0 +1,429 @@ +SamplerState LinearSampler : register(s0); +SamplerComparisonState ShadowSampler : register(s1); +Texture3D VBufferA : register(t0); +Texture2DArray DirectionalShadowMap : register(t1); +Texture3D LightScatteringHistory : register(t2); +Texture2D ConservativeDepthTexture : register(t3); +Texture2D PrevConservativeDepthTexture : register(t4); +RWTexture3D LightScattering : register(u0); + +#include "Common/Random.hlsli" +#include "ExponentialHeightFog/VolumetricFogCSCommon.hlsli" +#include "IBL/IBL.hlsli" +#if defined(TERRAIN_SHADOWS) +# include "TerrainShadows/TerrainShadows.hlsli" +#endif +#if defined(CLOUD_SHADOWS) +# include "CloudShadows/CloudShadows.hlsli" +#endif +#if defined(LIGHT_LIMIT_FIX) +# include "LightLimitFix/LightLimitFix.hlsli" +# include "InverseSquareLighting/InverseSquareLighting.hlsli" +#endif +#define SKYLIGHTING_PROBE_REGISTER t50 +#include "Skylighting/Skylighting.hlsli" + +struct DirectionalShadowLightData +{ + column_major float4x4 ShadowProj[2]; + column_major float4x4 InvShadowProj[2]; + float2 EndSplitDistances; + float2 StartSplitDistances; +}; + +StructuredBuffer DirectionalShadowLights : register(t98); + +// 4D PCG hash matching UE's Rand4DPCG32 (jcgt.org/published/0009/03/02/) +uint4 Rand4DPCG32(int4 p) +{ + uint4 v = uint4(p); + v = v * 1664525u + 1013904223u; + v.x += v.y * v.w; + v.y += v.z * v.x; + v.z += v.x * v.y; + v.w += v.y * v.z; + v ^= (v >> 16u); + v.x += v.y * v.w; + v.y += v.z * v.x; + v.z += v.x * v.y; + v.w += v.y * v.z; + return v; +} + +// Matches UE's MakePositiveFinite - ensures no NaN/Inf propagates into history chain +float4 MakePositiveFinite(float4 v) +{ + v = max(v, 0.0f.xxxx); + v.x = isfinite(v.x) ? v.x : 0.0f; + v.y = isfinite(v.y) ? v.y : 0.0f; + v.z = isfinite(v.z) ? v.z : 0.0f; + v.w = isfinite(v.w) ? v.w : 0.0f; + return v; +} + +bool IsFroxelBehindSceneDepth(uint3 coord) +{ + float frontDepth = ExponentialHeightFog::ComputeVolumetricSliceDepth(max(float(coord.z) - 0.5f, 0.0f)); + float sceneDepth = ConservativeDepthTexture[coord.xy]; + return sceneDepth < frontDepth; +} + +float3 ComputeHistoryVolumeUVAndDepth(float3 positionWS, uint eyeIndex, out bool validHistory, out float previousViewDepth) +{ + float3 previousPositionWS = positionWS + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPreviousPosAdjust[eyeIndex].xyz; + float4 previousClip = mul(FrameBuffer::CameraPreviousViewProjUnjittered[eyeIndex], float4(previousPositionWS, 1.0f)); + + previousViewDepth = abs(previousClip.w); + validHistory = previousClip.w > 0.0f; + if (!validHistory) + return 0.0f.xxx; + + float2 historyUV = previousClip.xy / previousClip.w * float2(0.5f, -0.5f) + 0.5f; +#if defined(VR) + historyUV = Stereo::ConvertToStereoUV(historyUV, eyeIndex); +#endif + + float historyZ = ExponentialHeightFog::ComputeVolumetricNormalizedSlice(previousViewDepth); + float3 volumeUV = float3(historyUV, historyZ); + validHistory = !any(volumeUV < 0.0f) && !any(volumeUV >= 1.0f); + return saturate(volumeUV); +} + +float3 ComputeHistoryVolumeUV(float3 positionWS, uint eyeIndex, out bool validHistory) +{ + float previousViewDepth; + return ComputeHistoryVolumeUVAndDepth(positionWS, eyeIndex, validHistory, previousViewDepth); +} + +float2 FixupHistoryUV(float2 uv, float previousCellDepth, out bool validHistory) +{ + float2 size = float2(VolumetricFogGridSize.xy); + float2 fullResUV = uv * size; + float2 screenCoord = floor(fullResUV - 0.5f); + float2 fullResOffset = fullResUV - screenCoord; + float2 gatherUV = (screenCoord + 1.0f) / size; + + float4 previousSceneDepths = PrevConservativeDepthTexture.Gather(LinearSampler, gatherUV); + bool4 validSamples = previousSceneDepths >= previousCellDepth; + + validHistory = true; + if (all(validSamples)) + return uv; + + if (all(validSamples.wz)) + return (screenCoord + float2(fullResOffset.x, 0.5f)) / size; + if (all(validSamples.xy)) + return (screenCoord + float2(fullResOffset.x, 1.5f)) / size; + if (all(validSamples.wx)) + return (screenCoord + float2(0.5f, fullResOffset.y)) / size; + if (all(validSamples.zy)) + return (screenCoord + float2(1.5f, fullResOffset.y)) / size; + + if (validSamples.x) + return (screenCoord + float2(0.5f, 1.5f)) / size; + if (validSamples.y) + return (screenCoord + float2(1.5f, 1.5f)) / size; + if (validSamples.w) + return (screenCoord + float2(0.5f, 0.5f)) / size; + if (validSamples.z) + return (screenCoord + float2(1.5f, 0.5f)) / size; + + validHistory = false; + return uv; +} + +float SampleDirectionalShadowPCF(float3 positionLS, uint cascadeIndex) +{ + uint shadowWidth; + uint shadowHeight; + uint shadowSlices; + DirectionalShadowMap.GetDimensions(shadowWidth, shadowHeight, shadowSlices); + if (cascadeIndex >= shadowSlices) + return 1.0f; + + float2 texelSize = rcp(float2(max(shadowWidth, 1), max(shadowHeight, 1))); + float compareDepth = positionLS.z - SharedData::exponentialHeightFogSettings.volumetricShadowBias; + + float2 uvMin = texelSize * 1.5f; + float2 uvMax = 1.0f.xx - uvMin; + if (any(positionLS.xy < uvMin) || any(positionLS.xy > uvMax)) + return DirectionalShadowMap.SampleCmpLevelZero(ShadowSampler, float3(saturate(positionLS.xy), cascadeIndex), compareDepth).x; + + float center = DirectionalShadowMap.SampleCmpLevelZero(ShadowSampler, float3(positionLS.xy, cascadeIndex), compareDepth).x; + float cross = + DirectionalShadowMap.SampleCmpLevelZero(ShadowSampler, float3(positionLS.xy + float2(texelSize.x, 0.0f), cascadeIndex), compareDepth).x + + DirectionalShadowMap.SampleCmpLevelZero(ShadowSampler, float3(positionLS.xy - float2(texelSize.x, 0.0f), cascadeIndex), compareDepth).x + + DirectionalShadowMap.SampleCmpLevelZero(ShadowSampler, float3(positionLS.xy + float2(0.0f, texelSize.y), cascadeIndex), compareDepth).x + + DirectionalShadowMap.SampleCmpLevelZero(ShadowSampler, float3(positionLS.xy - float2(0.0f, texelSize.y), cascadeIndex), compareDepth).x; + + return (center * 4.0f + cross) * rcp(8.0f); +} + +float SampleDirectionalShadow(float3 positionWS, uint eyeIndex) +{ + if (SharedData::InInterior || SharedData::HideSky || SharedData::InMapMenu) + return 1.0f; + if (!VolumetricFogHasDirectionalShadowMap) + return 1.0f; + + DirectionalShadowLightData directionalShadowLightData = DirectionalShadowLights[0]; + float shadowMapDepth = SharedData::GetScreenDepth(FrameBuffer::GetShadowDepth(positionWS, eyeIndex)); + if (shadowMapDepth >= directionalShadowLightData.EndSplitDistances.y) + return 1.0f; + + float splitDenom = max(directionalShadowLightData.EndSplitDistances.x - directionalShadowLightData.StartSplitDistances.y, 1e-4f); + float cascadeSelect = smoothstep(0.0f, 1.0f, saturate((shadowMapDepth - directionalShadowLightData.StartSplitDistances.y) / splitDenom)); + uint primaryCascade = (uint)cascadeSelect; + + float3 absolutePositionWS = positionWS + FrameBuffer::CameraPosAdjust[eyeIndex].xyz; + float3 positionLS = mul(directionalShadowLightData.ShadowProj[primaryCascade], float4(absolutePositionWS, 1.0f)).xyz; + if (any(positionLS.xy < 0.0f) || any(positionLS.xy > 1.0f)) + return 1.0f; + + float shadow = SampleDirectionalShadowPCF(positionLS, primaryCascade); + + [branch] if (cascadeSelect > 0.0f && cascadeSelect < 1.0f) + { + uint secondaryCascade = 1u - primaryCascade; + float3 secondaryLS = mul(directionalShadowLightData.ShadowProj[secondaryCascade], float4(absolutePositionWS, 1.0f)).xyz; + if (!any(secondaryLS.xy < 0.0f) && !any(secondaryLS.xy > 1.0f)) { + float secondaryShadow = SampleDirectionalShadowPCF(secondaryLS, secondaryCascade); + shadow = lerp(shadow, secondaryShadow, cascadeSelect); + } + } + + float fade = saturate(shadowMapDepth / max(directionalShadowLightData.EndSplitDistances.y, 1.0f)); + float fadeFactor = 1.0f - pow(fade * fade, 8.0f); + return lerp(1.0f, shadow, fadeFactor); +} + +float SampleDirectionalWorldShadow(float3 positionWS, uint eyeIndex) +{ + if (SharedData::InInterior || SharedData::HideSky || SharedData::InMapMenu) + return 1.0f; + + float worldShadow = 1.0f; +#if defined(TERRAIN_SHADOWS) + worldShadow *= TerrainShadows::GetTerrainShadow(positionWS + FrameBuffer::CameraPosAdjust[eyeIndex].xyz, LinearSampler); +#endif +#if defined(CLOUD_SHADOWS) + worldShadow *= CloudShadows::GetCloudShadowMult(positionWS, LinearSampler); +#endif + return worldShadow; +} + +float3 ComputeSkyLightScattering(float3 positionWS, float3 viewDirection, uint eyeIndex) +{ + float phaseG = SharedData::exponentialHeightFogSettings.volumetricFogScatteringDistribution; + float3 skyDirection = abs(phaseG) > 0.001f ? normalize(-viewDirection * phaseG) : 0.0f.xxx; + float3 skyVisibilityDirection = abs(phaseG) > 0.001f ? skyDirection : float3(0.0f, 0.0f, 1.0f); + float skyVisibility = 1.0f; + if (VolumetricFogHasSkylighting && !SharedData::InInterior) { +#if defined(VR) + float3 skylightingPosition = positionWS + FrameBuffer::CameraPosAdjust[eyeIndex].xyz - FrameBuffer::CameraPosAdjust[0].xyz; +#else + float3 skylightingPosition = positionWS; +#endif + sh2 skylightingSH = Skylighting::SampleNoBias(skylightingPosition); + skyVisibility = Skylighting::EvaluateDiffuse(skylightingSH, skyVisibilityDirection, Skylighting::GetFadeOutFactor(skylightingPosition)); + } + + float3 skyLighting = + SharedData::exponentialHeightFogSettings.fogInscatteringColor.rgb * + SharedData::exponentialHeightFogSettings.fogInscatteringColor.a * + skyVisibility; + [branch] if (VolumetricFogHasIBL) + skyLighting = ImageBasedLighting::GetIBLColorOccluded(skyDirection, skyVisibility); + + return skyLighting * + SharedData::exponentialHeightFogSettings.volumetricSkyLightingIntensity; +} + +#if defined(LIGHT_LIMIT_FIX) +float ComputeLocalLightAttenuation(float distanceSqr, float cellRadius, LightLimitFix::Light light) +{ + float distance = sqrt(max(distanceSqr, 1e-6f)); + + // UE biases local light integration by froxel size to avoid singular bright voxels close to the light. + if (light.lightFlags & LightLimitFix::LightFlags::InverseSquare) { + distance = sqrt(max(distanceSqr, cellRadius * cellRadius)); + } + + return InverseSquareLighting::GetAttenuation(distance, light); +} + +float3 AccumulateLocalLightScattering( + uint3 coord, + float3 cellOffset, + float3 positionWS, + float viewDepth, + float3 viewDirection, + uint eyeIndex, + float3 materialScattering) +{ + if (!VolumetricFogHasLocalLights) + return 0.0f.xxx; + + float2 volumeUV = (float2(coord.xy) + cellOffset.xy) * VolumetricFogInvGridSize.xy; + float2 screenUV = Stereo::ConvertFromStereoUV(volumeUV, eyeIndex); + + uint clusterIndex = 0; + if (!LightLimitFix::GetClusterIndex(screenUV, viewDepth, clusterIndex)) + return 0.0f.xxx; + + LightLimitFix::LightGrid grid = LightLimitFix::lightGrid[clusterIndex]; + uint lightCount = min(grid.lightCount, (uint)MAX_CLUSTER_LIGHTS); + + uint cornerEyeIndex; + float cornerViewDepth; + float3 cellCornerWS = ExponentialHeightFog::ComputeCellWorldPosition(coord + uint3(1, 1, 1), cellOffset, cornerEyeIndex, cornerViewDepth); + float cellRadius = max(length(cellCornerWS - positionWS), 1.0f); + + float phaseG = SharedData::exponentialHeightFogSettings.volumetricFogScatteringDistribution; + float3 localScattering = 0.0f.xxx; + [loop] for (uint lightIndex = 0; lightIndex < lightCount; lightIndex++) + { + uint clusteredLightIndex = LightLimitFix::lightList[grid.offset + lightIndex]; + LightLimitFix::Light light = LightLimitFix::lights[clusteredLightIndex]; + + if (light.lightFlags & LightLimitFix::LightFlags::Disabled) + continue; + + float3 toLight = light.positionWS[eyeIndex].xyz - positionWS; + float distanceSqr = dot(toLight, toLight); + if (distanceSqr < 1e-6f) + continue; + + float attenuation = ComputeLocalLightAttenuation(distanceSqr, cellRadius, light); + if (attenuation < 1e-5f) + continue; + + float3 L = toLight * rsqrt(distanceSqr); + float phase = ExponentialHeightFog::HenyeyGreenstein(dot(L, -viewDirection), phaseG); + + const bool isPointLightLinear = light.lightFlags & LightLimitFix::LightFlags::Linear; + float3 lightColor = Color::PointLight(light.color.xyz, isPointLightLinear) * attenuation * light.fade; + localScattering += lightColor * phase; + } + + return localScattering * + SharedData::exponentialHeightFogSettings.volumetricLocalLightScatteringIntensity * + materialScattering; +} +#else +float3 AccumulateLocalLightScattering( + uint3 coord, + float3 cellOffset, + float3 positionWS, + float viewDepth, + float3 viewDirection, + uint eyeIndex, + float3 materialScattering) +{ + return 0.0f.xxx; +} +#endif + +float4 ComputeLightScattering(uint3 coord, float3 cellOffset) +{ + uint eyeIndex; + float viewDepth; + float3 positionWS = ExponentialHeightFog::ComputeCellWorldPosition(coord, cellOffset, eyeIndex, viewDepth); + + float4 materialScatteringAndExtinction = VBufferA[coord]; + float extinction = materialScatteringAndExtinction.w; + + float3 viewDirection = normalize(positionWS); + + // Directional light uses isotropic phase (1/4PI) in the volume to avoid angular aliasing + // at the coarse froxel XY resolution. The actual per-pixel HG phase is applied at full + // resolution during compositing in SampleVolumetricFog(). + float directionalPhase = 1.0f / (4.0f * Math::PI); + + float directionalShadow = SampleDirectionalShadow(positionWS, eyeIndex) * + SampleDirectionalWorldShadow(positionWS, eyeIndex); + float3 directionalScattering = + SharedData::DirLightColor.xyz * + SharedData::exponentialHeightFogSettings.volumetricDirectionalScatteringIntensity * + directionalShadow * + directionalPhase * + materialScatteringAndExtinction.rgb; + + float3 skyScattering = ComputeSkyLightScattering(positionWS, viewDirection, eyeIndex) * + materialScatteringAndExtinction.rgb; + + float3 localScattering = AccumulateLocalLightScattering( + coord, + cellOffset, + positionWS, + viewDepth, + viewDirection, + eyeIndex, + materialScatteringAndExtinction.rgb); + + float3 emissive = SharedData::exponentialHeightFogSettings.volumetricFogEmissive.rgb * + SharedData::exponentialHeightFogSettings.volumetricFogEmissive.a * + extinction; + + return float4(max(directionalScattering + skyScattering + localScattering + emissive, 0.0f.xxx), extinction); +} + +[numthreads(8, 8, 4)] void main(uint3 dispatchID : SV_DispatchThreadID) { + if (!ExponentialHeightFog::IsInsideVolumetricGrid(dispatchID)) + return; + + uint eyeIndex; + float viewDepth; + float3 centerPositionWS = ExponentialHeightFog::ComputeCellWorldPosition(dispatchID, 0.5f.xxx, eyeIndex, viewDepth); + if (VolumetricFogHasConservativeDepth && IsFroxelBehindSceneDepth(dispatchID)) { + LightScattering[dispatchID] = 0.0f.xxxx; + return; + } + + bool validHistory; + float3 historyUV = ComputeHistoryVolumeUV(centerPositionWS, eyeIndex, validHistory); + if (VolumetricFogHasPrevConservativeDepth && validHistory) { + uint frontEyeIndex; + float frontDepth; + float3 frontPositionWS = ExponentialHeightFog::ComputeCellWorldPosition(dispatchID, float3(0.5f, 0.5f, -0.5f), frontEyeIndex, frontDepth); + bool validFrontHistory; + float previousFrontDepth; + ComputeHistoryVolumeUVAndDepth(frontPositionWS, frontEyeIndex, validFrontHistory, previousFrontDepth); + if (validFrontHistory) { + historyUV.xy = saturate(FixupHistoryUV(historyUV.xy, previousFrontDepth, validHistory)); + } else { + validHistory = false; + } + } + + float historyAlpha = VolumetricFogHistoryWeight; + [flatten] if (!validHistory || any(historyUV < 0.0f) || any(historyUV >= 1.0f)) + { + historyAlpha = 0.0f; + } + + uint sampleCount = historyAlpha < 0.001f ? VolumetricFogHistoryMissSampleCount : 1u; + float4 scatteringAndExtinction = 0.0f.xxxx; + [loop] for (uint sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + { + // Per-voxel random noise matching UE's LightScatteringCS: + // Rand4DPCG32(int4(GridCoordinate.xyz, StateFrameIndexMod8 + 8 * SampleIndex)) + // This decorrelates the jitter pattern across voxels, preventing coherent temporal artifacts + uint3 Rand32Bits = Rand4DPCG32(int4(dispatchID.xyz, VolumetricFogStateFrameIndexMod8 + 8 * sampleIndex)).xyz; + float3 Rand3D = (float3(Rand32Bits) / float(uint(0xffffffff))) * 2.0f - 1.0f; + float3 cellOffset = VolumetricFogFrameJitterOffsets[sampleIndex].xyz + VolumetricFogSampleJitterMultiplier * Rand3D; + + scatteringAndExtinction += ComputeLightScattering(dispatchID, cellOffset); + } + scatteringAndExtinction *= rcp(float(sampleCount)); + + [branch] if (historyAlpha > 0.0f) + { + float4 history = LightScatteringHistory.SampleLevel(LinearSampler, historyUV, 0); + // Sanitize history to prevent NaN/Inf propagation in the temporal chain + history = MakePositiveFinite(history); + scatteringAndExtinction = lerp(scatteringAndExtinction, history, historyAlpha); + } + + LightScattering[dispatchID] = MakePositiveFinite(scatteringAndExtinction); +} diff --git a/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogMaterialCS.hlsl b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogMaterialCS.hlsl new file mode 100644 index 0000000000..07687f7015 --- /dev/null +++ b/features/Exponential Height Fog/Shaders/ExponentialHeightFog/VolumetricFogMaterialCS.hlsl @@ -0,0 +1,18 @@ +#include "ExponentialHeightFog/VolumetricFogCSCommon.hlsli" + +RWTexture3D VBufferA : register(u0); + +[numthreads(8, 8, 4)] void main(uint3 dispatchID : SV_DispatchThreadID) { + if (!ExponentialHeightFog::IsInsideVolumetricGrid(dispatchID)) + return; + + uint eyeIndex; + float viewDepth; + float3 positionWS = ExponentialHeightFog::ComputeCellWorldPosition(dispatchID, 0.5f.xxx, eyeIndex, viewDepth); + + float extinction = ExponentialHeightFog::EvaluateHeightFogExtinction(positionWS, FrameBuffer::CameraPosAdjust[eyeIndex].xyz); + float3 albedo = saturate(SharedData::exponentialHeightFogSettings.volumetricFogAlbedo.rgb); + float3 scattering = extinction * albedo * SharedData::exponentialHeightFogSettings.volumetricFogAlbedo.a; + + VBufferA[dispatchID] = float4(scattering, extinction); +} diff --git a/features/Exponential Height Fog/Shaders/Features/ExponentialHeightFog.ini b/features/Exponential Height Fog/Shaders/Features/ExponentialHeightFog.ini index 9e325f8475..0a7412150c 100644 --- a/features/Exponential Height Fog/Shaders/Features/ExponentialHeightFog.ini +++ b/features/Exponential Height Fog/Shaders/Features/ExponentialHeightFog.ini @@ -1,5 +1,5 @@ [Info] -Version = 1-2-0 +Version = 1-3-0 [Nexus] autoupload = false diff --git a/package/SKSE/Plugins/CommunityShaders/Translations/en.json b/package/SKSE/Plugins/CommunityShaders/Translations/en.json index 7ae1794d11..36de2bcfe3 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/en.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/en.json @@ -567,21 +567,44 @@ "feature.exp_height_fog.apply_vanilla_fade": "Apply Vanilla Fade", "feature.exp_height_fog.apply_vanilla_fade_tooltip": "Applies vanilla fade brightness to exponential height fog.", "feature.exp_height_fog.cubemap_mip_level": "Cubemap Mip Level", + "feature.exp_height_fog.debug": "Debug", + "feature.exp_height_fog.depth_distribution_scale": "Depth Distribution Scale", "feature.exp_height_fog.dir_inscattering_anisotropy": "Directional Light Inscattering Anisotropy", "feature.exp_height_fog.dir_inscattering_anisotropy_tooltip": "Controls the asymmetry of inscattering via the Henyey-Greenstein phase function.\nPositive values produce forward scattering (glow around sun).\nZero is isotropic. Negative values produce back scattering.", "feature.exp_height_fog.dir_inscattering_mul": "Directional Light Inscattering Multiplier", + "feature.exp_height_fog.directional_scattering_intensity": "Directional Scattering Intensity", + "feature.exp_height_fog.directional_shadow_bias": "Directional Shadow Bias", "feature.exp_height_fog.disable_vanilla_fog": "Disable Vanilla Fog", "feature.exp_height_fog.disable_vanilla_fog_tooltip": "Disables the vanilla fog entirely. Only exponential height fog will be applied.", "feature.exp_height_fog.enable_exp_height_fog": "Enable Exponential Height Fog", + "feature.exp_height_fog.enable_volumetric_fog": "Enable Volumetric Fog", "feature.exp_height_fog.fog_density": "Fog Density", "feature.exp_height_fog.fog_height": "Fog Height", "feature.exp_height_fog.fog_height_falloff": "Fog Height Falloff", "feature.exp_height_fog.fog_inscattering_color": "Fog Inscattering Color", + "feature.exp_height_fog.grid_depth_slices": "Grid Depth Slices", + "feature.exp_height_fog.grid_pixel_size": "Grid Pixel Size", + "feature.exp_height_fog.history_miss_samples": "History Miss Samples", "feature.exp_height_fog.inscattering_cubemap_tint": "Inscattering Cubemap Tint", + "feature.exp_height_fog.local_light_scattering_intensity": "Local Light Scattering Intensity", + "feature.exp_height_fog.near_fade_in_distance": "Near Fade In Distance", "feature.exp_height_fog.original_fog_color_amount": "Original Fog Color Amount", + "feature.exp_height_fog.sample_jitter_multiplier": "Sample Jitter Multiplier", + "feature.exp_height_fog.sample_jitter_multiplier_tooltip": "Matches UE's r.VolumetricFog.LightScatteringSampleJitterMultiplier.\nAdds per-voxel random offset on top of the Halton sequence.\n0 = UE default; nonzero values need stronger temporal filtering.", + "feature.exp_height_fog.sky_lighting_scattering_intensity": "Sky Lighting Scattering Intensity", "feature.exp_height_fog.start_distance": "Start Distance", "feature.exp_height_fog.sunlight_attenuation": "Sunlight Attenuation Amount", + "feature.exp_height_fog.temporal_history_weight": "Temporal History Weight", + "feature.exp_height_fog.upsample_jitter_multiplier": "Upsample Jitter Multiplier", + "feature.exp_height_fog.upsample_jitter_multiplier_tooltip": "Matches UE's r.VolumetricFog.UpsampleJitterMultiplier.\nJitters the final 3D fog lookup in screen space to hide\nlow-resolution froxel pixelization. 0 = UE default.", "feature.exp_height_fog.use_dynamic_cubemaps": "Use Dynamic Cubemaps for Inscattering", + "feature.exp_height_fog.volumetric_albedo": "Volumetric Albedo", + "feature.exp_height_fog.volumetric_emissive": "Volumetric Emissive", + "feature.exp_height_fog.volumetric_extinction_scale": "Volumetric Extinction Scale", + "feature.exp_height_fog.volumetric_fog": "Volumetric Fog", + "feature.exp_height_fog.volumetric_scattering_distribution": "Volumetric Scattering Distribution", + "feature.exp_height_fog.volumetric_start_distance": "Volumetric Start Distance", + "feature.exp_height_fog.volumetric_view_distance": "Volumetric View Distance", "feature.exponential_height_fog.description": "Exponential Height Fog adds a realistic fog effect that increases in density with height, enhancing atmospheric depth and immersion in the game environment.", "feature.exponential_height_fog.key_feature_1": "Added exponential height fog effect", "feature.exponential_height_fog.key_feature_2": "Adapted to vanilla fog settings", diff --git a/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json b/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json index 0f38a04c23..f5c36c0f7f 100644 --- a/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json +++ b/package/SKSE/Plugins/CommunityShaders/Translations/zh_CN.json @@ -48,21 +48,44 @@ "feature.exp_height_fog.apply_vanilla_fade": "应用原版渐隐", "feature.exp_height_fog.apply_vanilla_fade_tooltip": "将原版渐隐亮度应用于指数高度雾。", "feature.exp_height_fog.cubemap_mip_level": "立方体贴图Mip级别", + "feature.exp_height_fog.debug": "调试", + "feature.exp_height_fog.depth_distribution_scale": "深度分布比例", "feature.exp_height_fog.dir_inscattering_anisotropy": "方向光内散射各向异性", "feature.exp_height_fog.dir_inscattering_anisotropy_tooltip": "通过Henyey-Greenstein相位函数控制内散射的不对称性。\n正值产生前向散射(太阳周围发光)。\n零为各向同性。负值产生后向散射。", "feature.exp_height_fog.dir_inscattering_mul": "方向光内散射倍率", + "feature.exp_height_fog.directional_scattering_intensity": "方向光散射强度", + "feature.exp_height_fog.directional_shadow_bias": "方向阴影偏移", "feature.exp_height_fog.disable_vanilla_fog": "禁用原版雾", "feature.exp_height_fog.disable_vanilla_fog_tooltip": "完全禁用原版雾。仅应用指数高度雾。", "feature.exp_height_fog.enable_exp_height_fog": "启用指数高度雾", + "feature.exp_height_fog.enable_volumetric_fog": "启用体积雾", "feature.exp_height_fog.fog_density": "雾密度", "feature.exp_height_fog.fog_height": "雾高度", "feature.exp_height_fog.fog_height_falloff": "雾高度衰减", "feature.exp_height_fog.fog_inscattering_color": "雾内散射颜色", + "feature.exp_height_fog.grid_depth_slices": "网格深度切片", + "feature.exp_height_fog.grid_pixel_size": "网格像素大小", + "feature.exp_height_fog.history_miss_samples": "历史缺失采样数", "feature.exp_height_fog.inscattering_cubemap_tint": "内散射立方体贴图色调", + "feature.exp_height_fog.local_light_scattering_intensity": "局部光散射强度", + "feature.exp_height_fog.near_fade_in_distance": "近处淡入距离", "feature.exp_height_fog.original_fog_color_amount": "原始雾颜色量", + "feature.exp_height_fog.sample_jitter_multiplier": "采样抖动倍率", + "feature.exp_height_fog.sample_jitter_multiplier_tooltip": "对应 UE 的 r.VolumetricFog.LightScatteringSampleJitterMultiplier。\n在 Halton 序列基础上为每个体素添加随机偏移。\n0 = UE 默认值;非零值需要更强的时域滤波。", + "feature.exp_height_fog.sky_lighting_scattering_intensity": "天空光照散射强度", "feature.exp_height_fog.start_distance": "起始距离", "feature.exp_height_fog.sunlight_attenuation": "阳光衰减量", + "feature.exp_height_fog.temporal_history_weight": "时域历史权重", + "feature.exp_height_fog.upsample_jitter_multiplier": "上采样抖动倍率", + "feature.exp_height_fog.upsample_jitter_multiplier_tooltip": "对应 UE 的 r.VolumetricFog.UpsampleJitterMultiplier。\n在屏幕空间抖动最终 3D 雾查找,以隐藏\n低分辨率 froxel 像素化。0 = UE 默认值。", "feature.exp_height_fog.use_dynamic_cubemaps": "使用动态立方体贴图进行内散射", + "feature.exp_height_fog.volumetric_albedo": "体积雾反照率", + "feature.exp_height_fog.volumetric_emissive": "体积雾自发光", + "feature.exp_height_fog.volumetric_extinction_scale": "体积雾消光比例", + "feature.exp_height_fog.volumetric_fog": "体积雾", + "feature.exp_height_fog.volumetric_scattering_distribution": "体积雾散射分布", + "feature.exp_height_fog.volumetric_start_distance": "体积雾起始距离", + "feature.exp_height_fog.volumetric_view_distance": "体积雾视距", "feature.exponential_height_fog.description": "添加逼真的高度雾效果,雾密度随高度变化,增强场景的大气深度和沉浸感。", "feature.exponential_height_fog.key_feature_1": "新增指数高度雾效果", "feature.exponential_height_fog.key_feature_2": "适配原版雾效设置", @@ -492,7 +515,7 @@ "feature.screenshot.hdr_bit_depth_tooltip": "48 bpp RGB PNG 负载的量化位深。11位是较好的默认值;更高的值会增加文件大小,但收益递减。", "feature.screenshot.hdr_note": "HDR 已启用:将显示帧保存为带有 HDR10 元数据的 PNG(48 bpp RGB,cICP/cLLi)。请使用支持 HDR 的查看器,如 Windows 照片(HDR 开启)或 Special K SKIF。", "feature.screenshot.hotkey": "热键", - "feature.screenshot.hotkey_collision": "此热键与原版PrintScreen冲突;两者都会触发保存。\n在Skyrim.ini中设置bAllowScreenShot=0以抑制原版,或在上方选择不同的热键。", + "feature.screenshot.hotkey_collision": "此热键与原版PrintScreen冲突;两者都会触发保存。在Skyrim.ini中设置bAllowScreenShot=0以抑制原版,或在上方选择不同的热键。", "feature.screenshot.name": "截图", "feature.screenshot.open": "打开", "feature.screenshot.output": "输出", diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index 05c257b034..e6ec03706b 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -266,7 +266,26 @@ namespace SharedData uint disableVanillaFog; float4 fogInscatteringColor; float originalFogColorAmount; - float3 pad; + uint volumetricFogEnabled; + uint volumetricGridPixelSize; + uint volumetricGridSizeZ; + float volumetricFogDistance; + float volumetricFogStartDistance; + float volumetricFogNearFadeInDistance; + float volumetricFogExtinctionScale; + float4 volumetricFogAlbedo; + float4 volumetricFogEmissive; + float volumetricDirectionalScatteringIntensity; + float volumetricShadowBias; + float volumetricDepthDistributionScale; + float volumetricSkyLightingIntensity; + float volumetricFogScatteringDistribution; + float volumetricHistoryWeight; + uint volumetricHistoryMissSampleCount; + float volumetricSampleJitterMultiplier; + float volumetricUpsampleJitterMultiplier; + float volumetricLocalLightScatteringIntensity; + float2 pad0; }; struct TruePBRSettings diff --git a/package/Shaders/DistantTree.hlsl b/package/Shaders/DistantTree.hlsl index 669d61342e..3e510142a2 100644 --- a/package/Shaders/DistantTree.hlsl +++ b/package/Shaders/DistantTree.hlsl @@ -2,6 +2,7 @@ #include "Common/FrameBuffer.hlsli" #include "Common/GBuffer.hlsli" #include "Common/MotionBlur.hlsli" +#include "Common/Permutation.hlsli" #include "Common/Random.hlsli" #include "Common/SharedData.hlsli" #include "Common/VR.hlsli" @@ -180,6 +181,15 @@ const static float DepthOffsets[16] = { # include "Common/ShadowSampling.hlsli" +# if defined(EXP_HEIGHT_FOG) +void ApplyReflectionExponentialHeightFog(inout float3 color, float3 positionWS, float4 screenPosition, uint eyeIndex) +{ + float3 fogColor = Color::Fog(AmbientColor.xyz); + float4 exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFogNoVolumetric(positionWS, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, fogColor, float4(screenPosition.xy * FrameBuffer::DynamicResolutionParams2.xy, screenPosition.z, 1)); + color = lerp(color, exponentialHeightFog.xyz, exponentialHeightFog.w); +} +# endif + PS_OUTPUT main(PS_INPUT input) { PS_OUTPUT psout; @@ -189,6 +199,9 @@ PS_OUTPUT main(PS_INPUT input) # else uint eyeIndex = input.EyeIndex; # endif // !VR +# if defined(EXP_HEIGHT_FOG) + const bool inReflection = (Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InReflection) != 0; +# endif # if defined(RENDER_DEPTH) uint2 temp = uint2(input.Position.xy); @@ -255,6 +268,12 @@ PS_OUTPUT main(PS_INPUT input) psout.Diffuse.xyz = diffuseColor * baseColor.xyz; psout.Diffuse.w = 1; +# if defined(EXP_HEIGHT_FOG) + if (inReflection && SharedData::exponentialHeightFogSettings.enabled) { + ApplyReflectionExponentialHeightFog(psout.Diffuse.xyz, input.WorldPosition.xyz, input.Position, eyeIndex); + } +# endif + psout.MotionVector = MotionBlur::GetSSMotionVector(input.WorldPosition, input.PreviousWorldPosition, eyeIndex); psout.Normal.xy = GBuffer::EncodeNormal(FrameBuffer::WorldToView(normal, false, eyeIndex)); @@ -287,6 +306,11 @@ PS_OUTPUT main(PS_INPUT input) diffuseColor += directionalAmbientColor; float3 color = diffuseColor * baseColor.xyz; +# if defined(EXP_HEIGHT_FOG) + if (inReflection && SharedData::exponentialHeightFogSettings.enabled) { + ApplyReflectionExponentialHeightFog(color, input.WorldPosition.xyz, input.Position, eyeIndex); + } +# endif psout.Diffuse = float4(color, 1.0); # endif // DEFERRED # endif // RENDER_DEPTH diff --git a/package/Shaders/Effect.hlsl b/package/Shaders/Effect.hlsl index a901e9bf2c..2094160a73 100644 --- a/package/Shaders/Effect.hlsl +++ b/package/Shaders/Effect.hlsl @@ -903,16 +903,18 @@ PS_OUTPUT main(PS_INPUT input) float3 vanillaFogColor = fogColor; float expFogFactor = 0; if (SharedData::exponentialHeightFogSettings.enabled) { - float4 exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFog(input.WorldPosition.xyz, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, fogColor); + float4 exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFog(input.WorldPosition.xyz, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, fogColor, float4(input.Position.xy * FrameBuffer::DynamicResolutionParams2.xy, input.Position.z, 1)); expFogFactor = exponentialHeightFog.w; # if defined(ADDBLEND) || defined(MULTBLEND) || defined(MULTBLEND_DECAL) fogColor = exponentialHeightFog.xyz; fogFactor = exponentialHeightFog.w; # else - fogColor = lightColor; + fogColor = exponentialHeightFog.xyz; + fogFactor = exponentialHeightFog.w; alpha *= 1 - exponentialHeightFog.w; # endif if (ExponentialHeightFog::ShouldDisableVanillaFog()) { + vanillaFogColor = lightColor; vanillaFogFactor = 0; } } @@ -933,6 +935,7 @@ PS_OUTPUT main(PS_INPUT input) # else # if defined(EXP_HEIGHT_FOG) float3 blendedColor = lerp(lightColor, vanillaFogColor, vanillaFogFactor.xxx); + blendedColor = lerp(blendedColor, fogColor, fogFactor.xxx); # else float3 blendedColor = lerp(lightColor, fogColor, fogFactor.xxx); # endif diff --git a/package/Shaders/ISSAOComposite.hlsl b/package/Shaders/ISSAOComposite.hlsl index 55785c7604..ecaf54ce1e 100644 --- a/package/Shaders/ISSAOComposite.hlsl +++ b/package/Shaders/ISSAOComposite.hlsl @@ -196,7 +196,8 @@ PS_OUTPUT main(PS_INPUT input) positionWS.xyz = positionWS.xyz / positionWS.w; float4 exponentialHeightFog = (float4)0; if (exponentialHeightFogEnabled) { - exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFog(positionWS.xyz, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, fogColor); + float4 fogScreenPosition = float4(Stereo::ConvertToStereoUV(monoUV, eyeIndex) * SharedData::BufferDim.xy, depth, 1.0f); + exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFog(positionWS.xyz, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, fogColor, fogScreenPosition); } if (isGeometryDepth || exponentialHeightFogEnabled) { float fogFade = exponentialHeightFogEnabled ? ExponentialHeightFog::GetVanillaFogFade(FogNearColor.w) : FogNearColor.w; diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index d89703119f..fc3fbef07c 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -3283,12 +3283,17 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace) float3 vanillaFogColor = fogColor; float vanillaFogFactor = fogFactor; if (SharedData::exponentialHeightFogSettings.enabled) { - float4 exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFog(input.WorldPosition.xyz, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, fogColor); + float4 exponentialHeightFog; + if (inReflection) { + exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFogNoVolumetric(input.WorldPosition.xyz, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, fogColor, float4(input.Position.xy * FrameBuffer::DynamicResolutionParams2.xy, input.Position.z, 1)); + } else { + exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFog(input.WorldPosition.xyz, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, fogColor, float4(input.Position.xy * FrameBuffer::DynamicResolutionParams2.xy, input.Position.z, 1)); + } fogColor = exponentialHeightFog.xyz; fogFactor = exponentialHeightFog.w; } # endif - if (FrameBuffer::FrameParams.y && FrameBuffer::FrameParams.z) { + if ((FrameBuffer::FrameParams.y && FrameBuffer::FrameParams.z) || inReflection) { # if defined(EXP_HEIGHT_FOG) if (SharedData::exponentialHeightFogSettings.enabled) { if (!ExponentialHeightFog::ShouldDisableVanillaFog()) { diff --git a/package/Shaders/Sky.hlsl b/package/Shaders/Sky.hlsl index a4c83b2f03..219962495b 100644 --- a/package/Shaders/Sky.hlsl +++ b/package/Shaders/Sky.hlsl @@ -45,6 +45,7 @@ struct VS_OUTPUT float4 WorldPosition: POSITION1; float4 PreviousWorldPosition: POSITION2; + float3 FogPosition: TEXCOORD4; #if defined(VR) float ClipDistance: SV_ClipDistance0; // o11 float CullDistance: SV_CullDistance0; // p11 @@ -138,6 +139,7 @@ VS_OUTPUT main(VS_INPUT input) vsout.Position = mul(WorldViewProj[eyeIndex], inputPosition).xyww; vsout.WorldPosition = mul(World[eyeIndex], inputPosition); + vsout.FogPosition = vsout.WorldPosition.xyz - EyePosition[eyeIndex].xyz; vsout.PreviousWorldPosition = mul(PreviousWorld[eyeIndex], inputPosition); # ifdef VR @@ -282,6 +284,15 @@ PS_OUTPUT main(PS_INPUT input) psout.Color = float4(0, 0, 0, 1.0); # endif // OCCLUSION +# if defined(EXP_HEIGHT_FOG) + const bool inReflection = (Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InReflection) != 0; + if (inReflection && SharedData::exponentialHeightFogSettings.enabled) { + float3 skyFogPosition = normalize(input.FogPosition.xyz) * SharedData::CameraData.x; + float4 exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFogNoVolumetric(skyFogPosition, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, psout.Color.xyz, float4(input.Position.xy * FrameBuffer::DynamicResolutionParams2.xy, input.Position.z, 1)); + psout.Color.xyz = lerp(psout.Color.xyz, exponentialHeightFog.xyz, exponentialHeightFog.w); + } +# endif + float2 screenMotionVector = MotionBlur::GetSSMotionVector(input.WorldPosition, input.PreviousWorldPosition, eyeIndex); psout.MotionVectors = float4(screenMotionVector, 0, psout.Color.w); diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 87927eaece..54998a24e3 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -1278,7 +1278,7 @@ PS_OUTPUT main(PS_INPUT input) # endif # if defined(EXP_HEIGHT_FOG) if (SharedData::exponentialHeightFogSettings.enabled) { - float4 exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFog(input.WPosition.xyz, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, fogColor); + float4 exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFog(input.WPosition.xyz, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, fogColor, float4(input.HPosition.xy * FrameBuffer::DynamicResolutionParams2.xy, input.HPosition.z, 1)); if (ExponentialHeightFog::ShouldDisableVanillaFog()) { fogColor = exponentialHeightFog.xyz; fogColor *= GetWaterFogFade(eyeIndex); @@ -1329,7 +1329,7 @@ PS_OUTPUT main(PS_INPUT input) # endif # if defined(EXP_HEIGHT_FOG) if (SharedData::exponentialHeightFogSettings.enabled) { - float4 exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFog(input.WPosition.xyz, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, preFogColor); + float4 exponentialHeightFog = ExponentialHeightFog::GetExponentialHeightFog(input.WPosition.xyz, FrameBuffer::CameraPosAdjust[eyeIndex].xyz, preFogColor, float4(input.HPosition.xy * FrameBuffer::DynamicResolutionParams2.xy, input.HPosition.z, 1)); if (ExponentialHeightFog::ShouldDisableVanillaFog()) { preFogColor = exponentialHeightFog.xyz; preFogColor *= GetWaterFogFade(eyeIndex); diff --git a/src/Features/ExponentialHeightFog.cpp b/src/Features/ExponentialHeightFog.cpp index cea43ff252..4eb4c1cb9d 100644 --- a/src/Features/ExponentialHeightFog.cpp +++ b/src/Features/ExponentialHeightFog.cpp @@ -1,6 +1,15 @@ #include "ExponentialHeightFog.h" +#include "Deferred.h" +#include "Features/CloudShadows.h" +#include "Features/IBL.h" +#include "Features/LightLimitFix.h" +#include "Features/Skylighting.h" +#include "Features/TerrainShadows.h" #include "I18n/I18n.h" +#include "State.h" +#include "Utils/D3D.h" +#include "Utils/Game.h" #include "WeatherVariableRegistry.h" #define I18N_KEY_PREFIX "feature.exp_height_fog." @@ -21,7 +30,42 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( respectVanillaFogFade, disableVanillaFog, fogInscatteringColor, - originalFogColorAmount) + originalFogColorAmount, + volumetricFogEnabled, + volumetricGridPixelSize, + volumetricGridSizeZ, + volumetricFogDistance, + volumetricFogStartDistance, + volumetricFogNearFadeInDistance, + volumetricFogExtinctionScale, + volumetricFogScatteringDistribution, + volumetricFogAlbedo, + volumetricFogEmissive, + volumetricDirectionalScatteringIntensity, + volumetricShadowBias, + volumetricDepthDistributionScale, + volumetricSkyLightingIntensity, + volumetricHistoryWeight, + volumetricHistoryMissSampleCount, + volumetricSampleJitterMultiplier, + volumetricUpsampleJitterMultiplier, + volumetricLocalLightScatteringIntensity) + +namespace +{ + float Halton(uint32_t a_index, uint32_t a_base) + { + float result = 0.0f; + float invBase = 1.0f / static_cast(a_base); + float fraction = invBase; + while (a_index > 0) { + result += static_cast(a_index % a_base) * fraction; + a_index /= a_base; + fraction *= invBase; + } + return result; + } +} void ExponentialHeightFog::RestoreDefaultSettings() { @@ -67,6 +111,475 @@ void ExponentialHeightFog::DrawSettings() ImGui::Checkbox(T(TKEY("use_dynamic_cubemaps"), "Use Dynamic Cubemaps for Inscattering"), (bool*)&settings.useDynamicCubemaps); Util::WeatherUI::ColorEdit4(T(TKEY("inscattering_cubemap_tint"), "Inscattering Cubemap Tint"), this, "inscatteringTint", (float*)&settings.inscatteringTint); ImGui::SliderFloat(T(TKEY("cubemap_mip_level"), "Cubemap Mip Level"), &settings.cubemapMipLevel, 1.0f, 7.0f, "%.1f"); + + ImGui::SeparatorText(T(TKEY("volumetric_fog"), "Volumetric Fog")); + Util::WeatherUI::Checkbox(T(TKEY("enable_volumetric_fog"), "Enable Volumetric Fog"), this, "volumetricFogEnabled", (bool*)&settings.volumetricFogEnabled); + if (settings.volumetricFogEnabled) { + Util::WeatherUI::SliderFloat(T(TKEY("volumetric_view_distance"), "Volumetric View Distance"), this, "volumetricFogDistance", &settings.volumetricFogDistance, 1000.0f, 200000.0f, "%.0f"); + Util::WeatherUI::SliderFloat(T(TKEY("volumetric_start_distance"), "Volumetric Start Distance"), this, "volumetricFogStartDistance", &settings.volumetricFogStartDistance, 0.0f, 20000.0f, "%.0f"); + Util::WeatherUI::SliderFloat(T(TKEY("near_fade_in_distance"), "Near Fade In Distance"), this, "volumetricFogNearFadeInDistance", &settings.volumetricFogNearFadeInDistance, 0.0f, 20000.0f, "%.0f"); + Util::WeatherUI::SliderFloat(T(TKEY("volumetric_extinction_scale"), "Volumetric Extinction Scale"), this, "volumetricFogExtinctionScale", &settings.volumetricFogExtinctionScale, 0.0f, 10.0f, "%.2f"); + Util::WeatherUI::SliderFloat(T(TKEY("volumetric_scattering_distribution"), "Volumetric Scattering Distribution"), this, "volumetricFogScatteringDistribution", &settings.volumetricFogScatteringDistribution, -0.9f, 0.9f, "%.2f"); + Util::WeatherUI::ColorEdit4(T(TKEY("volumetric_albedo"), "Volumetric Albedo"), this, "volumetricFogAlbedo", (float*)&settings.volumetricFogAlbedo); + Util::WeatherUI::ColorEdit4(T(TKEY("volumetric_emissive"), "Volumetric Emissive"), this, "volumetricFogEmissive", (float*)&settings.volumetricFogEmissive); + Util::WeatherUI::SliderFloat(T(TKEY("directional_scattering_intensity"), "Directional Scattering Intensity"), this, "volumetricDirectionalScatteringIntensity", &settings.volumetricDirectionalScatteringIntensity, 0.0f, 10.0f, "%.2f"); + Util::WeatherUI::SliderFloat(T(TKEY("sky_lighting_scattering_intensity"), "Sky Lighting Scattering Intensity"), this, "volumetricSkyLightingIntensity", &settings.volumetricSkyLightingIntensity, 0.0f, 10.0f, "%.2f"); + Util::WeatherUI::SliderFloat(T(TKEY("local_light_scattering_intensity"), "Local Light Scattering Intensity"), this, "volumetricLocalLightScatteringIntensity", &settings.volumetricLocalLightScatteringIntensity, 0.0f, 10.0f, "%.2f"); + if (ImGui::TreeNode(T(TKEY("debug"), "Debug"))) { + uint32_t minGridPixelSize = 4; + uint32_t maxGridPixelSize = 64; + uint32_t minGridSizeZ = 16; + uint32_t maxGridSizeZ = 160; + ImGui::SliderScalar(T(TKEY("grid_pixel_size"), "Grid Pixel Size"), ImGuiDataType_U32, &settings.volumetricGridPixelSize, &minGridPixelSize, &maxGridPixelSize, "%u", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderScalar(T(TKEY("grid_depth_slices"), "Grid Depth Slices"), ImGuiDataType_U32, &settings.volumetricGridSizeZ, &minGridSizeZ, &maxGridSizeZ, "%u", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T(TKEY("directional_shadow_bias"), "Directional Shadow Bias"), &settings.volumetricShadowBias, 0.0f, 0.05f, "%.4f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T(TKEY("depth_distribution_scale"), "Depth Distribution Scale"), &settings.volumetricDepthDistributionScale, 1.0f, 128.0f, "%.1f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T(TKEY("temporal_history_weight"), "Temporal History Weight"), &settings.volumetricHistoryWeight, 0.0f, 0.99f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + uint32_t minHistoryMissSampleCount = 1; + uint32_t maxHistoryMissSampleCount = 16; + ImGui::SliderScalar(T(TKEY("history_miss_samples"), "History Miss Samples"), ImGuiDataType_U32, &settings.volumetricHistoryMissSampleCount, &minHistoryMissSampleCount, &maxHistoryMissSampleCount, "%u", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat(T(TKEY("sample_jitter_multiplier"), "Sample Jitter Multiplier"), &settings.volumetricSampleJitterMultiplier, 0.0f, 1.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("%s", T(TKEY("sample_jitter_multiplier_tooltip"), + "Matches UE's r.VolumetricFog.LightScatteringSampleJitterMultiplier.\n" + "Adds per-voxel random offset on top of the Halton sequence.\n" + "0 = UE default; nonzero values need stronger temporal filtering.")); + } + ImGui::SliderFloat(T(TKEY("upsample_jitter_multiplier"), "Upsample Jitter Multiplier"), &settings.volumetricUpsampleJitterMultiplier, 0.0f, 1.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("%s", T(TKEY("upsample_jitter_multiplier_tooltip"), + "Matches UE's r.VolumetricFog.UpsampleJitterMultiplier.\n" + "Jitters the final 3D fog lookup in screen space to hide\n" + "low-resolution froxel pixelization. 0 = UE default.")); + } + ImGui::TreePop(); + } + } +} + +void ExponentialHeightFog::SetupResources() +{ + D3D11_SAMPLER_DESC samplerDesc = {}; + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + samplerDesc.MaxAnisotropy = 1; + samplerDesc.MinLOD = 0; + samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; + DX::ThrowIfFailed(globals::d3d::device->CreateSamplerState(&samplerDesc, linearSampler.put())); + Util::SetResourceName(linearSampler.get(), "ExponentialHeightFog::LinearSampler"); + + samplerDesc.Filter = D3D11_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR; + samplerDesc.ComparisonFunc = D3D11_COMPARISON_LESS_EQUAL; + DX::ThrowIfFailed(globals::d3d::device->CreateSamplerState(&samplerDesc, shadowSampler.put())); + Util::SetResourceName(shadowSampler.get(), "ExponentialHeightFog::ShadowSampler"); + + volumetricFogCB = std::make_unique(ConstantBufferDesc(), "ExponentialHeightFog::VolumetricFogCB"); +} + +void ExponentialHeightFog::ClearShaderCache() +{ + if (materialSetupCS) { + materialSetupCS->Release(); + materialSetupCS = nullptr; + } + if (conservativeDepthCS) { + conservativeDepthCS->Release(); + conservativeDepthCS = nullptr; + } + if (lightScatteringCS) { + lightScatteringCS->Release(); + lightScatteringCS = nullptr; + } + if (integrationCS) { + integrationCS->Release(); + integrationCS = nullptr; + } +} + +void ExponentialHeightFog::CaptureDirectionalShadowMap() +{ + ID3D11ShaderResourceView* shadowMap = nullptr; + globals::d3d::context->PSGetShaderResources(4, 1, &shadowMap); + directionalShadowMap.copy_from(shadowMap); + if (shadowMap) + shadowMap->Release(); +} + +void ExponentialHeightFog::EnsureVolumetricResources() +{ + uint32_t pixelSize = std::clamp(settings.volumetricGridPixelSize, 4u, 64u); + const uint32_t gridZ = std::clamp(settings.volumetricGridSizeZ, 16u, 160u); + auto renderSize = Util::ConvertToDynamic(globals::state->screenSize); + + auto getGridSize = [&renderSize, gridZ](uint32_t a_pixelSize) { + return DirectX::XMUINT4{ + std::max(1u, static_cast(std::ceil(renderSize.x / static_cast(a_pixelSize)))), + std::max(1u, static_cast(std::ceil(renderSize.y / static_cast(a_pixelSize)))), + gridZ, + 0u + }; + }; + DirectX::XMUINT4 gridSize = getGridSize(pixelSize); + + constexpr uint64_t maxVolumeVoxels = 16ull * 1024ull * 1024ull; + while (pixelSize < 64u && + static_cast(gridSize.x) * gridSize.y * gridSize.z > maxVolumeVoxels) { + pixelSize++; + gridSize = getGridSize(pixelSize); + } + + if (vBufferA && currentGridSize.x == gridSize.x && currentGridSize.y == gridSize.y && currentGridSize.z == gridSize.z) + return; + + currentGridSize = gridSize; + + D3D11_TEXTURE3D_DESC texDesc{}; + texDesc.Width = gridSize.x; + texDesc.Height = gridSize.y; + texDesc.Depth = gridSize.z; + texDesc.MipLevels = 1; + texDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; + texDesc.Usage = D3D11_USAGE_DEFAULT; + texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; + + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc{}; + srvDesc.Format = texDesc.Format; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D; + srvDesc.Texture3D.MipLevels = 1; + + D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc{}; + uavDesc.Format = texDesc.Format; + uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE3D; + uavDesc.Texture3D.MipSlice = 0; + uavDesc.Texture3D.FirstWSlice = 0; + uavDesc.Texture3D.WSize = gridSize.z; + + vBufferA = std::make_unique(texDesc, "ExponentialHeightFog::VBufferA"); + vBufferA->CreateSRV(srvDesc); + vBufferA->CreateUAV(uavDesc); + + D3D11_TEXTURE2D_DESC conservativeDepthDesc{}; + conservativeDepthDesc.Width = gridSize.x; + conservativeDepthDesc.Height = gridSize.y; + conservativeDepthDesc.MipLevels = 1; + conservativeDepthDesc.ArraySize = 1; + conservativeDepthDesc.Format = DXGI_FORMAT_R32_FLOAT; + conservativeDepthDesc.SampleDesc.Count = 1; + conservativeDepthDesc.Usage = D3D11_USAGE_DEFAULT; + conservativeDepthDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; + + D3D11_SHADER_RESOURCE_VIEW_DESC conservativeDepthSrvDesc{}; + conservativeDepthSrvDesc.Format = conservativeDepthDesc.Format; + conservativeDepthSrvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + conservativeDepthSrvDesc.Texture2D.MipLevels = 1; + + D3D11_UNORDERED_ACCESS_VIEW_DESC conservativeDepthUavDesc{}; + conservativeDepthUavDesc.Format = conservativeDepthDesc.Format; + conservativeDepthUavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D; + + conservativeDepth = std::make_unique(conservativeDepthDesc, "ExponentialHeightFog::ConservativeDepth"); + conservativeDepth->CreateSRV(conservativeDepthSrvDesc); + conservativeDepth->CreateUAV(conservativeDepthUavDesc); + + conservativeDepthHistory = std::make_unique(conservativeDepthDesc, "ExponentialHeightFog::ConservativeDepthHistory"); + conservativeDepthHistory->CreateSRV(conservativeDepthSrvDesc); + + lightScattering = std::make_unique(texDesc, "ExponentialHeightFog::LightScattering"); + lightScattering->CreateSRV(srvDesc); + lightScattering->CreateUAV(uavDesc); + + lightScatteringHistory = std::make_unique(texDesc, "ExponentialHeightFog::LightScatteringHistory"); + lightScatteringHistory->CreateSRV(srvDesc); + + integratedLightScattering = std::make_unique(texDesc, "ExponentialHeightFog::IntegratedLightScattering"); + integratedLightScattering->CreateSRV(srvDesc); + integratedLightScattering->CreateUAV(uavDesc); + + hasLightScatteringHistory = false; + hasConservativeDepthHistory = false; + lastPrepassFrame = UINT32_MAX; +} + +void ExponentialHeightFog::ReleaseVolumetricResources() +{ + vBufferA.reset(); + conservativeDepth.reset(); + conservativeDepthHistory.reset(); + lightScattering.reset(); + lightScatteringHistory.reset(); + integratedLightScattering.reset(); + currentGridSize = {}; + hasLightScatteringHistory = false; + hasConservativeDepthHistory = false; + lastPrepassFrame = UINT32_MAX; + ID3D11ShaderResourceView* nullSRV = nullptr; + globals::d3d::context->PSSetShaderResources(19, 1, &nullSRV); +} + +void ExponentialHeightFog::BindIntegratedLightScattering() +{ + ID3D11ShaderResourceView* srv = integratedLightScattering ? integratedLightScattering->srv.get() : nullptr; + globals::d3d::context->PSSetShaderResources(19, 1, &srv); +} + +ID3D11ComputeShader* ExponentialHeightFog::GetMaterialSetupCS() +{ + if (!materialSetupCS) + materialSetupCS = static_cast(Util::CompileShader(L"Data\\Shaders\\ExponentialHeightFog\\VolumetricFogMaterialCS.hlsl", {}, "cs_5_0")); + return materialSetupCS; +} + +ID3D11ComputeShader* ExponentialHeightFog::GetConservativeDepthCS() +{ + if (!conservativeDepthCS) + conservativeDepthCS = static_cast(Util::CompileShader(L"Data\\Shaders\\ExponentialHeightFog\\VolumetricFogConservativeDepthCS.hlsl", {}, "cs_5_0")); + return conservativeDepthCS; +} + +ID3D11ComputeShader* ExponentialHeightFog::GetLightScatteringCS() +{ + if (!lightScatteringCS) { + std::vector> defines; + if (globals::features::lightLimitFix.loaded) { + defines.emplace_back("LIGHT_LIMIT_FIX", ""); + } + if (globals::features::terrainShadows.loaded) { + defines.emplace_back("TERRAIN_SHADOWS", ""); + } + if (globals::features::cloudShadows.loaded) { + defines.emplace_back("CLOUD_SHADOWS", ""); + } + lightScatteringCS = static_cast(Util::CompileShader(L"Data\\Shaders\\ExponentialHeightFog\\VolumetricFogLightScatteringCS.hlsl", defines, "cs_5_0")); + } + return lightScatteringCS; +} + +ID3D11ComputeShader* ExponentialHeightFog::GetIntegrationCS() +{ + if (!integrationCS) + integrationCS = static_cast(Util::CompileShader(L"Data\\Shaders\\ExponentialHeightFog\\VolumetricFogIntegrationCS.hlsl", {}, "cs_5_0")); + return integrationCS; +} + +void ExponentialHeightFog::Prepass() +{ + if (!settings.enabled || !settings.volumetricFogEnabled || settings.volumetricFogExtinctionScale <= 0.0f) { + ReleaseVolumetricResources(); + return; + } + + EnsureVolumetricResources(); + + if (settings.fogDensity <= 0.0f) { + hasLightScatteringHistory = false; + hasConservativeDepthHistory = false; + lastPrepassFrame = UINT32_MAX; + BindIntegratedLightScattering(); + return; + } + + ID3D11ShaderResourceView* directionalShadowLightData = globals::deferred && globals::deferred->directionalShadowLights ? globals::deferred->directionalShadowLights->srv.get() : nullptr; + auto& lightLimitFix = globals::features::lightLimitFix; + const bool hasLocalLightData = + lightLimitFix.loaded && + lightLimitFix.lights && + lightLimitFix.lightIndexList && + lightLimitFix.lightGrid; + auto* depthSrv = Util::GetCurrentSceneDepthSRV(true); + auto& ibl = globals::features::ibl; + auto& skylighting = globals::features::skylighting; + const bool hasIBL = ibl.loaded && + ibl.settings.EnableIBL != 0 && + !(ibl.settings.DisableInInteriors && Util::IsInterior()) && + ibl.envIBLTexture && + ibl.skyIBLTexture; + const bool hasSkylighting = skylighting.loaded && skylighting.texProbeArray; + + const bool temporalReprojection = Util::GetTemporal(); + const bool temporalHistoryValid = + temporalReprojection && + hasLightScatteringHistory && + lastPrepassFrame != UINT32_MAX && + globals::state->frameCount == lastPrepassFrame + 1u; + + VolumetricFogCB cb{}; + cb.gridSizeAndFlags = { + currentGridSize.x, + currentGridSize.y, + currentGridSize.z, + (directionalShadowMap && directionalShadowLightData ? 1u : 0u) | + (depthSrv ? 2u : 0u) | + (hasIBL ? 4u : 0u) | + (hasSkylighting ? 8u : 0u) | + (depthSrv && temporalHistoryValid && hasConservativeDepthHistory ? 16u : 0u) | + (hasLocalLightData ? 32u : 0u) + }; + cb.invGridSizeAndNearFade = { + 1.0f / static_cast(currentGridSize.x), + 1.0f / static_cast(currentGridSize.y), + 1.0f / static_cast(currentGridSize.z), + settings.volumetricFogNearFadeInDistance > 0.0f ? 1.0f / settings.volumetricFogNearFadeInDistance : 100000000.0f + }; + + const auto cameraData = Util::GetCameraData(); + const double nearPlane = std::max(static_cast(cameraData.y), static_cast(std::max(settings.volumetricFogStartDistance, 0.0f))); + const double farPlane = std::max(nearPlane + 1.0, static_cast(std::max(settings.volumetricFogDistance, settings.volumetricFogStartDistance + 1.0f))); + const double nearWithOffset = nearPlane + 0.095 * 100.0; + const double depthDistributionScale = std::max( + static_cast(settings.volumetricDepthDistributionScale), + static_cast(currentGridSize.z) / 120.0); + const double farExp = std::exp2(std::min(static_cast(currentGridSize.z) / depthDistributionScale, 120.0)); + const double gridZOffset = (farPlane - nearWithOffset * farExp) / (farPlane - nearWithOffset); + const double gridZScale = (1.0 - gridZOffset) / nearWithOffset; + cb.gridZParams = { + static_cast(gridZScale), + static_cast(gridZOffset), + static_cast(depthDistributionScale), + 0.0f + }; + + const uint32_t eyeCount = globals::game::isVR ? 2u : 1u; + for (uint32_t eyeIndex = 0; eyeIndex < eyeCount; eyeIndex++) { + cb.clipToWorld[eyeIndex] = globals::game::frameBufferCached.GetCameraViewProjUnjittered(eyeIndex).Invert(); + } + if (eyeCount == 1u) { + cb.clipToWorld[1] = cb.clipToWorld[0]; + } + + for (uint32_t i = 0; i < std::size(cb.frameJitterOffsets); i++) { + const uint32_t temporalFrame = (globals::state->frameCount - i) & 1023u; + cb.frameJitterOffsets[i] = { + temporalReprojection ? Halton(temporalFrame, 2) : 0.5f, + temporalReprojection ? Halton(temporalFrame, 3) : 0.5f, + temporalReprojection ? Halton(temporalFrame, 5) : 0.5f, + 0.0f + }; + } + cb.historyParameters = { + temporalHistoryValid ? std::clamp(settings.volumetricHistoryWeight, 0.0f, 0.99f) : 0.0f, + static_cast(std::clamp(settings.volumetricHistoryMissSampleCount, 1u, 16u)), + 0.0f, + 0.0f + }; + cb.jitterParameters = { + temporalReprojection ? std::max(settings.volumetricSampleJitterMultiplier, 0.0f) : 0.0f, + static_cast(globals::state->frameCount % 8u), + 0.0f, + 0.0f + }; + volumetricFogCB->Update(cb); + + auto context = globals::d3d::context; + ID3D11Buffer* cbuffers[1]{ volumetricFogCB->CB() }; + context->CSSetConstantBuffers(0, 1, cbuffers); + + ID3D11Buffer* sharedBuffers[2]{ globals::state->sharedDataCB->CB(), globals::state->featureDataCB->CB() }; + context->CSSetConstantBuffers(5, 2, sharedBuffers); + + ID3D11Buffer* frameBuffers[1]{ *globals::game::perFrame.get() }; + context->CSSetConstantBuffers(12, 1, frameBuffers); + + ID3D11SamplerState* samplers[2]{ linearSampler.get(), shadowSampler.get() }; + context->CSSetSamplers(0, 2, samplers); + + const uint32_t groupX = (currentGridSize.x + 7) / 8; + const uint32_t groupY = (currentGridSize.y + 7) / 8; + const uint32_t groupZ = (currentGridSize.z + 3) / 4; + + context->CSSetShaderResources(17, 1, &depthSrv); + ID3D11ShaderResourceView* skylightingSrv = hasSkylighting ? skylighting.texProbeArray->srv.get() : nullptr; + ID3D11ShaderResourceView* iblSrvs[2]{ + hasIBL ? ibl.envIBLTexture->srv.get() : nullptr, + hasIBL ? ibl.skyIBLTexture->srv.get() : nullptr + }; + context->CSSetShaderResources(50, 1, &skylightingSrv); + context->CSSetShaderResources(76, 2, iblSrvs); + + if (depthSrv) { + ID3D11UnorderedAccessView* uavs[1]{ conservativeDepth->uav.get() }; + context->CSSetUnorderedAccessViews(0, 1, uavs, nullptr); + context->CSSetShader(GetConservativeDepthCS(), nullptr, 0); + context->Dispatch(groupX, groupY, 1); + uavs[0] = nullptr; + context->CSSetUnorderedAccessViews(0, 1, uavs, nullptr); + } + + { + ID3D11UnorderedAccessView* uavs[1]{ vBufferA->uav.get() }; + context->CSSetUnorderedAccessViews(0, 1, uavs, nullptr); + context->CSSetShader(GetMaterialSetupCS(), nullptr, 0); + context->Dispatch(groupX, groupY, groupZ); + uavs[0] = nullptr; + context->CSSetUnorderedAccessViews(0, 1, uavs, nullptr); + } + + { + ID3D11ShaderResourceView* srvs[5]{ + vBufferA->srv.get(), + directionalShadowMap.get(), + temporalHistoryValid ? lightScatteringHistory->srv.get() : nullptr, + conservativeDepth->srv.get(), + temporalHistoryValid && hasConservativeDepthHistory ? conservativeDepthHistory->srv.get() : nullptr + }; + ID3D11ShaderResourceView* localLightSrvs[3]{ + hasLocalLightData ? lightLimitFix.lights->srv.get() : nullptr, + hasLocalLightData ? lightLimitFix.lightIndexList->srv.get() : nullptr, + hasLocalLightData ? lightLimitFix.lightGrid->srv.get() : nullptr + }; + ID3D11UnorderedAccessView* uavs[1]{ lightScattering->uav.get() }; + context->CSSetShaderResources(0, 5, srvs); + context->CSSetShaderResources(35, 3, localLightSrvs); + context->CSSetShaderResources(98, 1, &directionalShadowLightData); + context->CSSetUnorderedAccessViews(0, 1, uavs, nullptr); + context->CSSetShader(GetLightScatteringCS(), nullptr, 0); + context->Dispatch(groupX, groupY, groupZ); + uavs[0] = nullptr; + context->CSSetUnorderedAccessViews(0, 1, uavs, nullptr); + } + + { + ID3D11ShaderResourceView* srvs[1]{ lightScattering->srv.get() }; + ID3D11UnorderedAccessView* uavs[1]{ integratedLightScattering->uav.get() }; + context->CSSetShaderResources(0, 1, srvs); + context->CSSetUnorderedAccessViews(0, 1, uavs, nullptr); + context->CSSetShader(GetIntegrationCS(), nullptr, 0); + context->Dispatch(groupX, groupY, 1); + } + + ID3D11ShaderResourceView* nullSrvs[5]{ nullptr, nullptr, nullptr, nullptr, nullptr }; + ID3D11ShaderResourceView* nullDepthSrv[1]{ nullptr }; + ID3D11UnorderedAccessView* nullUav[1]{ nullptr }; + ID3D11SamplerState* nullSamplers[2]{ nullptr, nullptr }; + ID3D11Buffer* nullCb[1]{ nullptr }; + context->CSSetShaderResources(0, 5, nullSrvs); + context->CSSetShaderResources(17, 1, nullDepthSrv); + context->CSSetShaderResources(35, 3, nullSrvs); + context->CSSetShaderResources(50, 1, nullDepthSrv); + context->CSSetShaderResources(76, 2, nullSrvs); + context->CSSetShaderResources(98, 1, nullSrvs); + context->CSSetUnorderedAccessViews(0, 1, nullUav, nullptr); + context->CSSetSamplers(0, 2, nullSamplers); + context->CSSetConstantBuffers(0, 1, nullCb); + context->CSSetShader(nullptr, nullptr, 0); + + if (temporalReprojection) { + context->CopyResource(lightScatteringHistory->resource.get(), lightScattering->resource.get()); + hasLightScatteringHistory = true; + if (depthSrv) { + context->CopyResource(conservativeDepthHistory->resource.get(), conservativeDepth->resource.get()); + hasConservativeDepthHistory = true; + } else { + hasConservativeDepthHistory = false; + } + } else { + hasLightScatteringHistory = false; + hasConservativeDepthHistory = false; + } + + lastPrepassFrame = globals::state->frameCount; + BindIntegratedLightScattering(); } void ExponentialHeightFog::RegisterWeatherVariables() @@ -140,7 +653,7 @@ void ExponentialHeightFog::RegisterWeatherVariables() "directionalInscatteringAnisotropy", "Henyey-Greenstein asymmetry parameter. Positive = forward scattering, 0 = isotropic, negative = back scattering.", &settings.directionalInscatteringAnisotropy, - 0.7f, + 0.2f, -0.99f, 0.99f)); registry->RegisterVariable(std::make_shared( @@ -169,5 +682,93 @@ void ExponentialHeightFog::RegisterWeatherVariables() [](const bool& from, const bool& to, float factor) { return factor > 0.5f ? to : from; })); + + registry->RegisterVariable(std::make_shared>( + "volumetricFogEnabled", + "Enable Volumetric Fog", + "Enables froxel-based volumetric fog for exponential height fog", + (bool*)&settings.volumetricFogEnabled, + false, + [](const bool& from, const bool& to, float factor) { + return factor > 0.5f ? to : from; + })); + + registry->RegisterVariable(std::make_shared( + "Volumetric View Distance", + "volumetricFogDistance", + "Maximum distance covered by exponential height volumetric fog", + &settings.volumetricFogDistance, + 60000.0f, + 1000.0f, 200000.0f)); + + registry->RegisterVariable(std::make_shared( + "Volumetric Start Distance", + "volumetricFogStartDistance", + "Start distance of volumetric fog from the camera", + &settings.volumetricFogStartDistance, + 0.0f, + 0.0f, 200000.0f)); + + registry->RegisterVariable(std::make_shared( + "Volumetric Near Fade In Distance", + "volumetricFogNearFadeInDistance", + "Distance over which volumetric fog fades in near the camera", + &settings.volumetricFogNearFadeInDistance, + 1000.0f, + 0.0f, 20000.0f)); + + registry->RegisterVariable(std::make_shared( + "Volumetric Extinction Scale", + "volumetricFogExtinctionScale", + "Scale applied to volumetric fog extinction", + &settings.volumetricFogExtinctionScale, + 1.0f, + 0.0f, 10.0f)); + + registry->RegisterVariable(std::make_shared( + "Volumetric Scattering Distribution", + "volumetricFogScatteringDistribution", + "Henyey-Greenstein scattering distribution for volumetric fog", + &settings.volumetricFogScatteringDistribution, + 0.2f, + -0.9f, 0.9f)); + + registry->RegisterVariable(std::make_shared( + "Volumetric Directional Scattering Intensity", + "volumetricDirectionalScatteringIntensity", + "Scale applied to volumetric fog directional light scattering", + &settings.volumetricDirectionalScatteringIntensity, + 1.0f, + 0.0f, 10.0f)); + + registry->RegisterVariable(std::make_shared( + "Volumetric Albedo", + "volumetricFogAlbedo", + "Volumetric fog albedo color", + &settings.volumetricFogAlbedo, + float4{ 1.0f, 1.0f, 1.0f, 1.0f })); + + registry->RegisterVariable(std::make_shared( + "Volumetric Emissive", + "volumetricFogEmissive", + "Volumetric fog emissive color", + &settings.volumetricFogEmissive, + float4{ 0.0f, 0.0f, 0.0f, 0.0f })); + + registry->RegisterVariable(std::make_shared( + "Volumetric Sky Lighting Intensity", + "volumetricSkyLightingIntensity", + "Scale applied to volumetric fog sky lighting", + &settings.volumetricSkyLightingIntensity, + 1.0f, + 0.0f, 10.0f)); + + registry->RegisterVariable(std::make_shared( + "Volumetric Local Light Scattering Intensity", + "volumetricLocalLightScatteringIntensity", + "Scale applied to volumetric fog local light scattering", + &settings.volumetricLocalLightScatteringIntensity, + 1.0f, + 0.0f, 100.0f)); } #undef I18N_KEY_PREFIX diff --git a/src/Features/ExponentialHeightFog.h b/src/Features/ExponentialHeightFog.h index f9e41e99cf..b386df78e5 100644 --- a/src/Features/ExponentialHeightFog.h +++ b/src/Features/ExponentialHeightFog.h @@ -1,5 +1,7 @@ #pragma once +#include "Buffer.h" + struct ExponentialHeightFog : Feature { private: @@ -25,31 +27,94 @@ struct ExponentialHeightFog : Feature bool HasShaderDefine(RE::BSShader::Type) override { return true; }; virtual void DrawSettings() override; + virtual void SetupResources() override; + virtual void ClearShaderCache() override; + virtual void Prepass() override; virtual void RestoreDefaultSettings() override; virtual void LoadSettings(json& o_json) override; virtual void SaveSettings(json& o_json) override; void RegisterWeatherVariables() override; + void CaptureDirectionalShadowMap(); - struct alignas(16) Settings + struct Settings { uint enabled = 0; - uint useDynamicCubemaps = 0; + uint useDynamicCubemaps = 1; float startDistance = 0.0f; float fogHeight = 0.0f; float fogHeightFalloff = 0.2f; - float fogDensity = 0.02f; + float fogDensity = 0.005f; float directionalInscatteringMultiplier = 1.0f; - float directionalInscatteringAnisotropy = 0.7f; + float directionalInscatteringAnisotropy = 0.2f; float4 inscatteringTint = { 1.0f, 1.0f, 1.0f, 1.0f }; - float cubemapMipLevel = 3.0f; + float cubemapMipLevel = 7.0f; float sunlightAttenuationAmount = 1.0f; uint respectVanillaFogFade = 0; - uint disableVanillaFog = 0; + uint disableVanillaFog = 1; float4 fogInscatteringColor = { 0.0f, 0.0f, 0.0f, 1.0f }; - float originalFogColorAmount = 1.0f; - float3 pad; + float originalFogColorAmount = 0.0f; + uint volumetricFogEnabled = 0; + uint volumetricGridPixelSize = 16; + uint volumetricGridSizeZ = 64; + float volumetricFogDistance = 60000.0f; + float volumetricFogStartDistance = 0.0f; + float volumetricFogNearFadeInDistance = 1000.0f; + float volumetricFogExtinctionScale = 1.0f; + float4 volumetricFogAlbedo = { 1.0f, 1.0f, 1.0f, 1.0f }; + float4 volumetricFogEmissive = { 0.0f, 0.0f, 0.0f, 0.0f }; + float volumetricDirectionalScatteringIntensity = 1.0f; + float volumetricShadowBias = 0.002f; + float volumetricDepthDistributionScale = 8.0f; + float volumetricSkyLightingIntensity = 1.0f; + float volumetricFogScatteringDistribution = 0.2f; + float volumetricHistoryWeight = 0.96f; + uint volumetricHistoryMissSampleCount = 4; + float volumetricSampleJitterMultiplier = 0.0f; + float volumetricUpsampleJitterMultiplier = 1.0f; + float volumetricLocalLightScatteringIntensity = 1.0f; + float2 pad0; } settings; - static_assert(sizeof(Settings) == sizeof(float4) * 6, "Settings must match HLSL ExponentialHeightFogSettings."); + STATIC_ASSERT_ALIGNAS_16(Settings); + +private: + struct VolumetricFogCB + { + DirectX::XMUINT4 gridSizeAndFlags = {}; + float4 invGridSizeAndNearFade = {}; + float4 gridZParams = {}; + float4x4 clipToWorld[2] = {}; + float4 frameJitterOffsets[16] = {}; + float4 historyParameters = {}; + float4 jitterParameters = {}; // x = LightScatteringSampleJitterMultiplier, y = StateFrameIndexMod8, zw = unused + }; + STATIC_ASSERT_ALIGNAS_16(VolumetricFogCB); + + void EnsureVolumetricResources(); + void ReleaseVolumetricResources(); + void BindIntegratedLightScattering(); + ID3D11ComputeShader* GetMaterialSetupCS(); + ID3D11ComputeShader* GetConservativeDepthCS(); + ID3D11ComputeShader* GetLightScatteringCS(); + ID3D11ComputeShader* GetIntegrationCS(); + + std::unique_ptr vBufferA; + std::unique_ptr conservativeDepth; + std::unique_ptr conservativeDepthHistory; + std::unique_ptr lightScattering; + std::unique_ptr lightScatteringHistory; + std::unique_ptr integratedLightScattering; + std::unique_ptr volumetricFogCB; + winrt::com_ptr linearSampler; + winrt::com_ptr shadowSampler; + winrt::com_ptr directionalShadowMap; + ID3D11ComputeShader* materialSetupCS = nullptr; + ID3D11ComputeShader* conservativeDepthCS = nullptr; + ID3D11ComputeShader* lightScatteringCS = nullptr; + ID3D11ComputeShader* integrationCS = nullptr; + DirectX::XMUINT4 currentGridSize = {}; + bool hasLightScatteringHistory = false; + bool hasConservativeDepthHistory = false; + uint32_t lastPrepassFrame = UINT32_MAX; }; diff --git a/src/State.cpp b/src/State.cpp index 9bc07e59fd..bf50000151 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -8,6 +8,7 @@ #include "FeatureIssues.h" #include "Features/CSEditor.h" #include "Features/CloudShadows.h" +#include "Features/ExponentialHeightFog.h" #include "Features/HDRDisplay.h" #include "Features/InteriorSun.h" #include "Features/PerformanceOverlay.h" @@ -103,6 +104,8 @@ void State::Draw() if (currentPixelDescriptor & static_cast(SIE::ShaderCache::UtilityShaderFlags::RenderShadowmask)) { if (volumetricShadows.loaded) volumetricShadows.CopyShadowLightData(); + if (globals::features::exponentialHeightFog.loaded) + globals::features::exponentialHeightFog.CaptureDirectionalShadowMap(); } } }