From 30150e889eb7e482276510a42f946f6a28605db3 Mon Sep 17 00:00:00 2001 From: myT <> Date: Fri, 29 Mar 2024 04:19:27 +0100 Subject: [PATCH] added sunlight and volumetric lighting fixed depth linearization --- changelog.txt | 7 +- code/renderer/crp_dof_accum.cpp | 3 - code/renderer/crp_dof_gather.cpp | 6 - code/renderer/crp_dynamic_lights.cpp | 46 +- code/renderer/crp_fog.cpp | 198 --- code/renderer/crp_gbuffer_viz.cpp | 23 +- code/renderer/crp_local.h | 177 +- code/renderer/crp_main.cpp | 215 ++- code/renderer/crp_opaque.cpp | 1 + code/renderer/crp_sun_editor.cpp | 210 +++ code/renderer/crp_sunlight.cpp | 137 ++ code/renderer/crp_transp_draw.cpp | 10 +- code/renderer/crp_transp_resolve.cpp | 47 +- code/renderer/crp_volumetric_light.cpp | 1545 +++++++++++++++++ .../shaders/common/state_bits.h.hlsli | 5 + code/renderer/shaders/crp/accumdof_debug.hlsl | 6 +- code/renderer/shaders/crp/add_light.hlsl | 3 +- code/renderer/shaders/crp/alpha_test.h.hlsli | 7 + code/renderer/shaders/crp/common.hlsli | 339 +++- code/renderer/shaders/crp/dl_denoising.hlsl | 17 +- code/renderer/shaders/crp/dl_draw.hlsl | 215 +-- .../renderer/shaders/crp/gatherdof_debug.hlsl | 6 +- .../renderer/shaders/crp/gatherdof_split.hlsl | 6 +- .../shaders/crp/gbufferviz_depth.hlsl | 16 +- code/renderer/shaders/crp/mblur_blur.hlsl | 4 +- code/renderer/shaders/crp/oit.h.hlsli | 6 +- code/renderer/shaders/crp/raytracing.h.hlsli | 115 ++ code/renderer/shaders/crp/scene_view.h.hlsli | 412 ++++- code/renderer/shaders/crp/simplex_noise.hlsli | 146 ++ code/renderer/shaders/crp/sun_blur.hlsl | 118 ++ .../crp/{fog_inside.hlsl => sun_overlay.hlsl} | 42 +- code/renderer/shaders/crp/sun_visibility.hlsl | 67 + code/renderer/shaders/crp/transp_draw.hlsl | 32 +- code/renderer/shaders/crp/transp_resolve.hlsl | 318 +++- code/renderer/shaders/crp/typedefs.h.hlsli | 7 + code/renderer/shaders/crp/vl_common.h.hlsli | 206 +++ .../shaders/crp/vl_debug_ambient.hlsl | 101 ++ .../shaders/crp/vl_debug_extinction.hlsl | 89 + .../shaders/crp/vl_debug_shadow_sun.hlsl | 89 + .../crp/vl_extinction_injection_fog.hlsl | 73 + .../vl_extinction_injection_particles.hlsl | 100 ++ ...lsl => vl_frustum_anisotropy_average.hlsl} | 32 +- .../shaders/crp/vl_frustum_injection_fog.hlsl | 77 + .../crp/vl_frustum_injection_particles.hlsl | 116 ++ .../crp/vl_frustum_inscatter_ambient.hlsl | 86 + .../crp/vl_frustum_inscatter_point_light.hlsl | 82 + .../crp/vl_frustum_inscatter_sunlight.hlsl | 86 + .../shaders/crp/vl_frustum_raymarch.hlsl | 73 + .../crp/vl_frustum_sunlight_visibility.hlsl | 70 + .../shaders/crp/vl_frustum_temporal.hlsl | 62 + .../{fog.hlsli => vl_particles_dispatch.hlsl} | 58 +- .../vl_particles_preprocess_extinction.hlsl | 73 + .../crp/vl_particles_preprocess_frustum.hlsl | 82 + .../shaders/crp/vl_shadow_point_light.hlsl | 65 + code/renderer/shaders/crp/vl_shadow_sun.hlsl | 75 + code/renderer/tr_cmds.cpp | 10 + code/renderer/tr_local.h | 17 +- code/renderer/tr_main.cpp | 18 +- code/shadercomp/shadercomp.cpp | 52 +- makefiles/windows_vs2019/renderer.vcxproj | 79 +- .../windows_vs2019/renderer.vcxproj.filters | 85 +- makefiles/windows_vs2022/renderer.vcxproj | 79 +- .../windows_vs2022/renderer.vcxproj.filters | 85 +- 63 files changed, 5867 insertions(+), 765 deletions(-) delete mode 100644 code/renderer/crp_fog.cpp create mode 100644 code/renderer/crp_sun_editor.cpp create mode 100644 code/renderer/crp_sunlight.cpp create mode 100644 code/renderer/crp_volumetric_light.cpp create mode 100644 code/renderer/shaders/crp/simplex_noise.hlsli create mode 100644 code/renderer/shaders/crp/sun_blur.hlsl rename code/renderer/shaders/crp/{fog_inside.hlsl => sun_overlay.hlsl} (53%) create mode 100644 code/renderer/shaders/crp/sun_visibility.hlsl create mode 100644 code/renderer/shaders/crp/vl_common.h.hlsli create mode 100644 code/renderer/shaders/crp/vl_debug_ambient.hlsl create mode 100644 code/renderer/shaders/crp/vl_debug_extinction.hlsl create mode 100644 code/renderer/shaders/crp/vl_debug_shadow_sun.hlsl create mode 100644 code/renderer/shaders/crp/vl_extinction_injection_fog.hlsl create mode 100644 code/renderer/shaders/crp/vl_extinction_injection_particles.hlsl rename code/renderer/shaders/crp/{fog_outside.hlsl => vl_frustum_anisotropy_average.hlsl} (60%) create mode 100644 code/renderer/shaders/crp/vl_frustum_injection_fog.hlsl create mode 100644 code/renderer/shaders/crp/vl_frustum_injection_particles.hlsl create mode 100644 code/renderer/shaders/crp/vl_frustum_inscatter_ambient.hlsl create mode 100644 code/renderer/shaders/crp/vl_frustum_inscatter_point_light.hlsl create mode 100644 code/renderer/shaders/crp/vl_frustum_inscatter_sunlight.hlsl create mode 100644 code/renderer/shaders/crp/vl_frustum_raymarch.hlsl create mode 100644 code/renderer/shaders/crp/vl_frustum_sunlight_visibility.hlsl create mode 100644 code/renderer/shaders/crp/vl_frustum_temporal.hlsl rename code/renderer/shaders/crp/{fog.hlsli => vl_particles_dispatch.hlsl} (52%) create mode 100644 code/renderer/shaders/crp/vl_particles_preprocess_extinction.hlsl create mode 100644 code/renderer/shaders/crp/vl_particles_preprocess_frustum.hlsl create mode 100644 code/renderer/shaders/crp/vl_shadow_point_light.hlsl create mode 100644 code/renderer/shaders/crp/vl_shadow_sun.hlsl diff --git a/changelog.txt b/changelog.txt index f7f3baa..4e344b2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -111,6 +111,9 @@ add: Cinematic Rendering Pipeline CVars 3 - camera and object blur crp_mblur_exposure <0.0 to 1.0> (default: 0.5) is the exposure time in percentage of frame time miscellaneous: + crp_sunlight <0|1> enables sunlight on non-lightmapped surfaces + crp_volLight <0|1> enables volumetric lighting + fog volumes are lit by ambient light, dynamic lights and sunlight crp_drawNormals <0|1> (default: 0) draws vertex normals as colorized wireframe lines crp_updateRTAS <0|1> (default: 1) enables raytracing acceleration structure builds every frame @@ -133,15 +136,17 @@ chg: reworked renderer with 2 new rendering pipelines - removed all the Direct3D 11 and OpenGL code, now using Direct3D 12 - much improved input latency when V-Sync is enabled - improved frame-time consistency ("frame pacing") - - fog handling has been completely overhauled (faster, simpler, decoupled from surfaces) - MSAA and alpha-to-coverage have been removed - Gameplay Rendering Pipeline (GRP) - improved performance and better worst case input latency - added SMAA for anti-aliasing (gamma-corrected and not applied to UI for best results) - added VRS (Variable Rate Shading) support + - overhauled fog handling (faster, simpler, decoupled from surfaces) - Cinematic Rendering Pipeline (CRP) - order-independent transparency - depth of field (scatter-as-gather or accumulation) + - shadowed point lights and sunlight + - volumetric lighting - all corresponding CVars have the "crp_" prefix chg: removed cl_drawMouseLag, r_backend, r_frameSleep, r_gpuMipGen, r_alphaToCoverage, r_alphaToCoverageMipBoost diff --git a/code/renderer/crp_dof_accum.cpp b/code/renderer/crp_dof_accum.cpp index 84242ba..5a74eb8 100644 --- a/code/renderer/crp_dof_accum.cpp +++ b/code/renderer/crp_dof_accum.cpp @@ -49,8 +49,6 @@ struct DOFDebugRC uint32_t debugMode; // 1: colorized coc, 2: constant intensity far field uint32_t tcScale; float focusDist; - float linearDepthA; // main view, to unproject to WS - float linearDepthB; float maxNearCocCS; float maxFarCocCS; }; @@ -325,7 +323,6 @@ void AccumDepthOfField::DrawDebug() rc.maxFarCocCS = maxFarCocCS; rc.tcScale = GetResolutionScale(); R_MultMatrix(modelViewMatrix, projMatrix, rc.mvp); - RB_LinearDepthConstants(&rc.linearDepthA, &rc.linearDepthB); R_MultMatrix(backEnd.viewParms.world.modelMatrix, backEnd.viewParms.projectionMatrix, mvp); R_InvMatrix(mvp, rc.invMvp); diff --git a/code/renderer/crp_dof_gather.cpp b/code/renderer/crp_dof_gather.cpp index df5b808..9f7099f 100644 --- a/code/renderer/crp_dof_gather.cpp +++ b/code/renderer/crp_dof_gather.cpp @@ -39,8 +39,6 @@ struct DOFDebugRC uint32_t colorTextureIndex; uint32_t depthTextureIndex; uint32_t debugMode; - float linearDepthA; - float linearDepthB; float focusNearMin; float focusNearMax; float focusFarMin; @@ -56,8 +54,6 @@ struct DOFSplitRC uint32_t farColorTextureIndex; uint32_t nearCocTextureIndex; uint32_t farCocTextureIndex; - float linearDepthA; - float linearDepthB; float focusNearMin; float focusNearMax; float focusFarMin; @@ -239,7 +235,6 @@ void GatherDepthOfField::DrawDebug() rc.colorTextureIndex = GetTextureIndexSRV(crp.GetReadRenderTarget()); rc.depthTextureIndex = GetTextureIndexSRV(crp.depthTexture); rc.debugMode = crp_dof_overlay->integer; - RB_LinearDepthConstants(&rc.linearDepthA, &rc.linearDepthB); rc.focusNearMin = crp_gatherDof_focusNearDist->value - 0.5f * crp_gatherDof_focusNearRange->value; rc.focusNearMax = crp_gatherDof_focusNearDist->value + 0.5f * crp_gatherDof_focusNearRange->value; rc.focusFarMin = crp_gatherDof_focusFarDist->value - 0.5f * crp_gatherDof_focusFarRange->value; @@ -272,7 +267,6 @@ void GatherDepthOfField::DrawSplit() rc.farColorTextureIndex = GetTextureIndexUAV(farColorTexture, 0); rc.nearCocTextureIndex = GetTextureIndexUAV(nearCocTexture, 0); rc.farCocTextureIndex = GetTextureIndexUAV(farCocTexture, 0); - RB_LinearDepthConstants(&rc.linearDepthA, &rc.linearDepthB); rc.focusNearMin = crp_gatherDof_focusNearDist->value - 0.5f * crp_gatherDof_focusNearRange->value; rc.focusNearMax = crp_gatherDof_focusNearDist->value + 0.5f * crp_gatherDof_focusNearRange->value; rc.focusFarMin = crp_gatherDof_focusFarDist->value - 0.5f * crp_gatherDof_focusFarRange->value; diff --git a/code/renderer/crp_dynamic_lights.cpp b/code/renderer/crp_dynamic_lights.cpp index 005e8ec..3e0b6c3 100644 --- a/code/renderer/crp_dynamic_lights.cpp +++ b/code/renderer/crp_dynamic_lights.cpp @@ -22,6 +22,7 @@ along with Challenge Quake 3. If not, see . #include "crp_local.h" +#include "shaders/crp/scene_view.h.hlsli" #include "compshaders/crp/fullscreen.h" #include "compshaders/crp/dl_draw.h" #include "compshaders/crp/dl_denoising.h" @@ -31,13 +32,18 @@ along with Challenge Quake 3. If not, see . struct DynamicLightsRC { + DynamicLight light; uint32_t blueNoiseTextureIndex; }; struct DenoiseRC { + vec3_t lightPosition; uint32_t textureIndex; + uint32_t vshadowTextureIndex; + uint32_t vshadowSamplerIndex; + float vshadowWorldScale; }; #pragma pack(pop) @@ -60,27 +66,27 @@ void DynamicLights::Init() { GraphicsPipelineDesc desc("Dynamic Lights Denoising"); MakeFullScreenPipeline(desc, ShaderByteCode(g_dl_denoising_ps)); - desc.AddRenderTarget(0, crp.renderTargetFormat); + desc.AddRenderTarget(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE, crp.renderTargetFormat); denoisingPipeline = CreateGraphicsPipeline(desc); } } -void DynamicLights::Draw() +void DynamicLights::DrawBegin() +{ + CmdBeginBarrier(); + CmdTextureBarrier(crp.lightTexture, ResourceStates::RenderTargetBit); + CmdEndBarrier(); + CmdClearColorTarget(crp.lightTexture, colorBlack); +} + +void DynamicLights::DrawPointLight(const dlight_t& light) { if(r_dynamiclight->integer == 0 || (backEnd.refdef.rdflags & RDF_NOWORLDMODEL) != 0 || - !IsViewportFullscreen(backEnd.viewParms)) - { - return; - } - - if(!crp.raytracing.CanRaytrace() || + !IsViewportFullscreen(backEnd.viewParms) || + !crp.raytracing.CanRaytrace() || backEnd.refdef.num_dlights <= 0) { - CmdBeginBarrier(); - CmdTextureBarrier(crp.lightTexture, ResourceStates::RenderTargetBit); - CmdEndBarrier(); - CmdClearColorTarget(crp.lightTexture, colorBlack); return; } @@ -94,12 +100,15 @@ void DynamicLights::Draw() CmdBeginBarrier(); CmdTextureBarrier(crp.shadingPositionTexture, ResourceStates::PixelShaderAccessBit); CmdTextureBarrier(crp.normalTexture, ResourceStates::PixelShaderAccessBit); - CmdTextureBarrier(crp.noisyLightTexture, ResourceStates::RenderTargetBit); + CmdTextureBarrier(crp.sunlightTexture, ResourceStates::RenderTargetBit); CmdEndBarrier(); DynamicLightsRC rc = {}; + VectorCopy(light.origin, rc.light.position); + VectorCopy(light.color, rc.light.color); + rc.light.radius = light.radius; rc.blueNoiseTextureIndex = GetTextureIndexSRV(crp.blueNoise2D); - CmdBindRenderTargets(1, &crp.noisyLightTexture, NULL); + CmdBindRenderTargets(1, &crp.sunlightTexture, NULL); CmdBindPipeline(pipeline); CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); CmdDraw(3, 0); @@ -112,12 +121,17 @@ void DynamicLights::Draw() CmdBeginBarrier(); CmdTextureBarrier(crp.shadingPositionTexture, ResourceStates::PixelShaderAccessBit); - CmdTextureBarrier(crp.noisyLightTexture, ResourceStates::PixelShaderAccessBit); + CmdTextureBarrier(crp.sunlightTexture, ResourceStates::PixelShaderAccessBit); + CmdTextureBarrier(crp.volumetricLight.pointShadowTexture, ResourceStates::PixelShaderAccessBit); CmdTextureBarrier(crp.lightTexture, ResourceStates::RenderTargetBit); CmdEndBarrier(); DenoiseRC rc = {}; - rc.textureIndex = GetTextureIndexSRV(crp.noisyLightTexture); + VectorCopy(light.origin, rc.lightPosition); + rc.textureIndex = GetTextureIndexSRV(crp.sunlightTexture); + rc.vshadowTextureIndex = GetTextureIndexSRV(crp.volumetricLight.pointShadowTexture); + rc.vshadowSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear); + rc.vshadowWorldScale = crp.volumetricLight.pointShadowVolumeScale; CmdBindRenderTargets(1, &crp.lightTexture, NULL); CmdBindPipeline(denoisingPipeline); CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); diff --git a/code/renderer/crp_fog.cpp b/code/renderer/crp_fog.cpp deleted file mode 100644 index 4aecc34..0000000 --- a/code/renderer/crp_fog.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/* -=========================================================================== -Copyright (C) 2024 Gian 'myT' Schellenbaum - -This file is part of Challenge Quake 3 (CNQ3). - -Challenge Quake 3 is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Challenge Quake 3 is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Challenge Quake 3. If not, see . -=========================================================================== -*/ -// Cinematic Rendering Pipeline - fog volumes - - -#include "crp_local.h" -#include "compshaders/crp/fog_outside.h" -#include "compshaders/crp/fog_inside.h" - - -#pragma pack(push, 4) -struct FogRC -{ - float modelViewMatrix[16]; - float projectionMatrix[16]; - float boxMin[4]; - float boxMax[4]; - float color[4]; - float depth; - float linearDepthA; - float linearDepthB; - uint32_t depthTextureIndex; -}; -#pragma pack(pop) - - -void Fog::Init() -{ - { - const uint32_t indices[] = - { - 0, 1, 2, 2, 1, 3, - 4, 0, 6, 6, 0, 2, - 7, 5, 6, 6, 5, 4, - 3, 1, 7, 7, 1, 5, - 4, 5, 0, 0, 5, 1, - 3, 7, 2, 2, 7, 6 - }; - - BufferDesc desc("box index", sizeof(indices), ResourceStates::IndexBufferBit); - desc.shortLifeTime = true; - boxIndexBuffer = CreateBuffer(desc); - - uint8_t* mapped = BeginBufferUpload(boxIndexBuffer); - memcpy(mapped, indices, sizeof(indices)); - EndBufferUpload(boxIndexBuffer); - } - { - const float vertices[] = - { - 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, - 0.0f, 0.0f, 1.0f, - 1.0f, 0.0f, 1.0f - }; - - BufferDesc desc("box vertex", sizeof(vertices), ResourceStates::VertexBufferBit); - desc.shortLifeTime = true; - boxVertexBuffer = CreateBuffer(desc); - - uint8_t* mapped = BeginBufferUpload(boxVertexBuffer); - memcpy(mapped, vertices, sizeof(vertices)); - EndBufferUpload(boxVertexBuffer); - } - { - GraphicsPipelineDesc desc("fog outside"); - desc.shortLifeTime = true; - desc.vertexShader = ShaderByteCode(g_outside_vs); - desc.pixelShader = ShaderByteCode(g_outside_ps); - desc.depthStencil.DisableDepth(); - desc.rasterizer.cullMode = CT_BACK_SIDED; - desc.rasterizer.polygonOffset = false; - desc.rasterizer.clampDepth = true; - desc.AddRenderTarget(GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA, crp.renderTargetFormat); - desc.vertexLayout.AddAttribute(0, ShaderSemantic::Position, DataType::Float32, 3, 0); - fogOutsidePipeline = CreateGraphicsPipeline(desc); - } - { - GraphicsPipelineDesc desc("fog inside"); - desc.shortLifeTime = true; - desc.vertexShader = ShaderByteCode(g_inside_vs); - desc.pixelShader = ShaderByteCode(g_inside_ps); - desc.depthStencil.DisableDepth(); - desc.rasterizer.cullMode = CT_FRONT_SIDED; - desc.rasterizer.polygonOffset = false; - desc.rasterizer.clampDepth = true; - desc.AddRenderTarget(GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA, crp.renderTargetFormat); - desc.vertexLayout.AddAttribute(0, ShaderSemantic::Position, DataType::Float32, 3, 0); - fogInsidePipeline = CreateGraphicsPipeline(desc); - } -} - -void Fog::Draw() -{ - // @NOTE: fog 0 is invalid, it must be skipped - if(tr.world == NULL || - tr.world->numfogs <= 1 || - (backEnd.refdef.rdflags & RDF_NOWORLDMODEL) != 0) - { - return; - } - - SCOPED_RENDER_PASS("Fog", 0.25f, 0.125f, 0.0f); - - srp.renderMode = RenderMode::World; - - const uint32_t stride = sizeof(vec3_t); - CmdBindVertexBuffers(1, &boxVertexBuffer, &stride, NULL); - CmdBindIndexBuffer(boxIndexBuffer, IndexType::UInt32, 0); - - CmdBeginBarrier(); - CmdTextureBarrier(crp.depthTexture, ResourceStates::PixelShaderAccessBit); - CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit); - CmdEndBarrier(); - - CmdBindRenderTargets(1, &crp.renderTarget, NULL); - - int insideIndex = -1; - for(int f = 1; f < tr.world->numfogs; ++f) - { - const fog_t& fog = tr.world->fogs[f]; - - bool inside = true; - for(int a = 0; a < 3; ++a) - { - if(backEnd.viewParms.orient.origin[a] <= fog.bounds[0][a] || - backEnd.viewParms.orient.origin[a] >= fog.bounds[1][a]) - { - inside = false; - break; - } - } - - if(inside) - { - insideIndex = f; - break; - } - } - - FogRC rc = {}; - memcpy(rc.modelViewMatrix, backEnd.viewParms.world.modelMatrix, sizeof(rc.modelViewMatrix)); - memcpy(rc.projectionMatrix, backEnd.viewParms.projectionMatrix, sizeof(rc.projectionMatrix)); - RB_LinearDepthConstants(&rc.linearDepthA, &rc.linearDepthB); - rc.depthTextureIndex = GetTextureIndexSRV(crp.depthTexture); - - CmdBindPipeline(fogOutsidePipeline); - for(int f = 1; f < tr.world->numfogs; ++f) - { - if(f == insideIndex) - { - continue; - } - - const fog_t& fog = tr.world->fogs[f]; - VectorScale(fog.parms.color, tr.identityLight, rc.color); - rc.depth = fog.parms.depthForOpaque; - VectorCopy(fog.bounds[0], rc.boxMin); - VectorCopy(fog.bounds[1], rc.boxMax); - CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); - CmdDrawIndexed(36, 0, 0); - } - - if(insideIndex > 0) - { - CmdBindPipeline(fogInsidePipeline); - - const fog_t& fog = tr.world->fogs[insideIndex]; - VectorScale(fog.parms.color, tr.identityLight, rc.color); - rc.depth = fog.parms.depthForOpaque; - VectorCopy(fog.bounds[0], rc.boxMin); - VectorCopy(fog.bounds[1], rc.boxMax); - CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); - CmdDrawIndexed(36, 0, 0); - } -} diff --git a/code/renderer/crp_gbuffer_viz.cpp b/code/renderer/crp_gbuffer_viz.cpp index f8827c5..a953065 100644 --- a/code/renderer/crp_gbuffer_viz.cpp +++ b/code/renderer/crp_gbuffer_viz.cpp @@ -32,14 +32,6 @@ along with Challenge Quake 3. If not, see . #pragma pack(push, 4) -struct LinearizeDepthRC -{ - uint32_t depthTextureIndex; - float linearDepthA; - float linearDepthB; - float zFarInv; -}; - struct DecodeNormalsRC { uint32_t normalTextureIndex; @@ -114,14 +106,8 @@ void GBufferViz::DrawGUI() CmdTextureBarrier(renderTarget, ResourceStates::RenderTargetBit); CmdEndBarrier(); - LinearizeDepthRC rc = {}; - rc.depthTextureIndex = GetTextureIndexSRV(crp.depthTexture); - RB_LinearDepthConstants(&rc.linearDepthA, &rc.linearDepthB); - rc.zFarInv = 1.0f / backEnd.viewParms.zFar; - CmdBindRenderTargets(1, &renderTarget, NULL); CmdBindPipeline(linearizeDepthPipeline); - CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); CmdDraw(3, 0); } else if(textureIndex == GBufferTexture::Normal) @@ -205,6 +191,12 @@ void GBufferViz::DrawGUI() ImGui::SameLine(); ImGui::RadioButton("Light", &textureIndex, GBufferTexture::Light); + ImGui::RadioButton("Sunlight", &textureIndex, GBufferTexture::Sunlight); + ImGui::SameLine(); + ImGui::RadioButton("Sunlight Visibility", &textureIndex, GBufferTexture::SunlightVisibility); + ImGui::SameLine(); + ImGui::RadioButton("Sunlight Penumbra", &textureIndex, GBufferTexture::SunlightPenumbra); + ImGui::RadioButton("Motion Raw", &textureIndex, GBufferTexture::MotionVectorRaw); ImGui::SameLine(); ImGui::RadioButton("Motion MB", &textureIndex, GBufferTexture::MotionVectorMB); @@ -232,6 +224,9 @@ void GBufferViz::DrawGUI() case GBufferTexture::ShadingPositionDelta: texture = renderTarget; break; case GBufferTexture::MotionVectorRaw: texture = renderTarget; break; case GBufferTexture::MotionVectorMB: texture = renderTarget; break; + case GBufferTexture::SunlightVisibility: texture = crp.sunlight.visibilityTexture; break; + case GBufferTexture::SunlightPenumbra: texture = crp.sunlight.penumbraTexture; break; + case GBufferTexture::Sunlight: texture = crp.sunlightTexture; break; default: Q_assert(!"Invalid G-Buffer texture index"); texture = crp.lightTexture; break; } ImGui::Image((ImTextureID)GetTextureIndexSRV(texture), resolution); diff --git a/code/renderer/crp_local.h b/code/renderer/crp_local.h index 6184c99..87bc26d 100644 --- a/code/renderer/crp_local.h +++ b/code/renderer/crp_local.h @@ -42,6 +42,8 @@ extern cvar_t* crp_accumDof_samples; extern cvar_t* crp_accumDof_preview; extern cvar_t* crp_mblur; extern cvar_t* crp_mblur_exposure; +extern cvar_t* crp_sunlight; +extern cvar_t* crp_volLight; extern cvar_t* crp_drawNormals; extern cvar_t* crp_updateRTAS; extern cvar_t* crp_debug0; @@ -169,25 +171,14 @@ private: bool batchDepthHack; }; -struct Fog -{ - void Init(); - void Draw(); - -private: - HBuffer boxIndexBuffer; - HBuffer boxVertexBuffer; - HPipeline fogInsidePipeline; - HPipeline fogOutsidePipeline; -}; - struct TranspResolve { void Init(); void Draw(const drawSceneViewCommand_t& cmd); private: - HPipeline pipeline; + HPipeline noVolPipeline; + HPipeline volPipeline; }; struct ToneMap @@ -305,6 +296,9 @@ private: ShadingPositionDelta, MotionVectorRaw, MotionVectorMB, + SunlightVisibility, + SunlightPenumbra, + Sunlight, Count }; }; @@ -322,7 +316,8 @@ private: struct DynamicLights { void Init(); - void Draw(); + void DrawBegin(); + void DrawPointLight(const dlight_t& light); bool WantRTASUpdate(const trRefdef_t& scene); private: @@ -416,6 +411,150 @@ private: uint32_t staticTLASInstanceCount = 0; }; +struct SunlightEditor +{ + void Init(); + void ProcessWorld(world_t& world); + void DrawOverlay(); + void DrawGUI(); + +private: + HPipeline pipeline; + bool windowActive = false; + bool drawOverlay = false; + const shader_t* skyShader = NULL; +}; + +struct Sunlight +{ + void Init(); + void Draw(); + bool WantRTASUpdate(const trRefdef_t& scene); + +//private: + HPipeline visibilityPipeline; + HPipeline blurPipeline; + HTexture visibilityTexture; + HTexture penumbraTexture; +}; + +struct VolumetricLight +{ + void Init(); + void ProcessWorld(world_t& world); + void DrawBegin(); + void DrawPointLight(const dlight_t& light); + void DrawSunlight(); + void DrawEnd(); + void DrawDebug(); + void DrawGUI(); + bool WantRTASUpdate(const trRefdef_t& scene); + bool ShouldDraw(); + bool ShouldDrawDebug(); + bool LoadFogFile(const char* filePath); + void SaveFogFile(const char* filePath); + + // GUI/user-friendly data layout + struct Fog + { + vec3_t scatterColor; + vec3_t emissiveColor; + vec3_t boxMin; + vec3_t boxMax; + float extinction; + float albedo; // scatter / extinction + float emissive; + float anisotropy; + float noiseStrength; + float noiseSpatialPeriod; + float noiseTimePeriod; + bool isGlobalFog; + bool isHeightFog; + }; + +#if defined(VL_CPU_PARTICLES) + HPipeline particleDispatchPipeline; + HPipeline particlePreProcessExtinctionPipeline; + HPipeline particlePreProcessFrustumPipeline; + HPipeline extinctionParticlePipeline; + HPipeline frustumParticlePipeline; + HBuffer particleBuffer; // should be double buffered... + HBuffer particleHitBuffer; // for each tile, is there at least 1 particle? + HBuffer particleTileBuffer; // array of tiles, each tile has a uint3 index + HBuffer particleDispatchBuffer; // indirect dispatch buffer + HBuffer particleDispatchClearBuffer; // indirect dispatch buffer with values (0, 1, 1) + uint32_t particleCount; +#endif + + Fog fogs[64]; + uint32_t fogCount = 0; + HPipeline extinctionFogPipeline; + HPipeline frustumAmbientPipeline; + HPipeline frustumAnisotropyPipeline; + HPipeline frustumFogPipeline; + HPipeline frustumPointLightScatterPipeline; + HPipeline frustumRaymarchPipeline; + HPipeline frustumSunlightVisPipeline; + HPipeline frustumTemporalPipeline; + HPipeline pointLightShadowPipeline; + HPipeline sunlightScatterPipeline; + HPipeline sunlightShadowPipeline; + HPipeline ambientVizPipeline; + HPipeline extinctionVizPipeline; + HPipeline sunShadowVizPipeline; + HTexture materialTextureA; // frustum, RGB = scatter, A = absorption + HTexture materialTextureB; // frustum, RGB = emissive, A = anisotropy (g) + HTexture materialTextureC; // frustum, R = anisotropy (g) weight sum + HTexture sunlightVisTexture; // frustum, R = sunlight visibility + HTexture prevSunlightVisTexture; // frustum, R = sunlight visibility + HTexture scatterExtTexture; // frustum, RGB = in-scattering, A = extinction + HTexture scatterTransTexture; // frustum, RGB = in-scattering, A = transmittance + HTexture extinctionTextures[4]; // cube, R = extinction + HTexture pointShadowTexture; // cube, R = transmittance + HTexture sunShadowTextures[4]; // cube, R = transmittance + HTexture ambientLightTextureA; // box, can be NULL, RGB = ambient.rgb, A = directional.r + HTexture ambientLightTextureB; // box, can be NULL, RG = directional.gb, B = longitude, A = latitude + uvec3_t frustumSize; // frustum volume pixel counts + uvec3_t frustumTileScale; // by how much do we divide + uvec3_t frustumTileSize; // frustum volume tile pixel counts + uvec3_t extinctionSize; // extinction volume pixel counts + uvec3_t extinctionTileScale; // by how much do we divide + uvec3_t extinctionTileSize; // extinction volume tile pixel counts + uint32_t shadowPixelCount; // @TODO: transform into uvec3_t as well + uint32_t jitterCounter; + uint32_t depthMip; // has to match the X/Y scale of frustumSize + vec4_t extinctionVolumeScale; // how many world units per pixel + float pointShadowVolumeScale; // how many world units per pixel + vec4_t sunShadowVolumeScale; // how many world units per pixel + uvec3_t sunShadowSize; // sunlight shadow volume pixel counts + vec3_t ambientColor; + float ambientIntensity; + vec3_t debugCameraPosition; + float debugBoxScale = 1.0f; + float debugExtinctionScale = 50.0f; + int debugExtinctionCascadeIndex = 0; + int debugSunShadowCascadeIndex = 0; + bool drawExtinctionDebug = false; + bool drawSunShadowDebug = false; + bool drawAmbientDebug = false; + bool lockCameraPosition = false; + bool firstFrame = true; + bool windowActive = false; + vec3_t mapBoxMin; + vec3_t mapBoxMax; + vec3_t lightGridCenter; + float debugSphereScale = 0.5f; +}; + +#pragma pack(push, 1) +struct SunlightData +{ + vec3_t direction; + vec3_t color; + float intensity; +}; +#pragma pack(pop) + struct BaseBufferId { enum Id @@ -498,6 +637,7 @@ struct CRP : IRenderPipeline void Blit(HTexture destination, HTexture source, const char* passName, bool hdr, const vec2_t tcScale, const vec2_t tcBias); void BlitRenderTarget(HTexture destination, const char* passName); void DrawSceneView(const drawSceneViewCommand_t& cmd); + void DrawSceneView3D(const drawSceneViewCommand_t& cmd); void UploadSceneViewData(); void BuildDepthPyramid(); @@ -513,7 +653,7 @@ struct CRP : IRenderPipeline HTexture normalTexture; HTexture motionVectorTexture; // raw, for TAA/denoisers/etc HTexture motionVectorMBTexture; // mangled, for motion blur only - HTexture noisyLightTexture; + HTexture sunlightTexture; HTexture lightTexture; HTexture shadingPositionTexture; HTexture renderTarget; @@ -557,14 +697,19 @@ struct CRP : IRenderPipeline GatherDepthOfField gatherDof; AccumDepthOfField accumDof; MotionBlur motionBlur; - Fog fog; Magnifier magnifier; DynamicLights dynamicLights; + Sunlight sunlight; + VolumetricLight volumetricLight; Raytracing raytracing; GBufferViz gbufferViz; + SunlightEditor sunlightEditor; + SunlightData sunlightData; }; HPipeline CreateComputePipeline(const char* name, const ShaderByteCode& shader); void MakeFullScreenPipeline(GraphicsPipelineDesc& desc, const ShaderByteCode& pixelShader); +void DirectionToAzimuthInclination(float* sc, const float* dir); +void AzimuthInclinationToDirection(float* dir, const float* sc); extern CRP crp; diff --git a/code/renderer/crp_main.cpp b/code/renderer/crp_main.cpp index 475b492..6577ed0 100644 --- a/code/renderer/crp_main.cpp +++ b/code/renderer/crp_main.cpp @@ -76,6 +76,8 @@ cvar_t* crp_accumDof_samples; cvar_t* crp_accumDof_preview; cvar_t* crp_mblur; cvar_t* crp_mblur_exposure; +cvar_t* crp_sunlight; +cvar_t* crp_volLight; cvar_t* crp_drawNormals; cvar_t* crp_updateRTAS; cvar_t* crp_debug0; @@ -181,6 +183,14 @@ static const cvarTableItem_t crp_cvars[] = "This is the exposure time in percentage of frame time.", "Motion blur exposure", CVARCAT_GRAPHICS, "Exposure time in percentage of frame time", "" }, + { + &crp_sunlight, "crp_sunlight", "1", CVAR_ARCHIVE, CVART_BOOL, NULL, NULL, "sunlight", + "Sunlight", CVARCAT_GRAPHICS, "Sunlight on non-lightmapped surfaces", "" + }, + { + &crp_volLight, "crp_volLight", "1", CVAR_ARCHIVE, CVART_BOOL, NULL, NULL, "volumetric light", + "Volumetric light", CVARCAT_GRAPHICS, "Sunlight scattering through the air", "" + }, { &crp_drawNormals, "crp_drawNormals", "0", CVAR_TEMP, CVART_BOOL, NULL, NULL, "draws vertex normals", "Draw vertex normals", CVARCAT_GRAPHICS | CVARCAT_DEBUGGING, "", "" @@ -256,6 +266,24 @@ static HTexture LoadTexture(const char* name, int flags, textureWrap_t glWrapCla return image->texture; } +static void SunToZMatrix(matrix3x3_t rot) +{ + float sc[2]; + DirectionToAzimuthInclination(sc, crp.sunlightData.direction); + const float azi = -(sc[0] + M_PI / 2.0f); + const float ele = M_PI - sc[1]; + const float rol = 0.0f; + rot[0] = cosf(rol) * cosf(azi) - sinf(rol) * cosf(ele) * sinf(azi); + rot[3] = sinf(rol) * cosf(azi) + cosf(rol) * cosf(ele) * sinf(azi); + rot[6] = sinf(ele) * sinf(azi); + rot[1] = -cosf(rol) * sinf(azi) - sinf(rol) * cosf(ele) * cosf(azi); + rot[4] = -sinf(rol) * sinf(azi) + cosf(rol) * cosf(ele) * cosf(azi); + rot[7] = sinf(ele) * cosf(azi); + rot[2] = sinf(rol) * sinf(ele); + rot[5] = -cosf(rol) * sinf(ele); + rot[8] = cosf(ele); +} + HPipeline CreateComputePipeline(const char* name, const ShaderByteCode& shader) { ComputePipelineDesc desc(name); @@ -504,7 +532,7 @@ void CRP::Init() desc.SetClearColor(colorBlack); lightTexture = RHI::CreateTexture(desc); desc.name = "GBuffer raw direct light"; - noisyLightTexture = RHI::CreateTexture(desc); + sunlightTexture = RHI::CreateTexture(desc); } { @@ -548,6 +576,7 @@ void CRP::Init() sceneViewBuffer = CreateBuffer(desc); } + raytracing.Init(); ui.Init(true, ShaderByteCode(g_ui_vs), ShaderByteCode(g_ui_ps), renderTargetFormat, RHI_MAKE_NULL_HANDLE(), NULL); imgui.Init(true, ShaderByteCode(g_imgui_vs), ShaderByteCode(g_imgui_ps), renderTargetFormat, RHI_MAKE_NULL_HANDLE(), NULL); nuklear.Init(true, ShaderByteCode(g_nuklear_vs), ShaderByteCode(g_nuklear_ps), renderTargetFormat, RHI_MAKE_NULL_HANDLE(), NULL); @@ -560,11 +589,12 @@ void CRP::Init() gatherDof.Init(); accumDof.Init(); motionBlur.Init(); - fog.Init(); magnifier.Init(); dynamicLights.Init(); - raytracing.Init(); + sunlight.Init(); + volumetricLight.Init(); gbufferViz.Init(); + sunlightEditor.Init(); srp.firstInit = false; } @@ -592,7 +622,10 @@ void CRP::BeginFrame() imgui.BeginFrame(); // must be run outside of the RHI::BeginFrame/RHI::EndFrame pair - const bool rtasUpdate = dynamicLights.WantRTASUpdate(tr.rtRefdef); + const bool rtasUpdate = + dynamicLights.WantRTASUpdate(tr.rtRefdef) || + sunlight.WantRTASUpdate(tr.rtRefdef) || + volumetricLight.WantRTASUpdate(tr.rtRefdef); raytracing.BeginFrame(rtasUpdate); RHI::BeginFrame(); @@ -632,6 +665,8 @@ void CRP::EndFrame() srp.DrawGUI(); gbufferViz.DrawGUI(); magnifier.DrawGUI(); + sunlightEditor.DrawGUI(); + volumetricLight.DrawGUI(); imgui.Draw(renderTarget); toneMap.DrawToneMap(); magnifier.Draw(); @@ -718,6 +753,8 @@ void CRP::EndTextureUpload() void CRP::ProcessWorld(world_t& world) { raytracing.ProcessWorld(world); + sunlightEditor.ProcessWorld(world); + volumetricLight.ProcessWorld(world); } void CRP::ProcessModel(model_t&) @@ -841,6 +878,45 @@ void CRP::TessellationOverflow() tess.numVertexes = 0; } +void CRP::DrawSceneView3D(const drawSceneViewCommand_t& cmd) +{ + const int lightCount = backEnd.refdef.num_dlights; + + prepass.Draw(cmd); + BuildDepthPyramid(); + dynamicLights.DrawBegin(); + if(volumetricLight.ShouldDraw()) + { + volumetricLight.DrawBegin(); + if(raytracing.CanRaytrace()) + { + { + SCOPED_RENDER_PASS("VL/DL Point Lights", 1.0f, 1.0f, 1.0f); + for(int i = 0; i < lightCount; i++) + { + volumetricLight.DrawPointLight(backEnd.refdef.dlights[i]); + dynamicLights.DrawPointLight(backEnd.refdef.dlights[i]); + } + } + volumetricLight.DrawSunlight(); + } + volumetricLight.DrawEnd(); + } + else + { + SCOPED_RENDER_PASS("DL Point Lights", 1.0f, 1.0f, 1.0f); + for(int i = 0; i < lightCount; i++) + { + dynamicLights.DrawPointLight(backEnd.refdef.dlights[i]); + } + } + sunlight.Draw(); + opaque.Draw(cmd); + volumetricLight.DrawDebug(); + transp.Draw(cmd); + transpResolve.Draw(cmd); +} + void CRP::DrawSceneView(const drawSceneViewCommand_t& cmd) { const viewParms_t& vp = cmd.viewParms; @@ -880,12 +956,7 @@ void CRP::DrawSceneView(const drawSceneViewCommand_t& cmd) CmdTextureBarrier(renderTarget, ResourceStates::RenderTargetBit); CmdEndBarrier(); CmdClearColorTarget(renderTarget, cmd.clearColor, &rect); - prepass.Draw(newCmd); - dynamicLights.Draw(); - opaque.Draw(newCmd); - fog.Draw(); - transp.Draw(newCmd); - transpResolve.Draw(newCmd); + DrawSceneView3D(newCmd); accumDof.Accumulate(); // geometry allocation is a linear allocation instead of a ring buffer @@ -908,12 +979,7 @@ void CRP::DrawSceneView(const drawSceneViewCommand_t& cmd) backEnd.viewParms = cmd.viewParms; UploadSceneViewData(); - prepass.Draw(cmd); - dynamicLights.Draw(); - opaque.Draw(cmd); - fog.Draw(); - transp.Draw(cmd); - transpResolve.Draw(cmd); + DrawSceneView3D(cmd); CmdSetViewportAndScissor(vp.viewportX, vp.viewportY, vp.viewportWidth, vp.viewportHeight); gatherDof.Draw(); if(freezeFrame == FreezeFrame::PendingBeforeMB && @@ -932,6 +998,8 @@ void CRP::DrawSceneView(const drawSceneViewCommand_t& cmd) } motionBlur.Draw(); } + + sunlightEditor.DrawOverlay(); } void CRP::UploadSceneViewData() @@ -944,62 +1012,91 @@ void CRP::UploadSceneViewData() SCOPED_DEBUG_LABEL("Scene View Upload", 1.0f, 1.0f, 1.0f); - const trRefdef_t& refdef = backEnd.refdef; const viewParms_t& vp = backEnd.viewParms; const HBuffer uploadBuffer = sceneViewUploadBuffers[GetFrameIndex()]; const uint32_t uploadByteOffset = sceneViewIndex * SceneViewConst::StructBytes; - SceneView& dest = *(SceneView*)(MapBuffer(uploadBuffer) + uploadByteOffset); + if(!vp.isPortal && IsViewportFullscreen(vp)) + { + Q_assert(tr.currZFar == vp.zFar); + Q_assert(tr.currZNear == vp.zNear); + } + + SceneView scene = {}; + +#if defined(_DEBUG) + scene.debug[0] = crp_debug0->value; + scene.debug[1] = crp_debug1->value; + scene.debug[2] = crp_debug2->value; + scene.debug[3] = crp_debug3->value; +#endif + + scene.frameSeed = (float)rand() / (float)RAND_MAX; // @NOTE: yes, world.modelMatrix is actually the view matrix // it's the model-view matrix for the world entity, thus the view matrix - memcpy(dest.projectionMatrix, vp.projectionMatrix, sizeof(dest.projectionMatrix)); - R_InvMatrix(dest.projectionMatrix, dest.invProjectionMatrix); - memcpy(dest.viewMatrix, vp.world.modelMatrix, sizeof(dest.viewMatrix)); - R_InvMatrix(dest.viewMatrix, dest.invViewMatrix); + memcpy(scene.projectionMatrix, vp.projectionMatrix, sizeof(scene.projectionMatrix)); + R_InvMatrix(scene.projectionMatrix, scene.invProjectionMatrix); + memcpy(scene.viewMatrix, vp.world.modelMatrix, sizeof(scene.viewMatrix)); + R_InvMatrix(scene.viewMatrix, scene.invViewMatrix); - memcpy(dest.prevViewProjMatrix, tr.prevViewProjMatrix, sizeof(dest.prevViewProjMatrix)); - memcpy(dest.prevViewMatrix, tr.prevViewMatrix, sizeof(dest.prevViewMatrix)); - memcpy(dest.prevProjectionMatrix, tr.prevProjMatrix, sizeof(dest.prevProjectionMatrix)); + memcpy(scene.prevViewProjMatrix, tr.prevViewProjMatrix, sizeof(scene.prevViewProjMatrix)); + memcpy(scene.prevViewMatrix, tr.prevViewMatrix, sizeof(scene.prevViewMatrix)); + memcpy(scene.prevProjectionMatrix, tr.prevProjMatrix, sizeof(scene.prevProjectionMatrix)); - RB_CreateClipPlane(dest.clipPlane); -#if defined(_DEBUG) - dest.debug[0] = crp_debug0->value; - dest.debug[1] = crp_debug1->value; - dest.debug[2] = crp_debug2->value; - dest.debug[3] = crp_debug3->value; -#else - const uint32_t deadBeef = 0xDEADBEEF; - dest.debug[0] = *(const float*)&deadBeef; - dest.debug[1] = *(const float*)&deadBeef; - dest.debug[2] = *(const float*)&deadBeef; - dest.debug[3] = *(const float*)&deadBeef; -#endif - dest.sceneViewIndex = sceneViewIndex; - dest.frameIndex = tr.frameCount; - dest.depthTextureIndex = GetTextureIndexSRV(depthTexture); - dest.normalTextureIndex = GetTextureIndexSRV(normalTexture); - dest.shadingPositionTextureIndex = GetTextureIndexSRV(shadingPositionTexture); - dest.motionVectorTextureIndex = GetTextureIndexSRV(motionVectorTexture); - dest.motionVectorMBTextureIndex = GetTextureIndexSRV(motionVectorMBTexture); - dest.lightTextureIndex = GetTextureIndexSRV(lightTexture); - dest.tlasBufferIndex = raytracing.GetTLASBufferIndex(); - dest.tlasInstanceBufferIndex = raytracing.GetInstanceBufferIndex(); - dest.lightCount = refdef.num_dlights; - RB_LinearDepthConstants(&dest.linearDepthA, &dest.linearDepthB); - dest.zNear = vp.zNear; - dest.zFar = vp.zFar; + // we want the first Z slice to be closest to the sun to simplify ray marching + vec3_t zDown; + VectorSet(zDown, 0, 0, -1); + SunToZMatrix(scene.sunToZMatrix); + R_InvMatrix3x3(scene.sunToZMatrix, scene.zToSunMatrix); - for(int i = 0; i < refdef.num_dlights; i++) + RB_CreateClipPlane(scene.clipPlane); + VectorCopy(vp.world.viewOrigin, scene.cameraPosition); + VectorCopy(tr.prevCameraPosition, scene.prevCameraPosition); + VectorCopy(vp.orient.axis[0], scene.cameraForward); + VectorCopy(vp.orient.axis[1], scene.cameraLeft); + VectorCopy(vp.orient.axis[2], scene.cameraUp); + scene.sceneViewIndex = sceneViewIndex; + scene.frameIndex = tr.frameCount; + scene.depthTextureIndex = GetTextureIndexSRV(depthTexture); + scene.depthMinMaxTextureIndex = GetTextureIndexSRV(depthMinMaxTexture); + scene.normalTextureIndex = GetTextureIndexSRV(normalTexture); + scene.shadingPositionTextureIndex = GetTextureIndexSRV(shadingPositionTexture); + scene.motionVectorTextureIndex = GetTextureIndexSRV(motionVectorTexture); + scene.motionVectorMBTextureIndex = GetTextureIndexSRV(motionVectorMBTexture); + scene.lightTextureIndex = GetTextureIndexSRV(lightTexture); + scene.sunlightTextureIndex = GetTextureIndexSRV(sunlightTexture); + scene.tlasBufferIndex = raytracing.GetTLASBufferIndex(); + scene.tlasInstanceBufferIndex = raytracing.GetInstanceBufferIndex(); + RB_LinearDepthConstants(scene.linearDepthConstants); + scene.zNear = vp.zNear; + scene.zFar = vp.zFar; + scene.prevZNear = tr.prevZNear; + scene.prevZFar = tr.prevZFar; + + VectorCopy(sunlightData.direction, scene.sunDirection); + VectorCopy(sunlightData.color, scene.sunColor); + scene.sunIntensity = sunlightData.intensity; + + VectorCopy(volumetricLight.ambientColor, scene.ambientColor); + scene.ambientIntensity = volumetricLight.ambientIntensity; + + Vector4Copy(volumetricLight.extinctionVolumeScale, scene.extinctionWorldScale); + for(int c = 0; c < 4; c++) { - const dlight_t& srcLight = refdef.dlights[i]; - DynamicLight& destLight = dest.lights[i]; - VectorCopy(srcLight.origin, destLight.position); - VectorCopy(srcLight.color, destLight.color); - destLight.radius = srcLight.radius; - destLight.padding = 0.0f; + scene.extinctionTextureIndices[c] = GetTextureIndexSRV(volumetricLight.extinctionTextures[c]); } + Vector4Copy(volumetricLight.sunShadowVolumeScale, scene.sunVShadowWorldScale); + for(int c = 0; c < 4; c++) + { + scene.sunVShadowTextureIndices[c] = GetTextureIndexSRV(volumetricLight.sunShadowTextures[c]); + } + + scene.linearClampSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear); + + SceneView* const mappedScene = (SceneView*)(MapBuffer(uploadBuffer) + uploadByteOffset); + memcpy(mappedScene, &scene, sizeof(scene)); UnmapBuffer(uploadBuffer); CmdBeginBarrier(); diff --git a/code/renderer/crp_opaque.cpp b/code/renderer/crp_opaque.cpp index 7138223..f9dd42c 100644 --- a/code/renderer/crp_opaque.cpp +++ b/code/renderer/crp_opaque.cpp @@ -100,6 +100,7 @@ void WorldOpaque::Draw(const drawSceneViewCommand_t& cmd) CmdBeginBarrier(); CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthReadBit); CmdTextureBarrier(crp.lightTexture, ResourceStates::PixelShaderAccessBit); + CmdTextureBarrier(crp.sunlightTexture, ResourceStates::PixelShaderAccessBit); CmdBufferBarrier(srp.traceRenderBuffer, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); diff --git a/code/renderer/crp_sun_editor.cpp b/code/renderer/crp_sun_editor.cpp new file mode 100644 index 0000000..494c6dc --- /dev/null +++ b/code/renderer/crp_sun_editor.cpp @@ -0,0 +1,210 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// Cinematic Rendering Pipeline - sunlight editor + + +#include "crp_local.h" +#include "../client/cl_imgui.h" +#include "compshaders/crp/fullscreen.h" +#include "compshaders/crp/sun_overlay.h" + + +#pragma pack(push, 4) +struct SunOverlayRC +{ + vec3_t direction; + float angle; + vec3_t color; + float padding; + float textureWidth; + float textureHeight; +}; +#pragma pack(pop) + + +static bool LoadSunFile(const char* filePath) +{ + bool success = false; + void* data = NULL; + if(ri.FS_ReadFile(filePath, &data) == sizeof(crp.sunlightData) && + data != NULL) + { + memcpy(&crp.sunlightData, data, sizeof(crp.sunlightData)); + success = true; + } + if(data != NULL) + { + ri.FS_FreeFile(data); + } + + return success; +} + +static void SaveSunFile(const char* filePath) +{ + FS_EnableCNQ3FolderWrites(qtrue); + ri.FS_WriteFile(filePath, &crp.sunlightData, sizeof(crp.sunlightData)); + FS_EnableCNQ3FolderWrites(qfalse); +} + +static void LoadSunFromShader(const shader_t* skyShader) +{ + vec2_t angles; + angles[0] = DEG2RAD(skyShader->sunAzimuth); + angles[1] = DEG2RAD(skyShader->sunInclination); + VectorCopy(skyShader->sunColor, crp.sunlightData.color); + crp.sunlightData.intensity = 1.0f; + AzimuthInclinationToDirection(crp.sunlightData.direction, angles); +} + +void DirectionToAzimuthInclination(float* sc, const float* dir) +{ + // 0=azimuth/phi, 1=inclination/theta + sc[0] = atan2f(dir[1], dir[0]); + sc[1] = atan2f(sqrtf(dir[0] * dir[0] + dir[1] * dir[1]), dir[2]); +} + +void AzimuthInclinationToDirection(float* dir, const float* sc) +{ + // 0=azimuth/phi, 1=inclination/theta + dir[0] = sinf(sc[1]) * cosf(sc[0]); + dir[1] = sinf(sc[1]) * sinf(sc[0]); + dir[2] = cosf(sc[1]); +} + + +void SunlightEditor::Init() +{ + { + GraphicsPipelineDesc desc("G-Buffer Depth"); + MakeFullScreenPipeline(desc, ShaderByteCode(g_sun_overlay_ps)); + desc.AddRenderTarget(GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA, crp.renderTargetFormat); + pipeline = CreateGraphicsPipeline(desc); + } +} + +void SunlightEditor::ProcessWorld(world_t& world) +{ + skyShader = NULL; + for(int i = 0; i < tr.numShaders; i++) + { + if(tr.shaders[i]->isSunDataValid) + { + skyShader = tr.shaders[i]; + break; + } + } + + if(!LoadSunFile(va("sun/%s.sun", world.baseName)) && + skyShader != NULL) + { + LoadSunFromShader(skyShader); + } +} + +void SunlightEditor::DrawOverlay() +{ + if(!drawOverlay || !IsViewportFullscreen(backEnd.viewParms)) + { + return; + } + + srp.renderMode = RenderMode::None; + + SCOPED_RENDER_PASS("Sun Overlay", 1.0f, 1.0f, 1.0f); + + CmdSetViewportAndScissor(0, 0, glConfig.vidWidth, glConfig.vidHeight); + + CmdBeginBarrier(); + CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit); + CmdEndBarrier(); + + SunOverlayRC rc = {}; + rc.angle = 0.0f; + VectorCopy(crp.sunlightData.color, rc.color); + VectorCopy(crp.sunlightData.direction, rc.direction); + rc.textureWidth = glConfig.vidWidth; + rc.textureHeight = glConfig.vidHeight; + + CmdBindRenderTargets(1, &crp.renderTarget, NULL); + CmdBindPipeline(pipeline); + CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); + CmdDraw(3, 0); +} + +void SunlightEditor::DrawGUI() +{ + if(tr.world == NULL) + { + return; + } + + GUI_AddMainMenuItem(GUI_MainMenu::Tools, "Edit Sunlight", "", &windowActive); + + if(!windowActive) + { + return; + } + + if(ImGui::Begin("Sunlight", &windowActive, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Checkbox("Draw Sun", &drawOverlay); + + float angles[2]; + DirectionToAzimuthInclination(angles, crp.sunlightData.direction); + ImGui::NewLine(); + ImGui::SliderAngle("Azimuth", &angles[0], 0.0f, 360.0f); + ImGui::SliderAngle("Inclination", &angles[1], 0.0f, 180.0f); + AzimuthInclinationToDirection(crp.sunlightData.direction, angles); + ImGui::ColorEdit3("Color", crp.sunlightData.color); + ImGui::SliderFloat("Light intensity", &crp.sunlightData.intensity, 0.0f, 10.0f); + + ImGui::NewLine(); + if(ImGui::Button("Save Config...")) + { + OpenSaveFileDialog("sun", ".sun"); + } + ImGui::SameLine(); + if(ImGui::Button("Open Config...")) + { + OpenOpenFileDialog("sun", ".sun"); + } + if(skyShader != NULL) + { + ImGui::SameLine(); + if(ImGui::Button("Import from Sky Shader")) + { + LoadSunFromShader(skyShader); + } + } + + if(SaveFileDialog()) + { + SaveSunFile(GetSaveFileDialogPath()); + } + + if(OpenFileDialog()) + { + LoadSunFile(GetOpenFileDialogPath()); + } + } + ImGui::End(); +} diff --git a/code/renderer/crp_sunlight.cpp b/code/renderer/crp_sunlight.cpp new file mode 100644 index 0000000..97765ef --- /dev/null +++ b/code/renderer/crp_sunlight.cpp @@ -0,0 +1,137 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// Cinematic Rendering Pipeline - sunlight on opaque surfaces + + +#include "crp_local.h" +#include "compshaders/crp/fullscreen.h" +#include "compshaders/crp/sun_visibility.h" +#include "compshaders/crp/sun_blur.h" + + +#pragma pack(push, 4) +struct SunBlurRC +{ + uint32_t visibilityTextureIndex; + uint32_t penumbraTextureIndex; +}; +#pragma pack(pop) + + +void Sunlight::Init() +{ + if(!rhiInfo.hasInlineRaytracing) + { + return; + } + + { + GraphicsPipelineDesc desc("Sunlight Visibility"); + MakeFullScreenPipeline(desc, ShaderByteCode(g_sun_visibility_ps)); + desc.AddRenderTarget(0, TextureFormat::R8_UNorm); + desc.AddRenderTarget(0, TextureFormat::R8_UNorm); + visibilityPipeline = CreateGraphicsPipeline(desc); + } + + { + GraphicsPipelineDesc desc("Sunlight Blur"); + MakeFullScreenPipeline(desc, ShaderByteCode(g_sun_blur_ps)); + desc.AddRenderTarget(0, crp.renderTargetFormat); + blurPipeline = CreateGraphicsPipeline(desc); + } + + { + TextureDesc desc("sunlight visibility", glConfig.vidWidth, glConfig.vidHeight); + desc.shortLifeTime = true; + desc.format = TextureFormat::R8_UNorm; + desc.initialState = ResourceStates::RenderTargetBit; + desc.allowedState = ResourceStates::RenderTargetBit | ResourceStates::PixelShaderAccessBit | ResourceStates::ComputeShaderAccessBit; + visibilityTexture = CreateTexture(desc); + desc.name = "sunlight penumbra"; + penumbraTexture = CreateTexture(desc); + } +} + +void Sunlight::Draw() +{ + if(crp_sunlight->integer == 0 || + (backEnd.refdef.rdflags & RDF_NOWORLDMODEL) != 0 || + !crp.raytracing.CanRaytrace() || + !IsViewportFullscreen(backEnd.viewParms)) + { + CmdBeginBarrier(); + CmdTextureBarrier(crp.sunlightTexture, ResourceStates::RenderTargetBit); + CmdEndBarrier(); + CmdClearColorTarget(crp.sunlightTexture, colorBlack); + return; + } + + srp.renderMode = RenderMode::None; + + { + SCOPED_RENDER_PASS("Sunlight Visibility", 1.0f, 1.0f, 1.0f); + + CmdSetViewportAndScissor(0, 0, glConfig.vidWidth, glConfig.vidHeight); + + CmdBeginBarrier(); + CmdTextureBarrier(crp.shadingPositionTexture, ResourceStates::PixelShaderAccessBit); + CmdTextureBarrier(visibilityTexture, ResourceStates::RenderTargetBit); + CmdTextureBarrier(penumbraTexture, ResourceStates::RenderTargetBit); + CmdEndBarrier(); + + const HTexture renderTargets[] = { visibilityTexture, penumbraTexture }; + CmdBindRenderTargets(ARRAY_LEN(renderTargets), renderTargets, NULL); + CmdBindPipeline(visibilityPipeline); + CmdDraw(3, 0); + } + + { + SCOPED_RENDER_PASS("Sunlight Blur", 1.0f, 1.0f, 1.0f); + + CmdSetViewportAndScissor(0, 0, glConfig.vidWidth, glConfig.vidHeight); + + CmdBeginBarrier(); + for(int c = 0; c < 4; c++) + { + CmdTextureBarrier(crp.volumetricLight.sunShadowTextures[c], ResourceStates::PixelShaderAccessBit); + } + CmdTextureBarrier(visibilityTexture, ResourceStates::PixelShaderAccessBit); + CmdTextureBarrier(penumbraTexture, ResourceStates::PixelShaderAccessBit); + CmdTextureBarrier(crp.shadingPositionTexture, ResourceStates::PixelShaderAccessBit); + CmdTextureBarrier(crp.normalTexture, ResourceStates::PixelShaderAccessBit); + CmdTextureBarrier(crp.sunlightTexture, ResourceStates::RenderTargetBit); + CmdEndBarrier(); + + SunBlurRC rc = {}; + rc.penumbraTextureIndex = GetTextureIndexSRV(penumbraTexture); + rc.visibilityTextureIndex = GetTextureIndexSRV(visibilityTexture); + + CmdBindRenderTargets(1, &crp.sunlightTexture, NULL); + CmdBindPipeline(blurPipeline); + CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); + CmdDraw(3, 0); + } +} + +bool Sunlight::WantRTASUpdate(const trRefdef_t& scene) +{ + return crp_sunlight->integer != 0; +} diff --git a/code/renderer/crp_transp_draw.cpp b/code/renderer/crp_transp_draw.cpp index 34e499c..a961ecd 100644 --- a/code/renderer/crp_transp_draw.cpp +++ b/code/renderer/crp_transp_draw.cpp @@ -44,7 +44,7 @@ struct TranspDrawPixelRC uint32_t shaderTrace; uint16_t hFadeDistance; uint16_t hFadeOffset; - uint32_t depthFadeScaleBias; // color bias: 4 - color scale: 4 + uint32_t depthFadeScaleBiasPO; // polygon offset: 1 - enable: 1 - color bias: 4 - color scale: 4 }; #pragma pack(pop) @@ -137,6 +137,11 @@ void WorldTransp::Draw(const drawSceneViewCommand_t& cmd) Q_assert(shader != NULL); Q_assert(!shader->isOpaque); + if(shader->isFog) + { + continue; + } + const bool shaderChanged = shader != oldShader; const bool entityChanged = entityNum != oldEntityNum; if(shaderChanged || entityChanged) @@ -290,6 +295,7 @@ void WorldTransp::EndBatch() const uint32_t alphaTest = AlphaTestShaderConstFromStateBits(stage->stateBits); const uint32_t enableShaderTrace = tr.traceWorldShader && s == 0 ? 1 : 0; const uint32_t enableDepthFade = shader->dfType != DFT_NONE ? 1 : 0; + const uint32_t polygonOffset = shader->polygonOffset ? 1 : 0; Q_assert(sampIdx < ARRAY_LEN(crp.samplers)); TranspDrawPixelRC pixelRC = {}; @@ -304,7 +310,7 @@ void WorldTransp::EndBatch() pixelRC.shaderTrace = ((uint32_t)shader->index << 1) | enableShaderTrace; pixelRC.hFadeDistance = f32tof16(shader->dfInvDist); pixelRC.hFadeOffset = f32tof16(shader->dfBias); - pixelRC.depthFadeScaleBias = (enableDepthFade << 8) | (uint32_t)r_depthFadeScaleAndBias[shader->dfType]; + pixelRC.depthFadeScaleBiasPO = (polygonOffset << 9) | (enableDepthFade << 8) | (uint32_t)r_depthFadeScaleAndBias[shader->dfType]; CmdSetGraphicsRootConstants(sizeof(vertexRC), sizeof(pixelRC), &pixelRC); db.DrawStage(vertexCount, indexCount); diff --git a/code/renderer/crp_transp_resolve.cpp b/code/renderer/crp_transp_resolve.cpp index b8ca96e..8ccd1fe 100644 --- a/code/renderer/crp_transp_resolve.cpp +++ b/code/renderer/crp_transp_resolve.cpp @@ -24,24 +24,24 @@ along with Challenge Quake 3. If not, see . #include "crp_local.h" #include "compshaders/crp/fullscreen.h" #include "compshaders/crp/transp_resolve.h" +#include "compshaders/crp/transp_resolve_vol.h" #pragma pack(push, 4) struct TranspResolveRC { + float scissorMinX; + float scissorMinY; + float scissorMaxX; + float scissorMaxY; uint32_t renderTargetTexture; uint32_t shaderIndexBuffer; uint32_t indexTexture; uint32_t fragmentBuffer; uint16_t centerPixelX; uint16_t centerPixelY; - uint32_t depthTexture; - float linearDepthA; - float linearDepthB; - float scissorMinX; - float scissorMinY; - float scissorMaxX; - float scissorMaxY; + uint32_t scatterTexture; + uint32_t scatterSampler; }; #pragma pack(pop) @@ -51,16 +51,26 @@ void TranspResolve::Init() GraphicsPipelineDesc desc("OIT Resolve"); MakeFullScreenPipeline(desc, ShaderByteCode(g_transp_resolve_ps)); desc.AddRenderTarget(0, crp.renderTargetFormat); - pipeline = CreateGraphicsPipeline(desc); + noVolPipeline = CreateGraphicsPipeline(desc); + + desc.pixelShader = ShaderByteCode(g_transp_resolve_vol_ps); + volPipeline = CreateGraphicsPipeline(desc); } void TranspResolve::Draw(const drawSceneViewCommand_t& cmd) { - if(cmd.numTranspSurfs <= 0) + if(cmd.numTranspSurfs <= 0 && crp_volLight->integer == 0) { return; } + const bool vlEnabled = crp.volumetricLight.ShouldDraw(); + HPipeline pipeline = noVolPipeline; + if(vlEnabled) + { + pipeline = volPipeline; + } + srp.renderMode = RenderMode::World; SCOPED_RENDER_PASS("OIT Resolve", 1.0f, 0.5f, 0.5f); @@ -70,6 +80,10 @@ void TranspResolve::Draw(const drawSceneViewCommand_t& cmd) crp.SwapRenderTargets(); CmdBeginBarrier(); + if(vlEnabled) + { + CmdTextureBarrier(crp.volumetricLight.scatterTransTexture, ResourceStates::PixelShaderAccessBit); + } CmdTextureBarrier(crp.GetReadRenderTarget(), ResourceStates::PixelShaderAccessBit); CmdTextureBarrier(crp.GetWriteRenderTarget(), ResourceStates::RenderTargetBit); CmdTextureBarrier(crp.oitIndexTexture, ResourceStates::UnorderedAccessBit); @@ -79,18 +93,21 @@ void TranspResolve::Draw(const drawSceneViewCommand_t& cmd) CmdEndBarrier(); TranspResolveRC rc = {}; + rc.scissorMinX = backEnd.viewParms.viewportX; + rc.scissorMinY = backEnd.viewParms.viewportY; + rc.scissorMaxX = rc.scissorMinX + backEnd.viewParms.viewportWidth - 1; + rc.scissorMaxY = rc.scissorMinY + backEnd.viewParms.viewportHeight - 1; rc.fragmentBuffer = GetBufferIndexUAV(crp.oitFragmentBuffer); rc.indexTexture = GetTextureIndexUAV(crp.oitIndexTexture, 0); rc.renderTargetTexture = GetTextureIndexSRV(crp.GetReadRenderTarget()); rc.shaderIndexBuffer = GetBufferIndexUAV(srp.traceRenderBuffer); rc.centerPixelX = glConfig.vidWidth / 2; rc.centerPixelY = glConfig.vidHeight / 2; - rc.depthTexture = GetTextureIndexSRV(crp.depthTexture); - RB_LinearDepthConstants(&rc.linearDepthA, &rc.linearDepthB); - rc.scissorMinX = backEnd.viewParms.viewportX; - rc.scissorMinY = backEnd.viewParms.viewportY; - rc.scissorMaxX = rc.scissorMinX + backEnd.viewParms.viewportWidth - 1; - rc.scissorMaxY = rc.scissorMinY + backEnd.viewParms.viewportHeight - 1; + if(vlEnabled) + { + rc.scatterTexture = GetTextureIndexSRV(crp.volumetricLight.scatterTransTexture); + } + rc.scatterSampler = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear); CmdBindRenderTargets(1, &crp.renderTarget, NULL); CmdBindPipeline(pipeline); diff --git a/code/renderer/crp_volumetric_light.cpp b/code/renderer/crp_volumetric_light.cpp new file mode 100644 index 0000000..1a44cb0 --- /dev/null +++ b/code/renderer/crp_volumetric_light.cpp @@ -0,0 +1,1545 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// Cinematic Rendering Pipeline - fog and particles lit by the sun and local lights + + +#include "crp_local.h" +#include "../client/cl_imgui.h" +#include "shaders/crp/scene_view.h.hlsli" +#include "shaders/crp/vl_common.h.hlsli" +#include "compshaders/crp/fullscreen.h" +#if defined(VL_CPU_PARTICLES) +#include "compshaders/crp/vl_extinction_injection_particles.h" +#include "compshaders/crp/vl_frustum_injection_particles.h" +#include "compshaders/crp/vl_particles_dispatch.h" +#include "compshaders/crp/vl_particles_preprocess_extinction.h" +#include "compshaders/crp/vl_particles_preprocess_frustum.h" +#endif +#include "compshaders/crp/vl_extinction_injection_fog.h" +#include "compshaders/crp/vl_frustum_anisotropy_average.h" +#include "compshaders/crp/vl_frustum_injection_fog.h" +#include "compshaders/crp/vl_frustum_inscatter_ambient.h" +#include "compshaders/crp/vl_frustum_inscatter_point_light.h" +#include "compshaders/crp/vl_frustum_inscatter_sunlight.h" +#include "compshaders/crp/vl_frustum_raymarch.h" +#include "compshaders/crp/vl_frustum_sunlight_visibility.h" +#include "compshaders/crp/vl_frustum_temporal.h" +#include "compshaders/crp/vl_shadow_point_light.h" +#include "compshaders/crp/vl_shadow_sun.h" +#include "compshaders/crp/vl_debug_ambient.h" +#include "compshaders/crp/vl_debug_extinction.h" +#include "compshaders/crp/vl_debug_shadow_sun.h" + + +#pragma pack(push, 4) + +struct VLGlobalFogRC +{ + FogVolume fog; + float time; + uint32_t materialTextureAIndex; + uint32_t materialTextureBIndex; + uint32_t materialTextureCIndex; +}; + +struct VLParticlePreProcessRC +{ + uvec3_t fullResolution; + uint32_t tileBufferIndex; + uvec3_t tileResolution; + uint32_t particleBufferIndex; + uvec3_t tileScale; + uint32_t particleCount; +}; + +struct VLParticlePreProcessExtinctionRC +{ + uvec3_t fullResolution; + uint32_t tileBufferIndex; + uvec3_t tileResolution; + uint32_t particleBufferIndex; + uvec3_t tileScale; + uint32_t particleCount; + float extinctionWorldScale; +}; + +struct VLParticleDispatchRC +{ + uvec3_t tileResolution; + uint32_t tileBufferIndex; + uint32_t dispatchBufferIndex; + uint32_t particleTileBufferIndex; +}; + +struct VLParticleRC +{ + uvec3_t tileScale; + uint32_t particleBufferIndex; + uint32_t particleCount; + uint32_t materialTextureAIndex; + uint32_t materialTextureBIndex; + uint32_t materialTextureCIndex; + uint32_t tileBufferIndex; + uint32_t tileCount; +}; + +struct VLParticleExtinctionRC +{ + uvec3_t tileScale; + uint32_t particleBufferIndex; + uint32_t particleCount; + uint32_t extinctionTextureIndex; + uint32_t tileBufferIndex; + uint32_t tileCount; + float extinctionWorldScale; +}; + +struct VLAnisotropyRC +{ + uint32_t materialTextureBIndex; + uint32_t materialTextureCIndex; +}; + +struct VLSunlightVisRC +{ + vec3_t jitter; + uint32_t visTextureIndex; + uint32_t depthMip; +}; + +struct VLSunlightRC +{ + uint32_t materialTextureAIndex; + uint32_t materialTextureBIndex; + uint32_t scatterExtTextureIndex; + uint32_t sunlightVisTextureIndex; +}; + +struct VLAmbientRC +{ + vec3_t centerPosition; + uint32_t materialTextureAIndex; + vec3_t worldScale; + uint32_t scatterExtTextureIndex; + uint32_t ambientLightTextureAIndex; + uint32_t ambientLightTextureBIndex; + uint32_t ambientSamplerIndex; + uint32_t isLightGridAvailable; +}; + +struct VLRaymarchRC +{ + uint32_t scatterTextureIndex; + uint32_t resolveTextureIndex; + uint32_t materialTextureBIndex; +}; + +struct VLExtinctionFogRC +{ + FogVolume fog; + float time; + uint32_t extinctionTextureIndex; + float worldScale; +}; + +struct VLPointLightShadowRC +{ + vec3_t lightPosition; + float extinctionWorldScale; + float shadowWorldScale; + uint32_t shadowTextureIndex; +}; + +struct VLSunlightShadowRC +{ + uint32_t shadowTextureIndex; + uint32_t sourceTextureIndex; + float shadowWorldScale; + float sourceWorldScale; +}; + +struct VLPointLightScatterRC +{ + DynamicLight light; + uint32_t materialTextureAIndex; + uint32_t materialTextureBIndex; + uint32_t scatterExtTextureIndex; + uint32_t transmittanceTextureIndex; + uint32_t transmittanceSamplerIndex; + float shadowWorldScale; +}; + +struct VLExtinctionVizRC +{ + vec3_t color; + float worldScale; + vec3_t cameraPosition; + float boxScale; // 1 for full size + float extinctionScale; + uint32_t extinctionTextureIndex; +}; + +struct VLSunShadowVizRC +{ + vec3_t color; + float worldScale; + vec3_t cameraPosition; + float boxScale; // 1 for full size + uint32_t shadowTextureIndex; +}; + +struct VLAmbientVizRC +{ + vec3_t centerPosition; + float sphereScale; + vec3_t worldScale; + uint32_t lightGridTextureAIndex; + uint32_t lightGridTextureBIndex; +}; + +struct VLTemporalRC +{ + uint32_t currTextureIndex; + uint32_t prevTextureIndex; + uint32_t prevTextureSamplerIndex; + float alpha; +}; + +#pragma pack(pop) + +const float MaxFogCoordinate = 69420.0f; + + +static uint32_t ReverseBits32(uint32_t n) +{ + n = (n << 16) | (n >> 16); + n = ((n & 0x00ff00ff) << 8) | ((n & 0xff00ff00) >> 8); + n = ((n & 0x0f0f0f0f) << 4) | ((n & 0xf0f0f0f0) >> 4); + n = ((n & 0x33333333) << 2) | ((n & 0xcccccccc) >> 2); + n = ((n & 0x55555555) << 1) | ((n & 0xaaaaaaaa) >> 1); + + return n; +} + +static float RadicalInverseBase2(uint32_t seqIndex) +{ + return ReverseBits32(seqIndex) * 0x1p-32; +} + +static float RadicalInverse(uint64_t seqIndex, uint32_t base) +{ + const float oneMinusEpsilon = 0x1.fffffep-1; + const float invBase = 1.0f / (float)base; + uint32_t reversedDigits = 0; + float invBaseN = 1.0f; + while(seqIndex) + { + uint32_t next = seqIndex / base; + uint32_t digit = seqIndex - next * base; + reversedDigits = reversedDigits * base + digit; + invBaseN *= invBase; + seqIndex = next; + } + + return fminf(reversedDigits * invBaseN, oneMinusEpsilon); +} + +static float VanDerCorputSequence(uint32_t seqIndex) +{ + return RadicalInverseBase2(seqIndex); +} + +static void Halton23Sequence(vec2_t values01, uint32_t seqIndex) +{ + values01[0] = RadicalInverseBase2(seqIndex); + values01[1] = RadicalInverse(seqIndex, 3); +} + +static float Brightness(const vec3_t color) +{ + return + color[0] * 0.299f + + color[1] * 0.587f + + color[2] * 0.114f; +} + +static void ConvertFog(FogVolume& dst, const VolumetricLight::Fog& src, const VolumetricLight& vl) +{ + const float scatter = src.albedo * src.extinction / Brightness(src.scatterColor); + VectorScale(src.scatterColor, scatter, dst.scatter); + dst.absorption = src.extinction - Brightness(dst.scatter); + VectorScale(src.emissiveColor, src.emissive, dst.emissive); + dst.anisotropy = src.anisotropy; + if(src.isGlobalFog) + { + VectorCopy(vl.mapBoxMin, dst.boxMin); + VectorCopy(vl.mapBoxMax, dst.boxMax); + } + else + { + VectorCopy(src.boxMin, dst.boxMin); + VectorCopy(src.boxMax, dst.boxMax); + } + dst.noiseMin = 1.0f; + dst.noiseMax = src.noiseStrength; + dst.noiseScale = 1.0f / src.noiseSpatialPeriod; + dst.noiseTimeScale = 1.0f / src.noiseTimePeriod; + dst.isHeightFog = src.isHeightFog; +} + +static const float OpaqueTransmittanceThreshold = 1.0f / 256.0f; +static const float LnOpaqueTransmittanceThreshold = logf(OpaqueTransmittanceThreshold); + +static float OpaqueDistanceToExtinction(float opaqueDistance) +{ + return -LnOpaqueTransmittanceThreshold / opaqueDistance; +} + +static float ExtinctionToOpaqueDistance(float extinction) +{ + return -LnOpaqueTransmittanceThreshold / extinction; +} + +#if defined(VL_CPU_PARTICLES) + +void CRP_AddParticle(const vec3_t position, float radius, float alpha) +{ + uint32_t& particleCount = crp.volumetricLight.particleCount; + if(particleCount >= MAX_PARTICLES) + { + return; + } + + Particle p = {}; + VectorCopy(position, p.position); + p.radius = radius; + p.scattering[0] = 0.1f * alpha; + p.scattering[1] = 0.1f * alpha; + p.scattering[2] = 0.1f * alpha; + p.absorption = 0.1f * alpha; + p.anisotropy = 0.0f; + p.isEmissive = 0; + + HBuffer buffer = crp.volumetricLight.particleBuffer; + Particle* const particle = (Particle*)MapBuffer(buffer) + particleCount; + memcpy(particle, &p, sizeof(p)); + UnmapBuffer(buffer); + particleCount++; +} + +// for use with billboarded quads such as CPMA's rocket smoke +void CRP_AddPolygonAsParticle(const polyVert_t* vertices, int vertexCount) +{ + vec3_t bounds[2]; + VectorCopy(vertices[0].xyz, bounds[0]); + VectorCopy(vertices[0].xyz, bounds[1]); + for(int i = 1; i < vertexCount; i++) + { + AddPointToBounds(vertices[i].xyz, bounds[0], bounds[1]); + } + + vec3_t position; + VectorAdd(bounds[0], bounds[1], position); + VectorScale(position, 0.5f, position); + + vec3_t extents; + VectorSubtract(bounds[1], bounds[0], extents); + + const float radius = max(max(extents[0], extents[1]), extents[2]) * 0.5f; + const float alpha = (float)vertices[0].modulate[3] / 255.0f; + + CRP_AddParticle(position, radius, alpha); +} + +#endif + +void VolumetricLight::Init() +{ + if(srp.firstInit) + { + VectorSet(ambientColor, 0.125f, 0.125f, 0.125f); + ambientIntensity = 1.0f; + } + + // patched on world load + VectorSet(mapBoxMin, -MaxFogCoordinate, -MaxFogCoordinate, -MaxFogCoordinate); + VectorSet(mapBoxMax, MaxFogCoordinate, MaxFogCoordinate, MaxFogCoordinate); + + VectorSet(frustumSize, glConfig.vidWidth / 8, glConfig.vidHeight / 8, 256); + depthMip = 3; + VectorSet(frustumTileScale, 8, 8, 16); // x*y*z == 1024, must match the shader + VectorSet(frustumTileSize, + (frustumSize[0] + frustumTileScale[0] - 1) / frustumTileScale[0], + (frustumSize[1] + frustumTileScale[1] - 1) / frustumTileScale[1], + (frustumSize[2] + frustumTileScale[2] - 1) / frustumTileScale[2]); + VectorSet(extinctionSize, 128, 128, 128); + VectorSet(extinctionTileScale, 8, 8, 8); // 8*8*8 == 512, must match the shader + VectorSet(extinctionTileSize, + (extinctionSize[0] + extinctionTileScale[0] - 1) / extinctionTileScale[0], + (extinctionSize[1] + extinctionTileScale[1] - 1) / extinctionTileScale[1], + (extinctionSize[2] + extinctionTileScale[2] - 1) / extinctionTileScale[2]); + Vector4Set(extinctionVolumeScale, 8, 16, 32, 64); // patched on world load + VectorSet(sunShadowSize, 128, 128, 128); + Vector4Set(sunShadowVolumeScale, 8, 16, 32, 64); // patched on world load + shadowPixelCount = 64; + pointShadowVolumeScale = 8.0f; + jitterCounter = 0; + +#if defined(VL_CPU_PARTICLES) + extinctionParticlePipeline = CreateComputePipeline("VL Extinction Particles", ShaderByteCode(g_vl_extinction_injection_particles_cs)); + frustumParticlePipeline = CreateComputePipeline("VL Frustum Particles", ShaderByteCode(g_vl_frustum_injection_particles_cs)); + particleDispatchPipeline = CreateComputePipeline("VL Particles Dispatch", ShaderByteCode(g_vl_particles_dispatch_cs)); + particlePreProcessExtinctionPipeline = CreateComputePipeline("VL Particles Extinction Pre-process", ShaderByteCode(g_vl_particles_preprocess_extinction_cs)); + particlePreProcessFrustumPipeline = CreateComputePipeline("VL Particles Frustum Pre-process", ShaderByteCode(g_vl_particles_preprocess_frustum_cs)); +#endif + extinctionFogPipeline = CreateComputePipeline("VL Extinction Fog", ShaderByteCode(g_vl_extinction_injection_fog_cs)); + frustumAmbientPipeline = CreateComputePipeline("VL Frustum Ambient Light Scatter", ShaderByteCode(g_vl_frustum_inscatter_ambient_cs)); + frustumAnisotropyPipeline = CreateComputePipeline("VL Frustum Finalize Material", ShaderByteCode(g_vl_frustum_anisotropy_average_cs)); + frustumFogPipeline = CreateComputePipeline("VL Frustum Fog", ShaderByteCode(g_vl_frustum_injection_fog_cs)); + frustumRaymarchPipeline = CreateComputePipeline("VL Frustum Raymarch", ShaderByteCode(g_vl_frustum_raymarch_cs)); + frustumTemporalPipeline = CreateComputePipeline("VL Frustum Temporal Reprojection", ShaderByteCode(g_vl_frustum_temporal_cs)); + pointLightShadowPipeline = CreateComputePipeline("VL Shadow Raymarch Point Light", ShaderByteCode(g_vl_shadow_point_light_cs)); + sunlightScatterPipeline = CreateComputePipeline("VL Frustum Sunlight Scatter", ShaderByteCode(g_vl_frustum_inscatter_sunlight_cs)); + sunlightShadowPipeline = CreateComputePipeline("VL Shadow Raymarch Sun", ShaderByteCode(g_vl_shadow_sun_cs)); + if(rhiInfo.hasInlineRaytracing) + { + frustumSunlightVisPipeline = CreateComputePipeline("VL Frustum Sunlight Visibility", ShaderByteCode(g_vl_frustum_sunlight_visibility_cs)); + frustumPointLightScatterPipeline = CreateComputePipeline("VL Frustum Point Light Scatter", ShaderByteCode(g_vl_frustum_inscatter_point_light_cs)); + } + else + { + frustumSunlightVisPipeline = RHI_MAKE_NULL_HANDLE(); + frustumPointLightScatterPipeline = RHI_MAKE_NULL_HANDLE(); + } + + { + GraphicsPipelineDesc desc("VL Extinction Viz"); + desc.rootSignature = RHI_MAKE_NULL_HANDLE(); + desc.vertexShader.Set(g_vl_debug_extinction_vs); + desc.pixelShader.Set(g_vl_debug_extinction_ps); + desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float; + desc.depthStencil.depthComparison = ComparisonFunction::GreaterEqual; + desc.depthStencil.enableDepthTest = true; + desc.depthStencil.enableDepthWrites = true; + desc.rasterizer.cullMode = CT_TWO_SIDED; // @TODO: + desc.AddRenderTarget(0, crp.renderTargetFormat); + extinctionVizPipeline = CreateGraphicsPipeline(desc); + } + + { + GraphicsPipelineDesc desc("VL Sun Shadow Viz"); + desc.rootSignature = RHI_MAKE_NULL_HANDLE(); + desc.vertexShader.Set(g_vl_debug_shadow_sun_vs); + desc.pixelShader.Set(g_vl_debug_shadow_sun_ps); + desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float; + desc.depthStencil.depthComparison = ComparisonFunction::GreaterEqual; + desc.depthStencil.enableDepthTest = true; + desc.depthStencil.enableDepthWrites = true; + desc.rasterizer.cullMode = CT_TWO_SIDED; // @TODO: + desc.AddRenderTarget(0, crp.renderTargetFormat); + sunShadowVizPipeline = CreateGraphicsPipeline(desc); + } + + { + GraphicsPipelineDesc desc("VL Ambient Viz"); + desc.rootSignature = RHI_MAKE_NULL_HANDLE(); + desc.vertexShader.Set(g_vl_debug_ambient_vs); + desc.pixelShader.Set(g_vl_debug_ambient_ps); + desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float; + desc.depthStencil.depthComparison = ComparisonFunction::GreaterEqual; + desc.depthStencil.enableDepthTest = true; + desc.depthStencil.enableDepthWrites = true; + desc.rasterizer.cullMode = CT_TWO_SIDED; // @TODO: + desc.AddRenderTarget(0, crp.renderTargetFormat); + ambientVizPipeline = CreateGraphicsPipeline(desc); + } + + { + TextureDesc desc("VL", frustumSize[0], frustumSize[1]); + desc.shortLifeTime = true; + desc.committedResource = true; + desc.initialState = ResourceStates::UnorderedAccessBit; + desc.allowedState = ResourceStates::UnorderedAccessBit | ResourceStates::ComputeShaderAccessBit | ResourceStates::PixelShaderAccessBit; + desc.depth = frustumSize[2]; + + desc.name = "VL scatter/absorption"; + desc.format = TextureFormat::R16G16B16A16_Float; + materialTextureA = CreateTexture(desc); + + desc.name = "VL emissive/anisotropy"; + desc.format = TextureFormat::R16G16B16A16_Float; + materialTextureB = CreateTexture(desc); + + desc.name = "VL anisotropy counter"; + desc.format = TextureFormat::R16_Float; + materialTextureC = CreateTexture(desc); + + desc.name = "VL sunlight vis"; + desc.format = TextureFormat::R8_UNorm; + sunlightVisTexture = CreateTexture(desc); + + desc.name = "VL sunlight vis temporal"; + desc.format = TextureFormat::R8_UNorm; + prevSunlightVisTexture = CreateTexture(desc); + + desc.name = "VL in-scatter/ext"; + desc.format = TextureFormat::R16G16B16A16_Float; + scatterExtTexture = CreateTexture(desc); + + desc.name = "VL in-scatter/trans"; + desc.format = TextureFormat::R16G16B16A16_Float; + scatterTransTexture = CreateTexture(desc); + + desc.width = extinctionSize[0]; + desc.height = extinctionSize[1]; + desc.depth = extinctionSize[2]; + desc.format = TextureFormat::R16_Float; + for(int i = 0; i < 4; i++) + { + desc.name = va("VL extinction #%d", i + 1); + extinctionTextures[i] = CreateTexture(desc); + } + + desc.width = sunShadowSize[0]; + desc.height = sunShadowSize[1]; + desc.depth = sunShadowSize[2]; + desc.format = TextureFormat::R16_Float; + for(int i = 0; i < 4; i++) + { + desc.name = va("VL sun shadow #%d", i + 1); + sunShadowTextures[i] = CreateTexture(desc); + } + + desc.width = shadowPixelCount; + desc.height = shadowPixelCount; + desc.depth = shadowPixelCount; + desc.name = "VL point light shadow"; + desc.format = TextureFormat::R16_Float; + pointShadowTexture = CreateTexture(desc); + + ambientLightTextureA = RHI_MAKE_NULL_HANDLE(); // created on world load when available + ambientLightTextureB = RHI_MAKE_NULL_HANDLE(); // created on world load when available + } + +#if defined(VL_CPU_PARTICLES) + const uint32_t tileCountF = frustumTileSize[0] * frustumTileSize[1] * frustumTileSize[2]; + const uint32_t tileCountE = extinctionTileSize[0] * extinctionTileSize[1] * extinctionTileSize[2]; + const uint32_t maxTileCount = max(tileCountF, tileCountE); + + { + BufferDesc desc("particle", MAX_PARTICLES * sizeof(Particle), ResourceStates::ComputeShaderAccessBit); + desc.shortLifeTime = true; + desc.memoryUsage = MemoryUsage::Upload; + desc.structureByteCount = sizeof(Particle); + particleBuffer = CreateBuffer(desc); + } + + { + const uint32_t tileSize = 4; // 1 uint + const uint32_t byteCount = maxTileCount * tileSize; + BufferDesc desc("particle hit", byteCount, ResourceStates::UnorderedAccessBit); + desc.shortLifeTime = true; + particleHitBuffer = CreateBuffer(desc); + } + + { + const uint32_t tileSize = 12; // 3 uint + const uint32_t byteCount = maxTileCount * tileSize; + BufferDesc desc("particle tile", byteCount, ResourceStates::UnorderedAccessBit); + desc.shortLifeTime = true; + desc.structureByteCount = tileSize; + particleTileBuffer = CreateBuffer(desc); + } + + { + BufferDesc desc("particle dispatch", 12, ResourceStates::UnorderedAccessBit); + desc.shortLifeTime = true; + particleDispatchBuffer = CreateBuffer(desc); + } + + { + BufferDesc desc("particle dispatch clear", 12, ResourceStates::CopySourceBit); + desc.shortLifeTime = true; + desc.memoryUsage = MemoryUsage::Upload; // @TODO: not ideal... + particleDispatchClearBuffer = CreateBuffer(desc); + + uint32_t* const groupCounts = (uint32_t*)MapBuffer(particleDispatchClearBuffer); + groupCounts[0] = 0; + groupCounts[1] = 1; + groupCounts[2] = 1; + UnmapBuffer(particleDispatchClearBuffer); + } +#endif +} + +void VolumetricLight::ProcessWorld(world_t& world) +{ + Q_assert(world.nodes != NULL); + Q_assert(world.numnodes > 0); + if(world.nodes == NULL || world.numnodes <= 0) + { + VectorSet(mapBoxMin, -MaxFogCoordinate, -MaxFogCoordinate, -MaxFogCoordinate); + VectorSet(mapBoxMax, MaxFogCoordinate, MaxFogCoordinate, MaxFogCoordinate); + return; + } + + const mnode_t& node = world.nodes[0]; + vec3_t mapDimensions; + VectorSubtract(node.maxs, node.mins, mapDimensions); + + VectorCopy(node.mins, mapBoxMin); + VectorCopy(node.maxs, mapBoxMax); + + if(world.lightGridData != NULL) + { + for(int i = 0; i < 3; i++) + { + lightGridCenter[i] = + world.lightGridOrigin[i] + + (world.lightGridBounds[i] * 0.5f - 0.5f) * world.lightGridSize[i]; + } + + TextureDesc desc("VL", world.lightGridBounds[0], world.lightGridBounds[1]); + desc.shortLifeTime = true; + desc.committedResource = true; + desc.initialState = ResourceStates::ComputeShaderAccessBit; + desc.allowedState = ResourceStates::ComputeShaderAccessBit | ResourceStates::PixelShaderAccessBit; + desc.depth = world.lightGridBounds[2]; + desc.format = TextureFormat::R8G8B8A8_UNorm; + desc.name = "VL ambient light A"; + ambientLightTextureA = CreateTexture(desc); + desc.name = "VL ambient light B"; + ambientLightTextureB = CreateTexture(desc); + + MappedTexture texture; + BeginTextureUpload(texture, ambientLightTextureA); + const uint32_t rowCount = texture.rowCount * texture.sliceCount; + const uint32_t columnCount = texture.columnCount; + const uint32_t srcRowByteCount = world.lightGridBounds[0] * 8; + for(uint32_t r = 0; r < rowCount; r++) + { + uint32_t* dst = (uint32_t*)(texture.mappedData + r * texture.dstRowByteCount); + const uint32_t* src = (const uint32_t*)(world.lightGridData + r * srcRowByteCount); + for(uint32_t c = 0; c < columnCount; c++) + { + *dst = src[0]; + dst += 1; + src += 2; + } + } + EndTextureUpload(); + + BeginTextureUpload(texture, ambientLightTextureB); + for(uint32_t r = 0; r < rowCount; r++) + { + uint32_t* dst = (uint32_t*)(texture.mappedData + r * texture.dstRowByteCount); + const uint32_t* src = (const uint32_t*)(world.lightGridData + r * srcRowByteCount); + for(uint32_t c = 0; c < columnCount; c++) + { + *dst = src[1]; + dst += 1; + src += 2; + } + } + EndTextureUpload(); + } + + { + const float largest = max(max(mapDimensions[0], mapDimensions[1]), mapDimensions[2]); + const float scale3 = largest / (float)extinctionSize[0]; + const float scale2 = extinctionVolumeScale[2]; + float downScale = 1.0f; + while(scale3 <= 1.5f * scale2 * downScale) + { + downScale *= 0.5f; + } + extinctionVolumeScale[0] *= downScale; + extinctionVolumeScale[1] *= downScale; + extinctionVolumeScale[2] *= downScale; + extinctionVolumeScale[3] = scale3; + } + + { + const float length = VectorLength(mapDimensions); + const float scale3 = length / (float)sunShadowSize[0]; + const float scale2 = sunShadowVolumeScale[2]; + float downScale = 1.0f; + while(scale3 <= 1.5f * scale2 * downScale) + { + downScale *= 0.5f; + } + sunShadowVolumeScale[0] *= downScale; + sunShadowVolumeScale[1] *= downScale; + sunShadowVolumeScale[2] *= downScale; + sunShadowVolumeScale[3] = scale3; + } + + if(!LoadFogFile(va("fogs/%s.fogs", world.baseName))) + { + fogCount = 0; + + // @NOTE: fog 0 is invalid + for(int f = 1; f < world.numfogs && fogCount < ARRAY_LEN(fogs); f++) + { + const float transmittanceThreshold = 1.0 / 256.0f; + const float lnThreshold = logf(transmittanceThreshold); + const fog_t& q3fog = world.fogs[f]; + const float distance = q3fog.parms.depthForOpaque; + const float extinction = -lnThreshold / distance; + + Fog& fog = fogs[fogCount++]; + fog = {}; + VectorCopy(q3fog.bounds[0], fog.boxMin); + VectorCopy(q3fog.bounds[1], fog.boxMax); + fog.extinction = extinction; + fog.albedo = 0.75f; + VectorCopy(q3fog.parms.color, fog.scatterColor); + fog.emissive = 0.0f; + VectorSet(fog.emissiveColor, 1, 1, 1); + fog.anisotropy = 0.0f; + fog.noiseStrength = 2.0f; + fog.noiseSpatialPeriod = 128.0f; + fog.noiseTimePeriod = 8.0f; + } + } +} + +void VolumetricLight::DrawBegin() +{ + { + SCOPED_RENDER_PASS("VL Clear", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit); + for(int i = 0; i < 4; i++) + { + CmdTextureBarrier(extinctionTextures[i], ResourceStates::UnorderedAccessBit); + } + CmdEndBarrier(); + + const uint32_t values[4] = {}; + CmdClearTextureUAV(materialTextureA, 0, values); + CmdClearTextureUAV(materialTextureB, 0, values); + CmdClearTextureUAV(materialTextureC, 0, values); + for(int i = 0; i < 4; i++) + { + CmdClearTextureUAV(extinctionTextures[i], 0, values); + } + } + + { + SCOPED_RENDER_PASS("VL Fog", 1.0f, 1.0f, 1.0f); + + for(int f = 0; f < fogCount; f++) + { + SCOPED_DEBUG_LABEL("VL Frustum Fog", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + VLGlobalFogRC rc = {}; + ConvertFog(rc.fog, fogs[f], *this); + rc.time = backEnd.refdef.floatTime; + rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0); + rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); + rc.materialTextureCIndex = GetTextureIndexUAV(materialTextureC, 0); + + CmdBindPipeline(frustumFogPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); + } + + for(int f = 0; f < fogCount; f++) + { + VLExtinctionFogRC rc = {}; + ConvertFog(rc.fog, fogs[f], *this); + rc.time = backEnd.refdef.floatTime; + + for(int c = 0; c < 4; c++) + { + SCOPED_DEBUG_LABEL("VL Extinction Fog", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(extinctionTextures[c], ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + rc.extinctionTextureIndex = GetTextureIndexUAV(extinctionTextures[c], 0); + rc.worldScale = extinctionVolumeScale[c]; + + CmdBindPipeline(extinctionFogPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((extinctionSize[0] + 3) / 4, (extinctionSize[1] + 3) / 4, (extinctionSize[2] + 3) / 4); + } + } + } + +#if defined(VL_CPU_PARTICLES) + if(particleCount > 0) + { + SCOPED_RENDER_PASS("VL Frustum Particles", 1.0f, 1.0f, 1.0f); + + { + SCOPED_DEBUG_LABEL("Pre-pass", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdBufferBarrier(particleBuffer, ResourceStates::ComputeShaderAccessBit); + CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + CmdClearBufferUAV(particleHitBuffer, 0); + + CmdBeginBarrier(); + CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + VLParticlePreProcessRC rc = {}; + rc.tileBufferIndex = GetBufferIndexUAV(particleHitBuffer); + rc.particleBufferIndex = GetBufferIndexSRV(particleBuffer); + rc.particleCount = particleCount; + VectorCopy(frustumSize, rc.fullResolution); + VectorCopy(frustumTileSize, rc.tileResolution); + VectorCopy(frustumTileScale, rc.tileScale); + + CmdBindPipeline(particlePreProcessFrustumPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((particleCount + 63) / 64, 1, 1); + } + + { + SCOPED_DEBUG_LABEL("Clear Dispatch", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdBufferBarrier(particleDispatchClearBuffer, ResourceStates::CopySourceBit); + CmdBufferBarrier(particleDispatchBuffer, ResourceStates::CopyDestinationBit); + CmdEndBarrier(); + + CmdCopyBuffer(particleDispatchBuffer, particleDispatchClearBuffer); + } + + { + SCOPED_DEBUG_LABEL("Prepare Dispatch", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); + CmdBufferBarrier(particleDispatchBuffer, ResourceStates::UnorderedAccessBit); + CmdBufferBarrier(particleTileBuffer, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + VLParticleDispatchRC rc = {}; + rc.tileBufferIndex = GetBufferIndexUAV(particleHitBuffer); + rc.dispatchBufferIndex = GetBufferIndexUAV(particleDispatchBuffer); + rc.particleTileBufferIndex = GetBufferIndexUAV(particleTileBuffer); + VectorCopy(frustumTileSize, rc.tileResolution); + + CmdBindPipeline(particleDispatchPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((frustumTileSize[0] + 3) / 4, (frustumTileSize[1] + 3) / 4, (frustumTileSize[2] + 3) / 4); + } + + { + SCOPED_DEBUG_LABEL("Injection", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdBufferBarrier(particleBuffer, ResourceStates::ComputeShaderAccessBit); + CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit); + CmdBufferBarrier(particleDispatchBuffer, ResourceStates::IndirectDispatchBit); + CmdEndBarrier(); + + VLParticleRC rc = {}; + rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0); + rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); + rc.materialTextureCIndex = GetTextureIndexUAV(materialTextureC, 0); + rc.tileBufferIndex = GetBufferIndexUAV(particleTileBuffer); + rc.particleBufferIndex = GetBufferIndexSRV(particleBuffer); + rc.particleCount = particleCount; + rc.tileCount = frustumTileSize[0] * frustumTileSize[1] * frustumTileSize[2]; + VectorCopy(frustumTileScale, rc.tileScale); + + CmdBindPipeline(frustumParticlePipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatchIndirect(particleDispatchBuffer, 0); + } + } + + if(particleCount > 0) + { + SCOPED_RENDER_PASS("VL Extinction Particles", 1.0f, 1.0f, 1.0f); + + for(int c = 0; c < 4; c++) + { + { + SCOPED_DEBUG_LABEL("Pre-pass", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdBufferBarrier(particleBuffer, ResourceStates::ComputeShaderAccessBit); + CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + CmdClearBufferUAV(particleHitBuffer, 0); + + CmdBeginBarrier(); + CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + VLParticlePreProcessExtinctionRC rc = {}; + rc.tileBufferIndex = GetBufferIndexUAV(particleHitBuffer); + rc.particleBufferIndex = GetBufferIndexSRV(particleBuffer); + rc.particleCount = particleCount; + VectorCopy(extinctionSize, rc.fullResolution); + VectorCopy(extinctionTileSize, rc.tileResolution); + VectorCopy(extinctionTileScale, rc.tileScale); + rc.extinctionWorldScale = extinctionVolumeScale[c]; + + CmdBindPipeline(particlePreProcessExtinctionPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((particleCount + 63) / 64, 1, 1); + } + + { + SCOPED_DEBUG_LABEL("Clear Dispatch", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdBufferBarrier(particleDispatchClearBuffer, ResourceStates::CopySourceBit); + CmdBufferBarrier(particleDispatchBuffer, ResourceStates::CopyDestinationBit); + CmdEndBarrier(); + + CmdCopyBuffer(particleDispatchBuffer, particleDispatchClearBuffer); + } + + { + SCOPED_DEBUG_LABEL("Prepare Dispatch", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); + CmdBufferBarrier(particleDispatchBuffer, ResourceStates::UnorderedAccessBit); + CmdBufferBarrier(particleTileBuffer, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + VLParticleDispatchRC rc = {}; + rc.tileBufferIndex = GetBufferIndexUAV(particleHitBuffer); + rc.dispatchBufferIndex = GetBufferIndexUAV(particleDispatchBuffer); + rc.particleTileBufferIndex = GetBufferIndexUAV(particleTileBuffer); + VectorCopy(extinctionTileSize, rc.tileResolution); + + CmdBindPipeline(particleDispatchPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((extinctionTileSize[0] + 3) / 4, (extinctionTileSize[1] + 3) / 4, (extinctionTileSize[2] + 3) / 4); + } + + { + SCOPED_DEBUG_LABEL("Injection", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdBufferBarrier(particleBuffer, ResourceStates::ComputeShaderAccessBit); + CmdTextureBarrier(extinctionTextures[c], ResourceStates::UnorderedAccessBit); + CmdBufferBarrier(particleDispatchBuffer, ResourceStates::IndirectDispatchBit); + CmdEndBarrier(); + + VLParticleExtinctionRC rc = {}; + rc.extinctionTextureIndex = GetTextureIndexUAV(extinctionTextures[c], 0); + rc.tileBufferIndex = GetBufferIndexUAV(particleTileBuffer); + rc.particleBufferIndex = GetBufferIndexSRV(particleBuffer); + rc.particleCount = particleCount; + rc.tileCount = extinctionTileSize[0] * extinctionTileSize[1] * extinctionTileSize[2]; + rc.extinctionWorldScale = extinctionVolumeScale[c]; + VectorCopy(extinctionTileScale, rc.tileScale); + + CmdBindPipeline(extinctionParticlePipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatchIndirect(particleDispatchBuffer, 0); + } + } + } +#endif + + { + SCOPED_RENDER_PASS("VL Anisotropy Avg", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + VLAnisotropyRC rc = {}; + rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); + rc.materialTextureCIndex = GetTextureIndexUAV(materialTextureC, 0); + + CmdBindPipeline(frustumAnisotropyPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); + } + + { + SCOPED_RENDER_PASS("VL Ambient", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit); + if(tr.world->lightGridData != NULL) + { + CmdTextureBarrier(ambientLightTextureA, ResourceStates::ComputeShaderAccessBit); + CmdTextureBarrier(ambientLightTextureB, ResourceStates::ComputeShaderAccessBit); + } + CmdEndBarrier(); + + VLAmbientRC rc = {}; + rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0); + rc.scatterExtTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0); + rc.isLightGridAvailable = tr.world->lightGridData != NULL; + if(rc.isLightGridAvailable) + { + rc.ambientLightTextureAIndex = GetTextureIndexSRV(ambientLightTextureA); + rc.ambientLightTextureBIndex = GetTextureIndexSRV(ambientLightTextureB); + VectorCopy(lightGridCenter, rc.centerPosition); + VectorCopy(tr.world->lightGridSize, rc.worldScale); + rc.ambientSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear); + } + + CmdBindPipeline(frustumAmbientPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); + } + + firstFrame = false; +} + +void VolumetricLight::DrawPointLight(const dlight_t& light) +{ + { + SCOPED_DEBUG_LABEL("VL DL Shadow", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + for(int c = 0; c < 4; c++) + { + CmdTextureBarrier(extinctionTextures[c], ResourceStates::ComputeShaderAccessBit); + } + CmdTextureBarrier(pointShadowTexture, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + VLPointLightShadowRC rc = {}; + rc.shadowTextureIndex = GetTextureIndexUAV(pointShadowTexture, 0); + rc.extinctionWorldScale = extinctionVolumeScale[0]; // pick the highest resolution possible + rc.shadowWorldScale = pointShadowVolumeScale; + VectorCopy(light.origin, rc.lightPosition); + + const uint32_t groupCount = (shadowPixelCount + 3) / 4; + CmdBindPipeline(pointLightShadowPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch(groupCount, groupCount, groupCount); + } + + { + SCOPED_DEBUG_LABEL("VL DL Scatter", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(pointShadowTexture, ResourceStates::ComputeShaderAccessBit); + CmdEndBarrier(); + + VLPointLightScatterRC rc = {}; + rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0); + rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); + rc.scatterExtTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0); + rc.transmittanceTextureIndex = GetTextureIndexSRV(pointShadowTexture); + rc.transmittanceSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear); + rc.shadowWorldScale = pointShadowVolumeScale; + VectorCopy(light.origin, rc.light.position); + rc.light.radius = light.radius; + VectorCopy(light.color, rc.light.color); + + CmdBindPipeline(frustumPointLightScatterPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); + } +} + +void VolumetricLight::DrawSunlight() +{ + SCOPED_RENDER_PASS("VL Sunlight", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + for(int c = 0; c < 4; c++) + { + CmdTextureBarrier(extinctionTextures[c], ResourceStates::ComputeShaderAccessBit); + } + for(int c = 0; c < 3; c++) + { + CmdTextureBarrier(sunShadowTextures[c], ResourceStates::UnorderedAccessBit); + } + CmdTextureBarrier(sunShadowTextures[3], ResourceStates::ComputeShaderAccessBit); + CmdEndBarrier(); + + // each cascade needs to sample the higher level for setting the initial transmittance value + for(int c = 3; c >= 0; c--) + { + SCOPED_DEBUG_LABEL("VL Sunlight Shadow", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(sunShadowTextures[c], ResourceStates::UnorderedAccessBit); + if(c < 3) + { + CmdTextureBarrier(sunShadowTextures[c + 1], ResourceStates::ComputeShaderAccessBit); + } + CmdEndBarrier(); + + VLSunlightShadowRC rc = {}; + rc.shadowTextureIndex = GetTextureIndexUAV(sunShadowTextures[c], 0); + rc.shadowWorldScale = sunShadowVolumeScale[c]; + if(c < 3) + { + rc.sourceTextureIndex = GetTextureIndexSRV(sunShadowTextures[c + 1]); + rc.sourceWorldScale = sunShadowVolumeScale[c + 1]; + } + + CmdBindPipeline(sunlightShadowPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((sunShadowSize[0] + 7) / 8, (sunShadowSize[1] + 7) / 8, 1); + } + + { + SCOPED_DEBUG_LABEL("VL Sunlight Vis", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(crp.depthMinMaxTexture, ResourceStates::ComputeShaderAccessBit); + CmdTextureBarrier(sunlightVisTexture, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + const float maxJitterDepthDistance = 16.0f; + const float maxJitterRadiusXY = 4.0f; + + vec2_t point01; + Halton23Sequence(point01, jitterCounter); + + // 6 points on the circle, rotated by 15 degrees + // ordered such that it oscillates around (0, 0) + const float jitterXY[] = + { + 0.965926f, 0.258819f, + -0.965926f, -0.258819f, + 0.707107f, -0.707107f, + -0.707107f, 0.707107f, + 0.258819f, 0.965926f, + -0.258819f, -0.965926f + }; + const int jitterXYSampleIndex = jitterCounter % 6; + + VLSunlightVisRC rc = {}; + rc.visTextureIndex = GetTextureIndexUAV(sunlightVisTexture, 0); + rc.depthMip = depthMip; + rc.jitter[0] = (0.5f * jitterXY[2 * jitterXYSampleIndex + 0]) * maxJitterRadiusXY; + rc.jitter[1] = (0.5f * jitterXY[2 * jitterXYSampleIndex + 1]) * maxJitterRadiusXY; + rc.jitter[2] = VanDerCorputSequence(jitterCounter) * maxJitterDepthDistance; + + CmdBindPipeline(frustumSunlightVisPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); + } + + if(!firstFrame) + { + SCOPED_DEBUG_LABEL("VL Sunlight Vis Temporal", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(prevSunlightVisTexture, ResourceStates::ComputeShaderAccessBit); + CmdTextureBarrier(sunlightVisTexture, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + VLTemporalRC rc = {}; + rc.currTextureIndex = GetTextureIndexUAV(sunlightVisTexture, 0); + rc.prevTextureIndex = GetTextureIndexSRV(prevSunlightVisTexture); + rc.prevTextureSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear); + rc.alpha = 0.9f; + + CmdBindPipeline(frustumTemporalPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); + } + + { + SCOPED_DEBUG_LABEL("VL Sunlight Vis Copy", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(sunlightVisTexture, ResourceStates::CopySourceBit); + CmdTextureBarrier(prevSunlightVisTexture, ResourceStates::CopyDestinationBit); + CmdEndBarrier(); + + CmdCopyTexture(prevSunlightVisTexture, sunlightVisTexture); + } + + { + SCOPED_DEBUG_LABEL("VL Sunlight", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + for(int c = 0; c < 4; c++) + { + CmdTextureBarrier(sunShadowTextures[c], ResourceStates::ComputeShaderAccessBit); + } + CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(sunlightVisTexture, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + VLSunlightRC rc = {}; + rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0); + rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); + rc.sunlightVisTextureIndex = GetTextureIndexUAV(sunlightVisTexture, 0); + rc.scatterExtTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0); + + CmdBindPipeline(sunlightScatterPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); + } +} + +void VolumetricLight::DrawEnd() +{ + SCOPED_RENDER_PASS("VL Raymarch", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit); + CmdTextureBarrier(scatterTransTexture, ResourceStates::UnorderedAccessBit); + CmdEndBarrier(); + + VLRaymarchRC rc = {}; + rc.scatterTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0); + rc.resolveTextureIndex = GetTextureIndexUAV(scatterTransTexture, 0); + rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); + + CmdBindPipeline(frustumRaymarchPipeline); + CmdSetComputeRootConstants(0, sizeof(rc), &rc); + CmdDispatch((frustumSize[0] + 7) / 8, (frustumSize[1] + 7) / 8, 1); + + jitterCounter++; +} + +void VolumetricLight::DrawDebug() +{ + if(!ShouldDrawDebug()) + { + return; + } + + if(drawExtinctionDebug) + { + SCOPED_RENDER_PASS("VL Extinction Viz", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + for(int c = 0; c < 4; c++) + { + CmdTextureBarrier(extinctionTextures[c], ResourceStates::UnorderedAccessBit); + } + CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit); + CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit); + CmdEndBarrier(); + + const int c = debugExtinctionCascadeIndex; + VLExtinctionVizRC rc = {}; + rc.extinctionTextureIndex = GetTextureIndexUAV(extinctionTextures[c], 0); + rc.worldScale = extinctionVolumeScale[c]; + rc.boxScale = debugBoxScale; + rc.extinctionScale = debugExtinctionScale; + VectorCopy(debugCameraPosition, rc.cameraPosition); + VectorCopy(colorRed, rc.color); + + const uint32_t voxelCount = extinctionSize[0] * extinctionSize[1] * extinctionSize[2]; + const uint32_t vertexCount = voxelCount * 36; + CmdBindRenderTargets(1, &crp.renderTarget, &crp.depthTexture); + CmdBindPipeline(extinctionVizPipeline); + CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); + CmdDraw(vertexCount, 0); + } + + if(drawSunShadowDebug) + { + SCOPED_RENDER_PASS("VL Sun Shadow Viz", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + for(int c = 0; c < 4; c++) + { + CmdTextureBarrier(sunShadowTextures[c], ResourceStates::UnorderedAccessBit); + } + CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit); + CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit); + CmdEndBarrier(); + + const int c = debugSunShadowCascadeIndex; + VLSunShadowVizRC rc = {}; + rc.shadowTextureIndex = GetTextureIndexUAV(sunShadowTextures[c], 0); + rc.worldScale = sunShadowVolumeScale[c]; + rc.boxScale = debugBoxScale; + VectorCopy(debugCameraPosition, rc.cameraPosition); + VectorCopy(colorRed, rc.color); + + const uint32_t voxelCount = sunShadowSize[0] * sunShadowSize[1] * sunShadowSize[2]; + const uint32_t vertexCount = voxelCount * 36; + CmdBindRenderTargets(1, &crp.renderTarget, &crp.depthTexture); + CmdBindPipeline(sunShadowVizPipeline); + CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); + CmdDraw(vertexCount, 0); + } + + if(drawAmbientDebug && tr.world->lightGridData != NULL) + { + SCOPED_RENDER_PASS("VL Ambient Viz", 1.0f, 1.0f, 1.0f); + + CmdBeginBarrier(); + CmdTextureBarrier(ambientLightTextureA, ResourceStates::PixelShaderAccessBit); + CmdTextureBarrier(ambientLightTextureB, ResourceStates::PixelShaderAccessBit); + CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit); + CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit); + CmdEndBarrier(); + + VLAmbientVizRC rc = {}; + rc.lightGridTextureAIndex = GetTextureIndexSRV(ambientLightTextureA); + rc.lightGridTextureBIndex = GetTextureIndexSRV(ambientLightTextureB); + VectorCopy(tr.world->lightGridSize, rc.worldScale); + rc.sphereScale = debugSphereScale; + VectorCopy(lightGridCenter, rc.centerPosition); + + const uint32_t voxelCount = tr.world->lightGridBounds[0] * tr.world->lightGridBounds[1] * tr.world->lightGridBounds[2]; + const uint32_t vertexCount = voxelCount * 6; + CmdBindRenderTargets(1, &crp.renderTarget, &crp.depthTexture); + CmdBindPipeline(ambientVizPipeline); + CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); + CmdDraw(vertexCount, 0); + } +} + +void VolumetricLight::DrawGUI() +{ + if(!tr.worldMapLoaded) + { + return; + } + + GUI_AddMainMenuItem(GUI_MainMenu::Tools, "Edit Volumetrics", "", &windowActive); + + if(!lockCameraPosition) + { + VectorCopy(backEnd.viewParms.world.viewOrigin, debugCameraPosition); + } + + if(!windowActive) + { + return; + } + + if(ImGui::Begin("Volumetric Data", &windowActive, ImGuiWindowFlags_AlwaysAutoResize)) + { + const ImGuiColorEditFlags colorEditFlags = + ImGuiColorEditFlags_Float | + ImGuiColorEditFlags_NoAlpha | + ImGuiColorEditFlags_DisplayRGB | + ImGuiColorEditFlags_InputRGB; + ImGui::ColorEdit3("Ambient light color", ambientColor, colorEditFlags); + ImGui::SliderFloat("Ambient light intensity", &ambientIntensity, 0.0f, 10.0f); + + ImGui::Separator(); + + if(fogCount < ARRAY_LEN(fogs) && ImGui::Button("Add Fog")) + { + const float fogSize = 40.0f; + Fog& fog = fogs[fogCount++]; + fog = {}; + fog.extinction = 0.1f; + fog.albedo = 0.5f; + VectorSet(fog.scatterColor, 1, 1, 1); + fog.emissive = 0.0f; + VectorSet(fog.emissiveColor, 1, 1, 1); + fog.anisotropy = 0.0f; + fog.noiseStrength = 1.0f; + fog.noiseSpatialPeriod = 50.0f; + fog.noiseTimePeriod = 8.0f; + fog.isHeightFog = false; + fog.isGlobalFog = false; + for(int a = 0; a < 3; a++) + { + fog.boxMin[a] = backEnd.refdef.vieworg[a] - fogSize; + fog.boxMax[a] = backEnd.refdef.vieworg[a] + fogSize; + } + } + + ImGui::SameLine(); + if(fogCount > 0 && ImGui::Button("Save Config...")) + { + OpenSaveFileDialog("fogs", ".fogs"); + } + ImGui::SameLine(); + if(ImGui::Button("Open Config...")) + { + OpenOpenFileDialog("fogs", ".fogs"); + } + + const char* axisNames[3] = { "X", "Y", "Z" }; + + ImGui::BeginTabBar("Tabs#Fogs", ImGuiTabBarFlags_AutoSelectNewTabs); + for(int i = 0; i < fogCount; i++) + { + if(ImGui::BeginTabItem(va("#%d", i + 1))) + { + Fog& fog = fogs[i]; + ImGui::Checkbox("Global fog", &fog.isGlobalFog); + if(!fog.isGlobalFog) + { + for(int a = 0; a < 3; a++) + { + ImGui::SliderFloat(va("Box min %s", axisNames[a]), &fog.boxMin[a], mapBoxMin[a], mapBoxMax[a], "%g"); + ImGui::SliderFloat(va("Box max %s", axisNames[a]), &fog.boxMax[a], mapBoxMin[a], mapBoxMax[a], "%g"); + } + } + float opaqueDistance = ExtinctionToOpaqueDistance(fog.extinction); + ImGui::SliderFloat("Distance to opaque", &opaqueDistance, 1.0f, 10000.0f, "%g"); + fog.extinction = OpaqueDistanceToExtinction(opaqueDistance); + ImGui::ColorEdit3("Reflective (scatter) color", fog.scatterColor, colorEditFlags); + ImGui::SliderFloat("Reflectivity (albedo)", &fog.albedo, 0.0f, 1.0f, "%g"); + ImGui::SliderFloat("Directionality (anisotropy)", &fog.anisotropy, 0.0f, 1.0f, "%g"); + ImGui::SliderFloat("Emissive strength", &fog.emissive, 0.0f, 0.001f, "%g"); + ImGui::ColorEdit3("Emissive color", fog.emissiveColor, colorEditFlags); + ImGui::SliderFloat("Noise strength", &fog.noiseStrength, 1.0f, 10.0f, "%g"); + ImGui::SliderFloat("Noise space period", &fog.noiseSpatialPeriod, 0.0f, 200.0f, "%g"); + ImGui::SliderFloat("Noise time period", &fog.noiseTimePeriod, 0.0f, 20.0f, "%g"); + ImGui::Checkbox("Height fog", &fog.isHeightFog); + ImGui::EndTabItem(); + + ImGui::Separator(); + if(ImGui::Button("Remove")) + { + if(i < fogCount - 1) + { + memmove(&fogs[i], &fogs[i + 1], (fogCount - 1 - i) * sizeof(fogs[0])); + } + fogCount--; + } + } + } + if(ImGui::BeginTabItem("Debug")) + { + if(tr.world->lightGridData != NULL) + { + ImGui::Checkbox("Draw ambient light grid", &drawAmbientDebug); + ImGui::SliderFloat("Voxel sphere scale", &debugSphereScale, 0.25f, 1.0f); + } + else + { + ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "No valid light grid was found for this map"); + } + + ImGui::NewLine(); + ImGui::SliderInt("Extinction cascade", &debugExtinctionCascadeIndex, 0, 3); + ImGui::Checkbox("Draw extinction volume", &drawExtinctionDebug); + ImGui::SliderFloat("Extinction value scale", &debugExtinctionScale, 1.0f, 1000.0f); + + ImGui::NewLine(); + ImGui::SliderInt("Sun shadow cascade", &debugSunShadowCascadeIndex, 0, 3); + ImGui::Checkbox("Draw sun shadow volume", &drawSunShadowDebug); + + ImGui::NewLine(); + ImGui::Checkbox("Lock camera position", &lockCameraPosition); + ImGui::SliderFloat("Voxel box scale", &debugBoxScale, 0.25f, 1.0f); + + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + + if(SaveFileDialog()) + { + SaveFogFile(GetSaveFileDialogPath()); + } + + if(OpenFileDialog()) + { + LoadFogFile(GetOpenFileDialogPath()); + } + } + ImGui::End(); +} + +bool VolumetricLight::WantRTASUpdate(const trRefdef_t& scene) +{ + return crp_volLight->integer != 0; +} + +bool VolumetricLight::ShouldDraw() +{ + if(crp_volLight->integer == 0 || + (backEnd.refdef.rdflags & RDF_NOWORLDMODEL) != 0 || + !IsViewportFullscreen(backEnd.viewParms) || + lockCameraPosition) + { + return false; + } + + return true; +} + +bool VolumetricLight::ShouldDrawDebug() +{ + if(crp_volLight->integer == 0 || + (backEnd.refdef.rdflags & RDF_NOWORLDMODEL) != 0 || + !IsViewportFullscreen(backEnd.viewParms)) + { + return false; + } + + return drawExtinctionDebug || drawSunShadowDebug || drawAmbientDebug; +} + +bool VolumetricLight::LoadFogFile(const char* filePath) +{ + bool success = false; + void* data = NULL; + const int byteCount = ri.FS_ReadFile(filePath, &data); + if(data != NULL && + byteCount > 0 && + byteCount <= sizeof(fogs) && + (byteCount % sizeof(fogs[0])) == 0) + { + memcpy(&fogs[0], data, byteCount); + fogCount = byteCount / sizeof(fogs[0]); + success = true; + } + if(data != NULL) + { + ri.FS_FreeFile(data); + } + + return success; +} + +void VolumetricLight::SaveFogFile(const char* filePath) +{ + if(fogCount > 0) + { + FS_EnableCNQ3FolderWrites(qtrue); + ri.FS_WriteFile(filePath, &fogs[0], fogCount * sizeof(fogs[0])); + FS_EnableCNQ3FolderWrites(qfalse); + } +} diff --git a/code/renderer/shaders/common/state_bits.h.hlsli b/code/renderer/shaders/common/state_bits.h.hlsli index 0cd7295..d374ddb 100644 --- a/code/renderer/shaders/common/state_bits.h.hlsli +++ b/code/renderer/shaders/common/state_bits.h.hlsli @@ -48,6 +48,11 @@ along with Challenge Quake 3. If not, see . #define GLS_DSTBLEND_BITS 0x000000f0u #define GLS_BLEND_BITS 0x000000ffu +#define GLS_BLEND_ADDITIVE (GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE) +#define GLS_BLEND_STD_ALPHA (GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA) +#define GLS_BLEND_PMUL_ALPHA (GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA) +#define GLS_BLEND_FILTER (GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO) +#define GLS_BLEND_FILTER_V2 (GLS_SRCBLEND_ZERO | GLS_DSTBLEND_SRC_COLOR) #define GLS_DEPTHMASK_TRUE 0x00000100u // enable depth writes diff --git a/code/renderer/shaders/crp/accumdof_debug.hlsl b/code/renderer/shaders/crp/accumdof_debug.hlsl index 90f0aee..44b0016 100644 --- a/code/renderer/shaders/crp/accumdof_debug.hlsl +++ b/code/renderer/shaders/crp/accumdof_debug.hlsl @@ -24,6 +24,7 @@ along with Challenge Quake 3. If not, see . #include "common.hlsli" #include "fullscreen.hlsli" #include "dof.hlsli" +#include "scene_view.h.hlsli" cbuffer RootConstants @@ -35,14 +36,13 @@ cbuffer RootConstants uint debugMode; // 1: colorized coc, 2: constant intensity far field int tcScale; float focusDist; - float linearDepthA; // main view, to unproject to WS - float linearDepthB; float maxNearCocCS; float maxFarCocCS; }; float4 ps(VOut input) : SV_Target { + SceneView scene = GetSceneView(); Texture2D colorTexture = ResourceDescriptorHeap[colorTextureIndex]; Texture2D depthTexture = ResourceDescriptorHeap[depthTextureIndex]; @@ -50,7 +50,7 @@ float4 ps(VOut input) : SV_Target int3 tcDepth = int3(input.position.xy / tcScale, 0); float3 color = colorTexture.Load(tcColor).rgb; float depthZW = depthTexture.Load(tcDepth); - float depth = LinearDepth(depthZW, linearDepthA, linearDepthB); + float depth = scene.LinearDepth(depthZW); bool nearField = depth < focusDist; float4 result; if(debugMode == 1) diff --git a/code/renderer/shaders/crp/add_light.hlsl b/code/renderer/shaders/crp/add_light.hlsl index 86d09c0..eae90ba 100644 --- a/code/renderer/shaders/crp/add_light.hlsl +++ b/code/renderer/shaders/crp/add_light.hlsl @@ -58,9 +58,10 @@ float4 ps(VOut input) : SV_Target { SceneView scene = GetSceneView(); Texture2D lightTexture = ResourceDescriptorHeap[scene.lightTextureIndex]; + Texture2D sunlightTexture = ResourceDescriptorHeap[scene.sunlightTextureIndex]; uint3 tc = uint3(input.position.xy, 0); - float3 color = lightTexture.Load(tc).rgb; + float3 color = lightTexture.Load(tc).rgb + sunlightTexture.Load(tc).rgb; float4 result = float4(color, 0); return result; diff --git a/code/renderer/shaders/crp/alpha_test.h.hlsli b/code/renderer/shaders/crp/alpha_test.h.hlsli index c8f3dc4..6484110 100644 --- a/code/renderer/shaders/crp/alpha_test.h.hlsli +++ b/code/renderer/shaders/crp/alpha_test.h.hlsli @@ -30,6 +30,7 @@ along with Challenge Quake 3. If not, see . #define ATEST_GE_HALF 3u #if !defined(__cplusplus) + bool FailsAlphaTest(float alpha, uint alphaTest) { if(alphaTest == ATEST_GT_0) @@ -41,4 +42,10 @@ bool FailsAlphaTest(float alpha, uint alphaTest) else // ATEST_NONE return false; } + +bool PassesAlphaTest(float alpha, uint alphaTest) +{ + return !FailsAlphaTest(alpha, alphaTest); +} + #endif diff --git a/code/renderer/shaders/crp/common.hlsli b/code/renderer/shaders/crp/common.hlsli index c9ad20e..98378d3 100644 --- a/code/renderer/shaders/crp/common.hlsli +++ b/code/renderer/shaders/crp/common.hlsli @@ -33,6 +33,24 @@ along with Challenge Quake 3. If not, see . #define PI_D4 (PI / 4.0) #define PI_M2 (PI * 2.0) +#define INT8_MIN 0x80 +#define INT16_MIN 0x8000 +#define INT32_MIN 0x80000000 +#define INT64_MIN 0x8000000000000000 + +#define INT8_MAX 0x7F +#define INT16_MAX 0x7FFF +#define INT32_MAX 0x7FFFFFFF +#define INT64_MAX 0x7FFFFFFFFFFFFFFF + +#define UINT8_MAX 0xFF +#define UINT16_MAX 0xFFFF +#define UINT32_MAX 0xFFFFFFFF +#define UINT64_MAX 0xFFFFFFFFFFFFFFFF + + +typedef RaytracingAccelerationStructure RTAS; + float DegToRad(float deg) { @@ -59,22 +77,29 @@ float4 MakeGreyscale(float4 input, float amount) return result; } -/* -f = far clip plane distance -n = near clip plane distance -exp = exponential depth value (as stored in the Z-buffer) - - 2 * f * n B -linear(exp) = ----------------------- = ------- - (f + n) - exp * (f - n) exp - A - - f + n -2 * f * n -with A = ----- and B = ---------- - f - n f - n -*/ -float LinearDepth(float zwDepth, float A, float B) +float LinearDepth(float zwDepth, float zNear, float zFar) { - return B / (zwDepth - A); + float n = zNear; + float f = zFar; + float zw = zwDepth; + float zl = (f * n) / (n + zw * (f - n)); + + return zl; +} + +float LinearDepth(float zwDepth, float3 constants) +{ + return constants.x / (constants.y + zwDepth * constants.z); +} + +float PostProjectionDepth(float viewDepth, float zNear, float zFar) +{ + float n = zNear; + float f = zFar; + float zv = viewDepth; + float zw = (n * (f - zv)) / ((f - n) * zv); + + return zw; } float4 FSTrianglePosFromVertexId(uint id) @@ -138,6 +163,16 @@ float EaseInQuad(float x) return x * x; } +float EaseInExp(float x) +{ + return x == 0.0 ? 0.0 : pow(2.0, 10.0 * x - 10.0); +} + +float EaseOutExp(float x) +{ + return x == 1.0 ? 1.0 : 1.0 - pow(2.0, -10.0 * x); +} + float smoothstep01(float x) { return smoothstep(0.0, 1.0, x); @@ -285,24 +320,34 @@ float InterleavedGradientNoise(float2 uv) } template -bool IsValueInRange(T p, T min, T max) +bool IsInRange(T p, T min, T max) { return all(p >= min) && all(p <= max); } -bool IsValue01(float2 p) +bool Is01(float2 p) { - return IsValueInRange(p, float2(0, 0), float2(1, 1)); + return IsInRange(p, float2(0, 0), float2(1, 1)); } -bool IsValue01(float3 p) +bool Is01(float3 p) { - return IsValueInRange(p, float3(0, 0, 0), float3(1, 1, 1)); + return IsInRange(p, float3(0, 0, 0), float3(1, 1, 1)); } -bool IsValue01(float4 p) +bool Is01(float4 p) { - return IsValueInRange(p, float4(0, 0, 0, 0), float4(1, 1, 1, 1)); + return IsInRange(p, float4(0, 0, 0, 0), float4(1, 1, 1, 1)); +} + +bool IsInTexture(int2 tc, int2 textureSize) +{ + return all(tc >= int2(0, 0)) && all(tc < textureSize); +} + +bool IsInTexture(int3 tc, int3 textureSize) +{ + return all(tc >= int3(0, 0, 0)) && all(tc < textureSize); } template @@ -323,6 +368,24 @@ uint2 GetTextureSize(RWTexture2D texture0) return size; } +template +uint3 GetTextureSize(Texture3D texture0) +{ + uint3 size; + texture0.GetDimensions(size.x, size.y, size.z); + + return size; +} + +template +uint3 GetTextureSize(RWTexture3D texture0) +{ + uint3 size; + texture0.GetDimensions(size.x, size.y, size.z); + + return size; +} + // by Sakib Saikia, https://sakibsaikia.github.io/graphics/2022/01/04/Nan-Checks-In-HLSL.html bool IsNan(float x) { @@ -343,7 +406,14 @@ float AnimateBlueNoise(float blueNoise, uint frameIndex) float2 NDCToTC(float2 ndc) { - float2 tc = ndc * float2(0.5, -0.5) + 0.5; + float2 tc = ndc * float2(0.5, -0.5) + float2(0.5, 0.5); + + return tc; +} + +float3 NDCToTC(float3 ndc) +{ + float3 tc = ndc * float3(0.5, -0.5, 0.5) + float3(0.5, 0.5, 0.5); return tc; } @@ -355,6 +425,13 @@ float2 TCToNDC(float2 tc) return ndc; } +float3 TCToNDC(float3 tc) +{ + float3 ndc = (2.0 * tc - 1.0) * float3(1, -1, 1); + + return ndc; +} + // returns the longest vector float2 vmax(float2 a, float2 b) { @@ -397,6 +474,176 @@ float2 PolarToCartesian(float2 polar) return cartesian; } +// Beer-Lambert law +float Transmittance(float distance, float extinction) +{ + float transmittance = exp(-distance * extinction); + + return transmittance; +} + +// phase function for Mie scattering +// g is in the range [-1;1] +// -1: backward scattering +// 0: isotropic +// 1: forward scattering +float HenyeyGreenstein(float cosTheta, float g) +{ + float g2 = g * g; + float num = 1.0 - g2; + float denom = 4.0 * PI * pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5); + float result = num / denom; + + return result; +} + +uint FlattenIndex(uint3 tileIndex, uint3 tileResolution) +{ + return + tileIndex.x + + tileIndex.y * tileResolution.x + + tileIndex.z * tileResolution.x * tileResolution.y; +} + +int FlattenIndex(int3 tileIndex, int3 tileResolution) +{ + return + tileIndex.x + + tileIndex.y * tileResolution.x + + tileIndex.z * tileResolution.x * tileResolution.y; +} + +uint3 UnflattenIndex(uint flatIndex, uint3 tileResolution) +{ + uint h = tileResolution.y; + uint wh = tileResolution.x * h; + uint z = flatIndex / wh; + flatIndex -= z * wh; + uint y = flatIndex / h; + uint x = flatIndex - y * h; + uint3 result = uint3(x, y, z); + + return result; +} + +int3 UnflattenIndex(int flatIndex, int3 tileResolution) +{ + int h = tileResolution.y; + int wh = tileResolution.x * h; + int z = flatIndex / wh; + flatIndex -= z * wh; + int y = flatIndex / h; + int x = flatIndex - y * h; + int3 result = int3(x, y, z); + + return result; +} + +void ClearBoundingBox(out int3 boxMin, out int3 boxMax) +{ + boxMin = int3(INT32_MAX, INT32_MAX, INT32_MAX); + boxMax = int3(INT32_MIN, INT32_MIN, INT32_MIN); +} + +template +void ExpandBoundingBox(inout T boxMin, inout T boxMax, T newPoint) +{ + boxMin = min(boxMin, newPoint); + boxMax = max(boxMax, newPoint); +} + +// Credit: Riku Salminen +// dispatch the draw call with 36 indices +float3 CubeFromVertexID(uint vertexId) +{ + int tri = int(vertexId) / 3; + int idx = int(vertexId) % 3; + int face = tri / 2; + int top = tri % 2; + + int dir = face % 3; + int pos = face / 3; + + int nz = dir >> 1; + int ny = dir & 1; + int nx = 1 ^ (ny | nz); + + float3 d = float3(nx, ny, nz); + float flip = 1 - 2 * pos; + + float3 n = flip * d; + float3 u = -d.yzx; + float3 v = flip * d.zxy; + + float mirror = -1 + 2 * top; + float3 xyz = n + mirror * (1 - 2 * (idx & 1)) * u + mirror * (1 - 2 * (idx >> 1)) * v; + + return xyz; +} + +// dispatch the draw call with 6 indices +float2 QuadFromVertexID(uint vertexId) +{ + float2 position; + position.x = (vertexId >= 1 && vertexId <= 3) ? 1.0 : -1.0; + position.y = (vertexId >= 2 && vertexId <= 4) ? -1.0 : 1.0; + + return position; +} + +float3 AABoxIndexToWorldSpace(int3 index, float3 centerPosition, float3 textureSize, float3 worldScale) +{ + float3 position = centerPosition + worldScale * (float3(index)+float3(0.5, 0.5, 0.5) - 0.5 * textureSize); + + return position; +} + +float3 AABoxTCToWorldSpace(float3 tc, float3 centerPosition, float3 textureSize, float3 worldScale) +{ + float3 position = centerPosition + worldScale * textureSize * (tc - float3(0.5, 0.5, 0.5)); + + return position; +} + +float3 AABoxWorldSpaceToTC(float3 position, float3 centerPosition, float3 textureSize, float3 worldScale) +{ + float3 boxSize = worldScale * textureSize; + float3 boxMin = centerPosition - 0.5 * boxSize; + float3 tc = (position - boxMin) / boxSize; + + return tc; +} + +int3 AABoxWorldSpaceToIndex(float3 position, float3 centerPosition, float3 textureSize, float3 worldScale) +{ + float3 boxSize = worldScale * textureSize; + float3 boxMin = centerPosition - 0.5 * boxSize; + float3 indexF = (position - boxMin) / worldScale; + int3 index = int3(indexF); + + return index; +} + +float3 AABoxIndexToWorldSpace(int3 index, float3 centerPosition, float3 textureSize, float worldScale) +{ + return AABoxIndexToWorldSpace(index, centerPosition, textureSize, worldScale.xxx); +} + +float3 AABoxTCToWorldSpace(float3 tc, float3 centerPosition, float3 textureSize, float worldScale) +{ + return AABoxTCToWorldSpace(tc, centerPosition, textureSize, worldScale.xxx); +} + +float3 AABoxWorldSpaceToTC(float3 position, float3 centerPosition, float3 textureSize, float worldScale) +{ + return AABoxWorldSpaceToTC(position, centerPosition, textureSize, worldScale.xxx); +} + +int3 AABoxWorldSpaceToIndex(float3 position, float3 centerPosition, float3 textureSize, float worldScale) +{ + return AABoxWorldSpaceToIndex(position, centerPosition, textureSize, worldScale.xxx); +} + template T min3(T v0, T v1, T v2) { @@ -420,3 +667,49 @@ T max4(T v0, T v1, T v2, T v3) { return max(max(v0, v1), max(v2, v3)); } + +// credit: Inigo Quilez +// returns t == -1 when nothing was hit +float RaytraceSphere(float3 rayOrigin, float3 rayDir, float3 spherePos, float sphereRadius) +{ + float3 oc = rayOrigin - spherePos; + float b = dot(oc, rayDir); + float c = dot(oc, oc) - sphereRadius * sphereRadius; + float h = b * b - c; + if(h < 0.0) + { + return -1.0; + } + h = sqrt(h); + float t = -b - h; + + return t; +} + +float3 DirectionFromLongLat(float longitude01, float latitude01) +{ + float lon = longitude01 * PI_M2; + float lat = latitude01 * PI_M2; + float sinLat, cosLat; + sincos(lat, sinLat, cosLat); + float sinLon, cosLon; + sincos(lon, sinLon, cosLon); + float3 direction = float3(cosLat * sinLon, sinLat * sinLon, cosLon); + + return direction; +} + +float3 AmbientColor(float4 payloadA, float4 payloadB, float3 normal, float3 fallbackColor) +{ + float3 ambColor = payloadA.rgb; + float3 localColor = float3(payloadA.a, payloadB.rg); + float3 localDir = DirectionFromLongLat(payloadB.b, payloadB.a); + float localScale = dot(localDir, normal) * 0.5 + 0.5; // wraps around + float3 interpColor = ambColor + localColor * localScale; + float brightNew = Brightness(interpColor); + float brightFall = Brightness(fallbackColor); + float t = saturate(brightNew / max(brightFall, 0.001)); + float3 color = lerp(fallbackColor, interpColor, t); + + return color; +} diff --git a/code/renderer/shaders/crp/dl_denoising.hlsl b/code/renderer/shaders/crp/dl_denoising.hlsl index c4b899a..756c90f 100644 --- a/code/renderer/shaders/crp/dl_denoising.hlsl +++ b/code/renderer/shaders/crp/dl_denoising.hlsl @@ -28,7 +28,11 @@ along with Challenge Quake 3. If not, see . cbuffer RootConstants { + float3 lightPosition; uint textureIndex; + uint vshadowTextureIndex; + uint vshadowSamplerIndex; + float vshadowWorldScale; }; float4 ps(VOut input) : SV_Target @@ -36,6 +40,8 @@ float4 ps(VOut input) : SV_Target SceneView scene = GetSceneView(); Texture2D shadingPositionTexture = ResourceDescriptorHeap[scene.shadingPositionTextureIndex]; Texture2D texture0 = ResourceDescriptorHeap[textureIndex]; + Texture3D vshadowTexture = ResourceDescriptorHeap[vshadowTextureIndex]; + SamplerState vshadowSampler = SamplerDescriptorHeap[vshadowSamplerIndex]; int2 textureMax = GetTextureSize(texture0) - int2(1, 1); int2 tcFrag = int2(input.position.xy); @@ -48,7 +54,7 @@ float4 ps(VOut input) : SV_Target for(int x = -4; x <= 4; x++) { int2 tc = tcFrag + int2(x, y); - if(!IsValueInRange(tcFrag, int2(0, 0), textureMax)) + if(!IsInRange(tcFrag, int2(0, 0), textureMax)) { continue; } @@ -68,7 +74,7 @@ float4 ps(VOut input) : SV_Target for(int x = -blurRadius; x <= blurRadius; x++) { int2 tc = tcFrag + int2(x, y); - if(!IsValueInRange(tcFrag, int2(0, 0), textureMax)) + if(!IsInRange(tcFrag, int2(0, 0), textureMax)) { continue; } @@ -87,7 +93,12 @@ float4 ps(VOut input) : SV_Target { accum /= weightSum; } - float4 result = float4(accum, 1); + + float3 vshadowTC = AABoxWorldSpaceToTC(positionFrag, lightPosition, GetTextureSize(vshadowTexture), vshadowWorldScale); + float transmittance = vshadowTexture.SampleLevel(vshadowSampler, vshadowTC, 0); + accum *= transmittance; + + float4 result = float4(accum, 0); return result; } diff --git a/code/renderer/shaders/crp/dl_draw.hlsl b/code/renderer/shaders/crp/dl_draw.hlsl index 24cb1d4..ac68269 100644 --- a/code/renderer/shaders/crp/dl_draw.hlsl +++ b/code/renderer/shaders/crp/dl_draw.hlsl @@ -25,73 +25,14 @@ along with Challenge Quake 3. If not, see . #include "fullscreen.hlsli" #include "raytracing.h.hlsli" #include "scene_view.h.hlsli" -#include "alpha_test.h.hlsli" cbuffer RootConstants { + DynamicLight light; uint blueNoiseTextureIndex; }; -#define CLASS_OPAQUE 0u -#define CLASS_INVISIBLE 1u -#define CLASS_TRANSLUCENT 2u - -uint ClassifyNonOpaqueTriangle(inout float3 light, StructuredBuffer tlasInstanceBuffer, uint instanceId, uint meshId, uint triangleId, float2 bary2, bool frontFace) -{ - TLASInstance instance = tlasInstanceBuffer[instanceId]; -#if 0 - // @TODO: is this needed or not? - // cull mode: 0 is front-sided, 1 is back-sided - if((frontFace && instance.cullMode == 1) || - (!frontFace && instance.cullMode == 0)) - { - return CLASS_INVISIBLE; - } -#endif - StructuredBuffer meshBuffer = ResourceDescriptorHeap[instance.meshBufferIndex]; - BLASMesh mesh = meshBuffer[meshId]; - float3 barycentrics = float3(1.0 - bary2.x - bary2.y, bary2.x, bary2.y); - StructuredBuffer vertexBuffer = ResourceDescriptorHeap[instance.vertexBufferIndex]; - StructuredBuffer indexBuffer = ResourceDescriptorHeap[instance.indexBufferIndex]; - uint firstIndex = mesh.firstIndex + triangleId * 3; - uint vtxIdx0 = mesh.firstVertex + indexBuffer[firstIndex + 0]; - uint vtxIdx1 = mesh.firstVertex + indexBuffer[firstIndex + 1]; - uint vtxIdx2 = mesh.firstVertex + indexBuffer[firstIndex + 2]; - BLASVertex v0 = vertexBuffer[vtxIdx0]; - BLASVertex v1 = vertexBuffer[vtxIdx1]; - BLASVertex v2 = vertexBuffer[vtxIdx2]; - float2 texCoords = trilerp(v0.texCoords, v1.texCoords, v2.texCoords, barycentrics); - float4 vertexColor = trilerp(UnpackColor(v0.color), UnpackColor(v1.color), UnpackColor(v2.color), barycentrics); - Texture2D texture0 = ResourceDescriptorHeap[mesh.textureIndex]; - SamplerState sampler0 = SamplerDescriptorHeap[mesh.samplerIndex]; - float4 textureColor = texture0.SampleLevel(sampler0, texCoords, 0); - float4 hitColor = vertexColor * textureColor; - if(mesh.alphaTestMode == 0) - { - float3 blended; - if(mesh.blendBits == (GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE)) - { - blended = lerp(light, hitColor.rgb, Brightness(hitColor.rgb)); - } - else - { - blended = Blend(hitColor, float4(light, 1), mesh.blendBits).rgb; - } - if(all(blended == light)) - { - return CLASS_INVISIBLE; - } - light = blended; - return CLASS_TRANSLUCENT; - } - if(FailsAlphaTest(hitColor.a, mesh.alphaTestMode)) - { - return CLASS_INVISIBLE; - } - return CLASS_OPAQUE; -} - float2 MapSquareToDisk(float2 square01) { float radius = sqrt(square01.x); @@ -115,142 +56,50 @@ float3 GetRayDirectionForSphereLight(float2 square01, float3 surfacePos, float3 return result; } -// true when fully in shadow -bool TraceShadowRay( - out float t, inout float3 light, - RaytracingAccelerationStructure rtas, StructuredBuffer instBuffer, - float3 position, float3 direction, float dist) -{ - RayDesc ray; - ray.Origin = position; - ray.Direction = direction; - ray.TMin = 0.0; - ray.TMax = dist; - - t = 0.0; - float translucentT = 0.0; - RayQuery q; - q.TraceRayInline(rtas, RAY_FLAG_NONE, 0xFF, ray); - while(q.Proceed()) - { - if(q.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) - { - uint type = ClassifyNonOpaqueTriangle( - light, - instBuffer, - q.CandidateInstanceIndex(), - q.CandidateGeometryIndex(), - q.CandidatePrimitiveIndex(), - q.CandidateTriangleBarycentrics(), - q.CandidateTriangleFrontFace()); - if(type == CLASS_OPAQUE) - { - q.CommitNonOpaqueTriangleHit(); - } - else if(type == CLASS_TRANSLUCENT) - { - translucentT = q.CandidateTriangleRayT(); - t = translucentT; - } - } - } - - if(q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) - { - t = q.CommittedRayT(); - } - - if(q.CommittedStatus() == COMMITTED_TRIANGLE_HIT && t > translucentT) - { - return true; - } - - return false; -} - -// true when fully in shadow -bool TraceShadowRayOpaqueOnly( - out float t, inout float3 light, - RaytracingAccelerationStructure rtas, StructuredBuffer instBuffer, - float3 position, float3 direction, float dist) -{ - RayDesc ray; - ray.Origin = position; - ray.Direction = direction; - ray.TMin = 0.0; - ray.TMax = dist; - - t = 0.0; - bool keepLight = false; - RayQuery q; - q.TraceRayInline(rtas, RAY_FLAG_NONE, 0xFF, ray); - q.Proceed(); - if(q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) - { - t = q.CommittedRayT(); - return true; - } - - return false; -} - float4 ps(VOut input) : SV_Target { SceneView scene = GetSceneView(); - RaytracingAccelerationStructure rtas = ResourceDescriptorHeap[scene.tlasBufferIndex]; - Texture2D normalTexture = ResourceDescriptorHeap[scene.normalTextureIndex]; Texture2D shadingPositionTexture = ResourceDescriptorHeap[scene.shadingPositionTextureIndex]; + uint3 tc = uint3(input.position.xy, 0); + float3 positionWS = shadingPositionTexture.Load(tc).xyz; + float3 lightPosition = light.position; + float dist = distance(positionWS, lightPosition); + float radius = light.radius; + if(dist >= radius) + { + return float4(0, 0, 0, 0); + } + + RTAS rtas = ResourceDescriptorHeap[scene.tlasBufferIndex]; + Texture2D normalTexture = ResourceDescriptorHeap[scene.normalTextureIndex]; StructuredBuffer tlasInstanceBuffer = ResourceDescriptorHeap[scene.tlasInstanceBufferIndex]; Texture2D blueNoiseTexture = ResourceDescriptorHeap[blueNoiseTextureIndex]; - uint2 blueNoiseTextureSize; - blueNoiseTexture.GetDimensions(blueNoiseTextureSize.x, blueNoiseTextureSize.y); - - uint3 tc = uint3(input.position.xy, 0); + uint2 blueNoiseTextureSize = GetTextureSize(blueNoiseTexture); float3 normalWS = normalize(OctDecode(normalTexture.Load(tc))); - float3 positionWS = shadingPositionTexture.Load(tc).xyz; + float innerRadius = radius / 100.0; + float intensity = saturate(1.0 - dist / radius); + float3 lightDir = normalize(lightPosition - positionWS); + float3 lightRaw = light.color * intensity * max(dot(normalWS, lightDir), 0.0); + const uint SampleCount = 4; + float3 lightAccum = float3(0, 0, 0); float error = 0.0; - float3 pixelAccum = float3(0, 0, 0); - for(uint i = 0; i < scene.lightCount; i++) + for(uint r = 0; r < SampleCount; r++) { - float3 lightPosition = scene.lights[i].position; - float dist = distance(positionWS, lightPosition); - float radius = scene.lights[i].radius; - if(dist >= radius) - { - continue; - } - - float innerRadius = radius / 100.0; - float intensity = saturate(1.0 - dist / radius); - float3 lightDir = normalize(lightPosition - positionWS); - float3 lightRaw = scene.lights[i].color * intensity * max(dot(normalWS, lightDir), 0.0); - const uint SampleCount = 4; - - float3 lightAccum = float3(0, 0, 0); - for(uint r = 0; r < SampleCount; r++) - { - float3 light = lightRaw; - uint2 pos = uint2(input.position.xy) + uint2(r * 17, r * 13 + 7); - uint2 tc = pos % blueNoiseTextureSize; - float2 square01 = blueNoiseTexture.Load(uint3(tc, 0)).xy; - float3 dir = GetRayDirectionForSphereLight(square01, positionWS, lightPosition, innerRadius); - float t; - bool inShadow = TraceShadowRay(t, light, rtas, tlasInstanceBuffer, positionWS, dir, dist); - error = max(error, t / radius); - if(inShadow) - { - continue; - } - - lightAccum += light; - } - - pixelAccum += lightAccum / float(SampleCount); + float3 light = lightRaw; + uint2 pos = uint2(input.position.xy) + uint2(r * 17, r * 13 + 7); + uint2 tc = pos % blueNoiseTextureSize; + float2 square01 = blueNoiseTexture.Load(uint3(tc, 0)).xy; + float3 dir = GetRayDirectionForSphereLight(square01, positionWS, lightPosition, innerRadius); + float t; + float vis = TraceVisibilityWithATt(t, rtas, tlasInstanceBuffer, positionWS, dir, dist); + error = max(error, t / radius); + lightAccum += light * vis; } + lightAccum /= float(SampleCount); - float4 result = float4(pixelAccum, saturate(error)); + float4 result = float4(lightAccum, saturate(error)); return result; } diff --git a/code/renderer/shaders/crp/gatherdof_debug.hlsl b/code/renderer/shaders/crp/gatherdof_debug.hlsl index 85a7770..4e1e1d9 100644 --- a/code/renderer/shaders/crp/gatherdof_debug.hlsl +++ b/code/renderer/shaders/crp/gatherdof_debug.hlsl @@ -25,6 +25,7 @@ along with Challenge Quake 3. If not, see . #include "fullscreen.hlsli" #include "gatherdof.hlsli" #include "dof.hlsli" +#include "scene_view.h.hlsli" cbuffer RootConstants : register(b0) @@ -32,8 +33,6 @@ cbuffer RootConstants : register(b0) uint colorTextureIndex; uint depthTextureIndex; uint debugMode; - float linearDepthA; - float linearDepthB; float focusNearMin; float focusNearMax; float focusFarMin; @@ -43,12 +42,13 @@ cbuffer RootConstants : register(b0) float4 ps(VOut input) : SV_Target { + SceneView scene = GetSceneView(); Texture2D colorTexture = ResourceDescriptorHeap[colorTextureIndex]; Texture2D depthTexture = ResourceDescriptorHeap[depthTextureIndex]; uint3 tc = uint3(input.position.x, input.position.y, 0); float3 color = colorTexture.Load(tc).rgb; float depthZW = depthTexture.Load(tc); - float depth = LinearDepth(depthZW, linearDepthA, linearDepthB); + float depth = scene.LinearDepth(depthZW); float coc = CircleOfConfusion(depth, focusNearMin, focusNearMax, focusFarMin, focusFarMax); float nearField = coc < 0.0; float4 result; diff --git a/code/renderer/shaders/crp/gatherdof_split.hlsl b/code/renderer/shaders/crp/gatherdof_split.hlsl index cc99ef0..d2b3691 100644 --- a/code/renderer/shaders/crp/gatherdof_split.hlsl +++ b/code/renderer/shaders/crp/gatherdof_split.hlsl @@ -23,6 +23,7 @@ along with Challenge Quake 3. If not, see . #include "common.hlsli" #include "gatherdof.hlsli" +#include "scene_view.h.hlsli" cbuffer RootConstants : register(b0) @@ -33,8 +34,6 @@ cbuffer RootConstants : register(b0) uint farColorTextureIndex; uint nearCocTextureIndex; uint farCocTextureIndex; - float linearDepthA; - float linearDepthB; float focusNearMin; float focusNearMax; float focusFarMin; @@ -54,6 +53,7 @@ void cs(uint3 dtid : SV_DispatchThreadID) return; } + SceneView scene = GetSceneView(); Texture2D depthTexture = ResourceDescriptorHeap[depthTextureIndex]; RWTexture2D nearColorTexture = ResourceDescriptorHeap[nearColorTextureIndex]; RWTexture2D farColorTexture = ResourceDescriptorHeap[farColorTextureIndex]; @@ -62,7 +62,7 @@ void cs(uint3 dtid : SV_DispatchThreadID) float4 color = colorTexture[tc]; float depthZW = depthTexture[tc]; - float depth = LinearDepth(depthZW, linearDepthA, linearDepthB); + float depth = scene.LinearDepth(depthZW); float coc = CircleOfConfusion(depth, focusNearMin, focusNearMax, focusFarMin, focusFarMax); float nearCoc = max(-coc, 0.0); float farCoc = max(coc, 0.0); diff --git a/code/renderer/shaders/crp/gbufferviz_depth.hlsl b/code/renderer/shaders/crp/gbufferviz_depth.hlsl index 7b7751f..a80d5e0 100644 --- a/code/renderer/shaders/crp/gbufferviz_depth.hlsl +++ b/code/renderer/shaders/crp/gbufferviz_depth.hlsl @@ -23,23 +23,17 @@ along with Challenge Quake 3. If not, see . #include "common.hlsli" #include "fullscreen.hlsli" +#include "scene_view.h.hlsli" -cbuffer RootConstants -{ - uint depthTextureIndex; - float linearDepthA; - float linearDepthB; - float zFarInv; -}; - float4 ps(VOut input) : SV_Target { - Texture2D depthTexture = ResourceDescriptorHeap[depthTextureIndex]; + SceneView scene = GetSceneView(); + Texture2D depthTexture = ResourceDescriptorHeap[scene.depthTextureIndex]; uint3 tc = uint3(input.position.xy, 0); float depthZW = depthTexture.Load(tc); - float depth = LinearDepth(depthZW, linearDepthA, linearDepthB); - float depth01 = depth * zFarInv; + float depth = scene.LinearDepth(depthZW); + float depth01 = depth / scene.zFar; float4 result = float4(depth01.xxx, 1); return result; diff --git a/code/renderer/shaders/crp/mblur_blur.hlsl b/code/renderer/shaders/crp/mblur_blur.hlsl index db02b68..1a7c84a 100644 --- a/code/renderer/shaders/crp/mblur_blur.hlsl +++ b/code/renderer/shaders/crp/mblur_blur.hlsl @@ -171,7 +171,7 @@ float4 ps(VOut input) : SV_Target float2 TC1 = input.texCoords + i1 * tcStep; [branch] - if(!IsValue01(TC0) || !IsValue01(TC1)) + if(!Is01(TC0) || !Is01(TC1)) { continue; } @@ -307,7 +307,7 @@ float4 ps(VOut input) : SV_Target dirn = Vcn; } - if(!IsValue01(tc)) + if(!Is01(tc)) { continue; } diff --git a/code/renderer/shaders/crp/oit.h.hlsli b/code/renderer/shaders/crp/oit.h.hlsli index fe12ff0..ba21d43 100644 --- a/code/renderer/shaders/crp/oit.h.hlsli +++ b/code/renderer/shaders/crp/oit.h.hlsli @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2023 Gian 'myT' Schellenbaum +Copyright (C) 2023-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). @@ -47,8 +47,8 @@ struct OIT_Fragment uint next; uint shaderTrace; // shader index: 14 - enable: 1 uint depthFadeDistOffset; // offset: fp16 - distance: fp16 - uint depthFadeScaleBias; // enable: 1 - color bias: 4 - color scale: 4 - // @TODO: move the 9 bits from depthFadeScaleBias into shaderTrace + uint depthFadeScaleBiasPO; // polygon offset: 1 - enable: 1 - color bias: 4 - color scale: 4 + // @TODO: move the 10 bits from depthFadeScaleBiasPO into shaderTrace }; #if defined(__cplusplus) diff --git a/code/renderer/shaders/crp/raytracing.h.hlsli b/code/renderer/shaders/crp/raytracing.h.hlsli index 9c040fd..e579bd1 100644 --- a/code/renderer/shaders/crp/raytracing.h.hlsli +++ b/code/renderer/shaders/crp/raytracing.h.hlsli @@ -56,3 +56,118 @@ struct TLASInstance #if defined(__cplusplus) # pragma pack(pop) #endif + +#if !defined(__cplusplus) + +#include "common.hlsli" +#include "alpha_test.h.hlsli" + +bool IsTriangleHitOpaque(StructuredBuffer tlasInstanceBuffer, uint instanceId, uint meshId, uint triangleId, float2 bary2, bool frontFace) +{ + TLASInstance instance = tlasInstanceBuffer[instanceId]; + StructuredBuffer meshBuffer = ResourceDescriptorHeap[instance.meshBufferIndex]; + BLASMesh mesh = meshBuffer[meshId]; + [branch] if(mesh.alphaTestMode == 0) + { + return false; // translucent -> ignored + } + + float3 barycentrics = float3(1.0 - bary2.x - bary2.y, bary2.x, bary2.y); + StructuredBuffer vertexBuffer = ResourceDescriptorHeap[instance.vertexBufferIndex]; + StructuredBuffer indexBuffer = ResourceDescriptorHeap[instance.indexBufferIndex]; + uint firstIndex = mesh.firstIndex + triangleId * 3; + uint vtxIdx0 = mesh.firstVertex + indexBuffer[firstIndex + 0]; + uint vtxIdx1 = mesh.firstVertex + indexBuffer[firstIndex + 1]; + uint vtxIdx2 = mesh.firstVertex + indexBuffer[firstIndex + 2]; + BLASVertex v0 = vertexBuffer[vtxIdx0]; + BLASVertex v1 = vertexBuffer[vtxIdx1]; + BLASVertex v2 = vertexBuffer[vtxIdx2]; + float2 texCoords = trilerp(v0.texCoords, v1.texCoords, v2.texCoords, barycentrics); + float4 vertexColor = trilerp(UnpackColor(v0.color), UnpackColor(v1.color), UnpackColor(v2.color), barycentrics); + Texture2D texture0 = ResourceDescriptorHeap[mesh.textureIndex]; + SamplerState sampler0 = SamplerDescriptorHeap[mesh.samplerIndex]; + float4 textureColor = texture0.SampleLevel(sampler0, texCoords, 0); + float4 hitColor = vertexColor * textureColor; + bool isOpaque = PassesAlphaTest(hitColor.a, mesh.alphaTestMode); + + return isOpaque; +} + +float TraceVisibilityWithATt( + out float t, RTAS rtas, StructuredBuffer instBuffer, + float3 position, float3 direction, float dist) +{ + RayDesc ray; + ray.Origin = position; + ray.Direction = direction; + ray.TMin = 0.0; + ray.TMax = dist; + + t = 0.0; + RayQuery q; + q.TraceRayInline(rtas, RAY_FLAG_NONE, 0xFF, ray); + while(q.Proceed()) + { + if(q.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) + { + bool isOpaque = IsTriangleHitOpaque( + instBuffer, + q.CandidateInstanceIndex(), + q.CandidateGeometryIndex(), + q.CandidatePrimitiveIndex(), + q.CandidateTriangleBarycentrics(), + q.CandidateTriangleFrontFace()); + if(isOpaque) + { + q.CommitNonOpaqueTriangleHit(); + } + } + } + + if(q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) + { + t = q.CommittedRayT(); + return 0.0; + } + + return 1.0; +} + +float TraceVisibilityWithAT( + RTAS rtas, StructuredBuffer instBuffer, + float3 position, float3 direction, float dist) +{ + float t; + return TraceVisibilityWithATt(t, rtas, instBuffer, position, direction, dist); +} + +float TraceVisibilityWithoutATt( + out float t, RTAS rtas, + float3 position, float3 direction, float dist) +{ + RayDesc ray; + ray.Origin = position; + ray.Direction = direction; + ray.TMin = 0.0; + ray.TMax = dist; + + t = 0.0; + RayQuery q; + q.TraceRayInline(rtas, RAY_FLAG_NONE, 0xFF, ray); + q.Proceed(); + if(q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) + { + t = q.CommittedRayT(); + return 0.0; + } + + return 1.0; +} + +float TraceVisibilityWithoutAT(RTAS rtas, float3 position, float3 direction, float dist) +{ + float t; + return TraceVisibilityWithoutATt(t, rtas, position, direction, dist); +} + +#endif diff --git a/code/renderer/shaders/crp/scene_view.h.hlsli b/code/renderer/shaders/crp/scene_view.h.hlsli index 80dad43..9ecead7 100644 --- a/code/renderer/shaders/crp/scene_view.h.hlsli +++ b/code/renderer/shaders/crp/scene_view.h.hlsli @@ -25,10 +25,29 @@ along with Challenge Quake 3. If not, see . #include "typedefs.h.hlsli" +#if !defined(__cplusplus) +# include "common.hlsli" +#endif + #if defined(__cplusplus) # pragma pack(push, 4) #endif +// @TODO: move out +struct Particle +{ + float3 position; + float radius; + float3 scattering; // or emissive + float absorption; + float anisotropy; + uint isEmissive; +}; + +// @TODO: move out +#define MAX_PARTICLES 8192 + +// @TODO: move out struct DynamicLight { float3 position; @@ -37,8 +56,164 @@ struct DynamicLight float padding; }; +// @TODO: move out #define SCENE_VIEW_MAX_LIGHTS 32 +#if !defined(__cplusplus) +struct ExtinctionCascade +{ + // copied over + Texture3D textures[4]; + SamplerState linearClampSampler; + float4 worldScale; + float3 cameraPosition; + float3 textureSize; + // set by SetSamplingVolume + uint lowestMipLevel; + float mipLerp; + + void SetSamplingVolume(float voxelSize) + { + float mipLevel = max(log2(voxelSize / worldScale.x), 0.0); + if(mipLevel <= 2.0) + { + lowestMipLevel = uint(floor(mipLevel)); + mipLerp = frac(mipLevel); + } + else if(voxelSize >= worldScale.w) + { + lowestMipLevel = 3; + mipLerp = 0.0; + } + else + { + float base = worldScale.w / worldScale.z; + float mipLevelFrac = log2(voxelSize / worldScale.z) / log2(base); + lowestMipLevel = 2; + mipLerp = mipLevelFrac; + } + } + + float ExtinctionAt(float3 position) + { + float ext; + if(ExtinctionAtMip(position, 0, ext)) + { + return ext; + } + if(ExtinctionAtMip(position, 1, ext)) + { + return ext; + } + if(ExtinctionAtMip(position, 2, ext)) + { + return ext; + } + + float3 tc = AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale.w); + ext = textures[3].SampleLevel(linearClampSampler, tc, 0); + return ext; + } + + bool ExtinctionAtMip(float3 position, uint mip, out float ext) + { + if(lowestMipLevel == mip) + { + float3 tc0 = AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale[mip + 0]); + if(Is01(tc0)) // @TODO: make sure it's at least a half-texel inside + { + float3 tc1 = AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale[mip + 1]); + float ext0 = textures[mip + 0].SampleLevel(linearClampSampler, tc0, 0); + float ext1 = textures[mip + 1].SampleLevel(linearClampSampler, tc1, 0); + ext = lerp(ext0, ext1, mipLerp); + return true; + } + } + + return false; + } +}; +#endif + +#if !defined(__cplusplus) +struct SunVShadowCascade +{ + // copied over + Texture3D textures[4]; + SamplerState linearClampSampler; + float4 worldScale; + float3 cameraPosition; + float3 textureSize; + float3x3 zToSunMatrix; + // set by SetSamplingVolume + uint lowestMipLevel; + float mipLerp; + + void SetSamplingVolume(float voxelSize) + { + float mipLevel = max(log2(voxelSize / worldScale.x), 0.0); + if(mipLevel <= 2.0) + { + lowestMipLevel = uint(floor(mipLevel)); + mipLerp = frac(mipLevel); + } + else if(voxelSize >= worldScale.w) + { + lowestMipLevel = 3; + mipLerp = 0.0; + } + else + { + float base = worldScale.w / worldScale.z; + float mipLevelFrac = log2(voxelSize / worldScale.z) / log2(base); + lowestMipLevel = 2; + mipLerp = mipLevelFrac; + } + } + + float TransmittanceAt(float3 positionWS) + { + float3 positionSS = cameraPosition + mul(zToSunMatrix, positionWS - cameraPosition); + + float ext; + if(TransmittanceAtMip(positionSS, 0, ext)) + { + return ext; + } + if(TransmittanceAtMip(positionSS, 1, ext)) + { + return ext; + } + if(TransmittanceAtMip(positionSS, 2, ext)) + { + return ext; + } + + float3 tc = AABoxWorldSpaceToTC(positionSS, cameraPosition, textureSize, worldScale.w); + ext = textures[3].SampleLevel(linearClampSampler, tc, 0); + return ext; + } + + bool TransmittanceAtMip(float3 position, uint mip, out float ext) + { + if(lowestMipLevel == mip) + { + float3 tc0 = AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale[mip + 0]); + if(Is01(tc0)) // @TODO: make sure it's at least a half-texel inside + { + float3 tc1 = AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale[mip + 1]); + float ext0 = textures[mip + 0].SampleLevel(linearClampSampler, tc0, 0); + float ext1 = textures[mip + 1].SampleLevel(linearClampSampler, tc1, 0); + ext = lerp(ext0, ext1, mipLerp); + return true; + } + } + + return false; + } +}; +#endif + struct SceneView { matrix projectionMatrix; @@ -48,29 +223,252 @@ struct SceneView matrix prevViewProjMatrix; matrix prevViewMatrix; matrix prevProjectionMatrix; + float3x3 zToSunMatrix; + float3x3 sunToZMatrix; float4 clipPlane; float4 debug; + float3 cameraPosition; + float sunIntensity; + float3 sunDirection; + float zNear; + float3 sunColor; + float zFar; + float3 prevCameraPosition; + float prevZNear; + float3 cameraForward; + float prevZFar; + float3 cameraLeft; + float padding0; + float3 cameraUp; + float padding1; + float3 linearDepthConstants; + float frameSeed; + float3 ambientColor; + float ambientIntensity; + float4 extinctionWorldScale; + uint4 extinctionTextureIndices; + float4 sunVShadowWorldScale; + uint4 sunVShadowTextureIndices; uint sceneViewIndex; uint frameIndex; uint depthTextureIndex; + uint depthMinMaxTextureIndex; uint normalTextureIndex; uint shadingPositionTextureIndex; uint motionVectorTextureIndex; uint motionVectorMBTextureIndex; uint lightTextureIndex; + uint sunlightTextureIndex; uint tlasBufferIndex; uint tlasInstanceBufferIndex; - uint lightCount; - float linearDepthA; - float linearDepthB; - float zNear; - float zFar; - DynamicLight lights[SCENE_VIEW_MAX_LIGHTS]; + uint linearClampSamplerIndex; #if !defined(__cplusplus) float LinearDepth(float zwDepth) { - return linearDepthB / (zwDepth - linearDepthA); + return ::LinearDepth(zwDepth, linearDepthConstants); + } + + float3 CamerayRay(float2 ndc) + { + float4 pointNDC = float4(ndc, 0.5, 1); + float4 pointWSw = mul(invViewMatrix, mul(invProjectionMatrix, pointNDC)); + float3 pointWS = pointWSw.xyz / pointWSw.w; + float3 dir = pointWS - cameraPosition; + float3 ray = normalize(dir); + + return ray; + } + +#if 1 // exponential depth distribution like The Last of Us Part II + + float TLOU2SliceToViewDepth(float slice, float C) + { + const float Q = 1.0; + float viewDepth = exp2((slice + Q * C) / C) - exp2(Q); + + return viewDepth; + } + + float TLOU2ViewDepthToSlice(float viewDepth, float C) + { + const float Q = 1.0; + float slice = C * (log2(exp2(Q) + viewDepth) - Q); + + return slice; + } + + float TLOU2CFromSliceAndViewDepth(float slice, float viewDepth) + { + const float Q = 1.0; + float C = slice / (log2(viewDepth + exp2(Q)) - Q); + + return C; + } + + float FroxelViewDepthToZ01Ex(float viewDepth, float sliceCount, float zn, float zf) + { + float depthRange = zf - zn; + float C = TLOU2CFromSliceAndViewDepth(sliceCount - 1.0, depthRange); + float slice = TLOU2ViewDepthToSlice(viewDepth - zn, C); + float depth01 = slice / sliceCount; + + return depth01; + } + + float FroxelZ01ToViewDepth(float depth01, float sliceCount) + { + float depthRange = zFar - zNear; + float C = TLOU2CFromSliceAndViewDepth(sliceCount - 1.0, depthRange); + float slice = depth01 * sliceCount; + float viewDepth = zNear + TLOU2SliceToViewDepth(slice, C); + + return viewDepth; + } + +#else // linear depth distribution + + float FroxelViewDepthToZ01Ex(float viewDepth, float sliceCount, float zn, float zf) + { + float depth01 = (viewDepth - zn) / (zf - zn); + + return depth01; + } + + float FroxelZ01ToViewDepth(float depth01, float sliceCount) + { + float viewDepth = zNear + (zFar - zNear) * depth01; + + return viewDepth; + } + +#endif + + float FroxelViewDepthToZ01(float viewDepth, float sliceCount) + { + float depth01 = FroxelViewDepthToZ01Ex(viewDepth, sliceCount, zNear, zFar); + + return depth01; + } + + float FroxelViewDepthToZ01PrevFrame(float viewDepth, float sliceCount) + { + float depth01 = FroxelViewDepthToZ01Ex(viewDepth, sliceCount, prevZNear, prevZFar); + + return depth01; + } + + float3 FroxelTCToWorldSpace(float3 tc, float3 textureSize) + { + float pointDepth = FroxelZ01ToViewDepth(tc.z, textureSize.z); + float2 xyNDC = TCToNDC(tc.xy); + float zNDC = PostProjectionDepth(pointDepth, zNear, zFar); + float4 pointNDC = float4(xyNDC, zNDC, 1); + float4 pointWSw = mul(invViewMatrix, mul(invProjectionMatrix, pointNDC)); + float3 pointWS = pointWSw.xyz / pointWSw.w; + + return pointWS; + } + + float3 FroxelIndexToWorldSpace(int3 index, float3 textureSize) + { + float3 tc = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize; + float3 pointWS = FroxelTCToWorldSpace(tc, textureSize); + + return pointWS; + } + + float3 FroxelWorldSpaceToTC(float3 positionWS, float3 textureSize) + { + float4 positionVSw = mul(viewMatrix, float4(positionWS, 1)); + float viewDepth = -positionVSw.z / positionVSw.w; + float z01 = FroxelViewDepthToZ01(viewDepth, textureSize.z); + float4 positionCSw = mul(projectionMatrix, positionVSw); + float2 xy01 = NDCToTC(positionCSw.xy / positionCSw.w); + float3 tc = float3(xy01, z01); + + return tc; + } + + int3 FroxelWorldSpaceToIndex(float3 positionWS, float3 textureSize) + { + float3 tc = FroxelWorldSpaceToTC(positionWS, textureSize); + int3 index = int3(tc * textureSize); + + return index; + } + + // @TODO: validate new logic + float3 FroxelReproject01(int3 currIndex, float3 textureSize) + { + float3 currTC = (float3(currIndex) + float3(0.5, 0.5, 0.5)) / textureSize; + float currDepth = FroxelZ01ToViewDepth(currTC.z, textureSize.z); + float2 xyNDC = TCToNDC(currTC.xy); + float zNDC = PostProjectionDepth(currDepth, zNear, zFar); + float4 currNDC = float4(xyNDC, zNDC, 1); + float4 positionWSw = mul(invViewMatrix, mul(invProjectionMatrix, currNDC)); + float3 positionWS = positionWSw.xyz / positionWSw.w; + float4 prevVSw = mul(prevViewMatrix, float4(positionWS, 1)); + float prevDepth = -prevVSw.z / prevVSw.w; + float z01 = FroxelViewDepthToZ01(prevDepth, textureSize.z); + float4 prevCSw = mul(prevProjectionMatrix, prevVSw); + float2 xy01 = NDCToTC(prevCSw.xy / prevCSw.w); + float3 prevTC = float3(xy01, z01); + + return prevTC; + } + + float3 ExtinctionIndexToWorldSpace(int3 index, float3 textureSize, float worldScale) + { + return AABoxIndexToWorldSpace(index, cameraPosition, textureSize, worldScale); + } + + float3 ExtinctionTCToWorldSpace(float3 tc, float3 textureSize, float worldScale) + { + return AABoxTCToWorldSpace(tc, cameraPosition, textureSize, worldScale); + } + + float3 ExtinctionWorldSpaceToTC(float3 position, float3 textureSize, float worldScale) + { + return AABoxWorldSpaceToTC(position, cameraPosition, textureSize, worldScale); + } + + int3 ExtinctionWorldSpaceToIndex(float3 position, float3 textureSize, float worldScale) + { + return AABoxWorldSpaceToIndex(position, cameraPosition, textureSize, worldScale); + } + + ExtinctionCascade GetExtinctionCascade(float voxelSize) + { + ExtinctionCascade cascade; + cascade.textures[0] = ResourceDescriptorHeap[extinctionTextureIndices.x]; + cascade.textures[1] = ResourceDescriptorHeap[extinctionTextureIndices.y]; + cascade.textures[2] = ResourceDescriptorHeap[extinctionTextureIndices.z]; + cascade.textures[3] = ResourceDescriptorHeap[extinctionTextureIndices.w]; + cascade.linearClampSampler = SamplerDescriptorHeap[linearClampSamplerIndex]; + cascade.worldScale = extinctionWorldScale; + cascade.cameraPosition = cameraPosition; + cascade.textureSize = GetTextureSize(cascade.textures[0]); + cascade.SetSamplingVolume(voxelSize); + + return cascade; + } + + SunVShadowCascade GetSunVShadowCascade(float voxelSize) + { + SunVShadowCascade cascade; + cascade.textures[0] = ResourceDescriptorHeap[sunVShadowTextureIndices.x]; + cascade.textures[1] = ResourceDescriptorHeap[sunVShadowTextureIndices.y]; + cascade.textures[2] = ResourceDescriptorHeap[sunVShadowTextureIndices.z]; + cascade.textures[3] = ResourceDescriptorHeap[sunVShadowTextureIndices.w]; + cascade.linearClampSampler = SamplerDescriptorHeap[linearClampSamplerIndex]; + cascade.worldScale = sunVShadowWorldScale; + cascade.cameraPosition = cameraPosition; + cascade.textureSize = GetTextureSize(cascade.textures[0]); + cascade.zToSunMatrix = zToSunMatrix; + cascade.SetSamplingVolume(voxelSize); + + return cascade; } #endif }; diff --git a/code/renderer/shaders/crp/simplex_noise.hlsli b/code/renderer/shaders/crp/simplex_noise.hlsli new file mode 100644 index 0000000..1cab565 --- /dev/null +++ b/code/renderer/shaders/crp/simplex_noise.hlsli @@ -0,0 +1,146 @@ +// -------------------------------------------------------------------- +// Optimized implementation of simplex noise. +// Based on stegu's simplex noise: https://github.com/stegu/webgl-noise. +// Contact : atyuwen@gmail.com +// Author : Yuwen Wu (https://atyuwen.github.io/) +// License : Distributed under the MIT License. +// -------------------------------------------------------------------- + +// Permuted congruential generator (only top 16 bits are well shuffled). +// References: 1. Mark Jarzynski and Marc Olano, "Hash Functions for GPU Rendering". +// 2. UnrealEngine/Random.ush. https://github.com/EpicGames/UnrealEngine +uint pcg3d16(uint3 p) +{ + uint3 v = p * 1664525u + 1013904223u; + v.x += v.y*v.z; v.y += v.z*v.x; v.z += v.x*v.y; + v.x += v.y*v.z; + return v.x; +} +uint pcg4d16(uint4 p) +{ + uint4 v = p * 1664525u + 1013904223u; + v.x += v.y*v.w; v.y += v.z*v.x; v.z += v.x*v.y; v.w += v.y*v.z; + v.x += v.y*v.w; + return v.x; +} + +// Get random gradient from hash value. +float3 gradient3d(uint hash) +{ + float3 g = float3(hash.xxx & uint3(0x80000, 0x40000, 0x20000)); + return g * float3(1.0 / 0x40000, 1.0 / 0x20000, 1.0 / 0x10000) - 1.0; +} +float4 gradient4d(uint hash) +{ + float4 g = float4(hash.xxxx & uint4(0x80000, 0x40000, 0x20000, 0x10000)); + return g * float4(1.0 / 0x40000, 1.0 / 0x20000, 1.0 / 0x10000, 1.0 / 0x8000) - 1.0; +} + +// 3D Simplex Noise. Approximately 71 instruction slots used. +// Assume p is in the range [-32768, 32767]. +float SimplexNoise3D(float3 p) +{ + const float2 C = float2(1.0 / 6.0, 1.0 / 3.0); + const float4 D = float4(0.0, 0.5, 1.0, 2.0); + + // First corner + float3 i = floor(p + dot(p, C.yyy)); + float3 x0 = p - i + dot(i, C.xxx); + + // Other corners + float3 g = step(x0.yzx, x0.xyz); + float3 l = 1.0 - g; + float3 i1 = min(g.xyz, l.zxy); + float3 i2 = max(g.xyz, l.zxy); + + // x0 = x0 - 0.0 + 0.0 * C.xxx; + // x1 = x0 - i1 + 1.0 * C.xxx; + // x2 = x0 - i2 + 2.0 * C.xxx; + // x3 = x0 - 1.0 + 3.0 * C.xxx; + float3 x1 = x0 - i1 + C.xxx; + float3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y + float3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y + + i = i + 32768.5; + uint hash0 = pcg3d16((uint3)i); + uint hash1 = pcg3d16((uint3)(i + i1)); + uint hash2 = pcg3d16((uint3)(i + i2)); + uint hash3 = pcg3d16((uint3)(i + 1 )); + + float3 p0 = gradient3d(hash0); + float3 p1 = gradient3d(hash1); + float3 p2 = gradient3d(hash2); + float3 p3 = gradient3d(hash3); + + // Mix final noise value. + float4 m = saturate(0.5 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3))); + float4 mt = m * m; + float4 m4 = mt * mt; + return 62.6 * dot(m4, float4(dot(x0, p0), dot(x1, p1), dot(x2, p2), dot(x3, p3))); +} + +// 4D Simplex Noise. Approximately 113 instruction slots used. +// Assume p is in the range [-32768, 32767]. +float SimplexNoise4D(float4 p) +{ + const float4 F4 = 0.309016994374947451; + const float4 C = float4( 0.138196601125011, // (5 - sqrt(5))/20 G4 + 0.276393202250021, // 2 * G4 + 0.414589803375032, // 3 * G4 + -0.447213595499958); // -1 + 4 * G4 + + // First corner + float4 i = floor(p + dot(p, F4) ); + float4 x0 = p - i + dot(i, C.xxxx); + + // Other corners + + // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) + float4 i0; + float3 isX = step( x0.yzw, x0.xxx ); + float3 isYZ = step( x0.zww, x0.yyz ); + // i0.x = dot( isX, float3( 1.0 ) ); + i0.x = isX.x + isX.y + isX.z; + i0.yzw = 1.0 - isX; + // i0.y += dot( isYZ.xy, float2( 1.0 ) ); + i0.y += isYZ.x + isYZ.y; + i0.zw += 1.0 - isYZ.xy; + i0.z += isYZ.z; + i0.w += 1.0 - isYZ.z; + + // i0 now contains the unique values 0,1,2,3 in each channel + float4 i3 = saturate( i0 ); + float4 i2 = saturate( i0 - 1.0 ); + float4 i1 = saturate( i0 - 2.0 ); + + // x0 = x0 - 0.0 + 0.0 * C.xxxx + // x1 = x0 - i1 + 1.0 * C.xxxx + // x2 = x0 - i2 + 2.0 * C.xxxx + // x3 = x0 - i3 + 3.0 * C.xxxx + // x4 = x0 - 1.0 + 4.0 * C.xxxx + float4 x1 = x0 - i1 + C.xxxx; + float4 x2 = x0 - i2 + C.yyyy; + float4 x3 = x0 - i3 + C.zzzz; + float4 x4 = x0 + C.wwww; + + i = i + 32768.5; + uint hash0 = pcg4d16((uint4)i); + uint hash1 = pcg4d16((uint4)(i + i1)); + uint hash2 = pcg4d16((uint4)(i + i2)); + uint hash3 = pcg4d16((uint4)(i + i3)); + uint hash4 = pcg4d16((uint4)(i + 1 )); + + float4 p0 = gradient4d(hash0); + float4 p1 = gradient4d(hash1); + float4 p2 = gradient4d(hash2); + float4 p3 = gradient4d(hash3); + float4 p4 = gradient4d(hash4); + + // Mix contributions from the five corners + float3 m0 = saturate(0.6 - float3(dot(x0,x0), dot(x1,x1), dot(x2,x2))); + float2 m1 = saturate(0.6 - float2(dot(x3,x3), dot(x4,x4) )); + float3 m03 = m0 * m0 * m0; + float2 m13 = m1 * m1 * m1; + return (dot(m03, float3(dot(p0, x0), dot(p1, x1), dot(p2, x2))) + + dot(m13, float2(dot(p3, x3), dot(p4, x4)))) * 9.0; +} diff --git a/code/renderer/shaders/crp/sun_blur.hlsl b/code/renderer/shaders/crp/sun_blur.hlsl new file mode 100644 index 0000000..ff7ef8c --- /dev/null +++ b/code/renderer/shaders/crp/sun_blur.hlsl @@ -0,0 +1,118 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// sunlight soft shadows + + +#include "common.hlsli" +#include "fullscreen.hlsli" +#include "scene_view.h.hlsli" + + +cbuffer RootConstants +{ + uint visibilityTextureIndex; + uint penumbraTextureIndex; +}; + +static const int Radius = 7; +static const float PenumbraRadiusWS = 64.0; + +float4 ps(VOut input) : SV_Target +{ + SceneView scene = GetSceneView(); + Texture2D shadingPositionTexture = ResourceDescriptorHeap[scene.shadingPositionTextureIndex]; + Texture2D normalTexture = ResourceDescriptorHeap[scene.normalTextureIndex]; + Texture2D visibilityTexture = ResourceDescriptorHeap[visibilityTextureIndex]; + Texture2D penumbraTexture = ResourceDescriptorHeap[penumbraTextureIndex]; + SunVShadowCascade cascade = scene.GetSunVShadowCascade(scene.sunVShadowWorldScale.x); + + int2 fragTC = int2(input.position.xy); + uint3 tc = uint3(input.position.xy, 0); + float3 positionWS = shadingPositionTexture.Load(tc).xyz; + float3 normalWS = normalize(OctDecode(normalTexture.Load(tc))); + float penumbraSize = penumbraTexture.Load(tc) * PenumbraRadiusWS; + int2 textureSize = int2(GetTextureSize(visibilityTexture)); + + // run a PCSS-style blocker search + if(penumbraSize == 0.0) + { + float count = 0.0; + for(int y = -Radius; y <= Radius; y++) + { + for(int x = -Radius; x <= Radius; x++) + { + int2 TCs2 = fragTC + int2(x, y); + if(!IsInTexture(TCs2, textureSize)) + { + continue; + } + + uint3 TCs = uint3(TCs2, 0); + float size = penumbraTexture.Load(TCs) * PenumbraRadiusWS; + penumbraSize += size; + count += 1.0; + } + } + + if(count > 0.0) + { + penumbraSize /= count; + } + } + if(penumbraSize == 0.0) + { + penumbraSize = PenumbraRadiusWS; + } + + float visSum = 0.0; + float weightSum = 0.0; + for(int y = -Radius; y <= Radius; y++) + { + for(int x = -Radius; x <= Radius; x++) + { + int2 TCs2 = fragTC + int2(x, y); + if(!IsInTexture(TCs2, textureSize)) + { + continue; + } + + uint3 TCs = uint3(TCs2, 0); + float Vs = visibilityTexture.Load(TCs); + float3 Ns = normalize(OctDecode(normalTexture.Load(TCs))); + float3 Ps = shadingPositionTexture.Load(TCs).xyz; + float normalWeight = max(dot(normalWS, Ns), 0.0); + float distanceWeight = max(1.0 - distance(positionWS, Ps) / penumbraSize, 0.0); + float weight = normalWeight * distanceWeight; + weightSum += weight; + visSum += weight * Vs; + } + } + + // @TODO: all light intensities are 50x for light scattering? + float visOpaque = weightSum > 0.0 ? visSum / weightSum : 0.0; + float visVolume = cascade.TransmittanceAt(positionWS); + float vis = visOpaque * visVolume; + float lambert = max(dot(normalWS, scene.sunDirection), 0.0); + float3 color = vis * scene.sunColor * min(scene.sunIntensity / 10.0, 5.0) * lambert; + float4 result = float4(color, 1); + + return result; +} diff --git a/code/renderer/shaders/crp/fog_inside.hlsl b/code/renderer/shaders/crp/sun_overlay.hlsl similarity index 53% rename from code/renderer/shaders/crp/fog_inside.hlsl rename to code/renderer/shaders/crp/sun_overlay.hlsl index 6765561..b7e14a3 100644 --- a/code/renderer/shaders/crp/fog_inside.hlsl +++ b/code/renderer/shaders/crp/sun_overlay.hlsl @@ -18,22 +18,44 @@ You should have received a copy of the GNU General Public License along with Challenge Quake 3. If not, see . =========================================================================== */ -// fog volume (AABB) seen from inside +// draws the sun's position #include "common.hlsli" -#include "fog.hlsli" +#include "fullscreen.hlsli" +#include "scene_view.h.hlsli" +cbuffer RootConstants +{ + float3 direction; + float angle; // @TODO: + float3 color; + float padding; + float2 textureSize; +}; + float4 ps(VOut input) : SV_Target { - Texture2D depthTexture = ResourceDescriptorHeap[depthTextureIndex]; - float depthZW = depthTexture.Load(int3(input.position.xy, 0)); - float depthBuff = LinearDepth(depthZW, linearDepthA, linearDepthB); - float depthFrag = input.depthVS; - float depthMin = min(depthBuff, depthFrag); - float fogOpacity = saturate(depthMin / depth); - float4 result = float4(color.rgb, fogOpacity); + SceneView scene = GetSceneView(); + float3 positionWS = scene.cameraPosition + scene.zFar * direction; + float4 positionCS = mul(scene.projectionMatrix, mul(scene.viewMatrix, float4(positionWS, 1))); + float3 positionNDC = positionCS.xyz / positionCS.w; + if(positionNDC.z < 0.0) + { + return float4(0, 0, 0, 0); + } + + float2 positionPx = NDCToTC(positionNDC.xy) * textureSize; + float2 fragmentPx = input.texCoords * textureSize; + if(distance(positionPx, fragmentPx) < 12.0) + { + return float4(saturate(color), 1); + } + if(distance(positionPx, fragmentPx) < 14.0) + { + return float4(0, 0, 0, 1); + } - return result; + return float4(0, 0, 0, 0); } diff --git a/code/renderer/shaders/crp/sun_visibility.hlsl b/code/renderer/shaders/crp/sun_visibility.hlsl new file mode 100644 index 0000000..a60e9b3 --- /dev/null +++ b/code/renderer/shaders/crp/sun_visibility.hlsl @@ -0,0 +1,67 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// sunlight visibility and penumbra size + + +#include "common.hlsli" +#include "fullscreen.hlsli" +#include "scene_view.h.hlsli" +#include "raytracing.h.hlsli" + + +cbuffer RootConstants +{ +}; + +static const float PenumbraRadiusWS = 64.0; + +struct POut +{ + float visible : SV_Target0; + float penumbra : SV_Target1; +}; + +POut ps(VOut input) +{ + SceneView scene = GetSceneView(); + RTAS rtas = ResourceDescriptorHeap[scene.tlasBufferIndex]; + Texture2D shadingPositionTexture = ResourceDescriptorHeap[scene.shadingPositionTextureIndex]; + StructuredBuffer tlasInstanceBuffer = ResourceDescriptorHeap[scene.tlasInstanceBufferIndex]; + + uint3 tc = uint3(input.position.xy, 0); + float3 positionWS = shadingPositionTexture.Load(tc).xyz; + float t; + float vis = TraceVisibilityWithATt(t, rtas, tlasInstanceBuffer, positionWS, scene.sunDirection, 10000.0); + + POut output; + if(vis == 0.0) // in shadow + { + output.visible = 0.0; + output.penumbra = saturate((t * 0.01) / PenumbraRadiusWS); + } + else + { + output.visible = 1.0; + output.penumbra = 0.0; + } + + return output; +} diff --git a/code/renderer/shaders/crp/transp_draw.hlsl b/code/renderer/shaders/crp/transp_draw.hlsl index 17456ea..1ca2dcf 100644 --- a/code/renderer/shaders/crp/transp_draw.hlsl +++ b/code/renderer/shaders/crp/transp_draw.hlsl @@ -43,7 +43,7 @@ cbuffer RootConstants uint stateBits; uint shaderTrace; uint depthFadeDistOffset; // offset: fp16 - distance: fp16 - uint depthFadeScaleBias; // enable: 1 - color bias: 4 - color scale: 4 + uint depthFadeScaleBiasPO; // polygon offset: 1 - enable: 1 - color bias: 4 - color scale: 4 }; struct VIn @@ -83,6 +83,29 @@ VOut vs(VIn input) return output; } +bool IsFragmentUseless(uint blendBits, float4 color) +{ + const float epsilon = 1.0 / 255.0; + + if(blendBits == GLS_BLEND_ADDITIVE && + all(color.rgb < epsilon.xxx)) + { + return true; + } + if((blendBits == GLS_BLEND_STD_ALPHA || blendBits == GLS_BLEND_PMUL_ALPHA) && + color.a < epsilon) + { + return true; + } + if((blendBits == GLS_BLEND_FILTER || blendBits == GLS_BLEND_FILTER_V2) && + all(color.rgb > (1.0 - epsilon).xxx) && all(color.rgb < (1.0 + epsilon).xxx)) + { + return true; + } + + return false; +} + [earlydepthstencil] void ps(VOut input) { @@ -95,6 +118,11 @@ void ps(VOut input) } dst = MakeGreyscale(dst, greyscale); + uint blendBits = stateBits & GLS_BLEND_BITS; + if(IsFragmentUseless(blendBits, dst)) + { + return; + } RWStructuredBuffer counter = ResourceDescriptorHeap[counterBuffer]; uint fragmentIndex; @@ -112,7 +140,7 @@ void ps(VOut input) fragment.next = prevFragmentIndex; fragment.shaderTrace = shaderTrace; fragment.depthFadeDistOffset = depthFadeDistOffset; - fragment.depthFadeScaleBias = depthFadeScaleBias; + fragment.depthFadeScaleBiasPO = depthFadeScaleBiasPO; fragments[fragmentIndex] = fragment; } else diff --git a/code/renderer/shaders/crp/transp_resolve.hlsl b/code/renderer/shaders/crp/transp_resolve.hlsl index a40ee3e..8ed7a2a 100644 --- a/code/renderer/shaders/crp/transp_resolve.hlsl +++ b/code/renderer/shaders/crp/transp_resolve.hlsl @@ -1,6 +1,6 @@ /* =========================================================================== -Copyright (C) 2023 Gian 'myT' Schellenbaum +Copyright (C) 2023-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). @@ -22,23 +22,23 @@ along with Challenge Quake 3. If not, see . #include "common.hlsli" -#include "oit.h.hlsli" #include "fullscreen.hlsli" +#include "oit.h.hlsli" +#include "scene_view.h.hlsli" #include "../common/state_bits.h.hlsli" cbuffer RootConstants { + float2 scissorRectMin; + float2 scissorRectMax; uint renderTargetTexture; uint shaderIndexBuffer; uint indexTexture; uint fragmentBuffer; uint centerPixel; // y: 16 - x: 16 - uint depthTexture; - float linearDepthA; - float linearDepthB; - float2 scissorRectMin; - float2 scissorRectMax; + uint scatterTextureIndex; + uint scatterSamplerIndex; }; uint GetShaderStage(uint stateBits) @@ -76,20 +76,19 @@ float GetBitAsFloat(uint bits, uint bitIndex) return (bits & (1u << bitIndex)) ? 1.0 : 0.0; } -float4 DepthFadeFragmentColor(float4 color, OIT_Fragment fragment, float storedDepthZW) +float4 DepthFadeFragmentColor(float4 color, OIT_Fragment fragment, float opaqueViewDepth) { - if(((fragment.depthFadeScaleBias >> 8) & 1) == 0) + if(((fragment.depthFadeScaleBiasPO >> 8) & 1) == 0) { return color; } -#define BIT(Index) GetBitAsFloat(fragment.depthFadeScaleBias, Index) +#define BIT(Index) GetBitAsFloat(fragment.depthFadeScaleBiasPO, Index) float4 dst = color; float2 distOffset = UnpackHalf2(fragment.depthFadeDistOffset); float4 fadeColorScale = float4(BIT(0), BIT(1), BIT(2), BIT(3)); float4 fadeColorBias = float4(BIT(4), BIT(5), BIT(6), BIT(7)); - float zwDepth = storedDepthZW; // stored depth, z/w - float depthS = LinearDepth(zwDepth, linearDepthA, linearDepthB); // stored depth, linear + float depthS = opaqueViewDepth; // stored depth, linear float depthP = fragment.depth - distOffset.y; // fragment depth, linear float fadeScale = Contrast((depthS - depthP) * distOffset.x, 2.0); dst = lerp(dst * fadeColorScale + fadeColorBias, dst, fadeScale); @@ -98,96 +97,253 @@ float4 DepthFadeFragmentColor(float4 color, OIT_Fragment fragment, float storedD return dst; } -float4 ps(VOut input) : SV_Target +float FragmentImpact(float4 fragColor, uint blendBits) { - Texture2D renderTarget = ResourceDescriptorHeap[renderTargetTexture]; - int2 tc = int2(input.position.x, input.position.y); - float4 color = renderTarget.Load(int3(tc.x, tc.y, 0)); - if(any(input.position.xy < scissorRectMin) || - any(input.position.xy > scissorRectMax)) + if(blendBits == GLS_BLEND_ADDITIVE) { - return color; + return Brightness(fragColor.rgb); } - RWTexture2D index = ResourceDescriptorHeap[indexTexture]; - RWStructuredBuffer fragments = ResourceDescriptorHeap[fragmentBuffer]; - Texture2D depthTex = ResourceDescriptorHeap[depthTexture]; - uint fragmentIndex = index[tc]; - uint i; - OIT_Fragment sorted[OIT_MAX_FRAGMENTS_PER_PIXEL]; - uint fragmentCount = 0; - - // grab this pixel's fragments - while(fragmentIndex != 0 && fragmentCount < OIT_MAX_FRAGMENTS_PER_PIXEL) + if(blendBits == GLS_BLEND_STD_ALPHA || blendBits == GLS_BLEND_PMUL_ALPHA) { - sorted[fragmentCount] = fragments[fragmentIndex]; - fragmentIndex = sorted[fragmentCount].next; - ++fragmentCount; + return fragColor.a; } - // sort the fragments using an insertion sort - for(i = 1; i < fragmentCount; ++i) + if(blendBits == GLS_BLEND_FILTER || blendBits == GLS_BLEND_FILTER_V2) { - OIT_Fragment insert = sorted[i]; - uint stage = GetShaderStage(insert.stateBits); - uint j = i; - while(j > 0 && IsBehind(insert.depth, sorted[j - 1].depth, stage, GetShaderStage(sorted[j - 1].stateBits))) - { - sorted[j] = sorted[j - 1]; - --j; - } - sorted[j] = insert; + return abs(1.0 - Brightness(fragColor.rgb)); } - // blend the results - int lastFragmentIndex = -1; - float storedDepthZW = depthTex.Load(int3(input.position.xy, 0)).x; // stored depth, z/w - float dstDepth = 1.0; - for(i = 0; i < fragmentCount; ++i) + return 1.0; +} + +struct OIT_Resolve +{ + bool InitScissorRect(VOut input) { - OIT_Fragment frag = sorted[i]; - uint stateBits = frag.stateBits; - float fragDepth = frag.depth; - if((stateBits & (GLS_DEPTHFUNC_EQUAL | GLS_DEPTHTEST_DISABLE)) == GLS_DEPTHFUNC_EQUAL && - fragDepth != dstDepth) + renderTarget = ResourceDescriptorHeap[renderTargetTexture]; + tcPx = int3(input.position.xy, 0); + opaqueColor = renderTarget.Load(tcPx); + if(any(input.position.xy < scissorRectMin) || + any(input.position.xy > scissorRectMax)) { - continue; - } - - float4 fragColor = UnpackColor(frag.color); - float4 prevColor = color; - fragColor = DepthFadeFragmentColor(fragColor, frag, storedDepthZW); - color = Blend(fragColor, color, frag.stateBits); - if((stateBits & GLS_DEPTHMASK_TRUE) != 0u && - fragDepth < dstDepth) - { - dstDepth = fragDepth; - } - - // we have to not include the alpha channel in this test for it to be correct - if(any(color.rgb != prevColor.rgb)) - { - lastFragmentIndex = (int)i; + return true; } + return false; } - // write out the fragment shader ID of the closest visible fragment of the center pixel - if(lastFragmentIndex >= 0) + void Init(VOut input) { - OIT_Fragment closest = sorted[lastFragmentIndex]; - uint shaderTrace = closest.shaderTrace; - if(shaderTrace & 1) + scene = GetSceneView(); +#if defined(VOLUMETRIC_LIGHT) + scatterTexture = ResourceDescriptorHeap[scatterTextureIndex]; + scatterSampler = SamplerDescriptorHeap[scatterSamplerIndex]; +#endif + + RWTexture2D index = ResourceDescriptorHeap[indexTexture]; + RWStructuredBuffer fragments = ResourceDescriptorHeap[fragmentBuffer]; + Texture2D depthTex = ResourceDescriptorHeap[scene.depthTextureIndex]; + uint fragmentIndex = index[tcPx.xy]; + fragmentCount = 0; + float storedDepthZW = depthTex.Load(tcPx).x; // stored depth, z/w + opaqueViewDepth = scene.LinearDepth(storedDepthZW); + + // grab this pixel's fragments + while(fragmentIndex != 0 && fragmentCount < OIT_MAX_FRAGMENTS_PER_PIXEL) { - uint2 fragmentCoords = uint2(input.position.xy); - uint2 centerCoords = uint2(centerPixel & 0xFFFF, centerPixel >> 16); - if(all(fragmentCoords == centerCoords)) + sorted[fragmentCount] = fragments[fragmentIndex]; + fragmentIndex = sorted[fragmentCount].next; + invisible[fragmentCount] = false; + ++fragmentCount; + } + + // sort the fragments using an insertion sort + for(uint i = 1; i < fragmentCount; ++i) + { + OIT_Fragment insert = sorted[i]; + uint stage = GetShaderStage(insert.stateBits); + uint j = i; + while(j > 0 && IsBehind(insert.depth, sorted[j - 1].depth, stage, GetShaderStage(sorted[j - 1].stateBits))) { - RWByteAddressBuffer shaderIdBuf = ResourceDescriptorHeap[shaderIndexBuffer]; - uint shaderIndex = shaderTrace >> 1; - shaderIdBuf.Store(0, shaderIndex); + sorted[j] = sorted[j - 1]; + --j; + } + sorted[j] = insert; + } + } + + void Resolve(VOut input, float impactThreshold) + { + color = opaqueColor; + smallestImpact = 666.0; + +#if defined(VOLUMETRIC_LIGHT) + // initialize volume traversal + float3 volumeSize = GetTextureSize(scatterTexture); + float opaqueFroxelDepth01 = scene.FroxelViewDepthToZ01(opaqueViewDepth, volumeSize.z); + // @TODO: do the depth bias only when global fog is enabled or a CVar is set + opaqueFroxelDepth01 = max(opaqueFroxelDepth01 - 1.0 / volumeSize.z, 0.0); + float3 scatterTC = float3(input.texCoords, opaqueFroxelDepth01); + float4 scatterData = scatterTexture.SampleLevel(scatterSampler, scatterTC, 0); + { + float4 closerScatterData = float4(0, 0, 0, 1); + for(uint i = 0; i < fragmentCount; ++i) + { + // @TODO: fix this loop to account for the depth test + OIT_Fragment frag = sorted[i]; + float fragDepth = frag.depth; + float froxelDepth01 = scene.FroxelViewDepthToZ01(fragDepth, volumeSize.z); + float3 scatterTC = float3(input.texCoords, froxelDepth01); + closerScatterData = scatterTexture.SampleLevel(scatterSampler, scatterTC, 0); + break; + } + float3 inScattering = scatterData.rgb - closerScatterData.rgb; + float transmittance = scatterData.a / max(closerScatterData.a, 0.000001); + color.rgb = color.rgb * transmittance + inScattering; + scatterData = closerScatterData; + } +#endif + + // blend the results + lastFragmentIndex = -1; + float dstDepth = 1.0; + for(uint i = 0; i < fragmentCount; ++i) + { + OIT_Fragment frag = sorted[i]; + uint stateBits = frag.stateBits; + float fragDepth = frag.depth; + if((stateBits & (GLS_DEPTHFUNC_EQUAL | GLS_DEPTHTEST_DISABLE)) == GLS_DEPTHFUNC_EQUAL && + fragDepth != dstDepth) + { + continue; + } + + float4 fragColor = UnpackColor(frag.color); + float4 prevColor = color; + fragColor = DepthFadeFragmentColor(fragColor, frag, opaqueViewDepth); + color = Blend(fragColor, color, frag.stateBits); + if((stateBits & GLS_DEPTHMASK_TRUE) != 0u && + fragDepth < dstDepth) + { + dstDepth = fragDepth; + } + + // we have to not include the alpha channel in this test for it to be correct + if(any(color.rgb != prevColor.rgb)) + { + lastFragmentIndex = (int)i; + } + +#if defined(VOLUMETRIC_LIGHT) + float fragmentImpact = FragmentImpact(fragColor, stateBits & GLS_BLEND_BITS); + invisible[i] = fragmentImpact < impactThreshold; + smallestImpact = min(smallestImpact, fragmentImpact); + float4 closerScatterData = float4(0, 0, 0, 1); + for(uint j = i + 1; j < fragmentCount; ++j) + { + // @TODO: fix this loop to account for the depth test + OIT_Fragment frag = sorted[j]; + float fragDepth = frag.depth; + float froxelDepth01 = scene.FroxelViewDepthToZ01(fragDepth, volumeSize.z); + float3 scatterTC = float3(input.texCoords, froxelDepth01); + closerScatterData = scatterTexture.SampleLevel(scatterSampler, scatterTC, 0); + break; + } + float3 inScattering = scatterData.rgb - closerScatterData.rgb; + float transmittance = scatterData.a / max(closerScatterData.a, 0.000001); + color.rgb = color.rgb * transmittance + inScattering; + scatterData = closerScatterData; +#endif + } + } + + void RemoveInvisible() + { + uint newCount = 0; + for(uint i = 0; i < fragmentCount; ++i) + { + if(invisible[i]) + { + continue; + } + if(newCount != i) + { + sorted[newCount] = sorted[i]; + } + newCount++; + } + fragmentCount = newCount; + } + + void WriteShaderID(VOut input) + { + // write out the fragment shader ID of the closest visible fragment of the center pixel + if(lastFragmentIndex >= 0) + { + OIT_Fragment closest = sorted[lastFragmentIndex]; + uint shaderTrace = closest.shaderTrace; + if(shaderTrace & 1) + { + uint2 fragmentCoords = uint2(input.position.xy); + uint2 centerCoords = uint2(centerPixel & 0xFFFF, centerPixel >> 16); + if(all(fragmentCoords == centerCoords)) + { + RWByteAddressBuffer shaderIdBuf = ResourceDescriptorHeap[shaderIndexBuffer]; + uint shaderIndex = shaderTrace >> 1; + shaderIdBuf.Store(0, shaderIndex); + } } } } + SceneView scene; + Texture2D renderTarget; + int3 tcPx; + float4 opaqueColor; + float4 color; + OIT_Fragment sorted[OIT_MAX_FRAGMENTS_PER_PIXEL]; + bool invisible[OIT_MAX_FRAGMENTS_PER_PIXEL]; + uint fragmentCount; + int lastFragmentIndex; + float opaqueViewDepth; + float smallestImpact; +#if defined(VOLUMETRIC_LIGHT) + Texture3D scatterTexture; + SamplerState scatterSampler; +#endif +}; + +float4 ps(VOut input) : SV_Target +{ + OIT_Resolve resolve; + if(resolve.InitScissorRect(input)) + { + return resolve.opaqueColor; + } + + resolve.Init(input); + +#if defined(VOLUMETRIC_LIGHT) + // To fight off discontinuities between adjacent pixels, + // we interpolate between the result computed normally + // and computed by rejecting low-impact fragments + // using a t value that only depends on the low-impact fragments. + // It's far from perfect but once we get rid of most sprites + // in favor of particles, we should be fine. + const float VL_ImpactThreshold = 4.0 / 255.0; + resolve.Resolve(input, VL_ImpactThreshold); + float diff = resolve.smallestImpact; + float4 color = resolve.color; + resolve.RemoveInvisible(); + resolve.Resolve(input, 0.0); + float4 color2 = resolve.color; + color = lerp(color2, color, saturate(diff / VL_ImpactThreshold)); +#else + resolve.Resolve(input, 666.0); + float4 color = resolve.color; +#endif + + resolve.WriteShaderID(input); + return color; } diff --git a/code/renderer/shaders/crp/typedefs.h.hlsli b/code/renderer/shaders/crp/typedefs.h.hlsli index ca3b746..e138532 100644 --- a/code/renderer/shaders/crp/typedefs.h.hlsli +++ b/code/renderer/shaders/crp/typedefs.h.hlsli @@ -25,11 +25,18 @@ along with Challenge Quake 3. If not, see . #if defined(__cplusplus) + typedef ivec2_t int2; + typedef ivec3_t int3; + typedef ivec4_t int4; typedef uint32_t uint; + typedef uvec2_t uint2; + typedef uvec3_t uint3; + typedef uvec4_t uint4; typedef vec2_t float2; typedef vec3_t float3; typedef vec4_t float4; typedef matrix4x4_t matrix; + typedef matrix3x3_t float3x3; typedef color4ub_t color4ub; #else typedef uint color4ub; diff --git a/code/renderer/shaders/crp/vl_common.h.hlsli b/code/renderer/shaders/crp/vl_common.h.hlsli new file mode 100644 index 0000000..13e8e60 --- /dev/null +++ b/code/renderer/shaders/crp/vl_common.h.hlsli @@ -0,0 +1,206 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: shared data structures and functions + + +#pragma once + + +#include "typedefs.h.hlsli" +#if !defined(__cplusplus) +#include "common.hlsli" +#include "simplex_noise.hlsli" +#endif + +#if defined(__cplusplus) +#pragma pack(push, 1) +#endif + +struct FogVolume +{ + float3 scatter; + float absorption; + float3 emissive; + float anisotropy; + float3 boxMin; + float noiseMin; + float3 boxMax; + float noiseMax; + float noiseScale; + float noiseTimeScale; + uint isHeightFog; + +#if !defined(__cplusplus) + bool IsPointInside(float3 position) + { + return IsInRange(position, boxMin, boxMax); + } + + float DensityAt(float3 position, float time) + { + float4 positionTime = float4(position * noiseScale, time * noiseTimeScale); + float density = noiseMin + (noiseMax - noiseMin) * SimplexNoise4D(positionTime); + if(isHeightFog) + { + float maxHeight = boxMax.z - boxMin.z; + float height = position.z - boxMin.z; + density *= 1.0 - EaseOutCubic(height / maxHeight); + } + + return density; + } +#endif +}; + +#if defined(__cplusplus) +#pragma pack(pop) +#endif + +#if !defined(__cplusplus) + +// defines voxel sampling offsets for super-sampled fog injection + +#if defined(VOXEL_SUPERSAMPLING_1X) +static const int VoxelSampleCount = 1; +static const float3 VoxelSamples[1] = +{ + float3(0, 0, 0) +}; +#elif defined(VOXEL_SUPERSAMPLING_2X) +static const float Offset = 0.1666666666666666666666666666666; +static const int VoxelSampleCount = 9; +static const float3 VoxelSamples[9] = +{ + float3(0, 0, 0), + float3(-Offset, -Offset, -Offset), + float3(-Offset, -Offset, +Offset), + float3(-Offset, +Offset, -Offset), + float3(-Offset, +Offset, +Offset), + float3(+Offset, -Offset, -Offset), + float3(+Offset, -Offset, +Offset), + float3(+Offset, +Offset, -Offset), + float3(+Offset, +Offset, +Offset) +}; +#elif defined(VOXEL_SUPERSAMPLING_3X) +static const int VoxelSampleCount = 27; +static const float3 VoxelSamples[27] = +{ + float3(-0.25, -0.25, -0.25), + float3(-0.25, -0.25, 0), + float3(-0.25, -0.25, 0.25), + float3(-0.25, 0, -0.25), + float3(-0.25, 0, 0), + float3(-0.25, 0, 0.25), + float3(-0.25, 0.25, -0.25), + float3(-0.25, 0.25, 0), + float3(-0.25, 0.25, 0.25), + float3(0, -0.25, -0.25), + float3(0, -0.25, 0), + float3(0, -0.25, 0.25), + float3(0, 0, -0.25), + float3(0, 0, 0), + float3(0, 0, 0.25), + float3(0, 0.25, -0.25), + float3(0, 0.25, 0), + float3(0, 0.25, 0.25), + float3(0.25, -0.25, -0.25), + float3(0.25, -0.25, 0), + float3(0.25, -0.25, 0.25), + float3(0.25, 0, -0.25), + float3(0.25, 0, 0), + float3(0.25, 0, 0.25), + float3(0.25, 0.25, -0.25), + float3(0.25, 0.25, 0), + float3(0.25, 0.25, 0.25) +}; +#elif defined(VOXEL_SUPERSAMPLING_4X) +static const int VoxelSampleCount = 65; +static const float3 VoxelSamples[65] = +{ + float3(0, 0, 0), + float3(-0.3, -0.3, -0.3), + float3(-0.3, -0.3, -0.1), + float3(-0.3, -0.3, 0.1), + float3(-0.3, -0.3, 0.3), + float3(-0.3, -0.1, -0.3), + float3(-0.3, -0.1, -0.1), + float3(-0.3, -0.1, 0.1), + float3(-0.3, -0.1, 0.3), + float3(-0.3, 0.1, -0.3), + float3(-0.3, 0.1, -0.1), + float3(-0.3, 0.1, 0.1), + float3(-0.3, 0.1, 0.3), + float3(-0.3, 0.3, -0.3), + float3(-0.3, 0.3, -0.1), + float3(-0.3, 0.3, 0.1), + float3(-0.3, 0.3, 0.3), + float3(-0.1, -0.3, -0.3), + float3(-0.1, -0.3, -0.1), + float3(-0.1, -0.3, 0.1), + float3(-0.1, -0.3, 0.3), + float3(-0.1, -0.1, -0.3), + float3(-0.1, -0.1, -0.1), + float3(-0.1, -0.1, 0.1), + float3(-0.1, -0.1, 0.3), + float3(-0.1, 0.1, -0.3), + float3(-0.1, 0.1, -0.1), + float3(-0.1, 0.1, 0.1), + float3(-0.1, 0.1, 0.3), + float3(-0.1, 0.3, -0.3), + float3(-0.1, 0.3, -0.1), + float3(-0.1, 0.3, 0.1), + float3(-0.1, 0.3, 0.3), + float3(0.1, -0.3, -0.3), + float3(0.1, -0.3, -0.1), + float3(0.1, -0.3, 0.1), + float3(0.1, -0.3, 0.3), + float3(0.1, -0.1, -0.3), + float3(0.1, -0.1, -0.1), + float3(0.1, -0.1, 0.1), + float3(0.1, -0.1, 0.3), + float3(0.1, 0.1, -0.3), + float3(0.1, 0.1, -0.1), + float3(0.1, 0.1, 0.1), + float3(0.1, 0.1, 0.3), + float3(0.1, 0.3, -0.3), + float3(0.1, 0.3, -0.1), + float3(0.1, 0.3, 0.1), + float3(0.1, 0.3, 0.3), + float3(0.3, -0.3, -0.3), + float3(0.3, -0.3, -0.1), + float3(0.3, -0.3, 0.1), + float3(0.3, -0.3, 0.3), + float3(0.3, -0.1, -0.3), + float3(0.3, -0.1, -0.1), + float3(0.3, -0.1, 0.1), + float3(0.3, -0.1, 0.3), + float3(0.3, 0.1, -0.3), + float3(0.3, 0.1, -0.1), + float3(0.3, 0.1, 0.1), + float3(0.3, 0.1, 0.3), + float3(0.3, 0.3, -0.3), + float3(0.3, 0.3, -0.1), + float3(0.3, 0.3, 0.1), + float3(0.3, 0.3, 0.3), +}; +#endif + +#endif diff --git a/code/renderer/shaders/crp/vl_debug_ambient.hlsl b/code/renderer/shaders/crp/vl_debug_ambient.hlsl new file mode 100644 index 0000000..4f53cf4 --- /dev/null +++ b/code/renderer/shaders/crp/vl_debug_ambient.hlsl @@ -0,0 +1,101 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: visualize the bsp light grid as a bunch of raytraced spheres + + +#include "common.hlsli" +#include "scene_view.h.hlsli" + + +cbuffer RootConstants +{ + float3 centerPosition; + float sphereScale; + float3 worldScale; + uint lightGridTextureAIndex; + uint lightGridTextureBIndex; +} + +struct VOut +{ + float4 position : SV_Position; + float3 positionWS : POSITIONWS; + float4 sphere : SPHERE; + int3 voxelIndex : VOXELINDEX; +}; + +VOut vs(uint vertexId : SV_VertexID) +{ + Texture3D lightGridTexture = ResourceDescriptorHeap[lightGridTextureAIndex]; + SceneView scene = GetSceneView(); + + uint3 textureSize = GetTextureSize(lightGridTexture); + uint flatVoxelIndex = vertexId / 6; + uint vertexIndex = vertexId % 6; + int3 voxelIndex = int3(UnflattenIndex(flatVoxelIndex, textureSize)); + float3 voxelCenter = AABoxIndexToWorldSpace(voxelIndex, centerPosition, float3(textureSize), worldScale); + float2 quadPosition = QuadFromVertexID(vertexIndex); + float radius = 0.5 * sphereScale * min3(worldScale.x, worldScale.y, worldScale.z); + float3 up = scene.cameraUp; + float3 forward = normalize(voxelCenter - scene.cameraPosition); + float3 right = normalize(cross(forward, up)); + up = cross(right, forward); + float3x3 rotMat = float3x3(right, up, forward); + float distToSphere = length(scene.cameraPosition - voxelCenter); + float sinAngle = radius / distToSphere; + float cosAngle = sqrt(1.0 - sinAngle * sinAngle); + float tanAngle = sinAngle / cosAngle; + float quadScale = tanAngle * distToSphere * 2.0; + float3 positionWS = voxelCenter + quadScale * mul(float3(quadPosition, 0), rotMat); + float4 positionVS = mul(scene.viewMatrix, float4(positionWS, 1)); + float4 position = mul(scene.projectionMatrix, positionVS); + + VOut output; + output.position = position; + output.positionWS = positionWS; + output.sphere = float4(voxelCenter, radius); + output.voxelIndex = voxelIndex; + + return output; +} + +float4 ps(VOut input) : SV_Target +{ + Texture3D lightGridTextureA = ResourceDescriptorHeap[lightGridTextureAIndex]; + Texture3D lightGridTextureB = ResourceDescriptorHeap[lightGridTextureBIndex]; + SceneView scene = GetSceneView(); + + float3 rayDir = normalize(input.positionWS - scene.cameraPosition); + float t = RaytraceSphere(scene.cameraPosition, rayDir, input.sphere.xyz, input.sphere.w); + if(t < 0.0) + { + discard; + } + + float4 payloadA = lightGridTextureA[input.voxelIndex]; + float4 payloadB = lightGridTextureB[input.voxelIndex]; + float3 hitPosition = scene.cameraPosition + rayDir * t; + float3 normal = normalize(hitPosition - input.sphere.xyz); + float3 color = AmbientColor(payloadA, payloadB, normal, scene.ambientColor); + float4 result = float4(color * 0.5, 1); + + return result; +} diff --git a/code/renderer/shaders/crp/vl_debug_extinction.hlsl b/code/renderer/shaders/crp/vl_debug_extinction.hlsl new file mode 100644 index 0000000..b950209 --- /dev/null +++ b/code/renderer/shaders/crp/vl_debug_extinction.hlsl @@ -0,0 +1,89 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: visualize the extinction volume as little cubes + + +#include "common.hlsli" +#include "scene_view.h.hlsli" + + +cbuffer RootConstants +{ + float3 color; + float worldScale; + float3 cameraPosition; + float boxScale; + float extinctionScale; + uint extinctionTextureIndex; +} + +struct VOut +{ + float4 position : SV_Position; + float3 positionInCube : POSITIONINCUBE; + int3 voxelIndex : VOXELINDEX; +}; + +VOut vs(uint vertexId : SV_VertexID) +{ + RWTexture3D extinctionTexture = ResourceDescriptorHeap[extinctionTextureIndex]; + SceneView scene = GetSceneView(); + + uint3 extinctionSize = GetTextureSize(extinctionTexture); + uint flatVoxelIndex = vertexId / 36; + uint vertexIndex = vertexId % 36; + int3 voxelIndex = int3(UnflattenIndex(flatVoxelIndex, extinctionSize)); + float3 voxelCenter = AABoxIndexToWorldSpace(voxelIndex, cameraPosition, float3(extinctionSize), worldScale); + float3 positionInCube = CubeFromVertexID(vertexIndex); + float3 positionWS = voxelCenter + 0.5 * boxScale * worldScale * positionInCube; + float4 positionVS = mul(scene.viewMatrix, float4(positionWS, 1)); + float4 position = mul(scene.projectionMatrix, positionVS); + + VOut output; + output.position = position; + output.voxelIndex = voxelIndex; + output.positionInCube = positionInCube; + + return output; +} + +float4 ps(VOut input) : SV_Target +{ + RWTexture3D extinctionTexture = ResourceDescriptorHeap[extinctionTextureIndex]; + + float alpha = saturate(extinctionScale * extinctionTexture[input.voxelIndex]); + if(alpha == 0.0) + { + discard; + } + + float threshold = 0.9; + float3 position = abs(input.positionInCube); + bool3 limits = position >= float3(threshold, threshold, threshold); + if(dot(uint3(limits), uint3(1, 1, 1)) >= 2) + { + alpha = 0.0; + } + + float4 result = float4(color * 0.5 * alpha, 1); + + return result; +} diff --git a/code/renderer/shaders/crp/vl_debug_shadow_sun.hlsl b/code/renderer/shaders/crp/vl_debug_shadow_sun.hlsl new file mode 100644 index 0000000..c14a669 --- /dev/null +++ b/code/renderer/shaders/crp/vl_debug_shadow_sun.hlsl @@ -0,0 +1,89 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: visualize the sun shadow volume as little cubes + + +#include "common.hlsli" +#include "scene_view.h.hlsli" + + +cbuffer RootConstants +{ + float3 color; + float worldScale; + float3 cameraPosition; + float boxScale; + uint shadowTextureIndex; +} + +struct VOut +{ + float4 position : SV_Position; + float3 positionInCube : POSITIONINCUBE; + int3 voxelIndex : VOXELINDEX; +}; + +VOut vs(uint vertexId : SV_VertexID) +{ + RWTexture3D shadowTexture = ResourceDescriptorHeap[shadowTextureIndex]; + SceneView scene = GetSceneView(); + + uint3 shadowSize = GetTextureSize(shadowTexture); + uint flatVoxelIndex = vertexId / 36; + uint vertexIndex = vertexId % 36; + int3 voxelIndex = int3(UnflattenIndex(flatVoxelIndex, shadowSize)); + float3 voxelCenterSS = AABoxIndexToWorldSpace(voxelIndex, cameraPosition, float3(shadowSize), worldScale); + float3 positionInCube = CubeFromVertexID(vertexIndex); + float3 positionSS = voxelCenterSS + 0.5 * boxScale * worldScale * positionInCube; + float3 positionWS = cameraPosition + mul(scene.sunToZMatrix, positionSS - cameraPosition); + float4 positionVS = mul(scene.viewMatrix, float4(positionWS, 1)); + float4 position = mul(scene.projectionMatrix, positionVS); + + VOut output; + output.position = position; + output.voxelIndex = voxelIndex; + output.positionInCube = positionInCube; + + return output; +} + +float4 ps(VOut input) : SV_Target +{ + RWTexture3D shadowTexture = ResourceDescriptorHeap[shadowTextureIndex]; + + float transmittance = saturate(shadowTexture[input.voxelIndex]); + if(transmittance == 1.0) + { + discard; + } + + float threshold = 0.9; + float3 position = abs(input.positionInCube); + bool3 limits = position >= float3(threshold, threshold, threshold); + float edge = 0.0; + if(dot(uint3(limits), uint3(1, 1, 1)) >= 2) + { + edge = 1.0; + } + float4 result = float4(saturate(0.5 * color * transmittance + edge.xxx), 1); + + return result; +} diff --git a/code/renderer/shaders/crp/vl_extinction_injection_fog.hlsl b/code/renderer/shaders/crp/vl_extinction_injection_fog.hlsl new file mode 100644 index 0000000..6d35b23 --- /dev/null +++ b/code/renderer/shaders/crp/vl_extinction_injection_fog.hlsl @@ -0,0 +1,73 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: inject fog into the extinction volume + + +#include "common.hlsli" +#include "scene_view.h.hlsli" +#define VOXEL_SUPERSAMPLING_1X +#include "vl_common.h.hlsli" + + +cbuffer RootConstants +{ + FogVolume fog; + float time; + uint extinctionTextureIndex; + float worldScale; +} + +[numthreads(4, 4, 4)] +void cs(uint3 id : SV_DispatchThreadID) +{ + RWTexture3D extinctionTexture = ResourceDescriptorHeap[extinctionTextureIndex]; + uint3 textureSize = GetTextureSize(extinctionTexture); + if(any(id >= textureSize)) + { + return; + } + + SceneView scene = GetSceneView(); + + float3 textureSizeF = float3(textureSize); + float3 tcBase = (float3(id) + float3(0.5, 0.5, 0.5)) / textureSizeF; + + float scale = 0.0; + float counter = 0.0; + for(int s = 0; s < VoxelSampleCount; s++) + { + float3 tcOffset = VoxelSamples[s] / textureSizeF; + float3 tc = tcBase + tcOffset; + float3 position = scene.ExtinctionIndexToWorldSpace(id, textureSizeF, worldScale); + if(fog.IsPointInside(position)) + { + scale += fog.DensityAt(position, time); + counter += 1.0; + } + } + + if(scale > 0.0 && counter > 0.0) + { + scale /= counter; + float extinction = (Brightness(fog.scatter) + fog.absorption) * scale; + extinctionTexture[id] += extinction; + } +} diff --git a/code/renderer/shaders/crp/vl_extinction_injection_particles.hlsl b/code/renderer/shaders/crp/vl_extinction_injection_particles.hlsl new file mode 100644 index 0000000..29d0f00 --- /dev/null +++ b/code/renderer/shaders/crp/vl_extinction_injection_particles.hlsl @@ -0,0 +1,100 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: inject particles into the extinction volume + + +#include "common.hlsli" +#include "scene_view.h.hlsli" +#define VOXEL_SUPERSAMPLING_2X +#include "vl_common.h.hlsli" + + +cbuffer RootConstants +{ + uint3 tileScale; + uint particleBufferIndex; + uint particleCount; + uint extinctionTextureIndex; + uint tileBufferIndex; + uint tileCount; + float extinctionWorldScale; +} + +[numthreads(512, 1, 1)] +void cs(uint3 dtid : SV_DispatchThreadID, uint gidx : SV_GroupIndex) +{ + uint tileIndex = dtid.x / 512; + if(tileIndex >= tileCount) + { + return; + } + + RWStructuredBuffer tileBuffer = ResourceDescriptorHeap[tileBufferIndex]; + RWTexture3D extinctionTexture = ResourceDescriptorHeap[extinctionTextureIndex]; + uint3 textureSize = GetTextureSize(extinctionTexture); + uint3 tileCornerIndex = tileBuffer[tileIndex]; + uint3 tileThreadIndex = UnflattenIndex(gidx, tileScale); + uint3 id = tileCornerIndex * tileScale + tileThreadIndex; + if(any(id >= textureSize)) + { + return; + } + + StructuredBuffer particleBuffer = ResourceDescriptorHeap[particleBufferIndex]; + SceneView scene = GetSceneView(); + + float3 textureSizeF = float3(textureSize); + float3 tcBase = (float3(id) + float3(0.5, 0.5, 0.5)) / textureSizeF; + float accumExtinction = 0.0; + for(uint i = 0; i < particleCount; i++) + { + Particle particle = particleBuffer[i]; + float extinction = particle.absorption; + [flatten] + if(particle.isEmissive == 0) + { + extinction += Brightness(particle.scattering); + } + + float particleCoverage = 0.0; + for(uint s = 0; s < VoxelSampleCount; s++) + { + float3 tcSample = tcBase + VoxelSamples[s] / textureSizeF; + float3 position = scene.ExtinctionTCToWorldSpace(tcSample, textureSizeF, extinctionWorldScale); + float dist = distance(position, particle.position); + if(dist >= particle.radius) + { + continue; + } + + float coverage = sqrt(saturate(1.0 - dist / particle.radius)); + particleCoverage += coverage; + } + particleCoverage /= float(VoxelSampleCount); + + accumExtinction += particleCoverage * extinction; + } + + if(accumExtinction > 0.0) + { + extinctionTexture[id] += accumExtinction; + } +} diff --git a/code/renderer/shaders/crp/fog_outside.hlsl b/code/renderer/shaders/crp/vl_frustum_anisotropy_average.hlsl similarity index 60% rename from code/renderer/shaders/crp/fog_outside.hlsl rename to code/renderer/shaders/crp/vl_frustum_anisotropy_average.hlsl index 0218ba0..6df02e6 100644 --- a/code/renderer/shaders/crp/fog_outside.hlsl +++ b/code/renderer/shaders/crp/vl_frustum_anisotropy_average.hlsl @@ -18,26 +18,32 @@ You should have received a copy of the GNU General Public License along with Challenge Quake 3. If not, see . =========================================================================== */ -// fog volume (AABB) seen from outside +// volumetric lighting: normalize the Henyey-Greenstein anisotropy factor g #include "common.hlsli" -#include "fog.hlsli" -float4 ps(VOut input) : SV_Target +cbuffer RootConstants { - Texture2D depthTexture = ResourceDescriptorHeap[depthTextureIndex]; - float depthZW = depthTexture.Load(int3(input.position.xy, 0)); - float depthBuff = LinearDepth(depthZW, linearDepthA, linearDepthB); - float depthFrag = input.depthVS; - if(depthFrag > depthBuff) + uint materialTextureBIndex; + uint materialTextureCIndex; +} + +[numthreads(4, 4, 4)] +void cs(uint3 id : SV_DispatchThreadID) +{ + RWTexture3D materialTextureB = ResourceDescriptorHeap[materialTextureBIndex]; + uint3 textureSize = GetTextureSize(materialTextureB); + if(any(id >= textureSize)) { - discard; + return; } - float fogOpacity = saturate((depthBuff - depthFrag) / depth); - float4 result = float4(color.rgb, fogOpacity); - - return result; + RWTexture3D materialTextureC = ResourceDescriptorHeap[materialTextureCIndex]; + float weightSum = materialTextureC[id]; + if(weightSum > 0.0) + { + materialTextureB[id].a /= weightSum; + } } diff --git a/code/renderer/shaders/crp/vl_frustum_injection_fog.hlsl b/code/renderer/shaders/crp/vl_frustum_injection_fog.hlsl new file mode 100644 index 0000000..31f40af --- /dev/null +++ b/code/renderer/shaders/crp/vl_frustum_injection_fog.hlsl @@ -0,0 +1,77 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: inject fog into the material textures + + +#include "common.hlsli" +#include "scene_view.h.hlsli" +#define VOXEL_SUPERSAMPLING_1X +#include "vl_common.h.hlsli" + + +cbuffer RootConstants +{ + FogVolume fog; + float time; + uint materialTextureAIndex; + uint materialTextureBIndex; + uint materialTextureCIndex; +} + +[numthreads(4, 4, 4)] +void cs(uint3 id : SV_DispatchThreadID) +{ + RWTexture3D materialTextureA = ResourceDescriptorHeap[materialTextureAIndex]; + uint3 textureSize = GetTextureSize(materialTextureA); + if(any(id >= textureSize)) + { + return; + } + + RWTexture3D materialTextureB = ResourceDescriptorHeap[materialTextureBIndex]; + RWTexture3D materialTextureC = ResourceDescriptorHeap[materialTextureCIndex]; + SceneView scene = GetSceneView(); + + float3 textureSizeF = float3(textureSize); + float3 tcBase = (float3(id) + float3(0.5, 0.5, 0.5)) / textureSizeF; + + float scale = 0.0; + float counter = 0.0; + for(int s = 0; s < VoxelSampleCount; s++) + { + float3 tcOffset = VoxelSamples[s] / textureSizeF; + float3 tc = tcBase + tcOffset; + float3 position = scene.FroxelTCToWorldSpace(tc, textureSizeF); + if(fog.IsPointInside(position)) + { + scale += fog.DensityAt(position, time); + counter += 1.0; + } + } + + if(scale > 0.0 && counter > 0.0) + { + scale /= counter; + materialTextureA[id] += float4(fog.scatter * scale, fog.absorption * scale); + materialTextureB[id] += float4(fog.emissive * scale, fog.anisotropy); + materialTextureC[id] += 1.0; + } +} diff --git a/code/renderer/shaders/crp/vl_frustum_injection_particles.hlsl b/code/renderer/shaders/crp/vl_frustum_injection_particles.hlsl new file mode 100644 index 0000000..caa907f --- /dev/null +++ b/code/renderer/shaders/crp/vl_frustum_injection_particles.hlsl @@ -0,0 +1,116 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: inject particles into the material textures + + +#include "common.hlsli" +#include "scene_view.h.hlsli" +#define VOXEL_SUPERSAMPLING_2X +#include "vl_common.h.hlsli" + + +cbuffer RootConstants +{ + uint3 tileScale; + uint particleBufferIndex; + uint particleCount; + uint materialTextureAIndex; + uint materialTextureBIndex; + uint materialTextureCIndex; + uint tileBufferIndex; + uint tileCount; +} + +[numthreads(1024, 1, 1)] +void cs(uint3 dtid : SV_DispatchThreadID, uint gidx : SV_GroupIndex) +{ + uint tileIndex = dtid.x / 1024; + if(tileIndex >= tileCount) + { + return; + } + + RWStructuredBuffer tileBuffer = ResourceDescriptorHeap[tileBufferIndex]; + RWTexture3D materialTextureA = ResourceDescriptorHeap[materialTextureAIndex]; + uint3 textureSize = GetTextureSize(materialTextureA); + uint3 tileCornerIndex = tileBuffer[tileIndex]; + uint3 tileThreadIndex = UnflattenIndex(gidx, tileScale); + uint3 id = tileCornerIndex * tileScale + tileThreadIndex; + if(any(id >= textureSize)) + { + return; + } + + RWTexture3D materialTextureB = ResourceDescriptorHeap[materialTextureBIndex]; + RWTexture3D materialTextureC = ResourceDescriptorHeap[materialTextureCIndex]; + StructuredBuffer particleBuffer = ResourceDescriptorHeap[particleBufferIndex]; + SceneView scene = GetSceneView(); + + float3 textureSizeF = float3(textureSize); + float3 tcBase = (float3(id) + float3(0.5, 0.5, 0.5)) / textureSizeF; + float4 accumScatterAbs = float4(0, 0, 0, 0); + float4 accumEmissiveAniso = float4(0, 0, 0, 0); + float accumCoverage = 0.0; + for(uint i = 0; i < particleCount; i++) + { + Particle particle = particleBuffer[i]; + float3 scattering; + float3 emissive; + [flatten] + if(particle.isEmissive != 0) + { + scattering = float3(0, 0, 0); + emissive = particle.scattering; + } + else + { + scattering = particle.scattering; + emissive = float3(0, 0, 0); + } + + float particleCoverage = 0.0; + for(uint s = 0; s < VoxelSampleCount; s++) + { + float3 tcSample = tcBase + VoxelSamples[s] / textureSizeF; + float3 position = scene.FroxelTCToWorldSpace(tcSample, textureSizeF); + float dist = distance(position, particle.position); + if(dist >= particle.radius) + { + continue; + } + + float coverage = sqrt(saturate(1.0 - dist / particle.radius)); + particleCoverage += coverage; + } + particleCoverage /= float(VoxelSampleCount); + + accumScatterAbs += particleCoverage * float4(scattering, particle.absorption); + accumEmissiveAniso += particleCoverage * float4(emissive, particle.anisotropy); + accumCoverage += particleCoverage; + } + + if(accumCoverage > 0.0) + { + materialTextureA[id] += accumScatterAbs; + materialTextureB[id] += accumEmissiveAniso; + materialTextureC[id] += accumCoverage; + } +} diff --git a/code/renderer/shaders/crp/vl_frustum_inscatter_ambient.hlsl b/code/renderer/shaders/crp/vl_frustum_inscatter_ambient.hlsl new file mode 100644 index 0000000..b002745 --- /dev/null +++ b/code/renderer/shaders/crp/vl_frustum_inscatter_ambient.hlsl @@ -0,0 +1,86 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: accumulates in-scattered ambient light + + +#include "common.hlsli" +#include "scene_view.h.hlsli" + + +cbuffer RootConstants +{ + float3 centerPosition; + uint materialTextureAIndex; + float3 worldScale; + uint scatterExtTextureIndex; + uint ambientLightTextureAIndex; + uint ambientLightTextureBIndex; + uint ambientSamplerIndex; + uint isLightGridAvailable; +} + +[numthreads(4, 4, 4)] +void cs(uint3 id : SV_DispatchThreadID) +{ + RWTexture3D scatterExtTexture = ResourceDescriptorHeap[scatterExtTextureIndex]; + uint3 textureSize = GetTextureSize(scatterExtTexture); + if(any(id >= textureSize)) + { + return; + } + + if(isLightGridAvailable != 0) + { + SceneView scene = GetSceneView(); + RWTexture3D materialTextureA = ResourceDescriptorHeap[materialTextureAIndex]; + Texture3D ambientLightTextureA = ResourceDescriptorHeap[ambientLightTextureAIndex]; + Texture3D ambientLightTextureB = ResourceDescriptorHeap[ambientLightTextureBIndex]; + SamplerState ambientSampler = SamplerDescriptorHeap[ambientSamplerIndex]; + float3 ambientTextureSize = float3(GetTextureSize(ambientLightTextureA)); + + float3 positionWS = scene.FroxelIndexToWorldSpace(id, textureSize); + float3 normalWS = normalize(scene.cameraPosition - positionWS); + float4 scatterAbs = materialTextureA[id]; + float3 scattering = scatterAbs.rgb; + float extinction = Brightness(scattering) + scatterAbs.a; + float3 ambientTC = AABoxWorldSpaceToTC(positionWS, centerPosition, ambientTextureSize, worldScale); + float4 ambientA = ambientLightTextureA.SampleLevel(ambientSampler, ambientTC, 0); + float4 ambientB = ambientLightTextureB.SampleLevel(ambientSampler, ambientTC, 0); + float3 ambientColor = AmbientColor(ambientA, ambientB, normalWS, scene.ambientColor); + float3 inScattering = scattering * ambientColor * scene.ambientIntensity; + + scatterExtTexture[id] = float4(inScattering, extinction); + } + else + { + SceneView scene = GetSceneView(); + RWTexture3D materialTextureA = ResourceDescriptorHeap[materialTextureAIndex]; + + float3 positionWS = scene.FroxelIndexToWorldSpace(id, textureSize); + float3 normalWS = normalize(scene.cameraPosition - positionWS); + float4 scatterAbs = materialTextureA[id]; + float3 scattering = scatterAbs.rgb; + float extinction = Brightness(scattering) + scatterAbs.a; + float3 inScattering = scattering * scene.ambientColor * scene.ambientIntensity; + + scatterExtTexture[id] = float4(inScattering, extinction); + } +} diff --git a/code/renderer/shaders/crp/vl_frustum_inscatter_point_light.hlsl b/code/renderer/shaders/crp/vl_frustum_inscatter_point_light.hlsl new file mode 100644 index 0000000..fa46bec --- /dev/null +++ b/code/renderer/shaders/crp/vl_frustum_inscatter_point_light.hlsl @@ -0,0 +1,82 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: accumulates in-scattered light from a local point light + + +#include "common.hlsli" +#include "scene_view.h.hlsli" +#include "raytracing.h.hlsli" + + +cbuffer RootConstants +{ + DynamicLight light; + uint materialTextureAIndex; + uint materialTextureBIndex; + uint scatterExtTextureIndex; + uint transmittanceTextureIndex; + uint transmittanceSamplerIndex; + float shadowWorldScale; +} + +[numthreads(4, 4, 4)] +void cs(uint3 id : SV_DispatchThreadID) +{ + RWTexture3D scatterExtTexture = ResourceDescriptorHeap[scatterExtTextureIndex]; + uint3 textureSize = GetTextureSize(scatterExtTexture); + if(any(id >= textureSize)) + { + return; + } + + RWTexture3D materialTextureA = ResourceDescriptorHeap[materialTextureAIndex]; + RWTexture3D materialTextureB = ResourceDescriptorHeap[materialTextureBIndex]; + Texture3D transmittanceTexture = ResourceDescriptorHeap[transmittanceTextureIndex]; + SamplerState transmittanceSampler = SamplerDescriptorHeap[transmittanceSamplerIndex]; + SceneView scene = GetSceneView(); + + float3 froxelPosition = scene.FroxelIndexToWorldSpace(id, float3(textureSize)); + float3 lightPosition = light.position; + float dist = distance(froxelPosition, lightPosition); + float radius = light.radius; + if(dist >= radius) + { + return; + } + + float3 scattering = materialTextureA[id].rgb; + float anisotropy = materialTextureB[id].a; + RTAS rtas = ResourceDescriptorHeap[scene.tlasBufferIndex]; + float3 lightDir = normalize(lightPosition - froxelPosition); + float vis = TraceVisibilityWithoutAT(rtas, froxelPosition, lightDir, dist); + float3 shadowTC = AABoxWorldSpaceToTC(froxelPosition, lightPosition, GetTextureSize(transmittanceTexture), shadowWorldScale); + float trans = transmittanceTexture.SampleLevel(transmittanceSampler, shadowTC, 0); + float intensity = saturate(1.0 - dist / radius); + float3 lightRaw = light.color * intensity * 50.0; // @TODO: + float2 froxelTC = (float2(id.xy) + float2(0.5, 0.5)) / float2(textureSize.xy); + float2 froxelNDC = TCToNDC(froxelTC); + float3 cameraRay = scene.CamerayRay(froxelNDC); + float cosTheta = dot(-lightDir, -cameraRay); + float phase = HenyeyGreenstein(cosTheta, anisotropy); + float3 inScattering = vis * lightRaw * trans * scattering * phase; + + scatterExtTexture[id].rgb += inScattering; +} diff --git a/code/renderer/shaders/crp/vl_frustum_inscatter_sunlight.hlsl b/code/renderer/shaders/crp/vl_frustum_inscatter_sunlight.hlsl new file mode 100644 index 0000000..9061daa --- /dev/null +++ b/code/renderer/shaders/crp/vl_frustum_inscatter_sunlight.hlsl @@ -0,0 +1,86 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: accumulates in-scattered sunlight + + +#include "common.hlsli" +#include "scene_view.h.hlsli" +#include "raytracing.h.hlsli" + + +cbuffer RootConstants +{ + uint materialTextureAIndex; + uint materialTextureBIndex; + uint scatterExtTextureIndex; + uint sunlightVisTextureIndex; +} + +float FroxelSize(uint3 index, float3 textureSize, SceneView scene) +{ + float3 tcBase = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize; + float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize; + float3 posL = scene.FroxelTCToWorldSpace(tcBase + float3(-halfTexel.x, 0, 0), textureSize); + float3 posR = scene.FroxelTCToWorldSpace(tcBase + float3( halfTexel.x, 0, 0), textureSize); + float w = distance(posL, posR); + float3 posU = scene.FroxelTCToWorldSpace(tcBase + float3(0, halfTexel.y, 0), textureSize); + float3 posD = scene.FroxelTCToWorldSpace(tcBase + float3(0, -halfTexel.y, 0), textureSize); + float h = distance(posU, posD); + float3 posF = scene.FroxelTCToWorldSpace(tcBase + float3(0, 0, halfTexel.z), textureSize); + float3 posB = scene.FroxelTCToWorldSpace(tcBase + float3(0, 0, -halfTexel.z), textureSize); + float d = distance(posF, posB); + float size = max3(w, h, d); + + return size; +} + +[numthreads(4, 4, 4)] +void cs(uint3 id : SV_DispatchThreadID) +{ + RWTexture3D scatterExtTexture = ResourceDescriptorHeap[scatterExtTextureIndex]; + uint3 textureSize = GetTextureSize(scatterExtTexture); + if(any(id >= textureSize)) + { + return; + } + + RWTexture3D materialTextureA = ResourceDescriptorHeap[materialTextureAIndex]; + RWTexture3D materialTextureB = ResourceDescriptorHeap[materialTextureBIndex]; + RWTexture3D sunlightVisTexture = ResourceDescriptorHeap[sunlightVisTextureIndex]; + SceneView scene = GetSceneView(); + float froxelSize = FroxelSize(id, float3(textureSize), scene); + SunVShadowCascade cascade = scene.GetSunVShadowCascade(froxelSize); + + float3 positionWS = scene.FroxelIndexToWorldSpace(id, textureSize); + float visOpaque = sunlightVisTexture[id]; + float visVolume = cascade.TransmittanceAt(positionWS); + float vis = visOpaque * visVolume; + float2 tc = (float2(id.xy) + float2(0.5, 0.5)) / float2(textureSize.xy); + float2 ndc = TCToNDC(tc); + float3 cameraRay = scene.CamerayRay(ndc); + float cosTheta = dot(-scene.sunDirection, -cameraRay); + float3 scattering = materialTextureA[id].rgb; + float anisotropy = materialTextureB[id].a; + float phase = HenyeyGreenstein(cosTheta, anisotropy); + float3 inScattering = vis * scene.sunColor * scene.sunIntensity * scattering * phase; + + scatterExtTexture[id].rgb += inScattering; +} diff --git a/code/renderer/shaders/crp/vl_frustum_raymarch.hlsl b/code/renderer/shaders/crp/vl_frustum_raymarch.hlsl new file mode 100644 index 0000000..87dc778 --- /dev/null +++ b/code/renderer/shaders/crp/vl_frustum_raymarch.hlsl @@ -0,0 +1,73 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: raymarch froxels + + +#include "common.hlsli" +#include "scene_view.h.hlsli" + + +cbuffer RootConstants +{ + uint scatterTextureIndex; + uint resolveTextureIndex; + uint materialTextureBIndex; +} + +[numthreads(8, 8, 1)] +void cs(uint3 id : SV_DispatchThreadID) +{ + RWTexture3D scatterTexture = ResourceDescriptorHeap[scatterTextureIndex]; + uint3 textureSize = GetTextureSize(scatterTexture); + if(any(id.xy >= textureSize.xy)) + { + return; + } + + // integScatter is computed using Frostbite's analytical solution: + // Int(S * T(Z) * dZ) == S * (1 - T(Z)) / extinction + + SceneView scene = GetSceneView(); + RWTexture3D resolveTexture = ResourceDescriptorHeap[resolveTextureIndex]; + RWTexture3D materialTextureB = ResourceDescriptorHeap[materialTextureBIndex]; + uint3 index0 = uint3(id.xy, 0); + float3 tc0 = (float3(index0) + float3(0.5, 0.5, 0)) / textureSize; // near edge of first voxel + float3 prevPosition = scene.FroxelTCToWorldSpace(tc0, float3(textureSize)); + float3 accumScatter = float3(0, 0, 0); + float accumTrans = 1.0; + for(uint d = 0; d < textureSize.z; d++) + { + uint3 index = uint3(id.xy, d); + float3 tc = (float3(index) + float3(0.5, 0.5, 1)) / textureSize; // far edge of current voxel + float4 froxelScatterExt = scatterTexture[index]; + float3 emissive = materialTextureB[index].rgb; + float3 froxelScatter = froxelScatterExt.rgb + emissive; + float froxelExtinction = froxelScatterExt.a; + float3 currPosition = scene.FroxelTCToWorldSpace(tc, float3(textureSize)); + float depthStep = distance(currPosition, prevPosition); + float froxelTrans = Transmittance(depthStep, froxelExtinction); + float3 integScatter = froxelScatter * (1.0 - froxelTrans) / (froxelExtinction == 0.0 ? 1.0 : froxelExtinction); + accumScatter += accumTrans * integScatter; + accumTrans *= froxelTrans; + resolveTexture[index] = float4(accumScatter, accumTrans); + prevPosition = currPosition; + } +} diff --git a/code/renderer/shaders/crp/vl_frustum_sunlight_visibility.hlsl b/code/renderer/shaders/crp/vl_frustum_sunlight_visibility.hlsl new file mode 100644 index 0000000..c96fe24 --- /dev/null +++ b/code/renderer/shaders/crp/vl_frustum_sunlight_visibility.hlsl @@ -0,0 +1,70 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: opaque sunlight visibility + + +#include "common.hlsli" +#include "scene_view.h.hlsli" +#include "raytracing.h.hlsli" + + +cbuffer RootConstants +{ + float3 jitter; + uint visTextureIndex; + uint depthMip; +} + +[numthreads(4, 4, 4)] +void cs(uint3 id : SV_DispatchThreadID) +{ + RWTexture3D visTexture = ResourceDescriptorHeap[visTextureIndex]; + uint3 textureSize = GetTextureSize(visTexture); + if(any(id >= textureSize)) + { + return; + } + + SceneView scene = GetSceneView(); + RTAS rtas = ResourceDescriptorHeap[scene.tlasBufferIndex]; + Texture2D depthMinMaxTexture = ResourceDescriptorHeap[scene.depthMinMaxTextureIndex]; + + float2 tc = (float2(id.xy) + float2(0.5, 0.5)) / float2(textureSize.xy); + float2 ndc = TCToNDC(tc); + float3 cameraRay = scene.CamerayRay(ndc); + float3 froxelPosition = + scene.FroxelIndexToWorldSpace(id, float3(textureSize)) + + jitter.x * scene.cameraLeft + + jitter.y * scene.cameraUp + + jitter.z * cameraRay; + float vis = TraceVisibilityWithoutAT(rtas, froxelPosition, scene.sunDirection, 10000.0); + + // this helps fix dark spots around opaque geometry set against the skybox + float storedDepth = depthMinMaxTexture.mips[depthMip][id.xy].x; + float4 positionCS = mul(scene.projectionMatrix, mul(scene.viewMatrix, float4(froxelPosition, 1))); + float froxelDepth = positionCS.z / positionCS.w; + if(froxelDepth < storedDepth) + { + vis = 0.0; + } + + visTexture[id] = vis; +} diff --git a/code/renderer/shaders/crp/vl_frustum_temporal.hlsl b/code/renderer/shaders/crp/vl_frustum_temporal.hlsl new file mode 100644 index 0000000..b53c51c --- /dev/null +++ b/code/renderer/shaders/crp/vl_frustum_temporal.hlsl @@ -0,0 +1,62 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: temporal reprojection + + +#include "common.hlsli" +#include "scene_view.h.hlsli" + + +cbuffer RootConstants +{ + uint currTextureIndex; + uint prevTextureIndex; + uint prevTextureSamplerIndex; + float alpha; +} + +[numthreads(4, 4, 4)] +void cs(uint3 id : SV_DispatchThreadID) +{ + RWTexture3D currTexture = ResourceDescriptorHeap[currTextureIndex]; + uint3 textureSize = GetTextureSize(currTexture); + if(any(id >= textureSize)) + { + return; + } + + SceneView scene = GetSceneView(); + Texture3D prevTexture = ResourceDescriptorHeap[prevTextureIndex]; + SamplerState prevTextureSampler = SamplerDescriptorHeap[prevTextureSamplerIndex]; + + float3 tc = scene.FroxelReproject01(id, float3(textureSize)); + float currValue = currTexture[id]; + float3 halfPixelSize = float3(0.5, 0.5, 0.5) / float3(textureSize); + if(IsInRange(tc, halfPixelSize, float3(1, 1, 1) - halfPixelSize)) + { + float prevValue = prevTexture.SampleLevel(prevTextureSampler, tc, 0); + float finalValue = lerp(currValue, prevValue, alpha); + if(finalValue != currValue) + { + currTexture[id] = finalValue; + } + } +} diff --git a/code/renderer/shaders/crp/fog.hlsli b/code/renderer/shaders/crp/vl_particles_dispatch.hlsl similarity index 52% rename from code/renderer/shaders/crp/fog.hlsli rename to code/renderer/shaders/crp/vl_particles_dispatch.hlsl index 12b4637..0683886 100644 --- a/code/renderer/shaders/crp/fog.hlsli +++ b/code/renderer/shaders/crp/vl_particles_dispatch.hlsl @@ -18,36 +18,38 @@ You should have received a copy of the GNU General Public License along with Challenge Quake 3. If not, see . =========================================================================== */ -// fog volume (AABB) rendering - shared code +// volumetric lighting: update indirect dispatch buffer for particle injection + + +#include "common.hlsli" cbuffer RootConstants { - matrix modelViewMatrix; - matrix projectionMatrix; - float4 boxMin; - float4 boxMax; - float4 color; - float depth; - float linearDepthA; - float linearDepthB; - uint depthTextureIndex; -}; - -struct VOut -{ - float4 position : SV_Position; - float depthVS : DEPTHVS; -}; - -VOut vs(float3 positionOS : POSITION) -{ - float3 positionWS = boxMin.xyz + positionOS * (boxMax.xyz - boxMin.xyz); - float4 positionVS = mul(modelViewMatrix, float4(positionWS, 1)); - - VOut output; - output.position = mul(projectionMatrix, positionVS); - output.depthVS = -positionVS.z; - - return output; + uint3 tileResolution; + uint tileBufferIndex; + uint dispatchBufferIndex; + uint particleTileBufferIndex; +} + +[numthreads(4, 4, 4)] +void cs(uint3 id : SV_DispatchThreadID) +{ + if(any(id >= tileResolution)) + { + return; + } + + RWByteAddressBuffer dispatchBuffer = ResourceDescriptorHeap[dispatchBufferIndex]; + RWByteAddressBuffer tileHitBuffer = ResourceDescriptorHeap[tileBufferIndex]; + RWStructuredBuffer tileWorkBuffer = ResourceDescriptorHeap[particleTileBufferIndex]; + + uint tileIndex = FlattenIndex(id, tileResolution); + uint hasParticle = tileHitBuffer.Load(tileIndex * 4); + if(hasParticle != 0) + { + uint workIndex; + dispatchBuffer.InterlockedAdd(0, 1, workIndex); + tileWorkBuffer[workIndex] = id; + } } diff --git a/code/renderer/shaders/crp/vl_particles_preprocess_extinction.hlsl b/code/renderer/shaders/crp/vl_particles_preprocess_extinction.hlsl new file mode 100644 index 0000000..d8332eb --- /dev/null +++ b/code/renderer/shaders/crp/vl_particles_preprocess_extinction.hlsl @@ -0,0 +1,73 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: pre-process particles for extinction volume injection + + +#include "common.hlsli" +#include "scene_view.h.hlsli" + + +cbuffer RootConstants +{ + uint3 fullResolution; + uint tileBufferIndex; + uint3 tileResolution; + uint particleBufferIndex; + uint3 tileScale; + uint particleCount; + float extinctionWorldScale; +} + +[numthreads(64, 1, 1)] +void cs(uint3 id : SV_DispatchThreadID) +{ + uint particleIndex = id.x; + if(particleIndex >= particleCount) + { + return; + } + + StructuredBuffer particleBuffer = ResourceDescriptorHeap[particleBufferIndex]; + RWByteAddressBuffer tileBuffer = ResourceDescriptorHeap[tileBufferIndex]; + SceneView scene = GetSceneView(); + + Particle particle = particleBuffer[particleIndex]; + float3 P = particle.position; + float r = particle.radius; + int3 boxMin = scene.ExtinctionWorldSpaceToIndex(P - float3(r, r, r), fullResolution, extinctionWorldScale); + int3 boxMax = scene.ExtinctionWorldSpaceToIndex(P + float3(r, r, r), fullResolution, extinctionWorldScale); + boxMin /= int3(tileScale); + boxMax /= int3(tileScale); + boxMin = max(boxMin, int3(0, 0, 0)); + boxMax = min(boxMax, int3(tileResolution) - int3(1, 1, 1)); + for(int x = boxMin.x; x <= boxMax.x; x++) + { + for(int y = boxMin.y; y <= boxMax.y; y++) + { + for(int z = boxMin.z; z <= boxMax.z; z++) + { + uint3 tileIndex = uint3(x, y, z); + uint index = FlattenIndex(tileIndex, tileResolution); + tileBuffer.Store(index * 4, 1); + } + } + } +} diff --git a/code/renderer/shaders/crp/vl_particles_preprocess_frustum.hlsl b/code/renderer/shaders/crp/vl_particles_preprocess_frustum.hlsl new file mode 100644 index 0000000..84158af --- /dev/null +++ b/code/renderer/shaders/crp/vl_particles_preprocess_frustum.hlsl @@ -0,0 +1,82 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: pre-process particles for frustum volume injection + + +#include "common.hlsli" +#include "scene_view.h.hlsli" + + +cbuffer RootConstants +{ + uint3 fullResolution; + uint tileBufferIndex; + uint3 tileResolution; + uint particleBufferIndex; + uint3 tileScale; + uint particleCount; +} + +[numthreads(64, 1, 1)] +void cs(uint3 id : SV_DispatchThreadID) +{ + uint particleIndex = id.x; + if(particleIndex >= particleCount) + { + return; + } + + StructuredBuffer particleBuffer = ResourceDescriptorHeap[particleBufferIndex]; + RWByteAddressBuffer tileBuffer = ResourceDescriptorHeap[tileBufferIndex]; + SceneView scene = GetSceneView(); + + Particle particle = particleBuffer[particleIndex]; + float3 P = particle.position; + float r = particle.radius * 1.0625; + float3 left = scene.cameraLeft; + float3 up = scene.cameraUp; + float3 fwd = scene.cameraForward; + int3 boxMin; + int3 boxMax; + ClearBoundingBox(boxMin, boxMax); + ExpandBoundingBox(boxMin, boxMax, scene.FroxelWorldSpaceToIndex(P + r * left, fullResolution)); + ExpandBoundingBox(boxMin, boxMax, scene.FroxelWorldSpaceToIndex(P - r * left, fullResolution)); + ExpandBoundingBox(boxMin, boxMax, scene.FroxelWorldSpaceToIndex(P + r * up, fullResolution)); + ExpandBoundingBox(boxMin, boxMax, scene.FroxelWorldSpaceToIndex(P - r * up, fullResolution)); + ExpandBoundingBox(boxMin, boxMax, scene.FroxelWorldSpaceToIndex(P + r * fwd, fullResolution)); + ExpandBoundingBox(boxMin, boxMax, scene.FroxelWorldSpaceToIndex(P - r * fwd, fullResolution)); + boxMin /= int3(tileScale); + boxMax /= int3(tileScale); + boxMin = max(boxMin, int3(0, 0, 0)); + boxMax = min(boxMax, int3(tileResolution) - int3(1, 1, 1)); + for(int x = boxMin.x; x <= boxMax.x; x++) + { + for(int y = boxMin.y; y <= boxMax.y; y++) + { + for(int z = boxMin.z; z <= boxMax.z; z++) + { + uint3 tileIndex = uint3(x, y, z); + uint index = FlattenIndex(tileIndex, tileResolution); + tileBuffer.Store(index * 4, 1); + } + } + } +} diff --git a/code/renderer/shaders/crp/vl_shadow_point_light.hlsl b/code/renderer/shaders/crp/vl_shadow_point_light.hlsl new file mode 100644 index 0000000..2e79051 --- /dev/null +++ b/code/renderer/shaders/crp/vl_shadow_point_light.hlsl @@ -0,0 +1,65 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: point light shadow volume + + +#include "common.hlsli" +#include "scene_view.h.hlsli" + + +cbuffer RootConstants +{ + float3 lightPosition; + float extinctionWorldScale; + float shadowWorldScale; + uint shadowTextureIndex; +} + +[numthreads(4, 4, 4)] +void cs(uint3 id : SV_DispatchThreadID) +{ + RWTexture3D shadowTexture = ResourceDescriptorHeap[shadowTextureIndex]; + uint3 shadowSize = GetTextureSize(shadowTexture); + if(any(id >= shadowSize)) + { + return; + } + + SceneView scene = GetSceneView(); + float3 voxelPosition = AABoxIndexToWorldSpace(id, lightPosition, shadowSize, shadowWorldScale); + float dist = distance(voxelPosition, lightPosition); + float stepDist = extinctionWorldScale; + uint stepCount = uint(dist / stepDist); + float3 step = normalize(lightPosition - voxelPosition) * stepDist; + ExtinctionCascade cascade = scene.GetExtinctionCascade(stepDist); + + float transmittance = 1.0; + float3 extinctionPositionWS = voxelPosition; + for(uint i = 0; i < stepCount; i++) + { + float extinction = cascade.ExtinctionAt(extinctionPositionWS); + float trans = Transmittance(stepDist, extinction); + transmittance *= saturate(trans); + extinctionPositionWS += step; + } + + shadowTexture[id] = transmittance; +} diff --git a/code/renderer/shaders/crp/vl_shadow_sun.hlsl b/code/renderer/shaders/crp/vl_shadow_sun.hlsl new file mode 100644 index 0000000..1feb587 --- /dev/null +++ b/code/renderer/shaders/crp/vl_shadow_sun.hlsl @@ -0,0 +1,75 @@ +/* +=========================================================================== +Copyright (C) 2024 Gian 'myT' Schellenbaum + +This file is part of Challenge Quake 3 (CNQ3). + +Challenge Quake 3 is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Challenge Quake 3 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Challenge Quake 3. If not, see . +=========================================================================== +*/ +// volumetric lighting: sunlight shadow volume + + +#include "common.hlsli" +#include "scene_view.h.hlsli" + + +cbuffer RootConstants +{ + uint shadowTextureIndex; + uint sourceTextureIndex; + float shadowWorldScale; + float sourceWorldScale; +} + +[numthreads(8, 8, 1)] +void cs(uint3 id : SV_DispatchThreadID) +{ + RWTexture3D shadowTexture = ResourceDescriptorHeap[shadowTextureIndex]; + uint3 shadowSize = GetTextureSize(shadowTexture); + if(any(id.xy >= shadowSize.xy)) + { + return; + } + + SceneView scene = GetSceneView(); + ExtinctionCascade cascade = scene.GetExtinctionCascade(shadowWorldScale); + + float3 cameraPosition = scene.cameraPosition; + float accumTrans = 1.0; + if(sourceTextureIndex != 0) + { + SamplerState linearClampSampler = SamplerDescriptorHeap[scene.linearClampSamplerIndex]; + Texture3D sourceTexture = ResourceDescriptorHeap[sourceTextureIndex]; + int3 index = int3(id.xy, -1); + float3 destPositionSS = AABoxIndexToWorldSpace(index, cameraPosition, shadowSize, shadowWorldScale); + float3 destPositionWS = cameraPosition + mul(scene.sunToZMatrix, destPositionSS - cameraPosition); + float3 sourcePositionSS = cameraPosition + mul(scene.zToSunMatrix, destPositionWS - cameraPosition); + float3 sourceSize = float3(GetTextureSize(sourceTexture)); + float3 tc = AABoxWorldSpaceToTC(sourcePositionSS, cameraPosition, sourceSize, sourceWorldScale); + float transmittance = sourceTexture.SampleLevel(linearClampSampler, tc, 0); + accumTrans = transmittance; + } + + for(uint d = 0; d < shadowSize.z; d++) + { + uint3 index = uint3(id.xy, d); + float3 voxelPositionSS = AABoxIndexToWorldSpace(index, cameraPosition, shadowSize, shadowWorldScale); + float3 voxelPositionWS = cameraPosition + mul(scene.sunToZMatrix, voxelPositionSS - cameraPosition); + float extinction = max(cascade.ExtinctionAt(voxelPositionWS), 0.0); + float transmittance = saturate(Transmittance(shadowWorldScale, extinction)); + accumTrans *= transmittance; + shadowTexture[index] = accumTrans; + } +} diff --git a/code/renderer/tr_cmds.cpp b/code/renderer/tr_cmds.cpp index f4b49e4..0467be9 100644 --- a/code/renderer/tr_cmds.cpp +++ b/code/renderer/tr_cmds.cpp @@ -239,6 +239,16 @@ void R_AddDrawSurfCmd( drawSurf_t* drawSurfs, int numDrawSurfs, int numTranspSur memcpy(tr.prevProjMatrix, tr.currProjMatrix, sizeof(tr.prevProjMatrix)); memcpy(tr.currViewMatrix, tr.viewParms.world.modelMatrix, sizeof(tr.currViewMatrix)); memcpy(tr.currProjMatrix, tr.viewParms.projectionMatrix, sizeof(tr.currProjMatrix)); + + // camera positions + VectorCopy(tr.currCameraPosition, tr.prevCameraPosition); + VectorCopy(tr.viewParms.world.viewOrigin, tr.currCameraPosition); + + // clip plane distances + tr.prevZNear = tr.currZNear; + tr.prevZFar = tr.currZFar; + tr.currZNear = tr.viewParms.zNear; + tr.currZFar = tr.viewParms.zFar; } } diff --git a/code/renderer/tr_local.h b/code/renderer/tr_local.h index 0812410..eaaea71 100644 --- a/code/renderer/tr_local.h +++ b/code/renderer/tr_local.h @@ -68,7 +68,7 @@ struct trRefEntity_t { struct orientationr_t { vec3_t origin; // in world coordinates - vec3_t axis[3]; // orientation in world + vec3_t axis[3]; // orientation in world, order: forward, left, up vec3_t viewOrigin; // viewParms->or.origin in local coordinates float modelMatrix[16]; }; @@ -1024,13 +1024,19 @@ typedef struct { trRefdef_t rtRefdef; // from current and last frame's non-portal full-screen scene view - // needed for motion vectors + // needed for motion vectors and temporal reprojection in general float currViewProjMatrix[16]; float prevViewProjMatrix[16]; float currViewMatrix[16]; float prevViewMatrix[16]; float currProjMatrix[16]; float prevProjMatrix[16]; + vec3_t currCameraPosition; + vec3_t prevCameraPosition; + float currZNear; + float currZFar; + float prevZNear; + float prevZFar; } trGlobals_t; @@ -1663,10 +1669,9 @@ void R_CameraAxisVectorsFromMatrix( const matrix4x4_t modelView, vec3_t axisX, v void R_MakeIdentityMatrix( matrix4x4_t m ); void R_MakeOrthoProjectionMatrix( matrix4x4_t m, float w, float h ); -// LinearDepth(depthZW, A, B) -> B / (depthZW - A) -void R_LinearDepthConstantsFromProjectionMatrix( const float* projMatrix, float* A, float* B ); -void R_LinearDepthConstantsFromClipPlanes( float zNear, float zFar, float* A, float* B ); -void RB_LinearDepthConstants( float* A, float* B ); +// LinearDepth(depthZW, A, B, C) -> A / (B + depthZW * C) +void R_LinearDepthConstantsFromClipPlanes( float zNear, float zFar, vec3_t constants ); +void RB_LinearDepthConstants( vec3_t constants ); /////////////////////////////////////////////////////////////// diff --git a/code/renderer/tr_main.cpp b/code/renderer/tr_main.cpp index 0416cc3..3b0a215 100644 --- a/code/renderer/tr_main.cpp +++ b/code/renderer/tr_main.cpp @@ -483,23 +483,17 @@ void R_MakeOrthoProjectionMatrix( matrix4x4_t m, float w, float h ) } -void R_LinearDepthConstantsFromProjectionMatrix( const float* projMatrix, float* A, float* B ) +void R_LinearDepthConstantsFromClipPlanes( float n, float f, vec3_t constants ) { - *A = -projMatrix[2 * 4 + 2]; - *B = projMatrix[3 * 4 + 2]; + constants[0] = f * n; + constants[1] = n; + constants[2] = f - n; } -void R_LinearDepthConstantsFromClipPlanes( float n, float f, float* A, float* B ) +void RB_LinearDepthConstants( vec3_t constants ) { - *A = -n / (f - n); - *B = f * (n / (f - n)); -} - - -void RB_LinearDepthConstants( float* A, float* B ) -{ - R_LinearDepthConstantsFromProjectionMatrix( backEnd.viewParms.projectionMatrix, A, B ); + R_LinearDepthConstantsFromClipPlanes( backEnd.viewParms.zNear, backEnd.viewParms.zFar, constants ); } diff --git a/code/shadercomp/shadercomp.cpp b/code/shadercomp/shadercomp.cpp index 1342da2..a79e965 100644 --- a/code/shadercomp/shadercomp.cpp +++ b/code/shadercomp/shadercomp.cpp @@ -241,20 +241,31 @@ void CompileVertexShader(const char* headerPath, const char* shaderPath, const c CompileShader(args, _countof(extras), extras); } -void CompilePixelShader(const char* headerPath, const char* shaderPath, const char* varName) +void CompilePixelShader(const char* headerPath, const char* shaderPath, const char* varName, int psOptionCount = 0, ...) { - const char* extras[] = + int psExtraCount = 4; + const char* psExtras[64] = { "-D", "PIXEL_SHADER=1", "-Vn", HeaderVariable(va("g_%s_ps", varName)) }; + assert(psExtraCount + psOptionCount <= _countof(psExtras)); + + va_list argPtr; + va_start(argPtr, psOptionCount); + for(int i = 0; i < psOptionCount; i++) + { + psExtras[psExtraCount++] = va_arg(argPtr, const char*); + } + va_end(argPtr); + ShaderArgs args; args.entryPoint = "ps"; args.headerPath = headerPath; args.shaderPath = shaderPath; args.targetProfile = targetPS; - CompileShader(args, _countof(extras), extras); + CompileShader(args, psExtraCount, psExtras); } void CompileCompute(const char* headerPath, const char* shaderPath, const char* varName) @@ -412,6 +423,7 @@ void ProcessCRP() CompileGraphics("opaque.h", "opaque.hlsl", "opaque"); CompileGraphics("transp_draw.h", "transp_draw.hlsl", "transp_draw"); CompilePixelShader("transp_resolve.h", "transp_resolve.hlsl", "transp_resolve"); + CompilePixelShader("transp_resolve_vol.h", "transp_resolve.hlsl", "transp_resolve_vol", 1, "-D VOLUMETRIC_LIGHT=1"); CompilePixelShader("tone_map.h", "tone_map.hlsl", "tone_map"); CompilePixelShader("tone_map_inverse.h", "tone_map_inverse.hlsl", "tone_map_inverse"); CompilePixelShader("accumdof_accum.h", "accumdof_accum.hlsl", "accum"); @@ -424,8 +436,6 @@ void ProcessCRP() CompileCompute("gatherdof_fill.h", "gatherdof_fill.hlsl", "fill"); CompilePixelShader("gatherdof_combine.h", "gatherdof_combine.hlsl", "combine"); CompilePixelShader("gatherdof_debug.h", "gatherdof_debug.hlsl", "debug"); - CompileGraphics("fog_inside.h", "fog_inside.hlsl", "inside"); - CompileGraphics("fog_outside.h", "fog_outside.hlsl", "outside"); CompilePixelShader("magnifier.h", "magnifier.hlsl", "magnifier"); CompilePixelShader("dl_draw.h", "dl_draw.hlsl", "dl_draw"); CompilePixelShader("dl_denoising.h", "dl_denoising.hlsl", "dl_denoising"); @@ -440,6 +450,38 @@ void ProcessCRP() CompileCompute("mblur_tile_max.h", "mblur_tile_max.hlsl", "tile_max"); CompilePixelShader("mblur_blur.h", "mblur_blur.hlsl", "blur"); CompilePixelShader("mblur_pack.h", "mblur_pack.hlsl", "pack"); + CompilePixelShader("sun_overlay.h", "sun_overlay.hlsl", "sun_overlay"); + CompilePixelShader("sun_visibility.h", "sun_visibility.hlsl", "sun_visibility"); + CompilePixelShader("sun_blur.h", "sun_blur.hlsl", "sun_blur"); + const char* vlComputeShaders[] = + { +#if 0 + "vl_particles_dispatch", + "vl_particles_preprocess_extinction", + "vl_particles_preprocess_frustum", + "vl_extinction_injection_particles", + "vl_frustum_injection_particles", +#endif + "vl_extinction_injection_fog", + "vl_frustum_anisotropy_average", + "vl_frustum_injection_fog", + "vl_frustum_inscatter_ambient", + "vl_frustum_inscatter_point_light", + "vl_frustum_inscatter_sunlight", + "vl_frustum_raymarch", + "vl_frustum_sunlight_visibility", + "vl_frustum_temporal", + "vl_shadow_point_light", + "vl_shadow_sun" + }; + for(int i = 0; i < _countof(vlComputeShaders); i++) + { + const char* const s = vlComputeShaders[i]; + CompileCompute(va("%s.h", s), va("%s.hlsl", s), s); + } + CompileGraphics("vl_debug_ambient.h", "vl_debug_ambient.hlsl", "vl_debug_ambient"); + CompileGraphics("vl_debug_extinction.h", "vl_debug_extinction.hlsl", "vl_debug_extinction"); + CompileGraphics("vl_debug_shadow_sun.h", "vl_debug_shadow_sun.hlsl", "vl_debug_shadow_sun"); CompileCompute("depth_pyramid.h", "depth_pyramid.hlsl", "depth_pyramid"); } diff --git a/makefiles/windows_vs2019/renderer.vcxproj b/makefiles/windows_vs2019/renderer.vcxproj index ab6e8a7..75ee910 100644 --- a/makefiles/windows_vs2019/renderer.vcxproj +++ b/makefiles/windows_vs2019/renderer.vcxproj @@ -130,7 +130,6 @@ - @@ -139,9 +138,12 @@ + + + @@ -202,12 +204,6 @@ true - - true - - - true - true @@ -283,6 +279,15 @@ true + + true + + + true + + + true + true @@ -298,6 +303,63 @@ true + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + true @@ -357,13 +419,14 @@ - + + diff --git a/makefiles/windows_vs2019/renderer.vcxproj.filters b/makefiles/windows_vs2019/renderer.vcxproj.filters index 3216d17..0491571 100644 --- a/makefiles/windows_vs2019/renderer.vcxproj.filters +++ b/makefiles/windows_vs2019/renderer.vcxproj.filters @@ -34,7 +34,6 @@ - @@ -43,9 +42,12 @@ + + + @@ -106,12 +108,6 @@ shaders\crp - - shaders\crp - - - shaders\crp - shaders\crp @@ -187,6 +183,15 @@ shaders\crp + + shaders\crp + + + shaders\crp + + + shaders\crp + shaders\crp @@ -202,6 +207,63 @@ shaders\crp + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + shaders\crp @@ -273,9 +335,6 @@ shaders\crp - - shaders\crp - shaders\crp @@ -291,9 +350,15 @@ shaders\crp + + shaders\crp + shaders\crp + + shaders\crp + shaders\grp diff --git a/makefiles/windows_vs2022/renderer.vcxproj b/makefiles/windows_vs2022/renderer.vcxproj index 854ba45..40b4f8f 100644 --- a/makefiles/windows_vs2022/renderer.vcxproj +++ b/makefiles/windows_vs2022/renderer.vcxproj @@ -132,7 +132,6 @@ - @@ -141,9 +140,12 @@ + + + @@ -204,12 +206,6 @@ true - - true - - - true - true @@ -285,6 +281,15 @@ true + + true + + + true + + + true + true @@ -300,6 +305,63 @@ true + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + true @@ -359,13 +421,14 @@ - + + diff --git a/makefiles/windows_vs2022/renderer.vcxproj.filters b/makefiles/windows_vs2022/renderer.vcxproj.filters index 3216d17..0491571 100644 --- a/makefiles/windows_vs2022/renderer.vcxproj.filters +++ b/makefiles/windows_vs2022/renderer.vcxproj.filters @@ -34,7 +34,6 @@ - @@ -43,9 +42,12 @@ + + + @@ -106,12 +108,6 @@ shaders\crp - - shaders\crp - - - shaders\crp - shaders\crp @@ -187,6 +183,15 @@ shaders\crp + + shaders\crp + + + shaders\crp + + + shaders\crp + shaders\crp @@ -202,6 +207,63 @@ shaders\crp + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + + + shaders\crp + shaders\crp @@ -273,9 +335,6 @@ shaders\crp - - shaders\crp - shaders\crp @@ -291,9 +350,15 @@ shaders\crp + + shaders\crp + shaders\crp + + shaders\crp + shaders\grp