diff --git a/betterRenderer/renderer/source/gbufferblitpass.cpp b/betterRenderer/renderer/source/gbufferblitpass.cpp index 8debef9a..d69a6dfe 100644 --- a/betterRenderer/renderer/source/gbufferblitpass.cpp +++ b/betterRenderer/renderer/source/gbufferblitpass.cpp @@ -188,6 +188,45 @@ void GbufferBlitPass::UpdateConstants(nvrhi::ICommandList* command_list, constants.m_altitude = Global.pCamera.Pos.y; constants.m_time = Timer::GetTime(); + { + float percipitation_intensity = glm::saturate(Global.Overcast - 1.); + constants.m_rain_params.x = percipitation_intensity; // % amount of droplets + constants.m_rain_params.y = + glm::mix(15., 1., percipitation_intensity); // Regeneration time + static glm::vec4 wiper_timer_out; + static glm::vec4 wiper_timer_return; + if (TDynamicObject const* owner = Global.pCamera.m_owner; + owner && !!owner->MoverParameters->CabActive) { + for (int i = 0; i < 4; ++i) { + if (i < owner->dWiperPos.size()) { + int index = owner->MoverParameters->CabActive > 0 + ? i + : static_cast(owner->dWiperPos.size() - 1) - i; + constants.m_wiper_pos[i] = owner->dWiperPos[index]; + if (owner->dWiperPos[index] > 0. && owner->wiperDirection[index]) { + constants.m_wiper_pos[i] += 1.; + } + if (owner->dWiperPos[index] < .025) { + wiper_timer_out[i] = constants.m_time; + } + if (owner->dWiperPos[index] > .975) { + wiper_timer_return[i] = constants.m_time; + } + constants.m_wiper_timer_out[i] = wiper_timer_out[i]; + constants.m_wiper_timer_return[i] = wiper_timer_return[i]; + } else { + constants.m_wiper_pos[i] = 0.; + wiper_timer_out[i] = constants.m_wiper_timer_out[i] = -1000.; + wiper_timer_return[i] = constants.m_wiper_timer_return[i] = -1000.; + } + } + } else { + constants.m_wiper_pos = glm::vec4{0.}; + wiper_timer_out = constants.m_wiper_timer_out = glm::vec4{-1000.}; + wiper_timer_return = constants.m_wiper_timer_return = glm::vec4{-1000.}; + } + } + command_list->writeBuffer(m_draw_constants, &constants, sizeof(constants)); } diff --git a/betterRenderer/renderer/source/gbufferblitpass.h b/betterRenderer/renderer/source/gbufferblitpass.h index 3504f827..28449c5c 100644 --- a/betterRenderer/renderer/source/gbufferblitpass.h +++ b/betterRenderer/renderer/source/gbufferblitpass.h @@ -29,6 +29,10 @@ struct GbufferBlitPass : public FullScreenPass, public MaResourceRegistry { float m_altitude; glm::vec3 m_light_color; float m_time; + glm::vec4 m_rain_params; + glm::vec4 m_wiper_pos; + glm::vec4 m_wiper_timer_out; + glm::vec4 m_wiper_timer_return; }; nvrhi::BindingLayoutHandle m_binding_layout; diff --git a/betterRenderer/renderer/source/materialparser.cpp b/betterRenderer/renderer/source/materialparser.cpp index 6668fbf2..ed949bac 100644 --- a/betterRenderer/renderer/source/materialparser.cpp +++ b/betterRenderer/renderer/source/materialparser.cpp @@ -133,6 +133,9 @@ std::string_view MaterialAdapterLegacyMatFile::GetShader() const { if (m_shader == "water") { return "legacy_water"; } + if (m_shader == "rain_windscreen") { + return "windshield_rain"; + } if (IsSpecGlossShader() && HasSpecGlossMap()) { if (IsNormalMapShader() && HasNormalMap()) { return "legacy_normalmap_specgloss"; diff --git a/betterRenderer/shaders/manul/random.hlsli b/betterRenderer/shaders/manul/random.hlsli new file mode 100644 index 00000000..e45ba37a --- /dev/null +++ b/betterRenderer/shaders/manul/random.hlsli @@ -0,0 +1,39 @@ + +uint Hash(uint s); +uint Hash(uint2 s); +uint Hash(uint3 s); +uint Hash(float s); +uint HashCombine(uint seed, uint value); + +// Evolving Sub-Grid Turbulence for Smoke Animation +// H. Schechter and R. Bridson +uint Hash(uint s) { + s ^= 2747636419u; + s *= 2654435769u; + s ^= s >> 16u; + s *= 2654435769u; + s ^= s >> 16u; + s *= 2654435769u; + return s; +} + +uint Hash(uint2 s) { + return HashCombine(Hash(s.x), s.y); +} + +uint Hash(uint3 s) { + return HashCombine(Hash(s.xy), s.z); +} + +uint HashCombine(uint seed, uint value) { + return seed ^ (Hash(value) + 0x9e3779b9u + (seed<<6u) + (seed>>2u)); +} + +uint Rand(inout uint seed) { + seed = Hash(seed); + return seed; +} + +float RandF(inout uint seed) { + return float(Rand(seed)) * 2.3283064365386963e-10; +} diff --git a/betterRenderer/shaders/manul/view_data.hlsli b/betterRenderer/shaders/manul/view_data.hlsli index 7a2fd2d6..fa59f069 100644 --- a/betterRenderer/shaders/manul/view_data.hlsli +++ b/betterRenderer/shaders/manul/view_data.hlsli @@ -8,6 +8,10 @@ cbuffer DrawConstants : register(b2) { float g_Altitude; float3 g_LightColor; float g_Time; + float4 g_RainParams; + float4 g_WiperPos; + float4 g_WiperTimerOut; + float4 g_WiperTimerReturn; } float2 PixelToCS(in float2 pixel, in float2 size) { diff --git a/betterRenderer/shaders/project.manul b/betterRenderer/shaders/project.manul index e82c75b3..92f1821d 100644 --- a/betterRenderer/shaders/project.manul +++ b/betterRenderer/shaders/project.manul @@ -47,18 +47,6 @@ shaders: binding: 2 hint: normalmap default: normalmap - # paramx: # Metalness.Roughness.[UNUSED].Normal[X] - # binding: 1 - # hint: linear - # default: legacy_params - # paramy: # Specular.Occlusion.[UNUSED].Normal[Y] - # binding: 2 - # hint: linear - # default: legacy_params - # selfillum: - # binding: 3 - # hint: color - # default: black masked_shadow_texture: albedo source: ps_default_lit legacy: @@ -137,6 +125,22 @@ shaders: default: normalmap masked_shadow_texture: diffuse source: ps_legacy_water + windshield_rain: + textures: + diffuse: + binding: 0 + hint: color # so that texture will be marked as sRGB + default: white + raindropsatlas: + binding: 1 + hint: color + default: white + wipermask: + binding: 2 + hint: color + default: white + masked_shadow_texture: diffuse + source: ps_windshield_rain utility: # Everything that does not belong to scene graph rendering # ImGui shaders diff --git a/betterRenderer/shaders/ps_windshield_rain.hlsl b/betterRenderer/shaders/ps_windshield_rain.hlsl new file mode 100644 index 00000000..f13e4440 --- /dev/null +++ b/betterRenderer/shaders/ps_windshield_rain.hlsl @@ -0,0 +1,165 @@ +#include "manul/math.hlsli" +#include "manul/material.hlsli" +#include "manul/color_transform.hlsli" +#include "manul/random.hlsli" + +sampler diffuse_sampler : register(s0); +Texture2D diffuse : register(t0); +Texture2D raindropsatlas : register(t1); +Texture2D wipermask : register(t2); + +float4 getDropTex(float choice, float2 uv) { + float2 offset; + if (choice < .25) offset = float2(0.0, 0.0); + else if (choice < .5) offset = float2(0.5, 0.0); + else if (choice < .75) offset = float2(0.0, 0.5); + else offset = float2(0.5, 0.5); + return raindropsatlas.Sample(diffuse_sampler, offset + uv * 0.5); +} + +float GetMixFactor(in float2 co, out float side); + +void MaterialPass(inout MaterialData material) { +#if PASS & FORWARD_LIGHTING + const float specular_intensity = 1.; + const float wobble_strength = .002; + const float wobble_speed = 30.; + + float4 tex_color = diffuse.Sample(diffuse_sampler, material.m_TexCoord); + if (tex_color.a < .01) discard; + + float2 rainCoord = material.m_TexCoord; + float gridSize = ceil(200.); + + const float numDrops = 20000.; + const float cycleDuration = 4.; + + float squareMin = .5 / gridSize; + float squareMax = 1.2 / gridSize; + + float2 cell = floor(rainCoord * gridSize); + + float3 dropLayer = 0.; + float dropMaskSum = 0.; + + // Grid of 9 droplets in immediate neighbourhood + for (int oy = -1; oy <= 1; oy++) { + for (int ox = -1; ox <= 1; ox++) { + + float2 neighborCell = cell + float2(ox, oy); + float2 neighborCenter = (neighborCell + .5) / gridSize; + + float side; + float mixFactor = GetMixFactor(neighborCenter, side); + + uint seed = Hash(uint3(neighborCell, side)); + + if(mixFactor < RandF(seed)) { + continue; + } + + // Show a percentage of droplets given by rain intensity param + float activationSeed = RandF(seed); + if (activationSeed > g_RainParams.x) + continue; // kropla nieaktywna + + // Randomly modulate droplet center & size + float2 dropCenter = (neighborCell + float2(RandF(seed), RandF(seed))) / gridSize; + float squareSize = lerp(squareMin, squareMax, RandF(seed)); + + float lifeTime = g_Time + RandF(seed) * cycleDuration; + float phase = frac(lifeTime / cycleDuration); + float active = saturate(1. - phase); + + // Gravity influence (TODO add vehicle speed & wind here!) + float gravityStart = .5; + float gravityPhase = smoothstep(gravityStart, 1., phase); + float dropMass = lerp(.3, 1.2, RandF(seed)); + float gravitySpeed = .15 * dropMass; + float2 gravityOffset = float2(0., gravityPhase * gravitySpeed * phase); + + // Random wobble + bool hasWobble = (RandF(seed) < .10); + float2 wobbleOffset = 0.; + if (hasWobble && gravityPhase > 0.) { + float intensity = sin(g_Time * wobble_speed + RandF(seed) * 100.) * wobble_strength * gravityPhase; + wobbleOffset = float2(intensity, 0.); + } + + float2 slideOffset = gravityOffset + wobbleOffset; + + // Flatten droplets influenced by gravity + float flattenAmount = smoothstep(0.1, 0.5, gravityPhase); + float flattenX = lerp(1.0, 0.4, flattenAmount); + float stretchY = lerp(1.0, 1.6, flattenAmount); + + // Droplet local position & mask + float2 diff = (rainCoord + slideOffset) - dropCenter; + diff.x *= 1.0 / flattenX; + diff.y *= 1.0 / stretchY; + float mask = smoothstep(squareSize * 0.5, squareSize * 0.45, max(abs(diff.x), abs(diff.y))); + + if (mask > .001) { + float2 localUV = (diff + squareSize * 0.5) / squareSize; + float choice = RandF(seed); + float4 dropTex = getDropTex(choice, localUV); + float sharpAlpha = smoothstep(0.3, 0.9, dropTex.a); + + float colorLuma = length(dropTex.rgb); + float alphaRange = smoothstep(0.1, 0.3, colorLuma); + float blackAlpha = lerp(0.25, 0.85, alphaRange); + + dropLayer += dropTex.rgb * sharpAlpha * active * blackAlpha * mask; + dropMaskSum += sharpAlpha * active * blackAlpha * mask; + } + } + } + float3 finalMix = dropLayer; + float alphaOut = clamp(dropMaskSum, 0.0, 1.0); + material.m_MaterialAlbedoAlpha = float4(finalMix, alphaOut); + + { // Overlay windshield texture with alpha + material.m_MaterialAlbedoAlpha.xyz = lerp(material.m_MaterialAlbedoAlpha.xyz, tex_color.xyz, tex_color.a); + material.m_MaterialAlbedoAlpha.a = lerp(material.m_MaterialAlbedoAlpha.a, 1., tex_color.a); + } +#endif +} + +#if PASS & FORWARD_LIGHTING +float GetMixFactor(in float2 co, out float side) { + float4 movePhase = g_WiperPos; + bool4 is_out = movePhase <= 1.; + movePhase = select(is_out, movePhase, 2. - movePhase); + + float4 mask = wipermask.Sample(diffuse_sampler, co); + + float4 areaMask = step(.001, mask); + + float4 maskVal = select(is_out, mask, 1. - mask); + float4 wipeWidth = smoothstep(1., .9, movePhase) * .25; + float4 cleaned = smoothstep(movePhase - wipeWidth, movePhase, maskVal) * areaMask; + float4 side_v = step(maskVal, movePhase); + cleaned *= side_v; + side_v = select(is_out, 1. - side_v, side_v); + + // "regeneration", raindrops gradually returning after wiper pass: + float4 regenPhase = saturate((g_Time - lerp(g_WiperTimerOut, g_WiperTimerReturn, side_v) - .2) / g_RainParams.y); + + side_v = lerp(0., 1. - side_v, areaMask); + + float4 factor_v = lerp(1., regenPhase * (1. - cleaned), areaMask); + + side = 0.; + float out_factor = 1.; + + // Find out the wiper blade that influences given grid cell the most + for(int i = 0; i < 4; ++i) + { + bool is_candidate = factor_v[i] < out_factor; + out_factor = select(is_candidate, factor_v[i], out_factor); + side = select(is_candidate, side_v[i], side); + } + + return out_factor; +} +#endif