From ff50fdf11081b7ae5f1ea33e2e7339cc852ee909 Mon Sep 17 00:00:00 2001 From: Wls50 Date: Sun, 23 Nov 2025 23:45:06 +0100 Subject: [PATCH] add refraction support for materials; two pass rain shader --- .../renderer/include/nvrenderer/nvrenderer.h | 1 + .../renderer/source/gbufferblitpass.cpp | 15 ++ .../renderer/source/gbufferblitpass.h | 4 +- betterRenderer/renderer/source/nvmaterial.cpp | 8 +- betterRenderer/renderer/source/nvrenderer.cpp | 6 + betterRenderer/renderer/source/nvtexture.cpp | 23 +- betterRenderer/renderer/source/nvtexture.h | 1 + .../renderer/source/windshield_rain.cpp | 246 ++++++++++++++++++ .../renderer/source/windshield_rain.h | 46 ++++ .../shaders/cs_downsample_depth.hlsl | 15 ++ betterRenderer/shaders/default_vertex.hlsl | 8 +- betterRenderer/shaders/manul/material.hlsli | 9 + .../shaders/manul/material_common.hlsli | 2 + betterRenderer/shaders/manul/view_data.hlsli | 1 + betterRenderer/shaders/project.manul | 18 ++ .../shaders/ps_windshield_rain.hlsl | 194 ++++---------- .../shaders/ps_windshield_rain_anim.hlsl | 162 ++++++++++++ 17 files changed, 614 insertions(+), 145 deletions(-) create mode 100644 betterRenderer/renderer/source/windshield_rain.cpp create mode 100644 betterRenderer/renderer/source/windshield_rain.h create mode 100644 betterRenderer/shaders/cs_downsample_depth.hlsl create mode 100644 betterRenderer/shaders/ps_windshield_rain_anim.hlsl diff --git a/betterRenderer/renderer/include/nvrenderer/nvrenderer.h b/betterRenderer/renderer/include/nvrenderer/nvrenderer.h index 17bf121c..6933e7e7 100644 --- a/betterRenderer/renderer/include/nvrenderer/nvrenderer.h +++ b/betterRenderer/renderer/include/nvrenderer/nvrenderer.h @@ -190,6 +190,7 @@ class NvRenderer : public gfx_renderer, public MaResourceRegistry { std::shared_ptr m_bloom; std::shared_ptr m_sky; std::shared_ptr m_auto_exposure; + std::shared_ptr m_windshield_rain; std::unordered_map> rt_models; std::shared_ptr GetRtModel(TModel3d const *); diff --git a/betterRenderer/renderer/source/gbufferblitpass.cpp b/betterRenderer/renderer/source/gbufferblitpass.cpp index d69a6dfe..b791c79f 100644 --- a/betterRenderer/renderer/source/gbufferblitpass.cpp +++ b/betterRenderer/renderer/source/gbufferblitpass.cpp @@ -85,6 +85,15 @@ void GbufferBlitPass::Init() { .setKeepInitialState(true)); RegisterResource(true, "scene_lit_texture", m_output, nvrhi::ResourceType::Texture_SRV); + m_output_copy = m_backend->GetDevice()->createTexture( + nvrhi::TextureDesc() + .setWidth(m_gbuffer->m_framebuffer->getFramebufferInfo().width) + .setHeight(m_gbuffer->m_framebuffer->getFramebufferInfo().height) + .setFormat(nvrhi::Format::RGBA16_FLOAT) + .setInitialState(nvrhi::ResourceStates::ShaderResource) + .setKeepInitialState(true)); + RegisterResource(true, "scene_lit_texture_copy", m_output_copy, + nvrhi::ResourceType::Texture_SRV); m_binding_layout = m_backend->GetDevice()->createBindingLayout( nvrhi::BindingLayoutDesc() .addItem(nvrhi::BindingLayoutItem::VolatileConstantBuffer(2)) @@ -187,6 +196,8 @@ void GbufferBlitPass::UpdateConstants(nvrhi::ICommandList* command_list, constants.m_light_color); constants.m_altitude = Global.pCamera.Pos.y; constants.m_time = Timer::GetTime(); + constants.m_vertical_fov = + glm::radians(Global.FieldOfView / Global.ZoomFactor); { float percipitation_intensity = glm::saturate(Global.Overcast - 1.); @@ -238,6 +249,10 @@ void GbufferBlitPass::Render(nvrhi::ICommandList* command_list, m_scene_depth, nvrhi::TextureSlice().resolve(m_scene_depth->getDesc()), m_gbuffer->m_gbuffer_depth, nvrhi::TextureSlice().resolve(m_scene_depth->getDesc())); + command_list->copyTexture( + m_output_copy, nvrhi::TextureSlice().resolve(m_output_copy->getDesc()), + m_output, + nvrhi::TextureSlice().resolve(m_output->getDesc())); } void GbufferBlitPass::Render(nvrhi::ICommandList* command_list) { diff --git a/betterRenderer/renderer/source/gbufferblitpass.h b/betterRenderer/renderer/source/gbufferblitpass.h index 28449c5c..c4513f73 100644 --- a/betterRenderer/renderer/source/gbufferblitpass.h +++ b/betterRenderer/renderer/source/gbufferblitpass.h @@ -22,7 +22,7 @@ struct GbufferBlitPass : public FullScreenPass, public MaResourceRegistry { const glm::dmat4& projection); virtual void Render(nvrhi::ICommandList* command_list) override; - struct DrawConstants { + struct alignas(16) DrawConstants { glm::mat4 m_inverse_model_view; glm::mat4 m_inverse_projection; glm::vec3 m_light_dir; @@ -33,6 +33,7 @@ struct GbufferBlitPass : public FullScreenPass, public MaResourceRegistry { glm::vec4 m_wiper_pos; glm::vec4 m_wiper_timer_out; glm::vec4 m_wiper_timer_return; + float m_vertical_fov; }; nvrhi::BindingLayoutHandle m_binding_layout; @@ -53,5 +54,6 @@ struct GbufferBlitPass : public FullScreenPass, public MaResourceRegistry { nvrhi::ComputePipelineHandle m_pso; nvrhi::TextureHandle m_output; + nvrhi::TextureHandle m_output_copy; virtual nvrhi::IFramebuffer* GetFramebuffer() override; }; \ No newline at end of file diff --git a/betterRenderer/renderer/source/nvmaterial.cpp b/betterRenderer/renderer/source/nvmaterial.cpp index a64f45bc..137c9f2b 100644 --- a/betterRenderer/renderer/source/nvmaterial.cpp +++ b/betterRenderer/renderer/source/nvmaterial.cpp @@ -121,13 +121,18 @@ void NvRenderer::MaterialTemplate::Init(const YAML::Node &conf) { binding.disable_anisotropy = it.second["no_anisotropy"].as(false); binding.disable_filter = it.second["no_filter"].as(false); binding.disable_mip_bias = it.second["no_mip_bias"].as(false); + size_t default_texture = m_renderer->GetTextureManager()->FetchTexture( + it.second["default"].as(""), binding.m_hint, 0, false); + if (!default_texture) { + default_texture = 1; + } texture_mappings.emplace_back( MaResourceMapping::Texture_SRV(index, binding.m_name.c_str())); sampler_mappings.emplace_back( MaResourceMapping::Sampler(index, binding.m_sampler_name.c_str())); - RegisterTexture(binding.m_name.c_str(), 1); + RegisterTexture(binding.m_name.c_str(), default_texture); RegisterResource( false, "masked_shadow_sampler", GetTextureManager()->GetSamplerForTraits(0, RenderPassType::ShadowMap), @@ -236,6 +241,7 @@ void NvRenderer::MaterialTemplate::Init(const YAML::Node &conf) { .Add(MaResourceMapping::Texture_SRV(10, "env_brdf_lut")) .Add(MaResourceMapping::Texture_SRV(11, "shadow_depths")) .Add(MaResourceMapping::Texture_SRV(12, "gbuffer_depth")) + .Add(MaResourceMapping::Texture_SRV(13, "scene_lit_texture_copy")) .Add(MaResourceMapping::Texture_SRV(14, "sky_aerial_lut")) .Add(MaResourceMapping::Texture_SRV( 16, "forwardplus_index_grid_transparent")) diff --git a/betterRenderer/renderer/source/nvrenderer.cpp b/betterRenderer/renderer/source/nvrenderer.cpp index fac7cfe8..3a41fd44 100644 --- a/betterRenderer/renderer/source/nvrenderer.cpp +++ b/betterRenderer/renderer/source/nvrenderer.cpp @@ -39,6 +39,7 @@ #include "rt_model.h" #include "tinyexr.h" +#include "windshield_rain.h" bool NvRenderer::Init(GLFWwindow *Window) { m_message_callback = std::make_shared(); @@ -92,6 +93,7 @@ bool NvRenderer::Init(GLFWwindow *Window) { m_auto_exposure = std::make_shared(this); m_fsr = std::make_shared(this); m_bloom = std::make_shared(GetBackend()); + m_windshield_rain = std::make_shared(); // protect from undefined framebuffer size in ini (default -1) int w = Global.gfx_framebuffer_width, h = Global.gfx_framebuffer_height; @@ -136,7 +138,10 @@ bool NvRenderer::Init(GLFWwindow *Window) { // RegisterResource(true, "gbuffer_depth", m_gbuffer->m_gbuffer_depth, // nvrhi::ResourceType::Texture_SRV); + m_windshield_rain->Init(this); + if (!InitMaterials()) return false; + return true; } @@ -631,6 +636,7 @@ bool NvRenderer::Render() { command_list->endMarker(); if (true) { + m_windshield_rain->Render(pass); command_list->beginMarker("Forward pass"); pass.m_framebuffer = m_framebuffer_forward; pass.m_type = RenderPassType::Forward; diff --git a/betterRenderer/renderer/source/nvtexture.cpp b/betterRenderer/renderer/source/nvtexture.cpp index 0894f9e3..192a88cc 100644 --- a/betterRenderer/renderer/source/nvtexture.cpp +++ b/betterRenderer/renderer/source/nvtexture.cpp @@ -373,6 +373,19 @@ size_t NvTextureManager::FetchTexture(std::string path, int format_hint, return index; } +size_t NvTextureManager::RegisterExternalTexture(std::string const& path, + nvrhi::ITexture *texture) { + auto [it, added] = m_texture_map.emplace(path, -1); + if (added) { + m_texture_cache.emplace_back(std::make_shared()); + it->second = m_texture_cache.size(); + } + auto cache = m_texture_cache[it->second - 1]; + cache->m_rhi_texture = texture; + cache->m_last_change = ++NvTexture::s_change_counter; + return it->second; +} + bool NvTexture::CreateRhiTexture() { if (m_rhi_texture) return true; m_last_change = ++s_change_counter; @@ -495,8 +508,14 @@ nvrhi::SamplerHandle NvTextureManager::GetSamplerForTraits( : nvrhi::SamplerAddressMode::Wrap) .setAllFilters(!traits[MaTextureTraits_NoFilter]) .setMipFilter(false) - .setMaxAnisotropy(traits[MaTextureTraits_NoAnisotropy] || traits[MaTextureTraits_NoFilter] ? 0.f : 16.f) - .setMipBias(traits[MaTextureTraits_NoMipBias] || traits[MaTextureTraits_NoFilter] ? 0.f : -1.76f); + .setMaxAnisotropy(traits[MaTextureTraits_NoAnisotropy] || + traits[MaTextureTraits_NoFilter] + ? 0.f + : 16.f) + .setMipBias(traits[MaTextureTraits_NoMipBias] || + traits[MaTextureTraits_NoFilter] + ? 0.f + : -1.76f); sampler = m_backend->GetDevice()->createSampler(desc); } return sampler; diff --git a/betterRenderer/renderer/source/nvtexture.h b/betterRenderer/renderer/source/nvtexture.h index c56e95fc..18bba50d 100644 --- a/betterRenderer/renderer/source/nvtexture.h +++ b/betterRenderer/renderer/source/nvtexture.h @@ -83,6 +83,7 @@ class NvTextureManager { NvTextureManager(class NvRenderer* renderer); size_t FetchTexture(std::string path, int format_hint, int size_bias, bool unload_on_location); + size_t RegisterExternalTexture(std::string const& path, nvrhi::ITexture *texture); void UpdateLastUse(size_t handle, const glm::dvec3& location); bool IsValidHandle(size_t handle); NvTexture* GetTexture(size_t handle); diff --git a/betterRenderer/renderer/source/windshield_rain.cpp b/betterRenderer/renderer/source/windshield_rain.cpp new file mode 100644 index 00000000..33af7b01 --- /dev/null +++ b/betterRenderer/renderer/source/windshield_rain.cpp @@ -0,0 +1,246 @@ +#include "windshield_rain.h" + +#include "gbuffer.h" +#include "gbufferblitpass.h" +#include "nvrendererbackend.h" +#include "nvrhi/utils.h" +#include "nvtexture.h" + +void WindshieldRain::Init(NvRenderer* in_renderer) { + renderer = in_renderer; + { + auto const src_desc = renderer->m_gbuffer->m_gbuffer_depth->getDesc(); + width = src_desc.width / 2; + height = src_desc.height / 2; + } + auto backend = renderer->GetBackend(); + tex_droplets = backend->GetDevice()->createTexture( + nvrhi::TextureDesc() + .setDebugName("Raindrop buffer") + .setWidth(width) + .setHeight(height) + .setFormat(nvrhi::Format::R32_FLOAT) + .setIsRenderTarget(true) + .setInitialState(nvrhi::ResourceStates::RenderTarget) + .setClearValue(nvrhi::Color(0.)) + .setUseClearValue(true) + .setKeepInitialState(true)); + tex_depth_temp = backend->GetDevice()->createTexture( + nvrhi::TextureDesc() + .setDebugName("Raindrop depths temp") + .setWidth(width) + .setHeight(height) + .setFormat(nvrhi::Format::D32) + .setIsUAV(true) + .setInitialState(nvrhi::ResourceStates::UnorderedAccess) + .setKeepInitialState(true)); + tex_depth = backend->GetDevice()->createTexture( + nvrhi::TextureDesc() + .setDebugName("Raindrop depths") + .setWidth(width) + .setHeight(height) + .setFormat(nvrhi::Format::D32) + .setIsRenderTarget(true) + .setInitialState(nvrhi::ResourceStates::DepthWrite) + .setClearValue(nvrhi::Color(0.)) + .setUseClearValue(true) + .setKeepInitialState(true)); + framebuffer = backend->GetDevice()->createFramebuffer( + nvrhi::FramebufferDesc() + .addColorAttachment(tex_droplets) + .setDepthAttachment(tex_depth)); + + ps_rain_anim = + backend->CreateShader("windshield_rain_anim", nvrhi::ShaderType::Pixel); + + vs_rain_anim = + backend->CreateShader("default_vertex_no_jitter", nvrhi::ShaderType::Vertex); + + cs_max_depth = + backend->CreateShader("max_depth_4x4", nvrhi::ShaderType::Compute); + + auto const texture_manager = renderer->GetTextureManager(); + + texture_handle_droplets = texture_manager->FetchTexture( + "textures/fx/raindrops_height", GL_R, 0, false); + + texture_handle_wipermask = texture_manager->FetchTexture( + "dynamic/pkp/ep09_v1/104ec/szyby_wipermask", GL_SRGB, 0, false); + + texture_manager->RegisterExternalTexture("system/raindrops_buffer", + tex_droplets); + + { + nvrhi::SamplerHandle sampler = backend->GetDevice()->createSampler( + nvrhi::SamplerDesc().setAllFilters(true).setAllAddressModes( + nvrhi::SamplerAddressMode::Clamp)); + nvrhi::BindingLayoutHandle binding_layout; + nvrhi::utils::CreateBindingSetAndLayout( + backend->GetDevice(), nvrhi::ShaderType::Compute, 0, + nvrhi::BindingSetDesc() + .addItem(nvrhi::BindingSetItem::Texture_SRV( + 0, renderer->m_gbuffer->m_gbuffer_depth)) + .addItem(nvrhi::BindingSetItem::Texture_UAV(0, tex_depth_temp)) + .addItem(nvrhi::BindingSetItem::Sampler(0, sampler)), + binding_layout, bindings_max_depth); + pso_max_depth = backend->GetDevice()->createComputePipeline( + nvrhi::ComputePipelineDesc() + .addBindingLayout(binding_layout) + .setComputeShader(cs_max_depth)); + } +} + +void WindshieldRain::Resize(int width, int height) {} + +nvrhi::ITexture* WindshieldRain::GetTexture() const { return tex_droplets; } + +void WindshieldRain::Render(NvRenderer::RenderPass const& pass) { + if (!pso_droplets) { + CreatePso(pass.m_command_list_draw); + } + if (renderer->m_dynamic_with_kabina < renderer->m_dynamics.size()) { + const auto& dynamic = renderer->m_dynamics[renderer->m_dynamic_with_kabina]; + pass.m_command_list_draw->beginMarker("Render cab rain buffer"); + pass.m_command_list_draw->clearTextureFloat( + tex_droplets, nvrhi::AllSubresources, nvrhi::Color(0.)); + { + nvrhi::ComputeState compute_state; + compute_state.setPipeline(pso_max_depth); + compute_state.addBindingSet(bindings_max_depth); + pass.m_command_list_draw->setComputeState(compute_state); + pass.m_command_list_draw->dispatch((width + 7) / 8, (height + 7) / 8); + } + pass.m_command_list_draw->copyTexture(tex_depth, nvrhi::TextureSlice(), + tex_depth_temp, nvrhi::TextureSlice()); + for (auto const& item : dynamic.m_renderable_kabina.m_items) { + auto const& material = renderer->m_material_cache[item.m_material - 1]; + if (!IsRainShader(material.m_template)) { + continue; + } + + nvrhi::GraphicsState gfx_state{}; + nvrhi::DrawArguments draw_arguments; + bool indexed; + + if (!renderer->BindGeometry(item.m_geometry, pass, gfx_state, + draw_arguments, indexed)) { + continue; + } + + auto transform = item.m_transform; + transform[3] -= pass.m_origin; + + auto& binding_set = binding_sets_per_material[item.m_material]; + if (!binding_set) { + auto const& material = renderer->m_material_cache[item.m_material - 1]; + auto texture_wipermask = renderer->GetTextureManager()->GetRhiTexture( + material.m_texture_handles[2], pass.m_command_list_draw); + binding_set = renderer->m_backend->GetDevice()->createBindingSet( + nvrhi::BindingSetDesc() + .addItem(nvrhi::BindingSetItem::ConstantBuffer( + 0, renderer->m_drawconstant_buffer)) + .addItem(nvrhi::BindingSetItem::ConstantBuffer( + 2, renderer->m_gbuffer_blit->m_draw_constants)) + .addItem(nvrhi::BindingSetItem::PushConstants( + 1, sizeof(NvRenderer::PushConstantsDraw))) + .addItem( + nvrhi::BindingSetItem::Texture_SRV(0, texture_droplets)) + .addItem( + nvrhi::BindingSetItem::Texture_SRV(1, texture_wipermask)) + .addItem(nvrhi::BindingSetItem::Sampler(0, sampler)) + .addItem(nvrhi::BindingSetItem::Sampler(1, sampler_point)), + binding_layout); + } + + gfx_state.setFramebuffer(framebuffer); + gfx_state.setPipeline(pso_droplets); + gfx_state.addBindingSet(binding_set); + gfx_state.setViewport(nvrhi::ViewportState().addViewportAndScissorRect( + framebuffer->getFramebufferInfo().getViewport())); + + pass.m_command_list_draw->beginMarker(item.m_name.data()); + + pass.m_command_list_draw->setGraphicsState(gfx_state); + + { + NvRenderer::PushConstantsDraw data{}; + data.m_modelview = static_cast( + transpose(static_cast(pass.m_transform) * + static_cast(transform))); + data.m_modelview_history = data.m_modelview; + pass.m_command_list_draw->setPushConstants(&data, sizeof(data)); + } + + if (indexed) + pass.m_command_list_draw->drawIndexed(draw_arguments); + else + pass.m_command_list_draw->draw(draw_arguments); + pass.m_command_list_draw->endMarker(); + } + pass.m_command_list_draw->endMarker(); + } + std::erase_if(binding_sets_per_material, [this](auto const& kv) { + auto const& material = renderer->m_material_cache[kv.first - 1]; + return renderer->GetCurrentFrame() - material.m_last_frame_requested > 100; + }); +} + +bool WindshieldRain::IsRainShader( + NvRenderer::MaterialTemplate const* mt) const { + return mt->m_name == "windshield_rain"; +} + +void WindshieldRain::CreatePso(nvrhi::ICommandList* command_list) { + auto backend = renderer->GetBackend(); + + texture_droplets = renderer->GetTextureManager()->GetRhiTexture( + texture_handle_droplets, command_list); + + sampler = backend->GetDevice()->createSampler( + nvrhi::SamplerDesc() + .setAllFilters(true) + .setMipFilter(false) + .setAllAddressModes(nvrhi::SamplerAddressMode::Repeat)); + + sampler_point = backend->GetDevice()->createSampler( + nvrhi::SamplerDesc().setAllFilters(false).setAllAddressModes( + nvrhi::SamplerAddressMode::Repeat)); + + binding_layout = backend->GetDevice()->createBindingLayout( + nvrhi::BindingLayoutDesc() + .setVisibility(nvrhi::ShaderType::AllGraphics) + .setRegisterSpace(0) + .addItem(nvrhi::BindingLayoutItem::ConstantBuffer(0)) + .addItem(nvrhi::BindingLayoutItem::VolatileConstantBuffer(2)) + .addItem(nvrhi::BindingLayoutItem::PushConstants( + 1, sizeof(NvRenderer::PushConstantsDraw))) + .addItem(nvrhi::BindingLayoutItem::Texture_SRV(0)) + .addItem(nvrhi::BindingLayoutItem::Texture_SRV(1)) + .addItem(nvrhi::BindingLayoutItem::Sampler(0)) + .addItem(nvrhi::BindingLayoutItem::Sampler(1))); + + pso_droplets = backend->GetDevice()->createGraphicsPipeline( + nvrhi::GraphicsPipelineDesc() + .addBindingLayout(binding_layout) + .setVertexShader(vs_rain_anim) + .setInputLayout(renderer->m_input_layout[static_cast( + RendererEnums::DrawType::Model)]) + .setPixelShader(ps_rain_anim) + .setRenderState( + nvrhi::RenderState() + .setDepthStencilState( + nvrhi::DepthStencilState() + .enableDepthTest() + .enableDepthWrite() + .disableStencil() + .setDepthFunc(nvrhi::ComparisonFunc::Greater)) + .setRasterState(nvrhi::RasterState() + .setFillSolid() + .enableDepthClip() + .disableScissor() + .setCullFront()) + .setBlendState(nvrhi::BlendState().setRenderTarget( + 0, nvrhi::BlendState::RenderTarget().disableBlend()))) + .setPrimType(nvrhi::PrimitiveType::TriangleList), + framebuffer); +} diff --git a/betterRenderer/renderer/source/windshield_rain.h b/betterRenderer/renderer/source/windshield_rain.h new file mode 100644 index 00000000..47ee2fc6 --- /dev/null +++ b/betterRenderer/renderer/source/windshield_rain.h @@ -0,0 +1,46 @@ +#pragma once + +#include "nvrenderer/nvrenderer.h" + +struct WindshieldRain { + void Init(NvRenderer *in_renderer); + void Resize(int width, int height); + + uint32_t width; + uint32_t height; + + [[nodiscard]] nvrhi::ITexture *GetTexture() const; + + void Render(NvRenderer::RenderPass const &pass); + bool IsRainShader(NvRenderer::MaterialTemplate const *mt) const; + + size_t texture_handle_droplets; + size_t texture_handle_wipermask; + + nvrhi::TextureHandle texture_droplets; + + nvrhi::SamplerHandle sampler; + nvrhi::SamplerHandle sampler_point; + + void CreatePso(nvrhi::ICommandList *command_list); + + NvRenderer *renderer; + nvrhi::FramebufferHandle framebuffer; + nvrhi::TextureHandle tex_droplets; + nvrhi::TextureHandle tex_depth; + nvrhi::TextureHandle tex_depth_temp; + + nvrhi::ShaderHandle ps_rain_anim; + nvrhi::ShaderHandle vs_rain_anim; + nvrhi::ShaderHandle cs_max_depth; + + nvrhi::BindingSetHandle bindings; + nvrhi::BindingSetHandle bindings_max_depth; + + nvrhi::GraphicsPipelineHandle pso_droplets; + nvrhi::ComputePipelineHandle pso_max_depth; + + nvrhi::BindingLayoutHandle binding_layout; + + std::unordered_map binding_sets_per_material; +}; \ No newline at end of file diff --git a/betterRenderer/shaders/cs_downsample_depth.hlsl b/betterRenderer/shaders/cs_downsample_depth.hlsl new file mode 100644 index 00000000..f124c1a9 --- /dev/null +++ b/betterRenderer/shaders/cs_downsample_depth.hlsl @@ -0,0 +1,15 @@ +Texture2D g_DepthTexture : register(t0); +RWTexture2D g_Output : register(u0); +sampler depth_sampler : register(s0); + +[numthreads(8, 8, 1)] +void main(uint3 PixCoord : SV_DispatchThreadID) { + uint2 dimensions; + g_Output.GetDimensions(dimensions.x, dimensions.y); + + float2 co = float2(PixCoord.xy) / float2(dimensions); + + float4 depths = g_DepthTexture.GatherRed(depth_sampler, co); + g_Output[PixCoord.xy] = min(min(depths.x, depths.y), min(depths.z, depths.w)); +} + diff --git a/betterRenderer/shaders/default_vertex.hlsl b/betterRenderer/shaders/default_vertex.hlsl index 481df309..793a0132 100644 --- a/betterRenderer/shaders/default_vertex.hlsl +++ b/betterRenderer/shaders/default_vertex.hlsl @@ -32,13 +32,19 @@ cbuffer VertexConstants : register(b0) { #include "manul/draw_constants.hlsli" +#ifdef NO_JITTER + #define PROJECTION g_Projection +#else + #define PROJECTION g_JitteredProjection +#endif + VertexOutput main(in VertexInput vs_in) { VertexOutput result; float4x3 model_view = GetModelView(); float4x3 model_view_history = GetModelViewHistory(); float3 view_space_position = mul(float4(vs_in.m_Position, 1.), model_view).xyz; result.m_TexCoord = vs_in.m_TexCoord; - result.m_PositionSV = mul(g_JitteredProjection, float4(view_space_position, 1.)); + result.m_PositionSV = mul(PROJECTION, float4(view_space_position, 1.)); #ifndef PREPASS result.m_Normal = mul(float4(vs_in.m_Normal, 0.), model_view).xyz; result.m_Position = view_space_position; diff --git a/betterRenderer/shaders/manul/material.hlsli b/betterRenderer/shaders/manul/material.hlsli index 5cac890a..4be5dae1 100644 --- a/betterRenderer/shaders/manul/material.hlsli +++ b/betterRenderer/shaders/manul/material.hlsli @@ -48,6 +48,7 @@ struct PixelInput { #include "sky.hlsli" Texture2D g_GbufferDepth : register(t12); +Texture2D g_LitScene : register(t13); #endif void MaterialPass(inout MaterialData material); @@ -61,12 +62,14 @@ PixelOutput main(in PixelInput ps_in) { material.m_Tangent = ps_in.m_Tangent.xyz; material.m_Bitangent = ps_in.m_Tangent.w * cross(ps_in.m_Normal, ps_in.m_Tangent.xyz); material.m_TexCoord = ps_in.m_TexCoord; + material.m_ScreenCoord = uv; material.m_PixelCoord = ps_in.m_PositionSV.xy; material.m_PositionNDC = ps_in.m_PositionCS / ps_in.m_PositionCS.w; material.m_MaterialAlbedoAlpha = float4(1., 1., 1., 1.); material.m_MaterialEmission = float3(0., 0., 0.); material.m_MaterialParams = float4(0., .5, 1., .5); // Metalness.Roughness.Occlusion.Specular material.m_MaterialNormal = float3(0., 0., 1.); + material.m_RefractionOffset = float2(0., 0.); MaterialPass(material); material.m_MaterialAlbedoAlpha.rgb = saturate(material.m_MaterialAlbedoAlpha.rgb); material.m_MaterialEmission = max(material.m_MaterialEmission, 0.); @@ -95,6 +98,12 @@ PixelOutput main(in PixelInput ps_in) { ps_out.m_Motion = (ps_in.m_HistoryPositionCS.xy / ps_in.m_HistoryPositionCS.w) - (ps_in.m_PositionCS.xy / ps_in.m_PositionCS.w); ps_out.m_Motion = ps_out.m_Motion * float2(.5, -.5); #endif + +#if (PASS & FORWARD_LIGHTING) && defined(REFRACTION) + float3 scene_color = g_LitScene.Sample(g_SkySampler, material.m_ScreenCoord + material.m_RefractionOffset * float2(1., -1.)); + ps_out.m_Color.rgb += (1. - ps_out.m_Color.a) * scene_color; + ps_out.m_Color.a = 1.; +#endif return ps_out; } diff --git a/betterRenderer/shaders/manul/material_common.hlsli b/betterRenderer/shaders/manul/material_common.hlsli index 3784d0c3..5011b187 100644 --- a/betterRenderer/shaders/manul/material_common.hlsli +++ b/betterRenderer/shaders/manul/material_common.hlsli @@ -9,12 +9,14 @@ struct MaterialData { float3 m_Bitangent; float3 m_Normal; float2 m_TexCoord; + float2 m_ScreenCoord; uint2 m_PixelCoord; float4 m_PositionNDC; float4 m_MaterialAlbedoAlpha; float3 m_MaterialEmission; float4 m_MaterialParams; // Metalness.Roughness.Occlusion.Specular float3 m_MaterialNormal; + float2 m_RefractionOffset; }; #endif diff --git a/betterRenderer/shaders/manul/view_data.hlsli b/betterRenderer/shaders/manul/view_data.hlsli index fa59f069..8b49d519 100644 --- a/betterRenderer/shaders/manul/view_data.hlsli +++ b/betterRenderer/shaders/manul/view_data.hlsli @@ -12,6 +12,7 @@ cbuffer DrawConstants : register(b2) { float4 g_WiperPos; float4 g_WiperTimerOut; float4 g_WiperTimerReturn; + float g_VerticalFov; } float2 PixelToCS(in float2 pixel, in float2 size) { diff --git a/betterRenderer/shaders/project.manul b/betterRenderer/shaders/project.manul index b8ef027f..1b610d08 100644 --- a/betterRenderer/shaders/project.manul +++ b/betterRenderer/shaders/project.manul @@ -141,6 +141,10 @@ shaders: hint: color default: white no_filter: true + rain: + binding: 3 + hint: linear + default: system/raindrops_buffer masked_shadow_texture: diffuse source: ps_windshield_rain utility: @@ -165,6 +169,14 @@ shaders: entrypoint: main definitions: PREPASS: 1 + windshield_rain_anim: + source: ps_windshield_rain_anim + target: pixel + entrypoint: main + max_depth_4x4: + source: cs_downsample_depth + target: compute + entrypoint: main # Contact shadows # TODO Depth conversion is broken since converting to reversed depth buffer contact_shadows: @@ -181,6 +193,12 @@ shaders: source: default_vertex target: vertex entrypoint: main + default_vertex_no_jitter: + source: default_vertex + target: vertex + entrypoint: main + definitions: + NO_JITTER: 1 default_prepass_vertex: source: default_vertex target: vertex diff --git a/betterRenderer/shaders/ps_windshield_rain.hlsl b/betterRenderer/shaders/ps_windshield_rain.hlsl index e22e1f04..0567490d 100644 --- a/betterRenderer/shaders/ps_windshield_rain.hlsl +++ b/betterRenderer/shaders/ps_windshield_rain.hlsl @@ -1,7 +1,8 @@ +#define REFRACTION 1 + #include "manul/math.hlsli" #include "manul/material.hlsli" #include "manul/color_transform.hlsli" -#include "manul/random.hlsli" sampler diffuse_sampler : register(s0); sampler raindrop_sampler : register(s1); @@ -9,163 +10,76 @@ sampler wipermask_sampler : register(s2); Texture2D diffuse : register(t0); Texture2D raindropsatlas : register(t1); Texture2D wipermask : register(t2); +Texture2D rain : register(t3); -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(raindrop_sampler, offset + uv * 0.5); +// Project the surface gradient (dhdx, dhdy) onto the surface (n, dpdx, dpdy) +float3 CalculateSurfaceGradient(float3 n, float3 dpdx, float3 dpdy, float dhdx, float dhdy) +{ + float3 r1 = cross(dpdy, n); + float3 r2 = cross(n, dpdx); + + return (r1 * dhdx + r2 * dhdy) / dot(dpdx, r1); } -float GetMixFactor(in float2 co, out float side); +// Move the normal away from the surface normal in the opposite surface gradient direction +float3 PerturbNormal(float3 n, float3 dpdx, float3 dpdy, float dhdx, float dhdy) +{ + return normalize(n - CalculateSurfaceGradient(n, dpdx, dpdy, dhdx, dhdy)); +} + +// Calculate the surface normal using screen-space partial derivatives of the height field +float3 CalculateSurfaceNormal(float3 position, float3 normal, float2 gradient) +{ + float3 dpdx = ddx(position); + float3 dpdy = ddy(position); + + float dhdx = gradient.x; + float dhdy = gradient.y; + + return PerturbNormal(normal, dpdx, dpdy, dhdx, dhdy); +} void MaterialPass(inout MaterialData material) { #if PASS & FORWARD_LIGHTING - const float specular_intensity = 1.; - const float wobble_strength = .002; - const float wobble_speed = 30.; + MaterialData material_glass = material; float4 tex_color = diffuse.Sample(diffuse_sampler, material.m_TexCoord); - if (tex_color.a < .01) discard; + uint2 size; + rain.GetDimensions(size.x, size.y); + float droplet_distance = rain.Sample(raindrop_sampler, material.m_ScreenCoord); + float droplet_distance_x = rain.Sample(raindrop_sampler, material.m_ScreenCoord, int2(1, 0)); + float droplet_distance_y = rain.Sample(raindrop_sampler, material.m_ScreenCoord, int2(0, 1)); + float2 gradient = float2(droplet_distance_x - droplet_distance, droplet_distance_y - droplet_distance); + material_glass.m_MaterialAlbedoAlpha.xyz = 0.; + material_glass.m_MaterialNormal = material.m_Normal; + material_glass.m_MaterialParams.g = .2; + float3 normal = CalculateSurfaceNormal(material_glass.m_Position, material_glass.m_Normal, gradient * -.005); + material_glass.m_MaterialNormal = normal; + float cosTheta = saturate(dot(-normalize(material_glass.m_Position), normal)); + material.m_MaterialAlbedoAlpha.a = lerp(.1, FresnelSchlickRoughness(cosTheta, .04, 0.), smoothstep(0., .15, droplet_distance)); - float2 rainCoord = material.m_TexCoord; - float gridSize = ceil(200.); + float3 normal_world = mul((float3x3)g_InverseModelView, material_glass.m_MaterialNormal); - const float numDrops = 20000.; - const float cycleDuration = 4.; + float4 glass_lit; + ApplyMaterialLighting(glass_lit, material_glass, material_glass.m_PixelCoord); - float squareMin = .5 / gridSize; - float squareMax = 1.2 / gridSize; + material.m_MaterialEmission = glass_lit * smoothstep(0., .15, droplet_distance) * saturate(normal_world.y * .5 + .5); - float2 cell = floor(rainCoord * gridSize); + material.m_MaterialAlbedoAlpha.xyz = 0.; + material.m_RefractionOffset = normal.xy * (.005 / (length(material.m_Position) * tan(.5 * g_VerticalFov))) * smoothstep(0., .15, droplet_distance); - float3 dropLayer = 0.; - float dropMaskSum = 0.; + float glass_opacity = FresnelSchlickRoughness(saturate(dot(-normalize(material.m_Position), material.m_Normal)), .2, 0.); + material.m_MaterialEmission = lerp(material.m_MaterialEmission, 0., glass_opacity); + material.m_MaterialAlbedoAlpha.a = lerp(material.m_MaterialAlbedoAlpha.a, 1., glass_opacity); + material.m_MaterialParams.g = .05; + material.m_MaterialNormal = material.m_Normal; - // Grid of 9 droplets in immediate neighbourhood - [unroll] - for (int oy = -1; oy <= 1; ++oy) { - - [unroll] - 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); + material.m_MaterialEmission.xyz = lerp(material.m_MaterialEmission.xyz, 0., tex_color.a); + material.m_MaterialParams.g = lerp(material.m_MaterialParams.g, float4(0., .5, 1., .5), 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(wipermask_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 - [unroll] - 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 diff --git a/betterRenderer/shaders/ps_windshield_rain_anim.hlsl b/betterRenderer/shaders/ps_windshield_rain_anim.hlsl new file mode 100644 index 00000000..81556849 --- /dev/null +++ b/betterRenderer/shaders/ps_windshield_rain_anim.hlsl @@ -0,0 +1,162 @@ +#include "manul/draw_constants.hlsli" +#include "manul/random.hlsli" +#include "manul/view_data.hlsli" + +struct PixelInput { + float3 m_Position : Position; + float3 m_Normal : Normal; + float2 m_TexCoord : TexCoord; + float4 m_Tangent : Tangent; + float4 m_PositionSV : SV_Position; + float4 m_PositionCS : PositionCS; +}; + +sampler raindrop_sampler : register(s0); +sampler wipermask_sampler : register(s1); +Texture2D raindropsatlas : register(t0); +Texture2D wipermask : register(t1); + +float 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(raindrop_sampler, offset + uv * 0.5); +} + +float GetMixFactor(in float2 co, out float side); + +float main(in PixelInput input) : SV_Target0 { + 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 = input.m_TexCoord; + float gridSize = ceil(200.); + + const float numDrops = 20000.; + const float cycleDuration = 4.; + + float squareMin = 1. / gridSize; + float squareMax = 2.5 / gridSize; + + float2 cell = floor(rainCoord * gridSize); + + float3 dropLayer = 0.; + float dropMaskSum = 0.; + + float output = 0.; + + // Grid of 9 droplets in immediate neighbourhood + [unroll] + for (int oy = -1; oy <= 1; ++oy) { + + [unroll] + 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); + float dropTex = getDropTex(choice, localUV) * mask; + output = max(output, dropTex); + } + } + } + + return output; +} + +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(wipermask_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 + [unroll] + 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; +}