Skip to content

Commit

Permalink
feat: New wetness system based on water depth as opposed to game weat…
Browse files Browse the repository at this point in the history
…her transitions
  • Loading branch information
TheRiverwoodModder committed Feb 5, 2024
1 parent eca469a commit c71f56d
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[Info]
Version = 1-0-0
Version = 1-1-0
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
struct PerPassWetnessEffects
{
float Wetness;
float PuddleWetness;
row_major float3x4 DirectionalAmbientWS;
uint EnableWetnessEffects;
float MaxRainWetness;
Expand All @@ -13,7 +14,7 @@ struct PerPassWetnessEffects
float PuddleMinWetness;
float MinRainWetness;
float SkinWetness;
float pad[2];
float WeatherTransitionSpeed;
};

StructuredBuffer<PerPassWetnessEffects> perPassWetnessEffects : register(t22);
Expand Down
17 changes: 10 additions & 7 deletions package/Shaders/Lighting.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -490,9 +490,9 @@ typedef VS_OUTPUT PS_INPUT;

struct PS_OUTPUT
{
float4 Albedo : SV_Target0;
float4 MotionVectors : SV_Target1;
float4 ScreenSpaceNormals : SV_Target2;
float4 Albedo : SV_Target0;
float4 MotionVectors : SV_Target1;
float4 ScreenSpaceNormals : SV_Target2;
#if defined(SNOW)
float2 SnowParameters : SV_Target3;
#endif
Expand Down Expand Up @@ -1577,8 +1577,8 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace
float minWetnessAngle = 0;
minWetnessAngle = saturate(max(minWetnessValue, worldSpaceNormal.z));

float rainWetness = perPassWetnessEffects[0].Wetness * minWetnessAngle;

float rainWetness = perPassWetnessEffects[0].Wetness * minWetnessAngle * perPassWetnessEffects[0].MaxRainWetness;
float puddleWetness = perPassWetnessEffects[0].PuddleWetness * minWetnessAngle * perPassWetnessEffects[0].MaxRainWetness;
# if defined(SKIN)
rainWetness = perPassWetnessEffects[0].SkinWetness * perPassWetnessEffects[0].Wetness;
# endif
Expand All @@ -1592,9 +1592,12 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace
float3 wetnessNormal = worldSpaceNormal;

float3 puddleCoords = ((input.WorldPosition.xyz + CameraPosAdjust[0]) * 0.5 + 0.5) * 0.01 * perPassWetnessEffects[0].PuddleRadius;
float puddle = 0;
if (wetness > 0.0) {
float puddle = wetness;
if (wetness > 0.0 || puddleWetness > 0) {
# if !defined(SKINNED)
puddle = noise(puddleCoords) * ((minWetnessAngle / perPassWetnessEffects[0].PuddleMaxAngle) / 1.5) + 0.5;
# endif
wetness = lerp(wetness, puddleWetness, saturate(puddle - 0.25));
puddle *= wetness;
if (shaderDescriptors[0].PixelShaderDescriptor & _DefShadow && shaderDescriptors[0].PixelShaderDescriptor & _ShadowDir) {
float upAngle = saturate(dot(float3(0, 0, 1), normalizedDirLightDirectionWS.xyz));
Expand Down
140 changes: 105 additions & 35 deletions src/Features/WetnessEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ const float DEFAULT_TRANSITION_PERCENTAGE = 1.0f;
const float TRANSITION_CURVE_MULTIPLIER = 2.0f;
const float TRANSITION_DENOMINATOR = 256.0f;
const float DRY_WETNESS = 0.0f;
const float RAIN_DELTA_PER_SECOND = 2.0f / 3600.0f;
const float SNOWY_DAY_DELTA_PER_SECOND = -0.489f / 3600.0f; // Only doing evaporation until snow wetness feature is added
const float CLOUDY_DAY_DELTA_PER_SECOND = -0.735f / 3600.0f;
const float CLEAR_DAY_DELTA_PER_SECOND = -1.518f / 3600.0f;
const float WETNESS_SCALE = 2.0; // Speed at which wetness builds up and drys.
const float PUDDLE_SCALE = 1.0; // Speed at which puddles build up and dry
const float MAX_PUDDLE_DEPTH = 3.0f;
const float MAX_WETNESS_DEPTH = 2.0f;
const float MAX_PUDDLE_WETNESS = 1.0f;
const float MAX_WETNESS = 1.0f;
const float SECONDS_IN_A_DAY = 86400;
const float MAX_TIME_DELTA = SECONDS_IN_A_DAY - 30;

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
WetnessEffects::Settings,
Expand All @@ -18,7 +30,8 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
PuddleMaxAngle,
PuddleMinWetness,
MinRainWetness,
SkinWetness)
SkinWetness,
WeatherTransitionSpeed)

void WetnessEffects::DrawSettings()
{
Expand All @@ -37,6 +50,7 @@ void WetnessEffects::DrawSettings()
ImGui::Spacing();

if (ImGui::TreeNodeEx("Advanced", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::SliderFloat("Wether transition speed", &settings.WeatherTransitionSpeed, 0.5f, 5.0f);
ImGui::SliderFloat("Min Rain Wetness", &settings.MinRainWetness, 0.0f, 0.9f);
ImGui::SliderFloat("Skin Wetness", &settings.SkinWetness, 0.0f, 1.0f);
if (auto _tt = Util::HoverTooltipWrapper()) {
Expand All @@ -51,37 +65,60 @@ void WetnessEffects::DrawSettings()

ImGui::TreePop();
}

ImGui::Spacing();
ImGui::Spacing();

if (ImGui::TreeNodeEx("Statistics", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Text(std::format("Wetness depth : {:.2f}", wetnessDepth / WETNESS_SCALE).c_str());
ImGui::Text(std::format("Puddle depth : {:.2f}", puddleDepth / PUDDLE_SCALE).c_str());
ImGui::Spacing();
ImGui::Spacing();
ImGui::Text(std::format("Current weather : {0:X}", currentWeatherID).c_str());
ImGui::Text(std::format("Previous weather : {0:X}", lastWeatherID).c_str());
ImGui::TreePop();
}
}

float WetnessEffects::CalculateWeatherTransitionPercentage(RE::TESWeather* weather, float skyCurrentWeatherPct, float beginFade)
float WetnessEffects::CalculateWeatherTransitionPercentage(float skyCurrentWeatherPct, float beginFade, bool fadeIn)
{
float weatherTransitionPercentage = DEFAULT_TRANSITION_PERCENTAGE;
if (weather) {
// 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);
startPercentage = startPercentage > MIN_START_PERCENTAGE ? startPercentage : MIN_START_PERCENTAGE;
// 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);

if (fadeIn) {
float currentPercentage = (skyCurrentWeatherPct - startPercentage) / (1 - startPercentage);
weatherTransitionPercentage = std::clamp(currentPercentage, 0.0f, 1.0f);
} else {
float currentPercentage = (startPercentage - skyCurrentWeatherPct) / (startPercentage);
weatherTransitionPercentage = 1 - std::clamp(currentPercentage, 0.0f, 1.0f);
}
return weatherTransitionPercentage;
}

float WetnessEffects::CalculateWetness(RE::TESWeather* weather, RE::Sky* sky)
void WetnessEffects::CalculateWetness(RE::TESWeather* weather, RE::Sky* sky, float seconds, float& weatherWetnessDepth, float& weatherPuddleDepth)
{
float wetness = DRY_WETNESS;
float wetnessDepthDelta = CLEAR_DAY_DELTA_PER_SECOND * WETNESS_SCALE * seconds;
float puddleDepthDelta = CLEAR_DAY_DELTA_PER_SECOND * PUDDLE_SCALE * seconds;
if (weather && sky) {
const char* name = weather->GetName();
if (name) {
// Figure out the weather type and set the wetness
if (weather->precipitationData && weather->data.flags.any(RE::TESWeather::WeatherDataFlag::kRainy)) {
// Raining
wetness = settings.MaxRainWetness;
}
// Figure out the weather type and set the wetness
if (weather->precipitationData && weather->data.flags.any(RE::TESWeather::WeatherDataFlag::kRainy)) {
// Raining
wetnessDepthDelta = RAIN_DELTA_PER_SECOND * WETNESS_SCALE * seconds;
puddleDepthDelta = RAIN_DELTA_PER_SECOND * PUDDLE_SCALE * seconds;
} else if (weather->precipitationData && weather->data.flags.any(RE::TESWeather::WeatherDataFlag::kSnow)) {
wetnessDepthDelta = SNOWY_DAY_DELTA_PER_SECOND * WETNESS_SCALE * seconds;
puddleDepthDelta = SNOWY_DAY_DELTA_PER_SECOND * PUDDLE_SCALE * seconds;
} else if (weather->data.flags.any(RE::TESWeather::WeatherDataFlag::kCloudy)) {
wetnessDepthDelta = CLOUDY_DAY_DELTA_PER_SECOND * WETNESS_SCALE * seconds;
puddleDepthDelta = CLOUDY_DAY_DELTA_PER_SECOND * PUDDLE_SCALE * seconds;
}
}
return wetness;

weatherWetnessDepth = wetnessDepthDelta > 0 ? std::min(weatherWetnessDepth + wetnessDepthDelta, MAX_WETNESS_DEPTH) : std::max(weatherWetnessDepth + wetnessDepthDelta, 0.0f);
weatherPuddleDepth = puddleDepthDelta > 0 ? std::min(weatherPuddleDepth + puddleDepthDelta, MAX_PUDDLE_DEPTH) : std::max(weatherPuddleDepth + puddleDepthDelta, 0.0f);
}

void WetnessEffects::Draw(const RE::BSShader* shader, const uint32_t)
Expand All @@ -91,36 +128,69 @@ void WetnessEffects::Draw(const RE::BSShader* shader, const uint32_t)

if (requiresUpdate) {
requiresUpdate = false;

PerPass data{};
data.Wetness = DRY_WETNESS;
data.PuddleWetness = DRY_WETNESS;
currentWeatherID = 0;
uint32_t previousLastWeatherID = lastWeatherID;
lastWeatherID = 0;

if (settings.EnableWetnessEffects) {
if (auto sky = RE::Sky::GetSingleton()) {
if (sky->mode.get() == RE::Sky::Mode::kFull) {
if (auto currentWeather = sky->currentWeather) {
float weatherTransitionPercentage = DEFAULT_TRANSITION_PERCENTAGE;
float wetnessCurrentWeather = CalculateWetness(currentWeather, sky);
float wetnessLastWeather = DRY_WETNESS;
currentWeatherID = currentWeather->GetFormID();
if (auto calendar = RE::Calendar::GetSingleton()) {
float currentWeatherWetnessDepth = wetnessDepth;
float currentWeatherPuddleDepth = puddleDepth;
float currentGameTime = calendar->GetCurrentGameTime() * SECONDS_IN_A_DAY;
lastGameTimeValue = lastGameTimeValue == 0 ? currentGameTime : lastGameTimeValue;
float seconds = currentGameTime - lastGameTimeValue;
lastGameTimeValue = currentGameTime;

// If there is a lastWeather, figure out what type it is and set the wetness
if (auto lastWeather = sky->lastWeather) {
wetnessLastWeather = CalculateWetness(lastWeather, sky);
if (abs(seconds) >= MAX_TIME_DELTA)
{
// If too much time has passed, snap wetness depths to the current weather.
seconds = 0.0f;
currentWeatherWetnessDepth = 0.0f;
currentWeatherPuddleDepth = 0.0f;
CalculateWetness(currentWeather, sky, 1.0f, currentWeatherWetnessDepth, currentWeatherPuddleDepth);
wetnessDepth = currentWeatherWetnessDepth > 0 ? MAX_WETNESS_DEPTH : 0.0f;
puddleDepth = currentWeatherPuddleDepth > 0 ? MAX_PUDDLE_DEPTH : 0.0f;
}

if (seconds > 0 || (seconds < 0 && (wetnessDepth > 0 || puddleDepth > 0))) {
float weatherTransitionPercentage = DEFAULT_TRANSITION_PERCENTAGE;
float lastWeatherWetnessDepth = wetnessDepth;
float lastWeatherPuddleDepth = puddleDepth;
seconds *= settings.WeatherTransitionSpeed;
CalculateWetness(currentWeather, sky, seconds, currentWeatherWetnessDepth, currentWeatherPuddleDepth);
// If there is a lastWeather, figure out what type it is and set the wetness
if (auto lastWeather = sky->lastWeather) {
lastWeatherID = lastWeather->GetFormID();
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)) {
weatherTransitionPercentage = CalculateWeatherTransitionPercentage(sky->currentWeatherPct, lastWeather->data.precipitationEndFadeOut, false);
} else {
weatherTransitionPercentage = CalculateWeatherTransitionPercentage(sky->currentWeatherPct, currentWeather->data.precipitationBeginFadeIn, true);
}
}

// 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)) {
weatherTransitionPercentage = CalculateWeatherTransitionPercentage(lastWeather, sky->currentWeatherPct, lastWeather->data.precipitationEndFadeOut);
// Transition between CurrentWeather and LastWeather depth values
wetnessDepth = std::lerp(lastWeatherWetnessDepth, currentWeatherWetnessDepth, weatherTransitionPercentage);
puddleDepth = std::lerp(lastWeatherPuddleDepth, currentWeatherPuddleDepth, weatherTransitionPercentage);
} else {
weatherTransitionPercentage = CalculateWeatherTransitionPercentage(currentWeather, sky->currentWeatherPct, currentWeather->data.precipitationBeginFadeIn);
lastWeatherID = previousLastWeatherID;
}
// Adjust the transition curve to ease in/out of the transition
weatherTransitionPercentage = (exp2(TRANSITION_CURVE_MULTIPLIER * log2(weatherTransitionPercentage)));

// Calculate the wetness value from the water depth
data.Wetness = std::min(wetnessDepth, MAX_WETNESS); //data.settings.PuddleMinWetness);
data.PuddleWetness = std::min(puddleDepth, MAX_PUDDLE_WETNESS);
}

// Transition between CurrentWeather and LastWeather wetness values
data.Wetness = std::lerp(wetnessLastWeather, wetnessCurrentWeather, weatherTransitionPercentage);
}
}
}
}
}

Expand Down
13 changes: 9 additions & 4 deletions src/Features/WetnessEffects.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,27 @@ struct WetnessEffects : Feature
float PuddleMinWetness = 0.85f;
float MinRainWetness = 0.65f;
float SkinWetness = 0.825f;
float WeatherTransitionSpeed = 3.0f;
};

struct alignas(16) PerPass
{
float Wetness;
float PuddleWetness;
DirectX::XMFLOAT3X4 DirectionalAmbientWS;
Settings settings;
float pad[2];
};

Settings settings;

std::unique_ptr<Buffer> perPass = nullptr;

bool requiresUpdate = true;
float wetnessDepth = 0.0f;
float puddleDepth = 0.0f;
float lastGameTimeValue = 0.0f;
uint32_t currentWeatherID = 0;
uint32_t lastWeatherID = 0;

virtual void SetupResources();
virtual void Reset();
Expand All @@ -56,7 +62,6 @@ struct WetnessEffects : Feature
virtual void Save(json& o_json);

virtual void RestoreDefaultSettings();

float CalculateWeatherTransitionPercentage(RE::TESWeather* weather, float skyCurrentWeatherPct, float beginFade);
float CalculateWetness(RE::TESWeather* weather, RE::Sky* sky);
float CalculateWeatherTransitionPercentage(float skyCurrentWeatherPct, float beginFade, bool fadeIn);
void CalculateWetness(RE::TESWeather* weather, RE::Sky* sky, float seconds, float& wetness, float& puddleWetness);
};

0 comments on commit c71f56d

Please sign in to comment.