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