From 66a0a97ca776e2a4b93e8e430eadce6199010c81 Mon Sep 17 00:00:00 2001 From: TheRiverwoodModder <125157333+TheRiverwoodModder@users.noreply.github.com> Date: Sun, 29 Sep 2024 18:50:28 -0700 Subject: [PATCH 01/18] feat: add raindrop ripples on water --- .../WetnessEffects/WetnessEffects.hlsli | 6 +-- .../textures/water/waterrainripples.dds | Bin 0 -> 1540 bytes package/Shaders/Water.hlsl | 37 +++++++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 features/Wetness Effects/textures/water/waterrainripples.dds diff --git a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli index 7e8e177036..e68bf1a17f 100644 --- a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli +++ b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli @@ -52,7 +52,7 @@ namespace WetnessEffects } // xyz - ripple normal, w - splotches - float4 GetRainDrops(float3 worldPos, float t, float3 normal) + float4 GetRainDrops(float3 worldPos, float t, float3 normal, float rippleStrengthModifier = 1) { const static float uintToFloat = rcp(4294967295.0); const float rippleBreadthRcp = rcp(wetnessEffectsSettings.RippleBreadth); @@ -110,7 +110,7 @@ namespace WetnessEffects 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(wetnessEffectsSettings.RippleStrength, 0, rippleT * rippleT); + lerp(wetnessEffectsSettings.RippleStrength * rippleStrengthModifier, 0, rippleT * rippleT); float3 grad = float3(normalize(vec2Centre), -deriv); float3 bitangent = float3(-grad.y, grad.x, 0); @@ -140,7 +140,7 @@ namespace WetnessEffects float3 R = reflect(-V, N); float NoV = saturate(dot(N, V)); -#if defined(DYNAMIC_CUBEMAPS) +#if defined(DYNAMIC_CUBEMAPS) && !defined(WATER) # if defined(DEFERRED) float level = roughness * 7.0; float3 specularIrradiance = 1.0; diff --git a/features/Wetness Effects/textures/water/waterrainripples.dds b/features/Wetness Effects/textures/water/waterrainripples.dds new file mode 100644 index 0000000000000000000000000000000000000000..e4aadcc3c524dbdca1c805a6ebc479bfb9b88f92 GIT binary patch literal 1540 zcmZ>930A0KU|?Vu;9^h!(jd&h!oa`?q}V`g5`Y3wEl?|iON5~THf 0 && wetnessEffectsSettings.Raining > 0.0f && wetnessEffectsSettings.EnableRaindropFx && + (rainDropDistance < maxRainDropDistance) && wetnessOcclusion > 0.05) { + float rippleStrengthModifier = (wetnessOcclusion * wetnessOcclusion) * distanceFadeout; + raindropInfo = WetnessEffects::GetRainDrops(input.WPosition + CameraPosAdjust[a_eyeIndex], wetnessEffectsSettings.Time, finalNormal, rippleStrengthModifier); + } + + float3 rippleNormal = normalize(raindropInfo.xyz); + finalNormal = WetnessEffects::ReorientNormal(rippleNormal, finalNormal); +# endif + return finalNormal; } From 2a08276437a97dd430c57b609fc7d84897e80bbb Mon Sep 17 00:00:00 2001 From: TheRiverwoodModder Date: Mon, 30 Sep 2024 01:52:36 +0000 Subject: [PATCH 02/18] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20clang-for?= =?UTF-8?q?mat=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli | 2 +- package/Shaders/Water.hlsl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli index e68bf1a17f..36964240c3 100644 --- a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli +++ b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli @@ -52,7 +52,7 @@ namespace WetnessEffects } // xyz - ripple normal, w - splotches - float4 GetRainDrops(float3 worldPos, float t, float3 normal, float rippleStrengthModifier = 1) + float4 GetRainDrops(float3 worldPos, float t, float3 normal, float rippleStrengthModifier = 1) { const static float uintToFloat = rcp(4294967295.0); const float rippleBreadthRcp = rcp(wetnessEffectsSettings.RippleBreadth); diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 4c3c0872d2..5befe451f2 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -565,7 +565,7 @@ float3 GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFa float3 rippleNormal = normalize(raindropInfo.xyz); finalNormal = WetnessEffects::ReorientNormal(rippleNormal, finalNormal); # endif - + return finalNormal; } From a2d66665ca3a4f6ccc28dc39570e5a9f96e3f605 Mon Sep 17 00:00:00 2001 From: TheRiverwoodModder <125157333+TheRiverwoodModder@users.noreply.github.com> Date: Sun, 6 Oct 2024 01:19:49 -0700 Subject: [PATCH 03/18] feat: ripple effect now supports water parallax --- package/Shaders/Water.hlsl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 4c3c0872d2..4eac20929c 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -559,7 +559,11 @@ float3 GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFa if (finalNormal.z > 0 && wetnessEffectsSettings.Raining > 0.0f && wetnessEffectsSettings.EnableRaindropFx && (rainDropDistance < maxRainDropDistance) && wetnessOcclusion > 0.05) { float rippleStrengthModifier = (wetnessOcclusion * wetnessOcclusion) * distanceFadeout; - raindropInfo = WetnessEffects::GetRainDrops(input.WPosition + CameraPosAdjust[a_eyeIndex], wetnessEffectsSettings.Time, finalNormal, rippleStrengthModifier); + float3 rippleWPosition = input.WPosition + finalNormal * 16; +# if defined(WATER_PARALLAX) + rippleWPosition.xy += parallaxOffset; +# endif + raindropInfo = WetnessEffects::GetRainDrops(rippleWPosition + CameraPosAdjust[a_eyeIndex], wetnessEffectsSettings.Time, finalNormal, rippleStrengthModifier); } float3 rippleNormal = normalize(raindropInfo.xyz); From 61eca33a26650a30a27b381bec9aad592cc7a917 Mon Sep 17 00:00:00 2001 From: TheRiverwoodModder Date: Sun, 6 Oct 2024 08:20:17 +0000 Subject: [PATCH 04/18] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20clang-for?= =?UTF-8?q?mat=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package/Shaders/Water.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 1952335a29..d3d0ed7bb1 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -561,7 +561,7 @@ float3 GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFa float rippleStrengthModifier = (wetnessOcclusion * wetnessOcclusion) * distanceFadeout; float3 rippleWPosition = input.WPosition + finalNormal * 16; # if defined(WATER_PARALLAX) - rippleWPosition.xy += parallaxOffset; + rippleWPosition.xy += parallaxOffset; # endif raindropInfo = WetnessEffects::GetRainDrops(rippleWPosition + CameraPosAdjust[a_eyeIndex], wetnessEffectsSettings.Time, finalNormal, rippleStrengthModifier); } From 1a5c8d4c2a0b4174a031b133dc7455245c0f00b8 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Sun, 8 Jun 2025 04:08:07 -0700 Subject: [PATCH 05/18] fix: fix compilation errors --- .../textures/water/waterrainripples.dds | Bin 1540 -> 0 bytes package/Shaders/Common/SharedData.hlsli | 11 ++- package/Shaders/Water.hlsl | 16 +-- src/Features/WetnessEffects.cpp | 91 +++++++++++++++++- src/Features/WetnessEffects.h | 10 +- 5 files changed, 113 insertions(+), 15 deletions(-) delete mode 100644 features/Wetness Effects/textures/water/waterrainripples.dds diff --git a/features/Wetness Effects/textures/water/waterrainripples.dds b/features/Wetness Effects/textures/water/waterrainripples.dds deleted file mode 100644 index e4aadcc3c524dbdca1c805a6ebc479bfb9b88f92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1540 zcmZ>930A0KU|?Vu;9^h!(jd&h!oa`?q}V`g5`Y3wEl?|iON5~THf 0 && wetnessEffectsSettings.Raining > 0.0f && wetnessEffectsSettings.EnableRaindropFx && + if (finalNormal.z > 0 && SharedData::wetnessEffectsSettings.Raining > 0.0f && SharedData::wetnessEffectsSettings.EnableRaindropFx && (rainDropDistance < maxRainDropDistance) && wetnessOcclusion > 0.05) { float rippleStrengthModifier = (wetnessOcclusion * wetnessOcclusion) * distanceFadeout; - float3 rippleWPosition = input.WPosition + finalNormal * 16; + float3 rippleWPosition = input.WPosition.xyz + finalNormal * 16; # if defined(WATER_PARALLAX) rippleWPosition.xy += parallaxOffset; # endif - raindropInfo = WetnessEffects::GetRainDrops(rippleWPosition + CameraPosAdjust[a_eyeIndex], wetnessEffectsSettings.Time, finalNormal, rippleStrengthModifier); + raindropInfo = WetnessEffects::GetRainDrops(rippleWPosition + FrameBuffer::CameraPosAdjust[eyeIndex], SharedData::wetnessEffectsSettings.Time, finalNormal, rippleStrengthModifier); } float3 rippleNormal = normalize(raindropInfo.xyz); diff --git a/src/Features/WetnessEffects.cpp b/src/Features/WetnessEffects.cpp index 535f405730..84cafa6d31 100644 --- a/src/Features/WetnessEffects.cpp +++ b/src/Features/WetnessEffects.cpp @@ -16,6 +16,8 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( EnableRaindropFx, EnableSplashes, EnableRipples, + EnableVanillaRipples, + RaindropFxRange, RaindropGridSize, RaindropInterval, RaindropChance, @@ -28,10 +30,67 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( RippleBreadth, RippleLifetime) +// Ripples code borrowed from po3 SplashesofStorms +// https://github.com/powerof3/SplashesOfStorms/blob/master/src/Util.h under MIT License +namespace Ripples +{ + // Cache settings to avoid repeated singleton access + static bool s_isEnabled = false; + static bool s_vanillaRipplesEnabled = false; + + struct ToggleWaterSplashes + { + static void thunk(RE::TESWaterSystem* a_waterSystem, bool a_enabled, float a_fadeAmount) + { + // Apply our logic only if wetness effects are enabled + if (s_isEnabled) { + a_enabled = a_enabled && s_vanillaRipplesEnabled; + } + + return func(a_waterSystem, a_enabled, a_fadeAmount); + } + static inline REL::Relocation func; + }; + + WetnessEffects* UpdateSettings() + { + const auto WetnessEffects = WetnessEffects::GetSingleton(); + if (WetnessEffects) { + s_isEnabled = WetnessEffects->settings.EnableWetnessEffects; + s_vanillaRipplesEnabled = WetnessEffects->settings.EnableVanillaRipples; + } + return WetnessEffects; + } + + void Install() + { + const auto WetnessEffects = UpdateSettings(); // Initialize cached values + if (!WetnessEffects) + return; + REL::Relocation target{ RELOCATION_ID(25638, 26179), REL::VariantOffset(0x238, 0x223, 0x238) }; + stl::write_thunk_call(target.address()); + logger::info("[{}] Installed ripple hooks", WetnessEffects->GetName()); + } +} + +void WetnessEffects::PostPostLoad() +{ + splashesOfStormsLoaded = static_cast(GetModuleHandle(L"po3_SplashesOfStorms.dll")); + if (splashesOfStormsLoaded) { + logger::info("[{}] Splashes of Storms detected, compatibility enabled", GetName()); + return; + } + + // Only hook if SoS is not loaded + Ripples::Install(); +} + void WetnessEffects::DrawSettings() { if (ImGui::TreeNodeEx("Wetness Effects", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Checkbox("Enable Wetness", (bool*)&settings.EnableWetnessEffects); + if (ImGui::Checkbox("Enable Wetness", (bool*)&settings.EnableWetnessEffects)) { + Ripples::UpdateSettings(); // Update cache when settings change + } if (auto _tt = Util::HoverTooltipWrapper()) { ImGui::Text("Enables a wetness effect near water and when it is raining."); } @@ -57,6 +116,19 @@ void WetnessEffects::DrawSettings() if (auto _tt = Util::HoverTooltipWrapper()) ImGui::Text("Enables circular ripples on puddles, and to a less extent other wet surfaces"); + ImGui::BeginDisabled(splashesOfStormsLoaded); + std::string checkboxLabel = splashesOfStormsLoaded ? + "Enable Vanilla Ripples - Controlled by Splashes of Storms" : + "Enable Vanilla Ripples"; + + if (ImGui::Checkbox(checkboxLabel.c_str(), (bool*)&settings.EnableVanillaRipples)) { + Ripples::UpdateSettings(); // Update cache when settings change + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Enables Skyrim's default ripples"); + } + ImGui::EndDisabled(); + 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" @@ -274,6 +346,7 @@ void WetnessEffects::Prepass() void WetnessEffects::LoadSettings(json& o_json) { settings = o_json; + Ripples::UpdateSettings(); // Sync cached values after loading } void WetnessEffects::SaveSettings(json& o_json) @@ -284,4 +357,20 @@ void WetnessEffects::SaveSettings(json& o_json) void WetnessEffects::RestoreDefaultSettings() { settings = {}; +} + +void WetnessEffects::DrawUnloadedUI() +{ + ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "This feature is not installed!"); + + ImGui::Spacing(); + ImGui::TextWrapped( + "Wetness Effects adds a collection of realistic wetness and weather effects to Skyrim.\n"); + ImGui::Spacing(); + ImGui::TextWrapped("Key features:"); + ImGui::BulletText("Rain Wetness"); + ImGui::BulletText("Puddles"); + ImGui::BulletText("Raindrop Effects (Splashes and Ripples)"); + ImGui::BulletText("Shore Wetness"); + ImGui::Spacing(); } \ No newline at end of file diff --git a/src/Features/WetnessEffects.h b/src/Features/WetnessEffects.h index 508f7dc64e..69190a81a1 100644 --- a/src/Features/WetnessEffects.h +++ b/src/Features/WetnessEffects.h @@ -33,6 +33,8 @@ struct WetnessEffects : Feature uint EnableRaindropFx = true; uint EnableSplashes = true; uint EnableRipples = true; + uint EnableVanillaRipples = false; + float RaindropFxRange = 1000.f; float RaindropGridSize = 4.f; float RaindropInterval = .5f; float RaindropChance = .3f; @@ -54,7 +56,7 @@ struct WetnessEffects : Feature float Wetness; float PuddleWetness; Settings settings; - uint pad0[3]; + uint pad0; }; Settings settings; @@ -62,8 +64,10 @@ struct WetnessEffects : Feature PerFrame GetCommonBufferData(); virtual void Prepass() override; + virtual void PostPostLoad() override; virtual void DrawSettings() override; + virtual std::string GetFeatureModLink() override { return "https://www.nexusmods.com/skyrimspecialedition/mods/112739"; }; virtual void LoadSettings(json& o_json) override; virtual void SaveSettings(json& o_json) override; @@ -71,4 +75,8 @@ struct WetnessEffects : Feature virtual void RestoreDefaultSettings() override; virtual bool SupportsVR() override { return true; }; + virtual void DrawUnloadedUI() override; + +private: + bool splashesOfStormsLoaded = false; }; From 4be0dc8d11f9247a9e7963a87b52017bd4efbdeb Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Sun, 8 Jun 2025 04:14:34 -0700 Subject: [PATCH 06/18] fix: fix wrong type Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- package/Shaders/Common/SharedData.hlsli | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index 9f13de97d0..a38b2cabc5 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -99,8 +99,8 @@ namespace SharedData bool EnableSplashes; bool EnableRipples; - float EnableVanillaRipples; - float RaindropFxRange; + uint EnableVanillaRipples; + float RaindropFxRange; float RaindropGridSizeRcp; float RaindropIntervalRcp; From e6e510b29e2e7669434b9c89dfe61912c7cca629 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Sun, 8 Jun 2025 04:15:03 -0700 Subject: [PATCH 07/18] chore: sync cache on defaults Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/Features/WetnessEffects.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Features/WetnessEffects.cpp b/src/Features/WetnessEffects.cpp index 84cafa6d31..e70cd4e83a 100644 --- a/src/Features/WetnessEffects.cpp +++ b/src/Features/WetnessEffects.cpp @@ -356,7 +356,8 @@ void WetnessEffects::SaveSettings(json& o_json) void WetnessEffects::RestoreDefaultSettings() { - settings = {}; + settings = {}; + Ripples::UpdateSettings(); // Sync cached values after restoring defaults } void WetnessEffects::DrawUnloadedUI() From f1122aaa6411c930103947585dc3f774681646c1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:15:16 +0000 Subject: [PATCH 08/18] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- src/Features/WetnessEffects.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Features/WetnessEffects.cpp b/src/Features/WetnessEffects.cpp index e70cd4e83a..7b69e93125 100644 --- a/src/Features/WetnessEffects.cpp +++ b/src/Features/WetnessEffects.cpp @@ -356,8 +356,8 @@ void WetnessEffects::SaveSettings(json& o_json) void WetnessEffects::RestoreDefaultSettings() { - settings = {}; - Ripples::UpdateSettings(); // Sync cached values after restoring defaults + settings = {}; + Ripples::UpdateSettings(); // Sync cached values after restoring defaults } void WetnessEffects::DrawUnloadedUI() From 467e4fe0e2309e25d7fb29535a2951287d6d9154 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Sun, 8 Jun 2025 16:00:47 -0700 Subject: [PATCH 09/18] fix: vanilla ripple display --- .../WetnessEffects/WetnessEffects.hlsli | 6 +++++- src/Features/WetnessEffects.cpp | 19 ++++++++++++++++--- src/Features/WetnessEffects.h | 4 ++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli index 179b35e4a1..e6829b6ccb 100644 --- a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli +++ b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli @@ -106,7 +106,11 @@ namespace WetnessEffects float distSqr = dot(vec2Centre, vec2Centre); float rippleT = residual * SharedData::wetnessEffectsSettings.RippleLifetimeRcp; if (rippleT < 1.) { - float ripple_r = lerp(0., SharedData::wetnessEffectsSettings.RippleRadius, rippleT); + // vary ripple size + float sizeRandom = frac(floatHash.x * floatHash.y * 1337.0); + float sizeVariation = lerp(0.7, 1.3, sizeRandom); + + float ripple_r = lerp(0.f, SharedData::wetnessEffectsSettings.RippleRadius * sizeVariation, rippleT); float ripple_inner_radius = ripple_r - SharedData::wetnessEffectsSettings.RippleBreadth; float band_lerp = (sqrt(distSqr) - ripple_inner_radius) * rippleBreadthRcp; diff --git a/src/Features/WetnessEffects.cpp b/src/Features/WetnessEffects.cpp index 7b69e93125..e9eaef32a7 100644 --- a/src/Features/WetnessEffects.cpp +++ b/src/Features/WetnessEffects.cpp @@ -31,7 +31,7 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( RippleLifetime) // Ripples code borrowed from po3 SplashesofStorms -// https://github.com/powerof3/SplashesOfStorms/blob/master/src/Util.h under MIT License +// https://github.com/powerof3/SplashesOfStorms/blob/master/src/Hooks.cpp under MIT License namespace Ripples { // Cache settings to avoid repeated singleton access @@ -46,8 +46,15 @@ namespace Ripples if (s_isEnabled) { a_enabled = a_enabled && s_vanillaRipplesEnabled; } + for (auto& waterObject : a_waterSystem->waterObjects) { + if (waterObject) { + if (const auto& rippleObject = waterObject->waterRippleObject; rippleObject) { + rippleObject->SetAppCulled(!a_enabled); + } + } + } - return func(a_waterSystem, a_enabled, a_fadeAmount); + func(a_waterSystem, a_enabled, a_fadeAmount); } static inline REL::Relocation func; }; @@ -58,6 +65,10 @@ namespace Ripples if (WetnessEffects) { s_isEnabled = WetnessEffects->settings.EnableWetnessEffects; s_vanillaRipplesEnabled = WetnessEffects->settings.EnableVanillaRipples; + logger::debug("[{}] UpdateSettings: EnableWetnessEffects={}, EnableVanillaRipples={}", + WetnessEffects->GetName(), s_isEnabled, s_vanillaRipplesEnabled); + } else { + logger::debug("[WetnessEffects] UpdateSettings: WetnessEffects singleton not found"); } return WetnessEffects; } @@ -125,7 +136,9 @@ void WetnessEffects::DrawSettings() Ripples::UpdateSettings(); // Update cache when settings change } if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Enables Skyrim's default ripples"); + ImGui::Text( + "Enables default ripples (e.g., Ripples01).\n" + "Disabling may not take effect until the next weather change.\n"); } ImGui::EndDisabled(); ImGui::SliderFloat("Effect Range", &settings.RaindropFxRange, 1e2f, 2e3f, "%.0f game unit(s)"); diff --git a/src/Features/WetnessEffects.h b/src/Features/WetnessEffects.h index 69190a81a1..8ca06c7d35 100644 --- a/src/Features/WetnessEffects.h +++ b/src/Features/WetnessEffects.h @@ -45,7 +45,7 @@ struct WetnessEffects : Feature float RippleStrength = 1.f; float RippleRadius = 1.f; float RippleBreadth = .5f; - float RippleLifetime = .15f; + float RippleLifetime = .5f; }; struct alignas(16) PerFrame @@ -67,7 +67,7 @@ struct WetnessEffects : Feature virtual void PostPostLoad() override; virtual void DrawSettings() override; - virtual std::string GetFeatureModLink() override { return "https://www.nexusmods.com/skyrimspecialedition/mods/112739"; }; + virtual std::string GetFeatureModLink() override { return "https://www.nexusmods.com/Core/Libs/Common/Widgets/DownloadPopUp?id=604014&nmm=1&game_id=1704"; }; virtual void LoadSettings(json& o_json) override; virtual void SaveSettings(json& o_json) override; From 60af9ea95cd20ffa86f71db1296b568a2e218b68 Mon Sep 17 00:00:00 2001 From: "Alan D. Tse" Date: Fri, 13 Jun 2025 14:47:47 -0700 Subject: [PATCH 10/18] refactor: add flowmap functions --- package/Shaders/Water.hlsl | 88 ++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 95116822a2..aced8b311e 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -420,15 +420,87 @@ float CalculateDepthMultFromUV(float2 uv, float depth, uint eyeIndex = 0) # if defined(SIMPLE) || defined(UNDERWATER) || defined(LOD) || defined(SPECULAR) # if defined(FLOWMAP) -float3 GetFlowmapNormal(PS_INPUT input, float2 uvShift, float multiplier, float offset, uint eyeIndex) +/** + * Calculates flow vector from flowmap texture data + * + * @param input Pixel shader input containing texture coordinates + * @param uvShift UV offset for sampling the flowmap texture + * @return float2 The calculated flow vector in world space, rotated according to flowmap direction + * + * @note This is a simplified version of GetFlowmapData() that only returns the flow vector + * @note Flow direction is encoded in flowmap RG channels, strength in B channel + */ +float2 GetFlowVector(PS_INPUT input, float2 uvShift) { float4 flowmapColor = FlowMapTex.Sample(FlowMapSampler, input.TexCoord2.zw + uvShift); float2 flowVector = (64 * input.TexCoord3.xy) * sqrt(1.01 - flowmapColor.z); float2 flowSinCos = flowmapColor.xy * 2 - 1; float2x2 flowRotationMatrix = float2x2(flowSinCos.x, flowSinCos.y, -flowSinCos.y, flowSinCos.x); - float2 rotatedFlowVector = mul(transpose(flowRotationMatrix), flowVector); - float2 uv = offset + (rotatedFlowVector - float2(multiplier * ((0.001 * ReflectionColor.w) * flowmapColor.w), 0)); - return float3(FlowMapNormalsTex.SampleBias(FlowMapNormalsSampler, uv, SharedData::MipBias).xy, flowmapColor.z); + return mul(transpose(flowRotationMatrix), flowVector); +} + +/** + * Structure containing complete flowmap information + */ +struct FlowmapData +{ + float4 color; // Raw flowmap color (R=flow_x, G=flow_y, B=flow_strength, A=flow_mask) + float2 flowVector; // Calculated rotated flow vector in world space +}; + +/** + * Samples flowmap texture and calculates complete flow data + * + * @param input Pixel shader input containing texture coordinates and world position data + * @param uvShift UV offset for sampling the flowmap texture (used for animation/variation) + * @return FlowmapData Complete flowmap information including raw color and calculated flow vector + * + * @details This function: + * - Samples the flowmap texture at the specified UV coordinates + * - Decodes flow direction from RG channels (remapped from [0,1] to [-1,1]) + * - Calculates flow strength using the blue channel with sqrt falloff + * - Applies rotation matrix to transform flow direction to world space + * - Scales flow vector by world position and strength factors + * + * @note Flowmap format: + * - Red channel: Flow direction X component (0.5 = no flow, 0/1 = negative/positive flow) + * - Green channel: Flow direction Y component (0.5 = no flow, 0/1 = negative/positive flow) + * - Blue channel: Flow strength (0 = no flow, 1 = maximum flow) + * - Alpha channel: Flow mask/intensity multiplier + */ +FlowmapData GetFlowmapData(PS_INPUT input, float2 uvShift) +{ + FlowmapData data; + data.color = FlowMapTex.Sample(FlowMapSampler, input.TexCoord2.zw + uvShift); + float2 flowVector = (64 * input.TexCoord3.xy) * sqrt(1.01 - data.color.z); + float2 flowSinCos = data.color.xy * 2 - 1; + float2x2 flowRotationMatrix = float2x2(flowSinCos.x, flowSinCos.y, -flowSinCos.y, flowSinCos.x); + data.flowVector = mul(transpose(flowRotationMatrix), flowVector); + return data; +} + +/** + * Generates flowmap-based normal perturbation for water surface + * + * @param input Pixel shader input containing texture coordinates and world position + * @param uvShift UV offset for flowmap sampling (used for animation phases) + * @param multiplier Intensity multiplier for the flow effect + * @param offset Base UV offset for the normal texture sampling + * @return float3 Normal perturbation (XY=normal offset, Z=flow strength mask) + * + * @details This function uses flowmap data to: + * - Calculate flow-displaced UV coordinates for normal texture sampling + * - Apply flow-based animation to water normal textures + * - Return both the normal perturbation and flow strength information + * + * @note The returned Z component contains the original flowmap strength value + * which can be used for blending between flow and non-flow normals + */ +float3 GetFlowmapNormal(PS_INPUT input, float2 uvShift, float multiplier, float offset) +{ + FlowmapData flowData = GetFlowmapData(input, uvShift); + float2 uv = offset + (flowData.flowVector - float2(multiplier * ((0.001 * ReflectionColor.w) * flowData.color.w), 0)); + return float3(FlowMapNormalsTex.SampleBias(FlowMapNormalsSampler, uv, SharedData::MipBias).xy, flowData.color.z); } # endif @@ -463,10 +535,10 @@ float3 GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFa 0.5 + -(-0.5 + abs(frac(input.TexCoord2.zw * (64 * input.TexCoord4)) * 2 - 1)); float uvShift = 1 / (128 * input.TexCoord4); - float3 flowmapNormal0 = GetFlowmapNormal(input, uvShift.xx, 9.92, 0, eyeIndex); - float3 flowmapNormal1 = GetFlowmapNormal(input, float2(0, uvShift), 10.64, 0.27, eyeIndex); - float3 flowmapNormal2 = GetFlowmapNormal(input, 0.0.xx, 8, 0, eyeIndex); - float3 flowmapNormal3 = GetFlowmapNormal(input, float2(uvShift, 0), 8.48, 0.62, eyeIndex); + float3 flowmapNormal0 = GetFlowmapNormal(input, uvShift.xx, 9.92, 0); + float3 flowmapNormal1 = GetFlowmapNormal(input, float2(0, uvShift), 10.64, 0.27); + float3 flowmapNormal2 = GetFlowmapNormal(input, 0.0.xx, 8, 0); + float3 flowmapNormal3 = GetFlowmapNormal(input, float2(uvShift, 0), 8.48, 0.62); float2 flowmapNormalWeighted = normalMul.y * (normalMul.x * flowmapNormal2.xy + (1 - normalMul.x) * flowmapNormal3.xy) + From f913c3b7a19491fcc9040a32510f4f60f5c7dc4e Mon Sep 17 00:00:00 2001 From: "Alan D. Tse" Date: Fri, 13 Jun 2025 14:48:27 -0700 Subject: [PATCH 11/18] feat: add flow maps to ripples --- package/Shaders/Water.hlsl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index aced8b311e..7146f5b9e5 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -629,6 +629,20 @@ float3 GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFa # if defined(WATER_PARALLAX) rippleWPosition.xy += parallaxOffset; # endif + + // Flow-following ripple enhancement: Makes raindrops follow water current +# if defined(FLOWMAP) + // Get flowmap data for ripple flow calculation + // Uses zero UV shift to sample current flow state at ripple location + FlowmapData flowData = GetFlowmapData(input, float2(0, 0)); + + // Apply time-based flow offset to ripple position + // This makes ripples drift downstream with the water current + float flowTimeScale = SharedData::wetnessEffectsSettings.Time * 0.1; // Flow animation speed + float2 flowOffset = flowData.flowVector * flowTimeScale * flowData.color.w; // Modulated by flow mask + rippleWPosition.xy += flowOffset; +# endif + raindropInfo = WetnessEffects::GetRainDrops(rippleWPosition + FrameBuffer::CameraPosAdjust[eyeIndex], SharedData::wetnessEffectsSettings.Time, finalNormal, rippleStrengthModifier); } From ac49e5d43b726512b4e9fcb4ae502e1b06bcd3b9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 21:49:18 +0000 Subject: [PATCH 12/18] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Water.hlsl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 7146f5b9e5..a60e65ff32 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -422,11 +422,11 @@ float CalculateDepthMultFromUV(float2 uv, float depth, uint eyeIndex = 0) # if defined(FLOWMAP) /** * Calculates flow vector from flowmap texture data - * + * * @param input Pixel shader input containing texture coordinates * @param uvShift UV offset for sampling the flowmap texture * @return float2 The calculated flow vector in world space, rotated according to flowmap direction - * + * * @note This is a simplified version of GetFlowmapData() that only returns the flow vector * @note Flow direction is encoded in flowmap RG channels, strength in B channel */ @@ -450,21 +450,21 @@ struct FlowmapData /** * Samples flowmap texture and calculates complete flow data - * + * * @param input Pixel shader input containing texture coordinates and world position data * @param uvShift UV offset for sampling the flowmap texture (used for animation/variation) * @return FlowmapData Complete flowmap information including raw color and calculated flow vector - * + * * @details This function: * - Samples the flowmap texture at the specified UV coordinates * - Decodes flow direction from RG channels (remapped from [0,1] to [-1,1]) * - Calculates flow strength using the blue channel with sqrt falloff * - Applies rotation matrix to transform flow direction to world space * - Scales flow vector by world position and strength factors - * + * * @note Flowmap format: * - Red channel: Flow direction X component (0.5 = no flow, 0/1 = negative/positive flow) - * - Green channel: Flow direction Y component (0.5 = no flow, 0/1 = negative/positive flow) + * - Green channel: Flow direction Y component (0.5 = no flow, 0/1 = negative/positive flow) * - Blue channel: Flow strength (0 = no flow, 1 = maximum flow) * - Alpha channel: Flow mask/intensity multiplier */ @@ -481,18 +481,18 @@ FlowmapData GetFlowmapData(PS_INPUT input, float2 uvShift) /** * Generates flowmap-based normal perturbation for water surface - * + * * @param input Pixel shader input containing texture coordinates and world position * @param uvShift UV offset for flowmap sampling (used for animation phases) * @param multiplier Intensity multiplier for the flow effect * @param offset Base UV offset for the normal texture sampling * @return float3 Normal perturbation (XY=normal offset, Z=flow strength mask) - * + * * @details This function uses flowmap data to: * - Calculate flow-displaced UV coordinates for normal texture sampling * - Apply flow-based animation to water normal textures * - Return both the normal perturbation and flow strength information - * + * * @note The returned Z component contains the original flowmap strength value * which can be used for blending between flow and non-flow normals */ From e724017250abc54d937d0d43098b72257c080c03 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 16 Jun 2025 13:22:51 -0700 Subject: [PATCH 13/18] fix: fix ripple and splash flows --- .../WetnessEffects/WetnessEffects.hlsli | 125 +++++++++++ package/Shaders/Water.hlsl | 199 +++++++++++++----- 2 files changed, 277 insertions(+), 47 deletions(-) diff --git a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli index e6829b6ccb..9274ff776c 100644 --- a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli +++ b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli @@ -136,6 +136,18 @@ namespace WetnessEffects return float4(rippleNormal, wetness); } + // Flow-aware version of GetRainDrops that applies flow offset to ripple positioning + // xyz - ripple normal, w - splotches + float4 GetRainDropsFlowAware(float3 worldPos, float2 flowOffset, float t, float3 normal, float rippleStrengthModifier = 1) + { + // Apply flow offset to world position before calculating ripples + float3 flowAwareWorldPos = worldPos; + flowAwareWorldPos.xy += flowOffset; + + // Use the standard GetRainDrops function with the flow-adjusted position + return GetRainDrops(flowAwareWorldPos, t, normal, rippleStrengthModifier); + } + float3 GetWetnessAmbientSpecular(float2 uv, float3 N, float3 VN, float3 V, float roughness) { float3 R = reflect(-V, N); @@ -172,4 +184,117 @@ namespace WetnessEffects { return LightingFuncGGX_OPT3(N, V, L, roughness, 0.02) * lightColor; } + + // Debug visualization functions for DEBUG_WETNESS_EFFECTS + #ifdef DEBUG_WETNESS_EFFECTS + /** + * Calculates ripple and splash effect intensities from water ripple info + * + * @param rippleInfo float4 containing ripple normal (xyz) and splash intensity (w) + * @param rippleMultiplier Multiplier for ripple effect intensity + * @param splashMultiplier Multiplier for splash effect intensity + * @return float2 where x=ripple effect, y=splash effect + */ + float2 GetDebugEffectIntensities(float4 rippleInfo, float rippleMultiplier, float splashMultiplier) + { + float rippleEffect = saturate(length(rippleInfo.xyz) * rippleMultiplier); + float splashEffect = saturate(rippleInfo.w * splashMultiplier); + return float2(rippleEffect, splashEffect); + } + + /** + * Generates debug color visualization for wetness effects + * + * @param effectIntensities float2 from GetDebugEffectIntensities() + * @param rippleColor Color to use for ripple visualization + * @param splashColor Color to use for splash visualization + * @param baseColor Base color to start with (default black) + * @param brightnessMultiplier Multiplier for effect brightness + * @return float3 Debug color, or (0,0,0) if no effects are active + */ + float3 GetDebugWetnessColor(float2 effectIntensities, float3 rippleColor, float3 splashColor, float3 baseColor = float3(0, 0, 0), float brightnessMultiplier = 1.0) + { + float rippleEffect = effectIntensities.x; + float splashEffect = effectIntensities.y; + + if (rippleEffect > 0.01 || splashEffect > 0.01) { + float3 debugColor = baseColor; + if (rippleEffect > 0.01) { + debugColor += rippleColor * rippleEffect * brightnessMultiplier; + } + if (splashEffect > 0.01) { + debugColor += splashColor * splashEffect * brightnessMultiplier; + } + return saturate(debugColor); + } + return float3(0, 0, 0); // No debug override + } + + /** + * Convenience function for standard water debug colors + */ + float3 GetDebugWetnessColorStandard(float4 rippleInfo, float rippleMultiplier, float splashMultiplier) + { + float2 effects = GetDebugEffectIntensities(rippleInfo, rippleMultiplier, splashMultiplier); + float3 rippleColor = float3(1.0, 0.0, 1.0); // BRIGHT MAGENTA + float3 splashColor = float3(0.0, 1.0, 0.0); // BRIGHT GREEN + return GetDebugWetnessColor(effects, rippleColor, splashColor); + } + + /** + * Convenience function for specular debug colors (extra bright) + */ + float3 GetDebugWetnessColorSpecular(float4 rippleInfo, float rippleMultiplier, float splashMultiplier) + { + float2 effects = GetDebugEffectIntensities(rippleInfo, rippleMultiplier, splashMultiplier); + float3 rippleColor = float3(1.0, 0.0, 1.0); // BRIGHT MAGENTA + float3 splashColor = float3(0.0, 1.0, 0.0); // BRIGHT GREEN + return GetDebugWetnessColor(effects, rippleColor, splashColor, float3(0, 0, 0), 1.5); // Extra bright + } + + /** + * Convenience function for underwater debug colors (darker) + */ + float3 GetDebugWetnessColorUnderwater(float4 rippleInfo, float rippleMultiplier, float splashMultiplier) + { + float2 effects = GetDebugEffectIntensities(rippleInfo, rippleMultiplier, splashMultiplier); + float3 rippleColor = float3(0.7, 0.0, 0.7); // DARK MAGENTA + float3 splashColor = float3(0.0, 0.7, 0.0); // DARK GREEN + return GetDebugWetnessColor(effects, rippleColor, splashColor, float3(0, 0, 0.2)); // Dark blue base + } + #endif + + /** + * Calculates flow-aware ripple positioning with proper timing synchronization + * + * @param worldFlowVector Flow vector in world coordinate space + * @param flowStrength Flow strength (0-1) from flowmap alpha channel + * @param reflectionTimingScale Timing scale factor (typically 0.001 * ReflectionColor.w) + * @param avgFlowmapMultiplier Average multiplier from flowmap normal calculations + * @param uvToWorldScale Scale factor converting UV coordinates to world positioning (typically 1/8) + * @return float2 Flow offset to apply to ripple positioning + * + * @details This function synchronizes ripple movement timing with flowmap normal animations + * by using the same mathematical relationship and dual-phase smoothstep timing. + * The timing creates natural flow-based ripple movement that matches the water surface animation. + */ + float2 GetFlowAwareRippleOffset(float2 worldFlowVector, float flowStrength, float reflectionTimingScale, float avgFlowmapMultiplier = 9.26, float uvToWorldScale = 0.125) + { + // Calculate flow timing scale matching flowmap normal timing + // Mathematical relationship: avgMultiplier × uvToWorldScale gives base flow scaling + // uvToWorldScale (1/8) relates to the 64× texture coordinate scaling: 64 × (1/8) = 8 + float baseFlowMultiplier = avgFlowmapMultiplier * uvToWorldScale; // ≈ 1.16 + float flowTimeScale = baseFlowMultiplier * reflectionTimingScale; // Match flowmap timing + + // Calculate base flow offset (strength-modulated) + float2 flowOffset = worldFlowVector * flowTimeScale * flowStrength; + + // Apply dual-phase smoothstep timing for natural flow animation + // This creates the essential dual-phase animation pattern used in flowmap blending + float smoothTime = smoothstep(0.0, 1.0, frac(flowTimeScale)); + smoothTime = 0.15 + 0.85 * smoothTime; // Range: 0.15→1.0→0.15 (avoids complete stops) + + return flowOffset * smoothTime; + } + } diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index a60e65ff32..a813e1dfe5 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -420,24 +420,6 @@ float CalculateDepthMultFromUV(float2 uv, float depth, uint eyeIndex = 0) # if defined(SIMPLE) || defined(UNDERWATER) || defined(LOD) || defined(SPECULAR) # if defined(FLOWMAP) -/** - * Calculates flow vector from flowmap texture data - * - * @param input Pixel shader input containing texture coordinates - * @param uvShift UV offset for sampling the flowmap texture - * @return float2 The calculated flow vector in world space, rotated according to flowmap direction - * - * @note This is a simplified version of GetFlowmapData() that only returns the flow vector - * @note Flow direction is encoded in flowmap RG channels, strength in B channel - */ -float2 GetFlowVector(PS_INPUT input, float2 uvShift) -{ - float4 flowmapColor = FlowMapTex.Sample(FlowMapSampler, input.TexCoord2.zw + uvShift); - float2 flowVector = (64 * input.TexCoord3.xy) * sqrt(1.01 - flowmapColor.z); - float2 flowSinCos = flowmapColor.xy * 2 - 1; - float2x2 flowRotationMatrix = float2x2(flowSinCos.x, flowSinCos.y, -flowSinCos.y, flowSinCos.x); - return mul(transpose(flowRotationMatrix), flowVector); -} /** * Structure containing complete flowmap information @@ -445,21 +427,49 @@ float2 GetFlowVector(PS_INPUT input, float2 uvShift) struct FlowmapData { float4 color; // Raw flowmap color (R=flow_x, G=flow_y, B=flow_strength, A=flow_mask) - float2 flowVector; // Calculated rotated flow vector in world space + float2 flowVector; // Flow vector (coordinate space depends on source function) }; /** - * Samples flowmap texture and calculates complete flow data + * Gets raw flowmap data before UV-space coordinate transformation + * + * @param input Pixel shader input containing texture coordinates + * @param uvShift UV offset for sampling the flowmap texture + * @return FlowmapData with raw components: + * - color: Raw flowmap texture sample (RG=rotation, B=strength, A=mask) + * - flowVector: Base flow vector before any coordinate transformation + * Ready for direct application of rotation matrix for world positioning + * + * @details This function provides flowmap data in its original coordinate space, suitable + * for world-space positioning effects (like ripple movement). The flowVector has + * NOT been transformed for UV-space normal sampling - that transformation is only + * applied in GetFlowmapDataUV() which uses transpose for UV coordinate perturbation. + * + * Use this function when you need to apply the rotation matrix directly for + * world-space effects without needing to reverse any existing transformations. + * + * @see GetFlowmapDataUV() for UV-space normal sampling (applies transpose transformation) + */ +FlowmapData GetFlowmapDataTextureSpace(PS_INPUT input, float2 uvShift) +{ + FlowmapData data; + data.color = FlowMapTex.Sample(FlowMapSampler, input.TexCoord2.zw + uvShift); + data.flowVector = (64 * input.TexCoord3.xy) * sqrt(1.01 - data.color.z); + // NOTE: flowVector is NOT transformed yet - this is the raw vector before rotation matrix + return data; +} +/** + * Samples flowmap texture and calculates UV-space flow data for texture sampling * * @param input Pixel shader input containing texture coordinates and world position data * @param uvShift UV offset for sampling the flowmap texture (used for animation/variation) - * @return FlowmapData Complete flowmap information including raw color and calculated flow vector + * @return FlowmapData Complete flowmap information with UV-space flow vector * * @details This function: * - Samples the flowmap texture at the specified UV coordinates * - Decodes flow direction from RG channels (remapped from [0,1] to [-1,1]) * - Calculates flow strength using the blue channel with sqrt falloff - * - Applies rotation matrix to transform flow direction to world space + * - Applies transpose rotation matrix to transform flow direction to UV space * - Scales flow vector by world position and strength factors * * @note Flowmap format: @@ -468,14 +478,12 @@ struct FlowmapData * - Blue channel: Flow strength (0 = no flow, 1 = maximum flow) * - Alpha channel: Flow mask/intensity multiplier */ -FlowmapData GetFlowmapData(PS_INPUT input, float2 uvShift) +FlowmapData GetFlowmapDataUV(PS_INPUT input, float2 uvShift) { - FlowmapData data; - data.color = FlowMapTex.Sample(FlowMapSampler, input.TexCoord2.zw + uvShift); - float2 flowVector = (64 * input.TexCoord3.xy) * sqrt(1.01 - data.color.z); + FlowmapData data = GetFlowmapDataTextureSpace(input, uvShift); float2 flowSinCos = data.color.xy * 2 - 1; float2x2 flowRotationMatrix = float2x2(flowSinCos.x, flowSinCos.y, -flowSinCos.y, flowSinCos.x); - data.flowVector = mul(transpose(flowRotationMatrix), flowVector); + data.flowVector = mul(transpose(flowRotationMatrix), data.flowVector); return data; } @@ -498,10 +506,50 @@ FlowmapData GetFlowmapData(PS_INPUT input, float2 uvShift) */ float3 GetFlowmapNormal(PS_INPUT input, float2 uvShift, float multiplier, float offset) { - FlowmapData flowData = GetFlowmapData(input, uvShift); + FlowmapData flowData = GetFlowmapDataUV(input, uvShift); float2 uv = offset + (flowData.flowVector - float2(multiplier * ((0.001 * ReflectionColor.w) * flowData.color.w), 0)); return float3(FlowMapNormalsTex.SampleBias(FlowMapNormalsSampler, uv, SharedData::MipBias).xy, flowData.color.z); } + +/** + * Gets flowmap data with world-space flow vector for positioning effects + * + * @param input Pixel shader input containing texture coordinates + * @param uvShift UV offset for flowmap sampling (used for animation phases) + * @return FlowmapData Complete flowmap information with world-space flow vector + * + * @details This function: + * - Samples raw flowmap data (before UV-space transformations) + * - Decodes flow direction from flowmap RG channels + * - Applies component-wise directional transformation + * - Returns complete flowmap data with world-space flow vector + * + * @note Use this for effects that need to move with water current (ripples, debris, foam, etc.) + * For UV-space normal sampling, use GetFlowmapDataUV() instead + */ +FlowmapData GetFlowmapDataWorldSpace(PS_INPUT input, float2 uvShift) +{ + FlowmapData data = GetFlowmapDataTextureSpace(input, uvShift); + float2 flowDirection = -(data.color.xy * 2 - 1); // Decode direction with 180° correction + data.flowVector = data.flowVector * flowDirection; // Transform to world space + return data; +} + +/** + * Converts existing texture-space flowmap data to world-space (avoids duplicate sampling) + * + * @param textureSpaceData FlowmapData from GetFlowmapDataTextureSpace() + * @return FlowmapData Complete flowmap data with world-space flow vector + * + * @note Use this overload when you already have texture-space flowmap data to avoid duplicate texture sampling + */ +FlowmapData GetFlowmapDataWorldSpace(FlowmapData textureSpaceData) +{ + FlowmapData data = textureSpaceData; + float2 flowDirection = -(data.color.xy * 2 - 1); // Decode direction with 180° correction + data.flowVector = data.flowVector * flowDirection; // Transform to world space + return data; +} # endif # if defined(LOD) @@ -522,8 +570,17 @@ float3 GetFlowmapNormal(PS_INPUT input, float2 uvShift, float multiplier, float # include "WetnessEffects/WetnessEffects.hlsli" # endif -float3 GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFactor, float3 viewDirection, float depth, uint eyeIndex) +// Structure to return both normal and ripple/splash color information +struct WaterNormalData { + float3 normal; + float4 rippleInfo; // xyz = ripple effect intensity, w = splash effect intensity +}; + +WaterNormalData GetWaterNormalWithEffects(PS_INPUT input, float distanceFactor, float normalsDepthFactor, float3 viewDirection, float depth, uint eyeIndex) +{ + WaterNormalData result; + result.rippleInfo = float4(0, 0, 0, 0); float3 normalScalesRcp = rcp(input.NormalsScale.xyz); # if defined(WATER_PARALLAX) @@ -560,8 +617,13 @@ float3 GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFa # endif # if defined(FLOWMAP) && !defined(BLEND_NORMALS) - float3 finalNormal = - normalize(lerp(normals1 + float3(0, 0, 1), flowmapNormal, distanceFactor)); +# ifdef DISABLE_FLOWMAP_NORMALS + // FLOWMAP NORMALS DISABLED: Using only base normals (flow system still active for ripples/splashes) + float3 finalNormal = normalize(normals1 + float3(0, 0, 1)); +# else + // FLOWMAP NORMALS ENABLED: Blending flow-based normals with base normals + float3 finalNormal = normalize(lerp(normals1 + float3(0, 0, 1), flowmapNormal, distanceFactor)); +# endif # elif !defined(LOD) # if defined(WATER_PARALLAX) @@ -603,6 +665,9 @@ float3 GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFa # endif # if defined(WETNESS_EFFECTS) + // Wetness Effects Debug System: + // DEBUG_WETNESS_EFFECTS Color Legend: + // - BRIGHT MAGENTA: Ripples, BRIGHT GREEN: Splashes, CYAN: Both effects const bool inWorld = (Permutation::ExtraShaderDescriptor & Permutation::ExtraFlags::InWorld); # if defined(SKYLIGHTING) # if defined(VR) @@ -629,28 +694,39 @@ float3 GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFa # if defined(WATER_PARALLAX) rippleWPosition.xy += parallaxOffset; # endif - - // Flow-following ripple enhancement: Makes raindrops follow water current # if defined(FLOWMAP) - // Get flowmap data for ripple flow calculation - // Uses zero UV shift to sample current flow state at ripple location - FlowmapData flowData = GetFlowmapData(input, float2(0, 0)); - - // Apply time-based flow offset to ripple position - // This makes ripples drift downstream with the water current - float flowTimeScale = SharedData::wetnessEffectsSettings.Time * 0.1; // Flow animation speed - float2 flowOffset = flowData.flowVector * flowTimeScale * flowData.color.w; // Modulated by flow mask + // Flow-following ripple enhancement: Makes raindrops follow water current + FlowmapData worldFlowData = GetFlowmapDataWorldSpace(input, float2(0, 0)); + + // Calculate flow-aware ripple offset using centralized timing logic + // Parameters: avgFlowmapMultiplier=9.26 (average of GetWaterNormal flowmap normal multipliers: 9.92, 10.64, 8, 8.48) + // uvToWorldScale=0.125 (1/8 - relates to 64× texture coordinate scaling factor) + float2 flowOffset = WetnessEffects::GetFlowAwareRippleOffset( + worldFlowData.flowVector, + worldFlowData.color.w, // Flow strength from flowmap alpha + 0.001 * ReflectionColor.w, // Reflection timing scale (matches GetFlowmapNormal) + 9.26, // Average flowmap normal multiplier + 0.125 // UV-to-world scale factor (1/8) + ); + rippleWPosition.xy += flowOffset; # endif - raindropInfo = WetnessEffects::GetRainDrops(rippleWPosition + FrameBuffer::CameraPosAdjust[eyeIndex], SharedData::wetnessEffectsSettings.Time, finalNormal, rippleStrengthModifier); - } + // Calculate ripple and splash color intensities + float rippleIntensity = length(raindropInfo.xy) * rippleStrengthModifier; + float splashIntensity = raindropInfo.w * distanceFadeout; + + // Store ripple and splash information for color effects + result.rippleInfo.xyz = raindropInfo.xyz * rippleIntensity; + result.rippleInfo.w = splashIntensity; + } float3 rippleNormal = normalize(raindropInfo.xyz); finalNormal = WetnessEffects::ReorientNormal(rippleNormal, finalNormal); # endif - return finalNormal; + result.normal = finalNormal; + return result; } float3 GetWaterSpecularColor(PS_INPUT input, float3 normal, float3 viewDirection, @@ -960,11 +1036,11 @@ PS_OUTPUT main(PS_INPUT input) # else float4 depthControl = DepthControl * (distanceMul - 1) + 1; # endif - float3 viewPosition = mul(FrameBuffer::CameraView[eyeIndex], float4(input.WPosition.xyz, 1)).xyz; float2 screenUV = FrameBuffer::ViewToUV(viewPosition, true, eyeIndex); - float3 normal = GetWaterNormal(input, distanceFactor, depthControl.z, viewDirection, depth, eyeIndex); + WaterNormalData waterData = GetWaterNormalWithEffects(input, distanceFactor, depthControl.z, viewDirection, depth, eyeIndex); + float3 normal = waterData.normal; float fresnel = GetFresnelValue(normal, viewDirection); @@ -980,8 +1056,14 @@ PS_OUTPUT main(PS_INPUT input) float3 lightColor = (LightColor[lightIndex].xyz * pow(LdotN, FresnelRI.z)) * lightColorMul; finalColor += lightColor; } - finalColor *= fresnel; +# if defined(WETNESS_EFFECTS) && defined(DEBUG_WETNESS_EFFECTS) + // DEBUG MODE: Override specular color with debug visualization + float3 debugColor = WetnessEffects::GetDebugWetnessColorSpecular(waterData.rippleInfo, 2.5, 4.0); + if (any(debugColor)) { + finalColor = debugColor; + } +# endif isSpecular = true; # else @@ -1039,6 +1121,14 @@ PS_OUTPUT main(PS_INPUT input) # if defined(UNDERWATER) float3 finalSpecularColor = lerp(ShallowColor.xyz, specularColor, 0.5); float3 finalColor = saturate(1 - input.WPosition.w * 0.002) * ((1 - fresnel) * (diffuseColor - finalSpecularColor)) + finalSpecularColor; + // Add ripple and splash color effects for underwater +# if defined(WETNESS_EFFECTS) && defined(DEBUG_WETNESS_EFFECTS) + // DEBUG MODE: Override water color with debug visualization (darker for underwater) + float3 debugColor = WetnessEffects::GetDebugWetnessColorUnderwater(waterData.rippleInfo, 1.5, 2.0); + if (any(debugColor)) { + finalColor = debugColor; + } +# endif # else float3 sunColor = GetSunColor(normal, viewDirection); @@ -1050,6 +1140,14 @@ PS_OUTPUT main(PS_INPUT input) float specularFraction = lerp(1, fresnel * diffuseOutput.refractionMul, distanceFactor); float3 finalColorPreFog = lerp(diffuseColor, specularColor, specularFraction) + sunColor * depthControl.w; float3 finalColor = lerp(finalColorPreFog, input.FogParam.xyz * PosAdjust[eyeIndex].w, input.FogParam.w); +# if defined(WETNESS_EFFECTS) && defined(DEBUG_WETNESS_EFFECTS) + // DEBUG MODE: Override water color with debug visualization + float3 debugColor = WetnessEffects::GetDebugWetnessColorStandard(waterData.rippleInfo, 2.0, 3.0); + if (any(debugColor)) { + finalColor = debugColor; + } +# endif + # else float specularFraction = lerp(1, fresnel, distanceFactor); float3 finalColorPreFog = lerp(diffuseOutput.refractionDiffuseColor, specularColor, specularFraction) + sunColor * depthControl.w; @@ -1062,6 +1160,13 @@ PS_OUTPUT main(PS_INPUT input) refractionColor = lerp(refractionColor, fogColor, fogFactor); float3 finalColor = lerp(refractionColor, finalColorPreFog, diffuseOutput.refractionMul); +# if defined(WETNESS_EFFECTS) && defined(DEBUG_WETNESS_EFFECTS) + // DEBUG MODE: Override water color with debug visualization + float3 debugColor = WetnessEffects::GetDebugWetnessColorStandard(waterData.rippleInfo, 2.0, 3.0); + if (any(debugColor)) { + finalColor = debugColor; + } +# endif # endif # endif From 41912b135bdcbd68ab43cb011464b8c9bcb71834 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 16 Jun 2025 21:09:33 -0700 Subject: [PATCH 14/18] fix: remove dupe GetFeatureModLink --- src/Features/WetnessEffects.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Features/WetnessEffects.h b/src/Features/WetnessEffects.h index 9bc3926463..127b68908d 100644 --- a/src/Features/WetnessEffects.h +++ b/src/Features/WetnessEffects.h @@ -83,7 +83,6 @@ struct WetnessEffects : Feature virtual void PostPostLoad() override; virtual void DrawSettings() override; - virtual std::string GetFeatureModLink() override { return "https://www.nexusmods.com/Core/Libs/Common/Widgets/DownloadPopUp?id=604014&nmm=1&game_id=1704"; }; virtual void LoadSettings(json& o_json) override; virtual void SaveSettings(json& o_json) override; From 215e27277486d3d7bcaa74017d25186c7ca384c7 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 16 Jun 2025 21:09:46 -0700 Subject: [PATCH 15/18] style: fix lint --- package/Shaders/Water.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index a813e1dfe5..51bb089ff3 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -711,7 +711,7 @@ WaterNormalData GetWaterNormalWithEffects(PS_INPUT input, float distanceFactor, rippleWPosition.xy += flowOffset; # endif - raindropInfo = WetnessEffects::GetRainDrops(rippleWPosition + FrameBuffer::CameraPosAdjust[eyeIndex], SharedData::wetnessEffectsSettings.Time, finalNormal, rippleStrengthModifier); + raindropInfo = WetnessEffects::GetRainDrops(rippleWPosition + FrameBuffer::CameraPosAdjust[eyeIndex].xyz, SharedData::wetnessEffectsSettings.Time, finalNormal, rippleStrengthModifier); // Calculate ripple and splash color intensities float rippleIntensity = length(raindropInfo.xy) * rippleStrengthModifier; From 90ffd50c207115f2488d801125ea8f213e8d97a1 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 16 Jun 2025 21:53:40 -0700 Subject: [PATCH 16/18] refactor: address further ai comments --- .../WetnessEffects/WetnessEffects.hlsli | 45 ++++++++----------- package/Shaders/Water.hlsl | 2 +- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli index 9274ff776c..dcce50d45f 100644 --- a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli +++ b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli @@ -54,8 +54,11 @@ namespace WetnessEffects } // xyz - ripple normal, w - splotches - float4 GetRainDrops(float3 worldPos, float t, float3 normal, float rippleStrengthModifier = 1) + float4 GetRainDrops(float3 worldPos, float t, float3 normal, float rippleStrengthModifier = 1.0, float2 flowOffset = float2(0.0, 0.0)) { + // Apply flow offset to world position for flow-aware ripple positioning + worldPos.xy += flowOffset; + const static float uintToFloat = rcp(4294967295.0); const float rippleBreadthRcp = rcp(SharedData::wetnessEffectsSettings.RippleBreadth); @@ -106,8 +109,8 @@ namespace WetnessEffects float distSqr = dot(vec2Centre, vec2Centre); float rippleT = residual * SharedData::wetnessEffectsSettings.RippleLifetimeRcp; if (rippleT < 1.) { - // vary ripple size - float sizeRandom = frac(floatHash.x * floatHash.y * 1337.0); + // vary ripple size using independent random value (floatHash.z is biased by RaindropChance test) + float sizeRandom = frac(dot(floatHash.xy, float2(12.9898, 78.233))); float sizeVariation = lerp(0.7, 1.3, sizeRandom); float ripple_r = lerp(0.f, SharedData::wetnessEffectsSettings.RippleRadius * sizeVariation, rippleT); @@ -132,22 +135,9 @@ namespace WetnessEffects } wetness *= SharedData::wetnessEffectsSettings.SplashesStrength; - return float4(rippleNormal, wetness); } - // Flow-aware version of GetRainDrops that applies flow offset to ripple positioning - // xyz - ripple normal, w - splotches - float4 GetRainDropsFlowAware(float3 worldPos, float2 flowOffset, float t, float3 normal, float rippleStrengthModifier = 1) - { - // Apply flow offset to world position before calculating ripples - float3 flowAwareWorldPos = worldPos; - flowAwareWorldPos.xy += flowOffset; - - // Use the standard GetRainDrops function with the flow-adjusted position - return GetRainDrops(flowAwareWorldPos, t, normal, rippleStrengthModifier); - } - float3 GetWetnessAmbientSpecular(float2 uv, float3 N, float3 VN, float3 V, float roughness) { float3 R = reflect(-V, N); @@ -185,18 +175,21 @@ namespace WetnessEffects return LightingFuncGGX_OPT3(N, V, L, roughness, 0.02) * lightColor; } - // Debug visualization functions for DEBUG_WETNESS_EFFECTS - #ifdef DEBUG_WETNESS_EFFECTS +// Debug visualization functions for DEBUG_WETNESS_EFFECTS +#ifdef DEBUG_WETNESS_EFFECTS /** * Calculates ripple and splash effect intensities from water ripple info * - * @param rippleInfo float4 containing ripple normal (xyz) and splash intensity (w) + * @param rippleInfo float4 containing scaled ripple normal (xyz) and splash intensity (w) + * Note: xyz = normalized ripple normal * intensity multiplier * @param rippleMultiplier Multiplier for ripple effect intensity * @param splashMultiplier Multiplier for splash effect intensity * @return float2 where x=ripple effect, y=splash effect */ float2 GetDebugEffectIntensities(float4 rippleInfo, float rippleMultiplier, float splashMultiplier) { + // rippleInfo.xyz is a scaled normal vector (normalized normal * intensity) + // length() gives us the intensity/magnitude of the ripple effect float rippleEffect = saturate(length(rippleInfo.xyz) * rippleMultiplier); float splashEffect = saturate(rippleInfo.w * splashMultiplier); return float2(rippleEffect, splashEffect); @@ -247,8 +240,8 @@ namespace WetnessEffects float3 GetDebugWetnessColorSpecular(float4 rippleInfo, float rippleMultiplier, float splashMultiplier) { float2 effects = GetDebugEffectIntensities(rippleInfo, rippleMultiplier, splashMultiplier); - float3 rippleColor = float3(1.0, 0.0, 1.0); // BRIGHT MAGENTA - float3 splashColor = float3(0.0, 1.0, 0.0); // BRIGHT GREEN + float3 rippleColor = float3(1.0, 0.0, 1.0); // BRIGHT MAGENTA + float3 splashColor = float3(0.0, 1.0, 0.0); // BRIGHT GREEN return GetDebugWetnessColor(effects, rippleColor, splashColor, float3(0, 0, 0), 1.5); // Extra bright } @@ -258,11 +251,11 @@ namespace WetnessEffects float3 GetDebugWetnessColorUnderwater(float4 rippleInfo, float rippleMultiplier, float splashMultiplier) { float2 effects = GetDebugEffectIntensities(rippleInfo, rippleMultiplier, splashMultiplier); - float3 rippleColor = float3(0.7, 0.0, 0.7); // DARK MAGENTA - float3 splashColor = float3(0.0, 0.7, 0.0); // DARK GREEN + float3 rippleColor = float3(0.7, 0.0, 0.7); // DARK MAGENTA + float3 splashColor = float3(0.0, 0.7, 0.0); // DARK GREEN return GetDebugWetnessColor(effects, rippleColor, splashColor, float3(0, 0, 0.2)); // Dark blue base } - #endif +#endif /** * Calculates flow-aware ripple positioning with proper timing synchronization @@ -283,8 +276,8 @@ namespace WetnessEffects // Calculate flow timing scale matching flowmap normal timing // Mathematical relationship: avgMultiplier × uvToWorldScale gives base flow scaling // uvToWorldScale (1/8) relates to the 64× texture coordinate scaling: 64 × (1/8) = 8 - float baseFlowMultiplier = avgFlowmapMultiplier * uvToWorldScale; // ≈ 1.16 - float flowTimeScale = baseFlowMultiplier * reflectionTimingScale; // Match flowmap timing + float baseFlowMultiplier = avgFlowmapMultiplier * uvToWorldScale; // ≈ 1.16 + float flowTimeScale = baseFlowMultiplier * reflectionTimingScale; // Match flowmap timing // Calculate base flow offset (strength-modulated) float2 flowOffset = worldFlowVector * flowTimeScale * flowStrength; diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 51bb089ff3..8177542daf 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -574,7 +574,7 @@ FlowmapData GetFlowmapDataWorldSpace(FlowmapData textureSpaceData) struct WaterNormalData { float3 normal; - float4 rippleInfo; // xyz = ripple effect intensity, w = splash effect intensity + float4 rippleInfo; // xyz = scaled ripple normal (normalized normal * intensity), w = splash effect intensity }; WaterNormalData GetWaterNormalWithEffects(PS_INPUT input, float distanceFactor, float normalsDepthFactor, float3 viewDirection, float depth, uint eyeIndex) From c8407daa1dbec5fa58098940f2d4b9fea74a742a Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 16 Jun 2025 22:46:42 -0700 Subject: [PATCH 17/18] fix: avoid entropy collapse in ripple hash --- .../Shaders/WetnessEffects/WetnessEffects.hlsli | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli index dcce50d45f..5625b24556 100644 --- a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli +++ b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli @@ -107,10 +107,10 @@ namespace WetnessEffects if (floatHash.z < (SharedData::wetnessEffectsSettings.RaindropChance)) { float2 vec2Centre = int2(i, j) + floatHash.xy - gridUV; float distSqr = dot(vec2Centre, vec2Centre); - float rippleT = residual * SharedData::wetnessEffectsSettings.RippleLifetimeRcp; - if (rippleT < 1.) { - // vary ripple size using independent random value (floatHash.z is biased by RaindropChance test) - float sizeRandom = frac(dot(floatHash.xy, float2(12.9898, 78.233))); + float rippleT = residual * SharedData::wetnessEffectsSettings.RippleLifetimeRcp; if (rippleT < 1.) { + // vary ripple size using high-quality random hash (preserves full entropy) + uint sizeHash = Random::iqint3(hash.xy); + float sizeRandom = float(sizeHash) * uintToFloat; float sizeVariation = lerp(0.7, 1.3, sizeRandom); float ripple_r = lerp(0.f, SharedData::wetnessEffectsSettings.RippleRadius * sizeVariation, rippleT); From 6f21811d7508c764f6de3e55d77a605952ccb7dc Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 16 Jun 2025 22:57:26 -0700 Subject: [PATCH 18/18] style: restore function names and lines --- .../Shaders/WetnessEffects/WetnessEffects.hlsli | 4 +++- package/Shaders/Water.hlsl | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli index 5625b24556..a1590134e4 100644 --- a/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli +++ b/features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli @@ -107,7 +107,8 @@ namespace WetnessEffects if (floatHash.z < (SharedData::wetnessEffectsSettings.RaindropChance)) { float2 vec2Centre = int2(i, j) + floatHash.xy - gridUV; float distSqr = dot(vec2Centre, vec2Centre); - float rippleT = residual * SharedData::wetnessEffectsSettings.RippleLifetimeRcp; if (rippleT < 1.) { + float rippleT = residual * SharedData::wetnessEffectsSettings.RippleLifetimeRcp; + if (rippleT < 1.) { // vary ripple size using high-quality random hash (preserves full entropy) uint sizeHash = Random::iqint3(hash.xy); float sizeRandom = float(sizeHash) * uintToFloat; @@ -135,6 +136,7 @@ namespace WetnessEffects } wetness *= SharedData::wetnessEffectsSettings.SplashesStrength; + return float4(rippleNormal, wetness); } diff --git a/package/Shaders/Water.hlsl b/package/Shaders/Water.hlsl index 8177542daf..9ab2a99c89 100644 --- a/package/Shaders/Water.hlsl +++ b/package/Shaders/Water.hlsl @@ -577,7 +577,7 @@ struct WaterNormalData float4 rippleInfo; // xyz = scaled ripple normal (normalized normal * intensity), w = splash effect intensity }; -WaterNormalData GetWaterNormalWithEffects(PS_INPUT input, float distanceFactor, float normalsDepthFactor, float3 viewDirection, float depth, uint eyeIndex) +WaterNormalData GetWaterNormal(PS_INPUT input, float distanceFactor, float normalsDepthFactor, float3 viewDirection, float depth, uint eyeIndex) { WaterNormalData result; result.rippleInfo = float4(0, 0, 0, 0); @@ -1039,7 +1039,7 @@ PS_OUTPUT main(PS_INPUT input) float3 viewPosition = mul(FrameBuffer::CameraView[eyeIndex], float4(input.WPosition.xyz, 1)).xyz; float2 screenUV = FrameBuffer::ViewToUV(viewPosition, true, eyeIndex); - WaterNormalData waterData = GetWaterNormalWithEffects(input, distanceFactor, depthControl.z, viewDirection, depth, eyeIndex); + WaterNormalData waterData = GetWaterNormal(input, distanceFactor, depthControl.z, viewDirection, depth, eyeIndex); float3 normal = waterData.normal; float fresnel = GetFresnelValue(normal, viewDirection); @@ -1056,6 +1056,7 @@ PS_OUTPUT main(PS_INPUT input) float3 lightColor = (LightColor[lightIndex].xyz * pow(LdotN, FresnelRI.z)) * lightColorMul; finalColor += lightColor; } + finalColor *= fresnel; # if defined(WETNESS_EFFECTS) && defined(DEBUG_WETNESS_EFFECTS) // DEBUG MODE: Override specular color with debug visualization