From 16ed41802a7ac484d310dc0f5688ac40e8054bfb Mon Sep 17 00:00:00 2001 From: "C. Shen" <76898260+Pentalimbed@users.noreply.github.com> Date: Tue, 12 Mar 2024 01:09:54 +0000 Subject: [PATCH] feat: add raindrop effects, splashes and ripples to rain wetness (#176) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: wetness + rain splotches * feat: ripples * feat: rain minor turbulence * chores: added raindrop settings * fix: use paused game time; def parameter tweak * Update CommonLibSSE-NG * chore: add chaotic ripple strength, correct normal reorient * feat: add rain occlusion * feat: add toggle for all raindrop fx * feat: add splashes strength option * fix: nullptr dereference of proj matrix, duplicate copyresource * chore: shift raindrop UV by normal * fix: regression in wetness fadeout * chore: increased wetness light intensity * chore: cull dynamic cubemaps by distance * feat: wetness + rain splotches * feat: ripples * feat: rain minor turbulence * chores: added raindrop settings * fix: use paused game time; def parameter tweak * chore: add chaotic ripple strength, correct normal reorient * feat: add rain occlusion * feat: add toggle for all raindrop fx * feat: add splashes strength option * fix: nullptr dereference of proj matrix, duplicate copyresource * chore: shift raindrop UV by normal * fix: regression in wetness fadeout * chore: increased wetness light intensity * fix: splash cutoff, camelCase, grammar error, default settings * fix: added fade in / fade out logic for raindrop fx * chore: move density calculation out of shader * chore: reduce SSS range * feat: only use envbrdrf appoximation * chore: SSS depth attenuation curve * chore: cleanup and fix timers * style: 🎨 apply clang-format changes --------- Co-authored-by: doodlum <15017472+doodlum@users.noreply.github.com> Co-authored-by: TheRiverwoodModder <125157333+TheRiverwoodModder@users.noreply.github.com> Co-authored-by: Pentalimbed --- .../DynamicCubemaps/DynamicCubemaps.hlsli | 37 ++-- .../Shaders/DynamicCubemaps/SpbrdfCS.hlsl | 110 ---------- .../DynamicCubemaps/UpdateCubemapCS.hlsl | 2 +- .../SubsurfaceScattering/SeparableSSS.hlsli | 4 +- .../WetnessEffects/WetnessEffects.hlsli | 198 +++++++++++++++--- package/Shaders/Lighting.hlsl | 39 +++- src/Features/DynamicCubemaps.cpp | 57 +---- src/Features/DynamicCubemaps.h | 5 - src/Features/LightLimitFix.cpp | 6 +- src/Features/WetnessEffects.cpp | 188 +++++++++++++++-- src/Features/WetnessEffects.h | 44 ++++ src/State.cpp | 4 +- 12 files changed, 440 insertions(+), 254 deletions(-) delete mode 100644 features/Dynamic Cubemaps/Shaders/DynamicCubemaps/SpbrdfCS.hlsl diff --git a/features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli b/features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli index 521b8243b..f81b3e9c5 100644 --- a/features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli +++ b/features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli @@ -1,13 +1,18 @@ TextureCube specularTexture : register(t64); -Texture2D specularBRDF_LUT : register(t65); -#if !defined(VR) -Texture2D ssrTexture : register(t66); -Texture2D ssrRawTexture : register(t67); -#endif +// https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile +float2 EnvBRDFApprox(float3 F0, float Roughness, float NoV) +{ + const float4 c0 = { -1, -0.0275, -0.572, 0.022 }; + const float4 c1 = { 1, 0.0425, 1.04, -0.04 }; + float4 r = Roughness * c0 + c1; + float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; + float2 AB = float2(-1.04, 1.04) * a004 + r.zw; + return AB; +} #if !defined(WATER) -float3 GetDynamicCubemap(float2 uv, float3 N, float3 V, float roughness, float3 F0, float complexMaterial) +float3 GetDynamicCubemap(float2 uv, float3 N, float3 VN, float3 V, float roughness, float3 F0, float complexMaterial) { float3 R = reflect(-V, N); float NoV = saturate(dot(N, V)); @@ -15,27 +20,15 @@ float3 GetDynamicCubemap(float2 uv, float3 N, float3 V, float roughness, float3 float level = roughness * 9.0; float3 specularIrradiance = specularTexture.SampleLevel(SampColorSampler, R, level); - -# if defined(DYNAMIC_CUBEMAPS) && !defined(VR) - // if (!FrameParams.z && FrameParams.y){ - // float4 ssrBlurred = ssrTexture.SampleLevel(SampColorSampler, uv, 0); - // float4 ssrRaw = ssrRawTexture.SampleLevel(SampColorSampler, uv, 0); - // float4 ssrTexture = lerp(ssrRaw, ssrBlurred, sqrt(roughness)); - // specularIrradiance = sRGB2Lin(lerp(specularIrradiance, ssrTexture.rgb, ssrTexture.a)); - // } else { - specularIrradiance = sRGB2Lin(specularIrradiance); - //} -# else specularIrradiance = sRGB2Lin(specularIrradiance); -# endif F0 = sRGB2Lin(F0); - // Split-sum approximation factors for Cook-Torrance specular BRDF. - float2 specularBRDF = specularBRDF_LUT.SampleLevel(SampColorSampler, float2(NoV, roughness), 0); + float2 specularBRDF = EnvBRDFApprox(F0, roughness, NoV); // Horizon specular occlusion - float horizon = min(1.0 + dot(R, N), 1.0); + // https://marmosetco.tumblr.com/post/81245981087 + float horizon = min(1.0 + dot(R, VN), 1.0); specularIrradiance *= horizon * horizon; // Roughness dependent fresnel @@ -43,6 +36,6 @@ float3 GetDynamicCubemap(float2 uv, float3 N, float3 V, float roughness, float3 float3 Fr = max(1.0.xxx - roughness.xxx, F0) - F0; float3 S = Fr * pow(1.0 - NoV, 5.0); - return lerp(specularIrradiance * F0, specularIrradiance * ((F0 + S) * specularBRDF.x + specularBRDF.y), false); + return lerp(specularIrradiance * F0, specularIrradiance * ((F0 * S) * specularBRDF.x + specularBRDF.y), complexMaterial); } #endif diff --git a/features/Dynamic Cubemaps/Shaders/DynamicCubemaps/SpbrdfCS.hlsl b/features/Dynamic Cubemaps/Shaders/DynamicCubemaps/SpbrdfCS.hlsl deleted file mode 100644 index 8f51d41cb..000000000 --- a/features/Dynamic Cubemaps/Shaders/DynamicCubemaps/SpbrdfCS.hlsl +++ /dev/null @@ -1,110 +0,0 @@ -// Physically Based Rendering -// Copyright (c) 2017-2018 Michał Siejak - -// Pre-integrates Cook-Torrance specular BRDF for varying roughness and viewing directions. -// Results are saved into 2D LUT texture in the form of DFG1 and DFG2 split-sum approximation terms, -// which act as a scale and bias to F0 (Fresnel reflectance at normal incidence) during rendering. - -static const float PI = 3.141592; -static const float TwoPI = 2 * PI; -static const float Epsilon = 0.001; // This program needs larger eps. - -static const uint NumSamples = 2048; -static const float InvNumSamples = 1.0 / float(NumSamples); - -RWTexture2D LUT : register(u0); - -// Compute Van der Corput radical inverse -// See: http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html -float radicalInverse_VdC(uint bits) -{ - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return float(bits) * 2.3283064365386963e-10; // / 0x100000000 -} - -// Sample i-th point from Hammersley point set of NumSamples points total. -float2 sampleHammersley(uint i) -{ - return float2(i * InvNumSamples, radicalInverse_VdC(i)); -} - -// Importance sample GGX normal distribution function for a fixed roughness value. -// This returns normalized half-vector between Li & Lo. -// For derivation see: http://blog.tobias-franke.eu/2014/03/30/notes_on_importance_sampling.html -float3 sampleGGX(float u1, float u2, float roughness) -{ - float alpha = roughness * roughness; - - float cosTheta = sqrt((1.0 - u2) / (1.0 + (alpha * alpha - 1.0) * u2)); - float sinTheta = sqrt(1.0 - cosTheta * cosTheta); // Trig. identity - float phi = TwoPI * u1; - - // Convert to Cartesian upon return. - return float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); -} - -// Single term for separable Schlick-GGX below. -float gaSchlickG1(float cosTheta, float k) -{ - return cosTheta / (cosTheta * (1.0 - k) + k); -} - -// Schlick-GGX approximation of geometric attenuation function using Smith's method (IBL version). -float gaSchlickGGX_IBL(float cosLi, float cosLo, float roughness) -{ - float r = roughness; - float k = (r * r) / 2.0; // Epic suggests using this roughness remapping for IBL lighting. - return gaSchlickG1(cosLi, k) * gaSchlickG1(cosLo, k); -} - -[numthreads(32, 32, 1)] void main(uint2 ThreadID - : SV_DispatchThreadID) { - // Get output LUT dimensions. - float outputWidth, outputHeight; - LUT.GetDimensions(outputWidth, outputHeight); - - // Get integration parameters. - float cosLo = ThreadID.x / outputWidth; - float roughness = ThreadID.y / outputHeight; - - // Make sure viewing angle is non-zero to avoid divisions by zero (and subsequently NaNs). - cosLo = max(cosLo, Epsilon); - - // Derive tangent-space viewing vector from angle to normal (pointing towards +Z in this reference frame). - float3 Lo = float3(sqrt(1.0 - cosLo * cosLo), 0.0, cosLo); - - // We will now pre-integrate Cook-Torrance BRDF for a solid white environment and save results into a 2D LUT. - // DFG1 & DFG2 are terms of split-sum approximation of the reflectance integral. - // For derivation see: "Moving Frostbite to Physically Based Rendering 3.0", SIGGRAPH 2014, section 4.9.2. - float DFG1 = 0; - float DFG2 = 0; - - for (uint i = 0; i < NumSamples; ++i) { - float2 u = sampleHammersley(i); - - // Sample directly in tangent/shading space since we don't care about reference frame as long as it's consistent. - float3 Lh = sampleGGX(u.x, u.y, roughness); - - // Compute incident direction (Li) by reflecting viewing direction (Lo) around half-vector (Lh). - float3 Li = 2.0 * dot(Lo, Lh) * Lh - Lo; - - float cosLi = Li.z; - float cosLh = Lh.z; - float cosLoLh = max(dot(Lo, Lh), 0.0); - - if (cosLi > 0.0) { - float G = gaSchlickGGX_IBL(cosLi, cosLo, roughness); - float Gv = G * cosLoLh / (cosLh * cosLo); - float Fc = pow(1.0 - cosLoLh, 5); - - DFG1 += (1 - Fc) * Gv; - DFG2 += Fc * Gv; - } - } - - LUT[ThreadID] = float2(DFG1, DFG2) * InvNumSamples; -} diff --git a/features/Dynamic Cubemaps/Shaders/DynamicCubemaps/UpdateCubemapCS.hlsl b/features/Dynamic Cubemaps/Shaders/DynamicCubemaps/UpdateCubemapCS.hlsl index 1117deae9..55465ee1f 100644 --- a/features/Dynamic Cubemaps/Shaders/DynamicCubemaps/UpdateCubemapCS.hlsl +++ b/features/Dynamic Cubemaps/Shaders/DynamicCubemaps/UpdateCubemapCS.hlsl @@ -182,7 +182,7 @@ float smoothbumpstep(float edge0, float edge1, float x) position.w = smoothstep(1.0, 4096.0 * 0.001, distance); // Objects which are far away from the perspective of the camera do not fade out - if (depth > 0.999) + if (linearDepth > (4096.0 * 5.0)) position.w = 0; DynamicCubemapPosition[ThreadID] = lerp(DynamicCubemapPosition[ThreadID], position, lerpFactor); diff --git a/features/Subsurface Scattering/Shaders/SubsurfaceScattering/SeparableSSS.hlsli b/features/Subsurface Scattering/Shaders/SubsurfaceScattering/SeparableSSS.hlsli index ee288a576..4f25cd2b2 100644 --- a/features/Subsurface Scattering/Shaders/SubsurfaceScattering/SeparableSSS.hlsli +++ b/features/Subsurface Scattering/Shaders/SubsurfaceScattering/SeparableSSS.hlsli @@ -172,8 +172,8 @@ float4 SSSSBlurCS( depth = GetScreenDepth(depth); // If the difference in depth is huge, we lerp color back to "colorM": - float s = saturate(profile.y * (1.0 / 3.0) * distanceToProjectionWindow * abs(depthM - depth)); - color = lerp(color, colorM.rgb, s); + float s = saturate(profile.y * distanceToProjectionWindow * abs(depthM - depth)); + color = lerp(color, colorM.rgb, s * s); // Accumulate: colorBlurred.rgb += Kernels[i].rgb * color.rgb; diff --git a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli index 38a1855f8..f04329361 100644 --- a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli +++ b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli @@ -2,9 +2,13 @@ struct PerPassWetnessEffects { + float Time; + float Raining; float Wetness; float PuddleWetness; row_major float3x4 DirectionalAmbientWS; + row_major float4x4 PrecipProj; + uint EnableWetnessEffects; float MaxRainWetness; float MaxPuddleWetness; @@ -19,14 +23,34 @@ struct PerPassWetnessEffects float MinRainWetness; float SkinWetness; float WeatherTransitionSpeed; + + uint EnableRaindropFx; + uint EnableSplashes; + uint EnableRipples; + uint EnableChaoticRipples; + float RaindropFxRange; + float RaindropGridSizeRcp; + float RaindropIntervalRcp; + float RaindropChance; + float SplashesStrength; + float SplashesMinRadius; + float SplashesMaxRadius; + float RippleStrength; + float RippleRadius; + float RippleBreadth; + float RippleLifetimeRcp; + float ChaoticRippleStrength; + float ChaoticRippleScaleRcp; + float ChaoticRippleSpeed; }; StructuredBuffer perPassWetnessEffects : register(t22); +Texture2D TexPrecipOcclusion : register(t31); #define LinearSampler SampShadowMaskSampler // https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile -float2 EnvBRDFApprox(float3 F0, float Roughness, float NoV) +float2 EnvBRDFApproxWater(float3 F0, float Roughness, float NoV) { const float4 c0 = { -1, -0.0275, -0.572, 0.022 }; const float4 c1 = { 1, 0.0425, 1.04, -0.04 }; @@ -58,42 +82,166 @@ float noise(float3 pos) u.z); } -float3 GetWetnessAmbientSpecular(float2 uv, float3 N, float3 V, float roughness) +// https://www.pcg-random.org/ +uint pcg(uint v) { -#if defined(DYNAMIC_CUBEMAPS) - float3 R = reflect(-V, N); - float NoV = saturate(dot(N, V)); + uint state = v * 747796405u + 2891336453u; + uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + return (word >> 22u) ^ word; +} - float level = roughness * 9.0; +uint3 pcg3d(uint3 v) +{ + v = v * 1664525u + 1013904223u; - float3 specularIrradiance = specularTexture.SampleLevel(SampColorSampler, R, level); + v.x += v.y * v.z; + v.y += v.z * v.x; + v.z += v.x * v.y; -# if defined(DYNAMIC_CUBEMAPS) && !defined(VR) - // float4 ssrBlurred = ssrTexture.SampleLevel(SampColorSampler, uv, 0); - // float4 ssrRaw = ssrRawTexture.SampleLevel(SampColorSampler, uv, 0); - // float4 ssrTexture = lerp(ssrRaw, ssrBlurred, sqrt(roughness)); - // specularIrradiance = sRGB2Lin(lerp(specularIrradiance, ssrTexture.rgb, ssrTexture.a)); - specularIrradiance = sRGB2Lin(specularIrradiance); -# else - specularIrradiance = sRGB2Lin(specularIrradiance); -# endif + v ^= v >> 16u; -#else + v.x += v.y * v.z; + v.y += v.z * v.x; + v.z += v.x * v.y; + + return v; +} + +uint iqint3(uint2 x) +{ + uint2 q = 1103515245U * ((x >> 1U) ^ (x.yx)); + uint n = 1103515245U * ((q.x) ^ (q.y >> 3U)); + + return n; +} + +float SmoothstepDeriv(float x) +{ + return 6.0 * x * (1. - x); +} + +float RainFade(float normalised_t) +{ + const float rain_stay = .5; + + if (normalised_t < rain_stay) + return 1.0; + + float val = lerp(1.0, 0.0, (normalised_t - rain_stay) / (1.0 - rain_stay)); + return val * val; +} + +// https://blog.selfshadow.com/publications/blending-in-detail/ +// geometric normal s, a base normal t and a secondary (or detail) normal u +float3 ReorientNormal(float3 u, float3 t, float3 s) +{ + // Build the shortest-arc quaternion + float4 q = float4(cross(s, t), dot(s, t) + 1) / sqrt(2 * (dot(s, t) + 1)); + + // Rotate the normal + return u * (q.w * q.w - dot(q.xyz, q.xyz)) + 2 * q.xyz * dot(q.xyz, u) + 2 * q.w * cross(q.xyz, u); +} + +// for when s = (0,0,1) +float3 ReorientNormal(float3 n1, float3 n2) +{ + n1 += float3(0, 0, 1); + n2 *= float3(-1, -1, 1); + + return n1 * dot(n1, n2) / n1.z - n2; +} + +// xyz - ripple normal, w - splotches +float4 GetRainDrops(float3 worldPos, float t, float3 normal) +{ + const static float uintToFloat = rcp(4294967295.0); + const float rippleBreadthRcp = rcp(perPassWetnessEffects[0].RippleBreadth); + + float2 gridUV = worldPos.xy * perPassWetnessEffects[0].RaindropGridSizeRcp; + gridUV += normal.xy * 0.5; + int2 grid = floor(gridUV); + gridUV -= grid; + + float3 rippleNormal = float3(0, 0, 1); + float wetness = 0; + + if (perPassWetnessEffects[0].EnableSplashes || perPassWetnessEffects[0].EnableRipples) + for (int i = -1; i <= 1; i++) + for (int j = -1; j <= 1; j++) { + int2 gridCurr = grid + int2(i, j); + float tOffset = float(iqint3(gridCurr)) * uintToFloat; + + float residual = t * perPassWetnessEffects[0].RaindropIntervalRcp + tOffset + worldPos.z * 0.001; + uint timestep = residual; + residual = residual - timestep; + + uint3 hash = pcg3d(uint3(gridCurr, timestep)); + float3 floatHash = float3(hash) * uintToFloat; + + if (floatHash.z < (perPassWetnessEffects[0].RaindropChance)) { + float2 vec2Centre = int2(i, j) + floatHash.xy - gridUV; + float distSqr = dot(vec2Centre, vec2Centre); + + // splashes + if (perPassWetnessEffects[0].EnableSplashes) { + float drop_radius = lerp(perPassWetnessEffects[0].SplashesMinRadius, perPassWetnessEffects[0].SplashesMaxRadius, + float(iqint3(hash.yz)) * uintToFloat); + if (distSqr < drop_radius * drop_radius) + wetness = max(wetness, RainFade(residual)); + } + + // ripples + if (perPassWetnessEffects[0].EnableRipples) { + float rippleT = residual * perPassWetnessEffects[0].RippleLifetimeRcp; + if (rippleT < 1.) { + float ripple_r = lerp(0., perPassWetnessEffects[0].RippleRadius, rippleT); + float ripple_inner_radius = ripple_r - perPassWetnessEffects[0].RippleBreadth; + + float band_lerp = (sqrt(distSqr) - ripple_inner_radius) * rippleBreadthRcp; + if (band_lerp > 0. && band_lerp < 1.) { + float deriv = (band_lerp < .5 ? SmoothstepDeriv(band_lerp * 2.) : -SmoothstepDeriv(2. - band_lerp * 2.)) * + lerp(perPassWetnessEffects[0].RippleStrength, 0, rippleT * rippleT); + + float3 grad = float3(normalize(vec2Centre), -deriv); + float3 bitangent = float3(-grad.y, grad.x, 0); + float3 normal = normalize(cross(grad, bitangent)); + + rippleNormal = ReorientNormal(normal, rippleNormal); + } + } + } + } + } + + if (perPassWetnessEffects[0].EnableChaoticRipples) { + float3 turbulenceNormal = noise(float3(worldPos.xy * perPassWetnessEffects[0].ChaoticRippleScaleRcp, t * perPassWetnessEffects[0].ChaoticRippleSpeed)); + turbulenceNormal.z = turbulenceNormal.z * .5 + 5; + turbulenceNormal = normalize(turbulenceNormal); + rippleNormal = normalize(rippleNormal + float3(turbulenceNormal.xy * perPassWetnessEffects[0].ChaoticRippleStrength, 0)); + } + + wetness *= perPassWetnessEffects[0].SplashesStrength; + + return float4(rippleNormal, wetness); +} + +float3 GetWetnessAmbientSpecular(float2 uv, float3 N, float3 VN, float3 V, float roughness) +{ float3 R = reflect(-V, N); float NoV = saturate(dot(N, V)); - float3 specularIrradiance = sRGB2Lin(mul(perPassWetnessEffects[0].DirectionalAmbientWS, float4(R, 1.0))) * noise(R * lerp(10.0, 1.0, roughness * roughness)); -#endif - - // Split-sum approximation factors for Cook-Torrance specular BRDF. #if defined(DYNAMIC_CUBEMAPS) - float2 specularBRDF = specularBRDF_LUT.Sample(LinearSampler, float2(NoV, roughness)); + float level = roughness * 9.0; + float3 specularIrradiance = sRGB2Lin(specularTexture.SampleLevel(SampColorSampler, R, level)); #else - float2 specularBRDF = EnvBRDFApprox(0.02, roughness, NoV); + float3 specularIrradiance = sRGB2Lin(mul(perPassWetnessEffects[0].DirectionalAmbientWS, float4(R, 1.0))) * noise(R * lerp(10.0, 1.0, roughness * roughness)); #endif + float2 specularBRDF = EnvBRDFApproxWater(0.02, roughness, NoV); + // Horizon specular occlusion - float horizon = min(1.0 + dot(R, N), 1.0); + // https://marmosetco.tumblr.com/post/81245981087 + float horizon = min(1.0 + dot(R, VN), 1.0); specularIrradiance *= horizon * horizon; // Roughness dependent fresnel @@ -106,6 +254,6 @@ float3 GetWetnessAmbientSpecular(float2 uv, float3 N, float3 V, float roughness) float3 GetWetnessSpecular(float3 N, float3 L, float3 V, float3 lightColor, float roughness) { - lightColor *= 0.01; + lightColor *= 0.1; return LightingFuncGGX_OPT3(N, V, L, roughness, 1.0 - roughness) * lightColor; } diff --git a/package/Shaders/Lighting.hlsl b/package/Shaders/Lighting.hlsl index f8ea36a25..5816e13c9 100644 --- a/package/Shaders/Lighting.hlsl +++ b/package/Shaders/Lighting.hlsl @@ -1585,6 +1585,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace [flatten] if (!input.WorldSpace) worldSpaceVertexNormal = normalize(mul(input.World[eyeIndex], float4(worldSpaceVertexNormal, 0))); # endif +# else + float3 worldSpaceVertexNormal = worldSpaceNormal; # endif float porosity = 1.0; @@ -1608,7 +1610,27 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace float minWetnessAngle = 0; minWetnessAngle = saturate(max(minWetnessValue, worldSpaceNormal.z)); + bool raindropOccluded = false; + + float4 raindropInfo = float4(0, 0, 1, 0); + if (perPassWetnessEffects[0].Raining > 0.0f && perPassWetnessEffects[0].EnableRaindropFx && + (dot(input.WorldPosition, input.WorldPosition) < perPassWetnessEffects[0].RaindropFxRange * perPassWetnessEffects[0].RaindropFxRange)) { + float4 precipPositionCS = float4(2 * float2(DynamicResolutionParams2.x * screenUV.x, -screenUV.y * DynamicResolutionParams2.y + 1) - 1, input.Position.z, 1); + float4 precipPositionMS = mul(CameraViewProjInverse[0], precipPositionCS); + precipPositionMS.xyz = precipPositionMS.xyz / precipPositionMS.w; + + float4 precipOcclusionTexCoord = mul(perPassWetnessEffects[0].PrecipProj, float4(precipPositionMS.xyz, 1)); + precipOcclusionTexCoord.y = -precipOcclusionTexCoord.y; + float2 precipOcclusionUv = precipOcclusionTexCoord.xy * 0.5.xx + 0.5.xx; + float precipOcclusionZ = TexPrecipOcclusion.SampleLevel(SampShadowMaskSampler, precipOcclusionUv, 0).x; + + if (precipOcclusionTexCoord.z < precipOcclusionZ + 1e-2) + raindropInfo = GetRainDrops((input.WorldPosition + CameraPosAdjust[0]).xyz, perPassWetnessEffects[0].Time, worldSpaceNormal); + } + float rainWetness = perPassWetnessEffects[0].Wetness * minWetnessAngle * perPassWetnessEffects[0].MaxRainWetness; + rainWetness = max(rainWetness, raindropInfo.w); + float puddleWetness = perPassWetnessEffects[0].PuddleWetness * minWetnessAngle; # if defined(SKIN) rainWetness = perPassWetnessEffects[0].SkinWetness * perPassWetnessEffects[0].Wetness; @@ -1618,7 +1640,6 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace # endif wetness = max(shoreFactor * perPassWetnessEffects[0].MaxShoreWetness, rainWetness); - wetness *= nearFactor; float3 wetnessNormal = worldSpaceNormal; @@ -1636,6 +1657,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace } } + puddle *= nearFactor; + float3 wetnessSpecular = 0.0; float wetnessGlossinessAlbedo = max(puddle, shoreFactorAlbedo * perPassWetnessEffects[0].MaxShoreWetness); @@ -1650,13 +1673,13 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace wetnessNormal = normalize(lerp(wetnessNormal, float3(0, 0, 1), saturate(flatnessAmount))); + float3 rippleNormal = normalize(lerp(float3(0, 0, 1), raindropInfo.xyz, clamp(flatnessAmount, .2, 1))); + wetnessNormal = ReorientNormal(rippleNormal, wetnessNormal); + float waterRoughnessSpecular = 1.0 - wetnessGlossinessSpecular; -# if defined(WETNESS_EFFECTS) if (waterRoughnessSpecular < 1.0) wetnessSpecular += GetWetnessSpecular(wetnessNormal, normalizedDirLightDirectionWS, worldSpaceViewDirection, sRGB2Lin(dirLightColor) * perPassWetnessEffects[0].MaxDALCSpecular, waterRoughnessSpecular); -# endif - # endif # if defined(LIGHT_LIMIT_FIX) @@ -1862,7 +1885,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace # if defined(DYNAMIC_CUBEMAPS) # if defined(EYE) bool dynamicCubemap = true; - envColor = GetDynamicCubemap(screenUV, worldSpaceNormal, worldSpaceViewDirection, 1.0 / 9.0, 0.25, true) * envMask; + envColor = GetDynamicCubemap(screenUV, worldSpaceNormal, worldSpaceVertexNormal, worldSpaceViewDirection, 1.0 / 9.0, 0.25, true) * envMask; # else uint2 envSize; TexEnvSampler.GetDimensions(envSize.x, envSize.y); @@ -1871,10 +1894,10 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace # if defined(CPM_AVAILABLE) && defined(ENVMAP) float3 F0 = lerp(envColorBase, 1.0, envColorBase.x == 0.0 && envColorBase.y == 0.0 && envColorBase.z == 0.0); - envColor = GetDynamicCubemap(screenUV, worldSpaceNormal, worldSpaceViewDirection, lerp(1.0 / 9.0, 1.0 - complexMaterialColor.y, complexMaterial), lerp(F0, complexSpecular, complexMaterial), complexMaterial) * envMask; + envColor = GetDynamicCubemap(screenUV, worldSpaceNormal, worldSpaceVertexNormal, worldSpaceViewDirection, lerp(1.0 / 9.0, 1.0 - complexMaterialColor.y, complexMaterial), lerp(F0, complexSpecular, complexMaterial), complexMaterial) * envMask; # else float3 F0 = lerp(envColorBase, 1.0, envColorBase.x == 0.0 && envColorBase.y == 0.0 && envColorBase.z == 0.0); - envColor = GetDynamicCubemap(screenUV, worldSpaceNormal, worldSpaceViewDirection, 1.0 / 9.0, F0, 0.0) * envMask; + envColor = GetDynamicCubemap(screenUV, worldSpaceNormal, worldSpaceVertexNormal, worldSpaceViewDirection, 1.0 / 9.0, F0, 0.0) * envMask; # endif if (shaderDescriptors[0].PixelShaderDescriptor & _DefShadow && shaderDescriptors[0].PixelShaderDescriptor & _ShadowDir) { float upAngle = saturate(dot(float3(0, 0, 1), normalizedDirLightDirectionWS.xyz)); @@ -1904,7 +1927,7 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace baseColor.xyz = lerp(baseColor.xyz, pow(baseColor.xyz, 1.0 + wetnessDarkeningAmount), 0.8); # endif if (waterRoughnessSpecular < 1.0) - wetnessSpecular += GetWetnessAmbientSpecular(screenUV, wetnessNormal, worldSpaceViewDirection, 1.0 - wetnessGlossinessSpecular) * perPassWetnessEffects[0].MaxAmbientSpecular; + wetnessSpecular += GetWetnessAmbientSpecular(screenUV, wetnessNormal, worldSpaceVertexNormal, worldSpaceViewDirection, 1.0 - wetnessGlossinessSpecular) * perPassWetnessEffects[0].MaxAmbientSpecular; # endif float4 color; diff --git a/src/Features/DynamicCubemaps.cpp b/src/Features/DynamicCubemaps.cpp index 645324d60..8b819ba4e 100644 --- a/src/Features/DynamicCubemaps.cpp +++ b/src/Features/DynamicCubemaps.cpp @@ -333,22 +333,8 @@ void DynamicCubemaps::Draw(const RE::BSShader* shader, const uint32_t) auto renderer = RE::BSGraphics::Renderer::GetSingleton(); auto context = renderer->GetRuntimeData().context; - if (REL::Module::IsVR()) { - ID3D11ShaderResourceView* views[2]{}; - views[0] = envTexture->srv.get(); - views[1] = spBRDFLUT->srv.get(); - context->PSSetShaderResources(64, 2, views); - } else { - auto ssrBlurred = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kSSR]; - auto ssrRaw = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kSSR_RAW]; - - ID3D11ShaderResourceView* views[4]{}; - views[0] = envTexture->srv.get(); - views[1] = spBRDFLUT->srv.get(); - views[2] = ssrBlurred.SRV; - views[3] = ssrRaw.SRV; - context->PSSetShaderResources(64, 4, views); - } + ID3D11ShaderResourceView* view = envTexture->srv.get(); + context->PSSetShaderResources(64, 1, &view); } } } @@ -361,7 +347,6 @@ void DynamicCubemaps::SetupResources() GetComputeShaderSpecularIrradiance(); auto renderer = RE::BSGraphics::Renderer::GetSingleton(); - auto context = renderer->GetRuntimeData().context; auto device = renderer->GetRuntimeData().forwarder; { @@ -376,44 +361,6 @@ void DynamicCubemaps::SetupResources() DX::ThrowIfFailed(device->CreateSamplerState(&samplerDesc, &computeSampler)); } - { - spBRDFProgram = (ID3D11ComputeShader*)Util::CompileShader(L"Data\\Shaders\\DynamicCubemaps\\SpbrdfCS.hlsl", {}, "cs_5_0"); - - D3D11_TEXTURE2D_DESC texDesc{}; - texDesc.Width = 256; - texDesc.Height = 256; - texDesc.MipLevels = 1; - texDesc.ArraySize = 1; - texDesc.Format = DXGI_FORMAT_R16G16_FLOAT; - texDesc.SampleDesc.Count = 1; - texDesc.Usage = D3D11_USAGE_DEFAULT; - texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS; - spBRDFLUT = new Texture2D(texDesc); - - D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; - uavDesc.Format = texDesc.Format; - uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D; - uavDesc.Texture2D.MipSlice = 0; - spBRDFLUT->CreateUAV(uavDesc); - - D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; - srvDesc.Format = texDesc.Format; - srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MostDetailedMip = 0; - srvDesc.Texture2D.MipLevels = 1; - spBRDFLUT->CreateSRV(srvDesc); - - { - // Compute Cook-Torrance BRDF 2D LUT for split-sum approximation. - auto uav = spBRDFLUT->uav.get(); - context->CSSetUnorderedAccessViews(0, 1, &uav, nullptr); - context->CSSetShader(spBRDFProgram, nullptr, 0); - context->Dispatch(spBRDFLUT->desc.Width / 32, spBRDFLUT->desc.Height / 32, 1); - uav = nullptr; - context->CSSetUnorderedAccessViews(0, 1, &uav, nullptr); - } - } - auto& cubemap = renderer->GetRendererData().cubemapRenderTargets[RE::RENDER_TARGETS_CUBEMAP::kREFLECTIONS]; { diff --git a/src/Features/DynamicCubemaps.h b/src/Features/DynamicCubemaps.h index 3466e1b25..f40a2711e 100644 --- a/src/Features/DynamicCubemaps.h +++ b/src/Features/DynamicCubemaps.h @@ -36,11 +36,6 @@ struct DynamicCubemaps : Feature Texture2D* envTexture = nullptr; winrt::com_ptr uavArray[9]; - // BRDF 2D LUT - - ID3D11ComputeShader* spBRDFProgram = nullptr; - Texture2D* spBRDFLUT = nullptr; - // Reflection capture struct alignas(16) UpdateCubemapCB diff --git a/src/Features/LightLimitFix.cpp b/src/Features/LightLimitFix.cpp index ca2796458..ca9196a6a 100644 --- a/src/Features/LightLimitFix.cpp +++ b/src/Features/LightLimitFix.cpp @@ -773,10 +773,6 @@ void LightLimitFix::UpdateLights() eastl::vector lightsData{}; lightsData.reserve(MAX_LIGHTS); - static float* deltaTime = (float*)REL::RelocationID(523660, 410199).address(); - static double timer = 0; - timer += *deltaTime; - // Process point lights for (auto& e : shadowSceneNode->GetRuntimeData().activePointLights) { @@ -912,7 +908,7 @@ void LightLimitFix::UpdateLights() SetLightPosition(light, position); // Light is complete for both eyes by now - AddCachedParticleLights(lightsData, light, &particleLight.second.config, particleLight.first, timer); + AddCachedParticleLights(lightsData, light, &particleLight.second.config, particleLight.first, State::GetSingleton()->timer); } } diff --git a/src/Features/WetnessEffects.cpp b/src/Features/WetnessEffects.cpp index 628dafbcb..65d4a9537 100644 --- a/src/Features/WetnessEffects.cpp +++ b/src/Features/WetnessEffects.cpp @@ -1,6 +1,6 @@ #include "WetnessEffects.h" -#include +#include "Util.h" const float MIN_START_PERCENTAGE = 0.05f; const float DEFAULT_TRANSITION_PERCENTAGE = 1.0f; @@ -21,6 +21,9 @@ const float SECONDS_IN_A_DAY = 86400; const float MAX_TIME_DELTA = SECONDS_IN_A_DAY - 30; const float MIN_WEATHER_TRANSITION_SPEED = 0.0f; const float MAX_WEATHER_TRANSITION_SPEED = 500.0f; +const float AVERAGE_RAIN_VOLUME = 4000.0f; +const float MIN_RAINDROP_CHANCE_MULTIPLIER = 0.1f; +const float MAX_RAINDROP_CHANCE_MULTIPLIER = 2.0f; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( WetnessEffects::Settings, @@ -37,7 +40,25 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( PuddleMinWetness, MinRainWetness, SkinWetness, - WeatherTransitionSpeed) + WeatherTransitionSpeed, + EnableRaindropFx, + EnableSplashes, + EnableRipples, + EnableChaoticRipples, + RaindropFxRange, + RaindropGridSize, + RaindropInterval, + RaindropChance, + SplashesStrength, + SplashesMinRadius, + SplashesMaxRadius, + RippleStrength, + RippleRadius, + RippleBreadth, + RippleLifetime, + ChaoticRippleStrength, + ChaoticRippleScale, + ChaoticRippleSpeed) void WetnessEffects::DrawSettings() { @@ -56,6 +77,73 @@ void WetnessEffects::DrawSettings() ImGui::Spacing(); ImGui::Spacing(); + if (ImGui::TreeNodeEx("Raindrop Effects", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Checkbox("Enable Raindrop Effects", (bool*)&settings.EnableRaindropFx); + + ImGui::BeginDisabled(!settings.EnableRaindropFx); + + ImGui::Checkbox("Enable Splashes", (bool*)&settings.EnableSplashes); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Enables small splashes of wetness on dry surfaces."); + ImGui::Checkbox("Enable Ripples", (bool*)&settings.EnableRipples); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Enables circular ripples on puddles, and to a less extent other wet surfaces"); + ImGui::Checkbox("Enable Chaotic Ripples", (bool*)&settings.EnableChaoticRipples); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Enables an additional layer of disturbance to wet surfaces."); + + ImGui::SliderFloat("Effect Range", &settings.RaindropFxRange, 1e2f, 2e3f, "%.0f game unit(s)"); + + if (ImGui::TreeNodeEx("Raindrops")) { + ImGui::BulletText( + "At every interval, a raindrop is placed within each grid cell.\n" + "Only a set portion of raindrops will actually trigger splashes and ripples.\n" + "Chaotic ripples are not affected by raindrop settings."); + + ImGui::SliderFloat("Grid Size", &settings.RaindropGridSize, 1.f, 10.f, "%.1f game unit(s)"); + ImGui::SliderFloat("Interval", &settings.RaindropInterval, 0.0f, 2.f, "%.1f sec"); + ImGui::SliderFloat("Chance", &settings.RaindropChance, 0.0f, 1.f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("Portion of raindrops that will actually cause splashes and ripples."); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Splashes")) { + ImGui::SliderFloat("Strength", &settings.SplashesStrength, 0.f, 2.f, "%.2f"); + ImGui::SliderFloat("Min Radius", &settings.SplashesMinRadius, 0.f, 1.f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("As portion of grid size."); + ImGui::SliderFloat("Max Radius", &settings.SplashesMaxRadius, 0.f, 1.f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("As portion of grid size."); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Ripples")) { + ImGui::SliderFloat("Strength", &settings.RippleStrength, 0.f, 2.f, "%.2f"); + ImGui::SliderFloat("Radius", &settings.RippleRadius, 0.f, 1.f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + if (auto _tt = Util::HoverTooltipWrapper()) + ImGui::Text("As portion of grid size."); + ImGui::SliderFloat("Breadth", &settings.RippleBreadth, 0.f, 1.f, "%.2f"); + ImGui::SliderFloat("Lifetime", &settings.RippleLifetime, 0.f, settings.RaindropInterval, "%.2f sec", ImGuiSliderFlags_AlwaysClamp); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Chaotic Ripples")) { + ImGui::SliderFloat("Strength", &settings.ChaoticRippleStrength, 0.f, .5f, "%.2f"); + ImGui::SliderFloat("Scale", &settings.ChaoticRippleScale, 0.1f, 5.f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat("Speed", &settings.ChaoticRippleSpeed, 0.f, 50.f, "%.1f"); + ImGui::TreePop(); + } + + ImGui::EndDisabled(); + + ImGui::TreePop(); + } + + ImGui::Spacing(); + ImGui::Spacing(); + if (ImGui::TreeNodeEx("Advanced", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::SliderFloat("Weather transition speed", &settings.WeatherTransitionSpeed, 0.5f, 5.0f); if (auto _tt = Util::HoverTooltipWrapper()) { @@ -141,7 +229,7 @@ float WetnessEffects::CalculateWeatherTransitionPercentage(float skyCurrentWeath // Correct if beginFade is zero or negative beginFade = beginFade > 0 ? beginFade : beginFade + TRANSITION_DENOMINATOR; // Wait to start transition until precipitation begins/ends - float startPercentage = (TRANSITION_DENOMINATOR - beginFade) * (1.0f / TRANSITION_DENOMINATOR); + float startPercentage = 1 - ((TRANSITION_DENOMINATOR - beginFade) * (1.0f / TRANSITION_DENOMINATOR)); if (fadeIn) { float currentPercentage = (skyCurrentWeatherPct - startPercentage) / (1 - startPercentage); @@ -190,11 +278,19 @@ void WetnessEffects::Draw(const RE::BSShader* shader, const uint32_t) currentWeatherID = 0; uint32_t previousLastWeatherID = lastWeatherID; lastWeatherID = 0; + float currentWeatherRaining = 0.0f; + float lastWeatherRaining = 0.0f; + float weatherTransitionPercentage = 0.0f; if (settings.EnableWetnessEffects) { if (auto sky = RE::Sky::GetSingleton()) { if (sky->mode.get() == RE::Sky::Mode::kFull) { if (auto currentWeather = sky->currentWeather) { + if (currentWeather->precipitationData && currentWeather->data.flags.any(RE::TESWeather::WeatherDataFlag::kRainy)) { + float rainDensity = currentWeather->precipitationData->data[static_cast(RE::BGSShaderParticleGeometryData::DataID::kParticleDensity)].f; + float rainGravity = currentWeather->precipitationData->data[static_cast(RE::BGSShaderParticleGeometryData::DataID::kGravityVelocity)].f; + currentWeatherRaining = std::clamp(((rainDensity * rainGravity) / AVERAGE_RAIN_VOLUME), MIN_RAINDROP_CHANCE_MULTIPLIER, MAX_RAINDROP_CHANCE_MULTIPLIER); + } currentWeatherID = currentWeather->GetFormID(); if (auto calendar = RE::Calendar::GetSingleton()) { float currentWeatherWetnessDepth = wetnessDepth; @@ -215,7 +311,7 @@ void WetnessEffects::Draw(const RE::BSShader* shader, const uint32_t) } if (seconds > 0 || (seconds < 0 && (wetnessDepth > 0 || puddleDepth > 0))) { - float weatherTransitionPercentage = DEFAULT_TRANSITION_PERCENTAGE; + weatherTransitionPercentage = DEFAULT_TRANSITION_PERCENTAGE; float lastWeatherWetnessDepth = wetnessDepth; float lastWeatherPuddleDepth = puddleDepth; seconds *= std::clamp(settings.WeatherTransitionSpeed, MIN_WEATHER_TRANSITION_SPEED, MAX_WEATHER_TRANSITION_SPEED); @@ -226,6 +322,9 @@ void WetnessEffects::Draw(const RE::BSShader* shader, const uint32_t) CalculateWetness(lastWeather, sky, seconds, lastWeatherWetnessDepth, lastWeatherPuddleDepth); // If it was raining, wait to transition until precipitation ends, otherwise use the current weather's fade in if (lastWeather->data.flags.any(RE::TESWeather::WeatherDataFlag::kRainy)) { + float rainDensity = lastWeather->precipitationData->data[static_cast(RE::BGSShaderParticleGeometryData::DataID::kParticleDensity)].f; + float rainGravity = lastWeather->precipitationData->data[static_cast(RE::BGSShaderParticleGeometryData::DataID::kGravityVelocity)].f; + lastWeatherRaining = std::clamp(((rainDensity * rainGravity) / AVERAGE_RAIN_VOLUME), MIN_RAINDROP_CHANCE_MULTIPLIER, MAX_RAINDROP_CHANCE_MULTIPLIER); weatherTransitionPercentage = CalculateWeatherTransitionPercentage(sky->currentWeatherPct, lastWeather->data.precipitationEndFadeOut, false); } else { weatherTransitionPercentage = CalculateWeatherTransitionPercentage(sky->currentWeatherPct, currentWeather->data.precipitationBeginFadeIn, true); @@ -242,6 +341,7 @@ void WetnessEffects::Draw(const RE::BSShader* shader, const uint32_t) // Calculate the wetness value from the water depth data.Wetness = std::min(wetnessDepth, MAX_WETNESS); data.PuddleWetness = std::min(puddleDepth, MAX_PUDDLE_WETNESS); + data.Raining = std::lerp(lastWeatherRaining, currentWeatherRaining, weatherTransitionPercentage); } } } @@ -252,9 +352,23 @@ void WetnessEffects::Draw(const RE::BSShader* shader, const uint32_t) RE::NiTransform& dalcTransform = state.directionalAmbientTransform; Util::StoreTransform3x4NoScale(data.DirectionalAmbientWS, dalcTransform); + data.PrecipProj = precipProj; + + static size_t rainTimer = 0; // size_t for precision + if (!RE::UI::GetSingleton()->GameIsPaused()) // from lightlimitfix + rainTimer += (size_t)(RE::GetSecondsSinceLastFrame() * 1000); // BSTimer::delta is always 0 for some reason + data.Time = rainTimer / 1000.f; + data.settings = settings; // Disable Shore Wetness if Wetness Effects are Disabled data.settings.MaxShoreWetness = settings.EnableWetnessEffects ? settings.MaxShoreWetness : 0.0f; + // calculating some parameters on cpu + data.settings.RaindropChance *= data.Raining; + data.settings.RaindropGridSize = 1.f / settings.RaindropGridSize; + data.settings.RaindropInterval = 1.f / settings.RaindropInterval; + data.settings.RippleLifetime = settings.RaindropInterval / settings.RippleLifetime; + data.settings.ChaoticRippleStrength *= std::clamp(data.Raining, 0.f, 1.f); + data.settings.ChaoticRippleScale = 1.f / settings.ChaoticRippleScale; D3D11_MAPPED_SUBRESOURCE mapped; DX::ThrowIfFailed(context->Map(perPass->resource.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped)); @@ -265,26 +379,45 @@ void WetnessEffects::Draw(const RE::BSShader* shader, const uint32_t) ID3D11ShaderResourceView* views[1]{}; views[0] = perPass->srv.get(); context->PSSetShaderResources(22, ARRAYSIZE(views), views); + + views[0] = precipOcclusionTex->srv.get(); + context->PSSetShaderResources(31, ARRAYSIZE(views), views); } } void WetnessEffects::SetupResources() { - D3D11_BUFFER_DESC sbDesc{}; - sbDesc.Usage = D3D11_USAGE_DYNAMIC; - sbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - sbDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - sbDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; - sbDesc.StructureByteStride = sizeof(PerPass); - sbDesc.ByteWidth = sizeof(PerPass); - perPass = std::make_unique(sbDesc); - - D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; - srvDesc.Format = DXGI_FORMAT_UNKNOWN; - srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER; - srvDesc.Buffer.FirstElement = 0; - srvDesc.Buffer.NumElements = 1; - perPass->CreateSRV(srvDesc); + { + D3D11_BUFFER_DESC sbDesc{}; + sbDesc.Usage = D3D11_USAGE_DYNAMIC; + sbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + sbDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + sbDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; + sbDesc.StructureByteStride = sizeof(PerPass); + sbDesc.ByteWidth = sizeof(PerPass); + perPass = std::make_unique(sbDesc); + + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; + srvDesc.Format = DXGI_FORMAT_UNKNOWN; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER; + srvDesc.Buffer.FirstElement = 0; + srvDesc.Buffer.NumElements = 1; + perPass->CreateSRV(srvDesc); + } + + { + auto renderer = RE::BSGraphics::Renderer::GetSingleton(); + + auto precipation = renderer->GetDepthStencilData().depthStencils[RE::RENDER_TARGETS_DEPTHSTENCIL::kPRECIPITATION_OCCLUSION_MAP]; + D3D11_TEXTURE2D_DESC texDesc{}; + precipation.texture->GetDesc(&texDesc); + + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; + precipation.depthSRV->GetDesc(&srvDesc); + + precipOcclusionTex = std::make_unique(texDesc); + precipOcclusionTex->CreateSRV(srvDesc); + } } void WetnessEffects::Reset() @@ -309,3 +442,20 @@ void WetnessEffects::RestoreDefaultSettings() { settings = {}; } + +void WetnessEffects::Hooks::BSParticleShader_SetupGeometry::thunk(RE::BSShader* This, RE::BSRenderPass* Pass, uint32_t RenderFlags) +{ + func(This, Pass, RenderFlags); + + if (auto particleShaderProperty = netimmerse_cast(Pass->shaderProperty)) + if (auto cube = skyrim_cast(particleShaderProperty->particleEmitter)) + GetSingleton()->precipProj = cube->occlusionProjection; + + static Util::FrameChecker frameChecker; + if (frameChecker.isNewFrame()) { + auto renderer = RE::BSGraphics::Renderer::GetSingleton(); + auto context = renderer->GetRuntimeData().context; + auto precipation = renderer->GetDepthStencilData().depthStencils[RE::RENDER_TARGETS_DEPTHSTENCIL::kPRECIPITATION_OCCLUSION_MAP]; + context->CopyResource(GetSingleton()->precipOcclusionTex->resource.get(), precipation.texture); + } +} diff --git a/src/Features/WetnessEffects.h b/src/Features/WetnessEffects.h index 1eaa49760..653b68fc9 100644 --- a/src/Features/WetnessEffects.h +++ b/src/Features/WetnessEffects.h @@ -34,26 +34,54 @@ struct WetnessEffects : Feature float MinRainWetness = 0.65f; float SkinWetness = 0.825f; float WeatherTransitionSpeed = 3.0f; + + // Raindrop fx settings + uint EnableRaindropFx = true; + uint EnableSplashes = true; + uint EnableRipples = true; + uint EnableChaoticRipples = true; + float RaindropFxRange = 1000.f; + float RaindropGridSize = 4.f; + float RaindropInterval = .5f; + float RaindropChance = .3f; + float SplashesStrength = 1.2f; + float SplashesMinRadius = .3f; + float SplashesMaxRadius = .5f; + float RippleStrength = 1.f; + float RippleRadius = 1.f; + float RippleBreadth = .5f; + float RippleLifetime = .1f; + float ChaoticRippleStrength = .1f; + float ChaoticRippleScale = 1.f; + float ChaoticRippleSpeed = 20.f; }; struct alignas(16) PerPass { + float Time; + float Raining; float Wetness; float PuddleWetness; DirectX::XMFLOAT3X4 DirectionalAmbientWS; + RE::DirectX::XMFLOAT4X4 PrecipProj; Settings settings; + + float pad[4 - (sizeof(Settings) / 4 + 16) % 4]; }; Settings settings; std::unique_ptr perPass = nullptr; + std::unique_ptr precipOcclusionTex = nullptr; + bool requiresUpdate = true; float wetnessDepth = 0.0f; float puddleDepth = 0.0f; float lastGameTimeValue = 0.0f; uint32_t currentWeatherID = 0; uint32_t lastWeatherID = 0; + RE::DirectX::XMFLOAT4X4 precipProj; virtual void SetupResources(); virtual void Reset(); @@ -68,4 +96,20 @@ struct WetnessEffects : Feature virtual void RestoreDefaultSettings(); float CalculateWeatherTransitionPercentage(float skyCurrentWeatherPct, float beginFade, bool fadeIn); void CalculateWetness(RE::TESWeather* weather, RE::Sky* sky, float seconds, float& wetness, float& puddleWetness); + + virtual inline void PostPostLoad() override { Hooks::Install(); } + + struct Hooks + { + struct BSParticleShader_SetupGeometry + { + static void thunk(RE::BSShader* This, RE::BSRenderPass* Pass, uint32_t RenderFlags); + static inline REL::Relocation func; + }; + + static void Install() + { + stl::write_vfunc<0x6, BSParticleShader_SetupGeometry>(RE::VTABLE_BSParticleShader[0]); + } + }; }; diff --git a/src/State.cpp b/src/State.cpp index d1573e7dc..2e40af3ba 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -166,8 +166,8 @@ void State::Reset() if (feature->loaded) feature->Reset(); Bindings::GetSingleton()->Reset(); - static float* deltaTime = (float*)REL::RelocationID(523660, 410199).address(); - timer += *deltaTime; + if (!RE::UI::GetSingleton()->GameIsPaused()) + timer += RE::GetSecondsSinceLastFrame(); } void State::Setup()