added sunlight and volumetric lighting

fixed depth linearization
This commit is contained in:
myT 2024-03-29 04:19:27 +01:00
parent a01c88707d
commit 30150e889e
63 changed files with 5867 additions and 765 deletions

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -22,6 +22,7 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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 <https://www.gnu.org/licenses/>.
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);

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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);
}
}

View file

@ -32,14 +32,6 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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);

View file

@ -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;

View file

@ -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();

View file

@ -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();

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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();
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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;
}

View file

@ -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);

View file

@ -24,24 +24,24 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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);

File diff suppressed because it is too large Load diff

View file

@ -48,6 +48,11 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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

View file

@ -24,6 +24,7 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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<float> 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)

View file

@ -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;

View file

@ -30,6 +30,7 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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

View file

@ -33,6 +33,24 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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<typename T>
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<typename T>
@ -323,6 +368,24 @@ uint2 GetTextureSize(RWTexture2D<T> texture0)
return size;
}
template<typename T>
uint3 GetTextureSize(Texture3D<T> texture0)
{
uint3 size;
texture0.GetDimensions(size.x, size.y, size.z);
return size;
}
template<typename T>
uint3 GetTextureSize(RWTexture3D<T> 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<typename T>
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<typename T>
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;
}

View file

@ -28,7 +28,11 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
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<float> 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;
}

View file

@ -25,73 +25,14 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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<TLASInstance> 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<BLASMesh> meshBuffer = ResourceDescriptorHeap[instance.meshBufferIndex];
BLASMesh mesh = meshBuffer[meshId];
float3 barycentrics = float3(1.0 - bary2.x - bary2.y, bary2.x, bary2.y);
StructuredBuffer<BLASVertex> vertexBuffer = ResourceDescriptorHeap[instance.vertexBufferIndex];
StructuredBuffer<uint> 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<TLASInstance> 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<RAY_FLAG_NONE> 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<TLASInstance> 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<RAY_FLAG_CULL_NON_OPAQUE> 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<float2> 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<float2> normalTexture = ResourceDescriptorHeap[scene.normalTextureIndex];
StructuredBuffer<TLASInstance> 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;
}

View file

@ -25,6 +25,7 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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<float> 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;

View file

@ -23,6 +23,7 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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<float> depthTexture = ResourceDescriptorHeap[depthTextureIndex];
RWTexture2D<float4> nearColorTexture = ResourceDescriptorHeap[nearColorTextureIndex];
RWTexture2D<float4> 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);

View file

@ -23,23 +23,17 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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<float> depthTexture = ResourceDescriptorHeap[depthTextureIndex];
SceneView scene = GetSceneView();
Texture2D<float> 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;

View file

@ -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;
}

View file

@ -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)

View file

@ -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<TLASInstance> tlasInstanceBuffer, uint instanceId, uint meshId, uint triangleId, float2 bary2, bool frontFace)
{
TLASInstance instance = tlasInstanceBuffer[instanceId];
StructuredBuffer<BLASMesh> 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<BLASVertex> vertexBuffer = ResourceDescriptorHeap[instance.vertexBufferIndex];
StructuredBuffer<uint> 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<TLASInstance> 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<RAY_FLAG_NONE> 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<TLASInstance> 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<RAY_FLAG_CULL_NON_OPAQUE> 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

View file

@ -25,10 +25,29 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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<float> 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<float> 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
};

View file

@ -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;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float2> normalTexture = ResourceDescriptorHeap[scene.normalTextureIndex];
Texture2D<float> visibilityTexture = ResourceDescriptorHeap[visibilityTextureIndex];
Texture2D<float> 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;
}

View file

@ -18,22 +18,44 @@ You should have received a copy of the GNU General Public License
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float> 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);
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<TLASInstance> 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;
}

View file

@ -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<OIT_Counter> 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

View file

@ -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 <https://www.gnu.org/licenses/>.
#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<uint> index = ResourceDescriptorHeap[indexTexture];
RWStructuredBuffer<OIT_Fragment> 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<uint> index = ResourceDescriptorHeap[indexTexture];
RWStructuredBuffer<OIT_Fragment> 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<float4> 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;
}

View file

@ -25,11 +25,18 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#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;

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float> 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<float> 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;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float> 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<float> 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;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float> 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;
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<uint3> tileBuffer = ResourceDescriptorHeap[tileBufferIndex];
RWTexture3D<float> 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<Particle> 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;
}
}

View file

@ -18,26 +18,32 @@ You should have received a copy of the GNU General Public License
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float> 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<float4> 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<float> materialTextureC = ResourceDescriptorHeap[materialTextureCIndex];
float weightSum = materialTextureC[id];
if(weightSum > 0.0)
{
materialTextureB[id].a /= weightSum;
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float4> materialTextureA = ResourceDescriptorHeap[materialTextureAIndex];
uint3 textureSize = GetTextureSize(materialTextureA);
if(any(id >= textureSize))
{
return;
}
RWTexture3D<float4> materialTextureB = ResourceDescriptorHeap[materialTextureBIndex];
RWTexture3D<float> 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;
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<uint3> tileBuffer = ResourceDescriptorHeap[tileBufferIndex];
RWTexture3D<float4> 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<float4> materialTextureB = ResourceDescriptorHeap[materialTextureBIndex];
RWTexture3D<float> materialTextureC = ResourceDescriptorHeap[materialTextureCIndex];
StructuredBuffer<Particle> 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;
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float4> scatterExtTexture = ResourceDescriptorHeap[scatterExtTextureIndex];
uint3 textureSize = GetTextureSize(scatterExtTexture);
if(any(id >= textureSize))
{
return;
}
if(isLightGridAvailable != 0)
{
SceneView scene = GetSceneView();
RWTexture3D<float4> 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<float4> 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);
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float4> scatterExtTexture = ResourceDescriptorHeap[scatterExtTextureIndex];
uint3 textureSize = GetTextureSize(scatterExtTexture);
if(any(id >= textureSize))
{
return;
}
RWTexture3D<float4> materialTextureA = ResourceDescriptorHeap[materialTextureAIndex];
RWTexture3D<float4> materialTextureB = ResourceDescriptorHeap[materialTextureBIndex];
Texture3D<float> 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;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float4> scatterExtTexture = ResourceDescriptorHeap[scatterExtTextureIndex];
uint3 textureSize = GetTextureSize(scatterExtTexture);
if(any(id >= textureSize))
{
return;
}
RWTexture3D<float4> materialTextureA = ResourceDescriptorHeap[materialTextureAIndex];
RWTexture3D<float4> materialTextureB = ResourceDescriptorHeap[materialTextureBIndex];
RWTexture3D<float> 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;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float4> 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<float4> resolveTexture = ResourceDescriptorHeap[resolveTextureIndex];
RWTexture3D<float4> 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;
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float> visTexture = ResourceDescriptorHeap[visTextureIndex];
uint3 textureSize = GetTextureSize(visTexture);
if(any(id >= textureSize))
{
return;
}
SceneView scene = GetSceneView();
RTAS rtas = ResourceDescriptorHeap[scene.tlasBufferIndex];
Texture2D<float2> 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;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float> currTexture = ResourceDescriptorHeap[currTextureIndex];
uint3 textureSize = GetTextureSize(currTexture);
if(any(id >= textureSize))
{
return;
}
SceneView scene = GetSceneView();
Texture3D<float> 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;
}
}
}

View file

@ -18,36 +18,38 @@ You should have received a copy of the GNU General Public License
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<uint3> 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;
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<Particle> 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);
}
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<Particle> 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);
}
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float> 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;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// 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<float> 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<float> 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;
}
}

View file

@ -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;
}
}

View file

@ -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 );
///////////////////////////////////////////////////////////////

View file

@ -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 );
}

View file

@ -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");
}

View file

@ -130,7 +130,6 @@
<ClCompile Include="..\..\code\renderer\crp_dof_accum.cpp" />
<ClCompile Include="..\..\code\renderer\crp_dof_gather.cpp" />
<ClCompile Include="..\..\code\renderer\crp_dynamic_lights.cpp" />
<ClCompile Include="..\..\code\renderer\crp_fog.cpp" />
<ClCompile Include="..\..\code\renderer\crp_gbuffer_viz.cpp" />
<ClCompile Include="..\..\code\renderer\crp_geometry.cpp" />
<ClCompile Include="..\..\code\renderer\crp_magnifier.cpp" />
@ -139,9 +138,12 @@
<ClCompile Include="..\..\code\renderer\crp_opaque.cpp" />
<ClCompile Include="..\..\code\renderer\crp_prepass.cpp" />
<ClCompile Include="..\..\code\renderer\crp_raytracing.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sun_editor.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sunlight.cpp" />
<ClCompile Include="..\..\code\renderer\crp_tone_map.cpp" />
<ClCompile Include="..\..\code\renderer\crp_transp_draw.cpp" />
<ClCompile Include="..\..\code\renderer\crp_transp_resolve.cpp" />
<ClCompile Include="..\..\code\renderer\crp_volumetric_light.cpp" />
<ClCompile Include="..\..\code\renderer\grp_geometry.cpp" />
<ClCompile Include="..\..\code\renderer\grp_main.cpp" />
<ClCompile Include="..\..\code\renderer\grp_post.cpp" />
@ -202,12 +204,6 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\dl_draw.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fog_inside.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fog_outside.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fullscreen.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -283,6 +279,15 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\skybox_motion.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_blur.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_overlay.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_visibility.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\tone_map.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -298,6 +303,63 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\ui.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_ambient.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_extinction.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_shadow_sun.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_fog.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_particles.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_anisotropy_average.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_fog.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_particles.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_ambient.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_point_light.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_sunlight.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_raymarch.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_sunlight_visibility.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_temporal.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_dispatch.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_extinction.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_frustum.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_point_light.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_sun.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\wireframe_normals.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -357,13 +419,14 @@
<None Include="..\..\code\renderer\shaders\crp\alpha_test.h.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\common.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\dof.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\fog.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\fullscreen.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\gatherdof.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\oit.h.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\raytracing.h.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\scene_view.h.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\simplex_noise.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\typedefs.h.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\vl_common.h.hlsli" />
<None Include="..\..\code\renderer\shaders\grp\fog.hlsli" />
<None Include="..\..\code\renderer\shaders\grp\shared.hlsli" />
<None Include="..\..\code\renderer\shaders\grp\smaa.hlsli" />

View file

@ -34,7 +34,6 @@
<ClCompile Include="..\..\code\renderer\crp_dof_accum.cpp" />
<ClCompile Include="..\..\code\renderer\crp_dof_gather.cpp" />
<ClCompile Include="..\..\code\renderer\crp_dynamic_lights.cpp" />
<ClCompile Include="..\..\code\renderer\crp_fog.cpp" />
<ClCompile Include="..\..\code\renderer\crp_gbuffer_viz.cpp" />
<ClCompile Include="..\..\code\renderer\crp_geometry.cpp" />
<ClCompile Include="..\..\code\renderer\crp_magnifier.cpp" />
@ -43,9 +42,12 @@
<ClCompile Include="..\..\code\renderer\crp_opaque.cpp" />
<ClCompile Include="..\..\code\renderer\crp_prepass.cpp" />
<ClCompile Include="..\..\code\renderer\crp_raytracing.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sun_editor.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sunlight.cpp" />
<ClCompile Include="..\..\code\renderer\crp_tone_map.cpp" />
<ClCompile Include="..\..\code\renderer\crp_transp_draw.cpp" />
<ClCompile Include="..\..\code\renderer\crp_transp_resolve.cpp" />
<ClCompile Include="..\..\code\renderer\crp_volumetric_light.cpp" />
<ClCompile Include="..\..\code\renderer\grp_geometry.cpp" />
<ClCompile Include="..\..\code\renderer\grp_main.cpp" />
<ClCompile Include="..\..\code\renderer\grp_post.cpp" />
@ -106,12 +108,6 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\dl_draw.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fog_inside.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fog_outside.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fullscreen.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -187,6 +183,15 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\skybox_motion.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_blur.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_overlay.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_visibility.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\tone_map.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -202,6 +207,63 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\ui.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_ambient.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_extinction.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_shadow_sun.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_fog.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_particles.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_anisotropy_average.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_fog.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_particles.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_ambient.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_point_light.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_sunlight.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_raymarch.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_sunlight_visibility.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_temporal.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_dispatch.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_extinction.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_frustum.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_point_light.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_sun.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\wireframe_normals.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -273,9 +335,6 @@
<None Include="..\..\code\renderer\shaders\crp\dof.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\fog.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\fullscreen.hlsli">
<Filter>shaders\crp</Filter>
</None>
@ -291,9 +350,15 @@
<None Include="..\..\code\renderer\shaders\crp\scene_view.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\simplex_noise.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\typedefs.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\vl_common.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\grp\fog.hlsli">
<Filter>shaders\grp</Filter>
</None>

View file

@ -132,7 +132,6 @@
<ClCompile Include="..\..\code\renderer\crp_dof_accum.cpp" />
<ClCompile Include="..\..\code\renderer\crp_dof_gather.cpp" />
<ClCompile Include="..\..\code\renderer\crp_dynamic_lights.cpp" />
<ClCompile Include="..\..\code\renderer\crp_fog.cpp" />
<ClCompile Include="..\..\code\renderer\crp_gbuffer_viz.cpp" />
<ClCompile Include="..\..\code\renderer\crp_geometry.cpp" />
<ClCompile Include="..\..\code\renderer\crp_magnifier.cpp" />
@ -141,9 +140,12 @@
<ClCompile Include="..\..\code\renderer\crp_opaque.cpp" />
<ClCompile Include="..\..\code\renderer\crp_prepass.cpp" />
<ClCompile Include="..\..\code\renderer\crp_raytracing.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sun_editor.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sunlight.cpp" />
<ClCompile Include="..\..\code\renderer\crp_tone_map.cpp" />
<ClCompile Include="..\..\code\renderer\crp_transp_draw.cpp" />
<ClCompile Include="..\..\code\renderer\crp_transp_resolve.cpp" />
<ClCompile Include="..\..\code\renderer\crp_volumetric_light.cpp" />
<ClCompile Include="..\..\code\renderer\grp_geometry.cpp" />
<ClCompile Include="..\..\code\renderer\grp_main.cpp" />
<ClCompile Include="..\..\code\renderer\grp_post.cpp" />
@ -204,12 +206,6 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\dl_draw.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fog_inside.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fog_outside.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fullscreen.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -285,6 +281,15 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\skybox_motion.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_blur.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_overlay.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_visibility.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\tone_map.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -300,6 +305,63 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\ui.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_ambient.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_extinction.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_shadow_sun.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_fog.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_particles.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_anisotropy_average.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_fog.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_particles.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_ambient.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_point_light.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_sunlight.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_raymarch.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_sunlight_visibility.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_temporal.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_dispatch.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_extinction.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_frustum.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_point_light.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_sun.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\wireframe_normals.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -359,13 +421,14 @@
<None Include="..\..\code\renderer\shaders\crp\alpha_test.h.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\common.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\dof.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\fog.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\fullscreen.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\gatherdof.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\oit.h.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\raytracing.h.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\scene_view.h.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\simplex_noise.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\typedefs.h.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\vl_common.h.hlsli" />
<None Include="..\..\code\renderer\shaders\grp\fog.hlsli" />
<None Include="..\..\code\renderer\shaders\grp\shared.hlsli" />
<None Include="..\..\code\renderer\shaders\grp\smaa.hlsli" />

View file

@ -34,7 +34,6 @@
<ClCompile Include="..\..\code\renderer\crp_dof_accum.cpp" />
<ClCompile Include="..\..\code\renderer\crp_dof_gather.cpp" />
<ClCompile Include="..\..\code\renderer\crp_dynamic_lights.cpp" />
<ClCompile Include="..\..\code\renderer\crp_fog.cpp" />
<ClCompile Include="..\..\code\renderer\crp_gbuffer_viz.cpp" />
<ClCompile Include="..\..\code\renderer\crp_geometry.cpp" />
<ClCompile Include="..\..\code\renderer\crp_magnifier.cpp" />
@ -43,9 +42,12 @@
<ClCompile Include="..\..\code\renderer\crp_opaque.cpp" />
<ClCompile Include="..\..\code\renderer\crp_prepass.cpp" />
<ClCompile Include="..\..\code\renderer\crp_raytracing.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sun_editor.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sunlight.cpp" />
<ClCompile Include="..\..\code\renderer\crp_tone_map.cpp" />
<ClCompile Include="..\..\code\renderer\crp_transp_draw.cpp" />
<ClCompile Include="..\..\code\renderer\crp_transp_resolve.cpp" />
<ClCompile Include="..\..\code\renderer\crp_volumetric_light.cpp" />
<ClCompile Include="..\..\code\renderer\grp_geometry.cpp" />
<ClCompile Include="..\..\code\renderer\grp_main.cpp" />
<ClCompile Include="..\..\code\renderer\grp_post.cpp" />
@ -106,12 +108,6 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\dl_draw.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fog_inside.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fog_outside.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\fullscreen.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -187,6 +183,15 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\skybox_motion.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_blur.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_overlay.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\sun_visibility.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\tone_map.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -202,6 +207,63 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\ui.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_ambient.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_extinction.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_debug_shadow_sun.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_fog.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_particles.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_anisotropy_average.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_fog.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_particles.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_ambient.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_point_light.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_sunlight.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_raymarch.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_sunlight_visibility.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_temporal.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_dispatch.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_extinction.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_frustum.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_point_light.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_sun.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\wireframe_normals.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -273,9 +335,6 @@
<None Include="..\..\code\renderer\shaders\crp\dof.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\fog.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\fullscreen.hlsli">
<Filter>shaders\crp</Filter>
</None>
@ -291,9 +350,15 @@
<None Include="..\..\code\renderer\shaders\crp\scene_view.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\simplex_noise.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\typedefs.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\vl_common.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\grp\fog.hlsli">
<Filter>shaders\grp</Filter>
</None>