added NanoVDB support

- added the foundation for a GPU particle system
- reworked volumetric particle injection
This commit is contained in:
myT 2024-07-02 02:06:15 +02:00
parent 385a75c9cd
commit afc81437c3
50 changed files with 7938 additions and 742 deletions

View file

@ -146,7 +146,7 @@ chg: reworked renderer with 2 new rendering pipelines
- order-independent transparency
- depth of field (scatter-as-gather or accumulation)
- shadowed point lights and sunlight
- volumetric lighting
- volumetric lighting: fog and NanoVDB animations lit by ambient light, point lights and sunlight
- all corresponding CVars have the "crp_" prefix
chg: removed cl_drawMouseLag, r_backend, r_frameSleep, r_gpuMipGen, r_alphaToCoverage, r_alphaToCoverageMipBoost

View file

@ -30,6 +30,7 @@ const vec3_t vec2_one = { 1, 1 };
const vec3_t vec3_origin = { 0, 0, 0 };
const vec3_t vec3_zero = { 0, 0, 0 };
const vec3_t vec3_one = { 1, 1, 1 };
const vec3_t vec3_axis[3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
const vec4_t vec4_zero = { 0, 0, 0, 0 };
const vec4_t vec4_one = { 1, 1, 1, 1 };

View file

@ -65,8 +65,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#if defined(__cplusplus) && !defined(min)
template <typename T> __inline T min( T a, T b ) { return (a < b) ? a : b; }
template <typename T> __inline T max( T a, T b ) { return (a > b) ? a : b; }
template <typename T> T min( T a, T b ) { return (a < b) ? a : b; }
template <typename T> T max( T a, T b ) { return (a > b) ? a : b; }
template <typename T> T min3(T a, T b, T c) { return min(a, min(b, c)); }
template <typename T> T max3(T a, T b, T c) { return max(a, max(b, c)); }
#elif defined(Q3_VM) // #elif !defined(min) doesn't work here, because the VC headers are shit
#define min( a, b ) ((a) < (b) ? (a) : (b))
#define max( a, b ) ((a) > (b) ? (a) : (b))
@ -279,6 +281,7 @@ extern const vec3_t vec2_one;
extern const vec3_t vec3_origin;
extern const vec3_t vec3_zero;
extern const vec3_t vec3_one;
extern const vec3_t vec3_axis[3];
extern const vec4_t vec4_zero;
extern const vec4_t vec4_one;
@ -393,6 +396,7 @@ void ByteToDir( int b, vec3_t dir );
#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2])
#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2])
#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2])
#define VectorMultiply(a,b,c) ((c)[0]=(a)[0]*(b)[0],(c)[1]=(a)[1]*(b)[1],(c)[2]=(a)[2]*(b)[2])
#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2])
#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s))
#define VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s))

View file

@ -438,6 +438,142 @@ struct Sunlight
HTexture penumbraTexture;
};
struct VDBSequenceDesc
{
const char* folderPath = NULL;
const char* smokeGridName = "density";
const char* fireGridName = "flames";
vec3_t originOffset = {}; // index space
vec3_t position = { 700 }; // world space
vec3_t anglesRad = {}; // in radians
vec3_t scale = { 1.0f, 1.0f, 1.0f };
float smokeExtinctionScale = 1.0f;
float smokeAlbedo = 0.9f; // real smoke: 0.9 to 0.97
float fireEmissionScale = 0.1f;
float fireTemperatureScale = 1000.0f;
float frameRate = 60.0f;
int startTimeMS = 0;
int startTimeUS = 0;
bool loop = false;
bool useSequenceOffset = true;
bool gpuResident = false;
};
struct NanoVDBManager
{
struct Instance;
struct DrawInstance;
struct CPUFrame;
void Init();
void DrawGUI();
void BeforeFrame();
bool AddSequence(const VDBSequenceDesc& desc);
void MakeWorldToIndexMatrix(matrix3x3_t matrix, const Instance& instance);
void Purge();
int FindStreamedFrameIndex(uint32_t sequenceIndex, uint32_t frameIndex);
struct Sequence
{
char folderPath[64];
vec3_t originOffset;
vec3_t scale;
HBuffer buffer;
uint32_t bufferByteCount;
uint32_t frameCount;
uint32_t firstFrameIndex;
};
struct Instance
{
char smokeGridName[64];
char fireGridName[64];
vec3_t originOffset; // index space
vec3_t position; // world space
vec3_t anglesRad; // in radians
vec3_t scale;
float smokeExtinctionScale;
float smokeAlbedo;
float fireEmissionScale;
float fireTemperatureScale;
float frameRate;
int startTimeMS;
int startTimeUS;
uint32_t sequenceIndex;
bool loop;
};
struct DrawInstance
{
HBuffer buffer;
uint32_t smokeByteOffset;
uint32_t fireByteOffset;
uint32_t smokeByteOffset2;
uint32_t fireByteOffset2;
float t;
};
struct GPUFrame
{
uint32_t smokeByteOffset;
uint32_t fireByteOffset;
};
struct CPUFrame
{
char filePath[64];
uint32_t smokeByteOffset;
uint32_t smokeByteCount;
uint32_t fireByteOffset;
uint32_t fireByteCount;
};
struct StreamedFrame
{
uint32_t sequenceIndex;
uint32_t frameIndex;
uint32_t smokeByteOffset;
uint32_t flamesByteOffset;
};
StaticArray<Sequence, 16> sequences;
StaticArray<Instance, 64> instances;
StaticArray<DrawInstance, 64> drawInstances; // for the current frame
StaticArray<StreamedFrame, 128> streamedFrames; // for the current frame
StaticArray<GPUFrame, 4096> gpuFrames;
StaticArray<CPUFrame, 4096> cpuFrames;
HBuffer streamBuffers[FrameCount + 1];
uint32_t streamBufferByteCount;
uint32_t streamBufferIndex;
bool windowActive = false;
bool linearInterpolation = false;
bool accurateOverlapTest = false;
bool supersampling = false;
int ambientRaymarchLOD = 8;
bool ambientIncreasedCoverage = true;
bool previewMode = false;
float emissiveScatterScale = 0.5f;
};
struct ParticleSystem
{
void Init();
void Draw();
//private:
HPipeline clearPipeline;
HPipeline setupPipeline;
HPipeline emitPipeline;
HPipeline simulatePipeline;
HBuffer particleBuffer;
HBuffer liveBuffers[2]; // indices before and after simulation
HBuffer deadBuffer; // indices
HBuffer emitterBuffer;
HBuffer indirectBuffer; // 0: emit dispatch, 1: simulate dispatch
uint32_t liveBufferReadIndex;
bool needsClearing;
};
struct VolumetricLight
{
void Init();
@ -453,6 +589,7 @@ struct VolumetricLight
bool ShouldDrawDebug();
bool LoadFogFile(const char* filePath);
void SaveFogFile(const char* filePath);
void SetLightGridRootConstants(struct LightGridRC& rc);
// GUI/user-friendly data layout
struct Fog
@ -462,9 +599,9 @@ struct VolumetricLight
vec3_t boxMin;
vec3_t boxMax;
float extinction;
float albedo; // scatter / extinction
float albedo; // thin fog: 0.3 to 0.5, thick fog: 0.6 to 0.9
float emissive;
float anisotropy;
float anisotropy; // thin fog: 0.9, thick fog: 0.9 with strong backscatter
float noiseStrength;
float noiseSpatialPeriod;
float noiseTimePeriod;
@ -472,30 +609,30 @@ struct VolumetricLight
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 extinctionVDBPipeline;
HPipeline frustumAmbientPipeline;
HPipeline frustumAnisotropyPipeline;
HPipeline frustumFogPipeline;
HPipeline frustumLightPropNXPipeline;
HPipeline frustumLightPropNYPipeline;
HPipeline frustumLightPropPXPipeline;
HPipeline frustumLightPropPYPipeline;
HPipeline frustumParticlePipeline;
HPipeline frustumPointLightScatterPipeline;
HPipeline frustumRaymarchPipeline;
HPipeline frustumSunlightVisPipeline;
HPipeline frustumTemporalPipeline;
HPipeline frustumTemporalFloatPipeline;
HPipeline frustumTemporalFloat4Pipeline;
HPipeline frustumVDBPipeline;
HPipeline frustumVDBLQPipeline;
HPipeline frustumDepthTestPipeline;
HPipeline particleClearPipeline;
HPipeline particleHitPipeline;
HPipeline particleListPipeline;
HPipeline particleTilesPipeline;
HPipeline pointLightShadowPipeline;
HPipeline sunlightScatterPipeline;
HPipeline sunlightShadowPipeline;
@ -514,12 +651,19 @@ struct VolumetricLight
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
HTexture frustumVisTexture; // screen tiles, R = Z index of furthest visible froxel tile
HBuffer particleTileBuffer; // voxel tiles: StructuredBuffer<Tile>
HBuffer particleCounterBuffer; // global counters: StructuredBuffer<Counters>
HBuffer particleTileIndexBuffer; // flattened voxel tile indices: StructuredBuffer<uint>
HBuffer particleIndexBuffer; // particle indices: StructuredBuffer<uint>
HBuffer particleDispatchBuffer; // indirect dispatch buffer
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 maxParticleIndexCount; // uint count in particleIndexBuffer
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
@ -527,8 +671,10 @@ struct VolumetricLight
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;
vec3_t ambientColorGUI;
vec3_t ambientColor; // normalized to 0.5 brightness
float ambientIntensity;
float pointLightIntensity = 20.0f;
vec3_t debugCameraPosition;
float debugBoxScale = 1.0f;
float debugExtinctionScale = 50.0f;
@ -540,10 +686,17 @@ struct VolumetricLight
bool lockCameraPosition = false;
bool firstFrame = true;
bool windowActive = false;
bool drawSunlight = true;
bool enableLightGrid = true;
vec3_t mapBoxMin;
vec3_t mapBoxMax;
vec3_t lightGridCenter;
float debugSphereScale = 0.5f;
int xySubsampling = 2;
int zResolution = 256;
int extinctionResolution = 128;
int sunShadowResolution = 128;
int pointShadowResolution = 64;
};
#pragma pack(push, 1)
@ -551,7 +704,8 @@ struct SunlightData
{
vec3_t direction;
vec3_t color;
float intensity;
float intensityVL = 40.0f;
float intensityDL = 2.0f;
};
#pragma pack(pop)
@ -657,6 +811,7 @@ struct CRP : IRenderPipeline
HTexture lightTexture;
HTexture shadingPositionTexture;
HTexture renderTarget;
HTexture blackbodyTexture;
TextureFormat::Id renderTargetFormat;
HTexture renderTargets[2];
uint32_t renderTargetIndex; // the one to write to
@ -705,6 +860,8 @@ struct CRP : IRenderPipeline
GBufferViz gbufferViz;
SunlightEditor sunlightEditor;
SunlightData sunlightData;
ParticleSystem particleSystem;
NanoVDBManager vdbManager;
};
HPipeline CreateComputePipeline(const char* name, const ShaderByteCode& shader);

View file

@ -42,7 +42,6 @@ struct SceneViewConst
{
MaxViews = 1024,
LightBytes = sizeof(DynamicLight),
MaxLights = SCENE_VIEW_MAX_LIGHTS,
StructBytes = sizeof(SceneView),
BufferBytes = MaxViews * StructBytes
};
@ -595,6 +594,8 @@ void CRP::Init()
volumetricLight.Init();
gbufferViz.Init();
sunlightEditor.Init();
particleSystem.Init();
vdbManager.Init();
srp.firstInit = false;
}
@ -603,6 +604,7 @@ void CRP::LoadResources()
{
const int flags = IMG_NOPICMIP | IMG_NOMIPMAP | IMG_NOIMANIP | IMG_NOAF;
blueNoise2D = LoadTexture("textures/stbn_2d.tga", flags, TW_REPEAT);
blackbodyTexture = LoadTexture("textures/blackbody.tga", flags, TW_CLAMP_TO_EDGE);
}
void CRP::ShutDown(bool fullShutDown)
@ -621,6 +623,8 @@ void CRP::BeginFrame()
// have it be first to we can use ImGUI in the other components too
imgui.BeginFrame();
vdbManager.BeforeFrame();
// must be run outside of the RHI::BeginFrame/RHI::EndFrame pair
const bool rtasUpdate =
dynamicLights.WantRTASUpdate(tr.rtRefdef) ||
@ -667,6 +671,7 @@ void CRP::EndFrame()
magnifier.DrawGUI();
sunlightEditor.DrawGUI();
volumetricLight.DrawGUI();
vdbManager.DrawGUI();
imgui.Draw(renderTarget);
toneMap.DrawToneMap();
magnifier.Draw();
@ -834,15 +839,12 @@ void CRP::ExecuteRenderCommands(const byte* data, bool /*readbackRequested*/)
ui.End();
break;
case RC_BEGIN_3D:
// @TODO:
srp.renderMode = RenderMode::None;
break;
case RC_END_3D:
// @TODO:
srp.renderMode = RenderMode::None;
break;
case RC_END_SCENE:
// @TODO: post-processing
break;
case RC_BEGIN_NK:
nuklear.Begin(renderTarget);
@ -884,6 +886,7 @@ void CRP::DrawSceneView3D(const drawSceneViewCommand_t& cmd)
prepass.Draw(cmd);
BuildDepthPyramid();
particleSystem.Draw();
dynamicLights.DrawBegin();
if(volumetricLight.ShouldDraw())
{
@ -1045,8 +1048,6 @@ void CRP::UploadSceneViewData()
memcpy(scene.prevProjectionMatrix, tr.prevProjMatrix, sizeof(scene.prevProjectionMatrix));
// 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);
@ -1076,7 +1077,9 @@ void CRP::UploadSceneViewData()
VectorCopy(sunlightData.direction, scene.sunDirection);
VectorCopy(sunlightData.color, scene.sunColor);
scene.sunIntensity = sunlightData.intensity;
scene.sunIntensityVL = sunlightData.intensityVL;
scene.sunIntensityDL = sunlightData.intensityDL;
scene.pointLightIntensityVL = volumetricLight.pointLightIntensity;
VectorCopy(volumetricLight.ambientColor, scene.ambientColor);
scene.ambientIntensity = volumetricLight.ambientIntensity;

View file

@ -0,0 +1,941 @@
/*
===========================================================================
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 - NanoVDB support
#include "crp_local.h"
#include "../client/cl_imgui.h"
#define NANOVDB_MAGIC_NUMBER 0x304244566F6E614Eul // "NanoVDB0"
#define NANOVDB_MAGIC_GRID 0x314244566F6E614Eul // "NanoVDB1"
#define NANOVDB_MAGIC_FILE 0x324244566F6E614Eul // "NanoVDB2"
#define NANOVDB_GRID_BUFFER_ALIGNMENT 32
/*
File structure:
FileHeader [GridHeader GridName]+ [GridData]+
*/
#pragma pack(push, 1)
struct FileHeader
{
uint64_t magic;
uint32_t version;
uint16_t gridCount;
uint16_t codec;
bool IsValid()
{
return magic == NANOVDB_MAGIC_NUMBER || magic == NANOVDB_MAGIC_FILE;
}
};
static_assert(sizeof(FileHeader) == 16, "Invalid FileHeader size");
struct FileGridHeader
{
uint64_t memoryByteCount;
uint64_t fileByteCount;
uint64_t gridNameHashKey;
uint64_t activeVoxelCount;
uint32_t gridType;
uint32_t gridClass;
double worldBBox[6]; // AABB in world space
int32_t indexBBox[6]; // AABB in index space
double voxelSize[3]; // in world units
uint32_t gridNameLength; // it includes the NULL terminator
uint32_t nodeCount[4]; // # nodes per level
uint32_t tileCount[3]; // # of active tiles per level
uint16_t codec;
uint16_t padding;
uint32_t versionNumber;
};
static_assert(sizeof(FileGridHeader) == 176, "Invalid FileHeader size");
#pragma pack(pop)
struct FileGrid
{
uint32_t byteOffset;
uint32_t byteCount;
bool IsValid() const
{
return byteOffset > 0 && byteCount > 0;
}
enum Id
{
Smoke,
Fire,
Count
};
};
static void ScaleMatrix(matrix3x3_t m, const vec3_t scale)
{
m[0] = scale[0];
m[1] = 0.0f;
m[2] = 0.0f;
m[3] = 0.0f;
m[4] = scale[1];
m[5] = 0.0f;
m[6] = 0.0f;
m[7] = 0.0f;
m[8] = scale[2];
}
static void RotationMatrixX(matrix3x3_t m, float angleRad)
{
const float c = cosf(angleRad);
const float s = sinf(angleRad);
m[0] = 1.0f;
m[1] = 0.0f;
m[2] = 0.0f;
m[3] = 0.0f;
m[4] = c;
m[5] = -s;
m[6] = 0.0f;
m[7] = s;
m[8] = c;
}
static void RotationMatrixY(matrix3x3_t m, float angleRad)
{
const float c = cosf(angleRad);
const float s = sinf(angleRad);
m[0] = c;
m[1] = 0.0f;
m[2] = s;
m[3] = 0.0f;
m[4] = 1.0f;
m[5] = 0.0f;
m[6] = -s;
m[7] = 0.0f;
m[8] = c;
}
static void RotationMatrixZ(matrix3x3_t m, float angleRad)
{
const float c = cosf(angleRad);
const float s = sinf(angleRad);
m[0] = c;
m[1] = -s;
m[2] = 0.0f;
m[3] = s;
m[4] = c;
m[5] = 0.0f;
m[6] = 0.0f;
m[7] = 0.0f;
m[8] = 1.0f;
}
static void MultMatrix(matrix3x3_t m, const matrix3x3_t a, const matrix3x3_t b)
{
m[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6];
m[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7];
m[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8];
m[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6];
m[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7];
m[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8];
m[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6];
m[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7];
m[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8];
}
static void FindGrids(FileGrid* grids, fileHandle_t fh, int byteCount, const VDBSequenceDesc& desc)
{
FileHeader fileHeader;
FS_Read(&fileHeader, sizeof(fileHeader), fh);
if(!fileHeader.IsValid())
{
return;
}
// for all grids
uint32_t gridByteCounts[16] = {};
Q_assert(fileHeader.gridCount <= ARRAY_LEN(gridByteCounts));
// for grids of interest
int fileToCNQ3[FileGrid::Count];
for(int g = 0; g < FileGrid::Count; g++)
{
fileToCNQ3[g] = -1;
grids[g].byteOffset = 0;
grids[g].byteCount = 0;
}
const uint32_t fileGridCount = (uint32_t)fileHeader.gridCount;
for(uint32_t g = 0; g < fileGridCount; g++)
{
FileGridHeader gridHeader;
FS_Read(&gridHeader, sizeof(gridHeader), fh);
char gridName[64];
Q_assert(gridHeader.gridNameLength <= ARRAY_LEN(gridName));
FS_Read(gridName, (int)gridHeader.gridNameLength, fh);
// vdb_lod.exe auto-renames "density" to "density_level_2" for mip level 2
if(Q_stristr(gridName, desc.smokeGridName) != NULL)
{
fileToCNQ3[g] = (int)FileGrid::Smoke;
}
else if(Q_stristr(gridName, desc.fireGridName) != NULL)
{
fileToCNQ3[g] = (int)FileGrid::Fire;
}
gridByteCounts[g] = gridHeader.fileByteCount;
if(fileToCNQ3[g] >= 0 && fileToCNQ3[g] < FileGrid::Count)
{
grids[fileToCNQ3[g]].byteOffset = 0;
grids[fileToCNQ3[g]].byteCount = gridHeader.fileByteCount;
}
}
for(uint32_t g = 0; g < fileGridCount; g++)
{
uint64_t magic;
FS_Read(&magic, sizeof(magic), fh);
Q_assert(magic == NANOVDB_MAGIC_NUMBER || magic == NANOVDB_MAGIC_GRID);
if(fileToCNQ3[g] >= 0 && fileToCNQ3[g] < FileGrid::Count)
{
grids[fileToCNQ3[g]].byteOffset = (uint32_t)FS_FTell(fh) - 8;
}
FS_Seek(fh, gridByteCounts[g] - 8, FS_SEEK_CUR);
}
Q_assert(grids[FileGrid::Smoke].IsValid() || grids[FileGrid::Fire].IsValid());
}
static void ReadTransform(vec3_t originOffset, vec3_t scale, fileHandle_t fh, int byteOffset)
{
FS_Seek(fh, byteOffset + 296, FS_SEEK_SET);
FS_Read(&scale[0], 4, fh);
FS_Seek(fh, byteOffset + 312, FS_SEEK_SET);
FS_Read(&scale[1], 4, fh);
FS_Seek(fh, byteOffset + 328, FS_SEEK_SET);
FS_Read(&scale[2], 4, fh);
FS_Seek(fh, byteOffset + 368, FS_SEEK_SET);
FS_Read(originOffset, 12, fh);
}
static void ReadTransform(vec3_t originOffset, vec3_t scale, fileHandle_t fh, const FileGrid* grids)
{
if(grids[FileGrid::Smoke].IsValid())
{
ReadTransform(originOffset, scale, fh, grids[FileGrid::Smoke].byteOffset);
}
else if(grids[FileGrid::Fire].IsValid())
{
ReadTransform(originOffset, scale, fh, grids[FileGrid::Fire].byteOffset);
}
}
static void VectorScaleGUI(vec3_t vector, const char* id)
{
ImGui::Text(" ");
ImGui::SameLine();
if(ImGui::Button(va("x2##%s", id)))
{
VectorScale(vector, 2.0f, vector);
}
ImGui::SameLine();
if(ImGui::Button(va("/2##%s", id)))
{
VectorScale(vector, 0.5f, vector);
}
}
static void UploadFrame(
uint32_t& smokeByteOffset, uint32_t& fireByteOffset, uint32_t& gpuBufferOffset,
HBuffer buffer, const NanoVDBManager::CPUFrame& frame)
{
if(frame.fireByteCount > 0 || frame.smokeByteCount > 0)
{
fileHandle_t fh;
const int fileByteCount = FS_FOpenFileRead(frame.filePath, &fh, qfalse);
if(fileByteCount > 0)
{
if(frame.smokeByteCount > 0)
{
smokeByteOffset = gpuBufferOffset;
FS_Seek(fh, frame.smokeByteOffset, FS_SEEK_SET);
const uint32_t gridByteCount = AlignUp<uint32_t>(frame.smokeByteCount, NANOVDB_GRID_BUFFER_ALIGNMENT);
uint8_t* const mapped = BeginBufferUpload(buffer, gpuBufferOffset, gridByteCount);
FS_Read(mapped, frame.smokeByteCount, fh);
EndBufferUpload(buffer);
gpuBufferOffset += gridByteCount;
}
if(frame.fireByteCount > 0)
{
fireByteOffset = gpuBufferOffset;
FS_Seek(fh, frame.fireByteOffset, FS_SEEK_SET);
const uint32_t gridByteCount = AlignUp<uint32_t>(frame.fireByteCount, NANOVDB_GRID_BUFFER_ALIGNMENT);
uint8_t* const mapped = BeginBufferUpload(buffer, gpuBufferOffset, gridByteCount);
FS_Read(mapped, frame.fireByteCount, fh);
EndBufferUpload(buffer);
gpuBufferOffset += gridByteCount;
}
}
if(fileByteCount >= 0)
{
FS_FCloseFile(fh);
}
}
}
static int64_t GetTimeStampUS(int ms, int us)
{
return int64_t(1000) * (int64_t)ms + (int64_t)us;
}
void NanoVDBManager::Init()
{
sequences.Clear();
instances.Clear();
drawInstances.Clear();
cpuFrames.Clear();
gpuFrames.Clear();
streamBufferIndex = 0;
{
streamBufferByteCount = 256 << 20; // @TODO: CVar
BufferDesc desc("", streamBufferByteCount, ResourceStates::ComputeShaderAccessBit);
desc.shortLifeTime = true;
desc.structureByteCount = 4;
for(int i = 0; i < ARRAY_LEN(streamBuffers); i++)
{
desc.name = va("NanoVDB stream #%d", i + 1);
streamBuffers[i] = CreateBuffer(desc);
}
}
}
void NanoVDBManager::BeforeFrame()
{
drawInstances.Clear();
streamedFrames.Clear();
if(!tr.hasWorldRender)
{
return;
}
streamBufferIndex = (streamBufferIndex + 1) % ARRAY_LEN(streamBuffers);
const HBuffer streamBuffer = streamBuffers[streamBufferIndex];
uint32_t gpuBufferOffset = NANOVDB_GRID_BUFFER_ALIGNMENT;
const int64_t renderTimeUS = GetTimeStampUS(tr.worldRenderTimeMS, tr.worldRenderTimeUS);
for(int i = (int)instances.count - 1; i >= 0; i--)
{
const Instance& inst = instances[i];
if(inst.loop)
{
continue;
}
const Sequence& seq = sequences[inst.sequenceIndex];
const float durationSec = (float)seq.frameCount / inst.frameRate;
const int64_t durationUS = (int64_t)ceilf(durationSec * 1000000.0f);
const int64_t endTimeUS = GetTimeStampUS(inst.startTimeMS, inst.startTimeUS) + durationUS;
if(renderTimeUS >= endTimeUS)
{
instances.RemoveUnordered((uint32_t)i);
}
}
for(uint32_t i = 0; i < instances.count; i++)
{
const Instance& inst = instances[i];
const Sequence& seq = sequences[inst.sequenceIndex];
const int64_t startTimeUS = GetTimeStampUS(inst.startTimeMS, inst.startTimeUS);
const int64_t usPerFrame = (int64_t)(1000000.0f / instances[i].frameRate);
const uint32_t frameIndex = (uint32_t)((renderTimeUS - startTimeUS) / usPerFrame) % seq.frameCount;
const uint32_t remainder = (uint32_t)((renderTimeUS - startTimeUS) % usPerFrame);
const uint32_t frameIndex2 = min(frameIndex + 1, seq.frameCount - 1);
const float t = (float)remainder / (float)usPerFrame; // lerp(frame, frame2, t)
DrawInstance drawInst = {};
if(IsNullHandle(seq.buffer))
{
const CPUFrame& frame = cpuFrames[seq.firstFrameIndex + frameIndex];
const CPUFrame& frame2 = cpuFrames[seq.firstFrameIndex + frameIndex2];
const int sf1 = FindStreamedFrameIndex(inst.sequenceIndex, frameIndex);
const int sf2 = FindStreamedFrameIndex(inst.sequenceIndex, frameIndex2);
uint32_t requestedByteCount = 0;
if(sf1 >= 0)
{
drawInst.smokeByteOffset = streamedFrames[sf1].smokeByteOffset;
drawInst.fireByteOffset = streamedFrames[sf1].flamesByteOffset;
}
else
{
requestedByteCount += frame.smokeByteCount + frame.fireByteCount;
}
if(sf2 >= 0)
{
drawInst.smokeByteOffset2 = streamedFrames[sf2].smokeByteOffset;
drawInst.fireByteOffset2 = streamedFrames[sf2].flamesByteOffset;
}
else
{
requestedByteCount += frame2.smokeByteOffset + frame2.fireByteCount;
}
drawInst.buffer = streamBuffer;
if(requestedByteCount > 0 &&
gpuBufferOffset + requestedByteCount <= streamBufferByteCount)
{
UploadFrame(drawInst.smokeByteOffset, drawInst.fireByteOffset, gpuBufferOffset, streamBuffer, frame);
UploadFrame(drawInst.smokeByteOffset2, drawInst.fireByteOffset2, gpuBufferOffset, streamBuffer, frame2);
StreamedFrame sf = {};
sf.sequenceIndex = inst.sequenceIndex;
if(drawInst.smokeByteOffset > 0 || drawInst.fireByteOffset > 0)
{
sf.frameIndex = frameIndex;
sf.smokeByteOffset = drawInst.smokeByteOffset;
sf.flamesByteOffset = drawInst.fireByteOffset;
streamedFrames.Add(sf);
}
if(drawInst.smokeByteOffset2 > 0 || drawInst.fireByteOffset2 > 0)
{
sf.frameIndex = frameIndex2;
sf.smokeByteOffset = drawInst.smokeByteOffset2;
sf.flamesByteOffset = drawInst.fireByteOffset2;
streamedFrames.Add(sf);
}
}
}
else
{
const NanoVDBManager::GPUFrame& frame = gpuFrames[seq.firstFrameIndex + frameIndex];
const NanoVDBManager::GPUFrame& frame2 = gpuFrames[seq.firstFrameIndex + frameIndex2];
drawInst.buffer = seq.buffer;
drawInst.fireByteOffset = frame.fireByteOffset;
drawInst.fireByteOffset2 = frame2.fireByteOffset;
drawInst.smokeByteOffset = frame.smokeByteOffset;
drawInst.smokeByteOffset2 = frame2.smokeByteOffset;
}
drawInst.t = t;
drawInstances.Add(drawInst);
}
}
bool NanoVDBManager::AddSequence(const VDBSequenceDesc& desc)
{
if(!tr.worldMapLoaded)
{
return false;
}
if(instances.IsFull())
{
ri.Printf(PRINT_WARNING, "^3WARNING: NanoVDB instance limit reached\n");
return false;
}
vec3_t originOffset = {};
vec3_t scale;
VectorSet(scale, 1, 1, 1);
int sequenceIndex = -1;
for(uint32_t i = 0; i < sequences.count; i++)
{
if(Q_stricmp(sequences[i].folderPath, desc.folderPath) == 0)
{
sequenceIndex = (int)i;
break;
}
}
if(sequenceIndex < 0 && !sequences.IsFull())
{
HBuffer gpuBuffer = RHI_MAKE_NULL_HANDLE();
uint32_t gpuByteCount = 0;
uint32_t firstFrameIndex = 0;
int fileCount = 0;
if(desc.gpuResident)
{
int startTimeMS = Sys_Milliseconds();
gpuByteCount = NANOVDB_GRID_BUFFER_ALIGNMENT;
char** fileList = ri.FS_ListFiles(desc.folderPath, ".nvdb", &fileCount);
for(int f = 0; f < fileCount; f++)
{
FileGrid grids[FileGrid::Count] = {};
fileHandle_t fh;
const int byteCount = FS_FOpenFileRead(va("%s/%s", desc.folderPath, fileList[f]), &fh, qfalse);
if(byteCount > 0)
{
FindGrids(grids, fh, byteCount, desc);
}
if(byteCount >= 0)
{
FS_FCloseFile(fh);
}
for(uint32_t g = 0; g < FileGrid::Count; g++)
{
if(grids[g].byteCount > 0)
{
gpuByteCount += AlignUp<uint32_t>(grids[g].byteCount, NANOVDB_GRID_BUFFER_ALIGNMENT);
}
}
}
ri.FS_FreeFileList(fileList);
if(fileCount <= 0 || gpuByteCount <= NANOVDB_GRID_BUFFER_ALIGNMENT)
{
ri.Printf(PRINT_WARNING, "^3WARNING: invalid NanoVDB folder '%s'\n", desc.folderPath);
return false;
}
if(gpuByteCount >= uint32_t(1 << 31))
{
ri.Printf(PRINT_WARNING, "^3WARNING: NanoVDB sequence '%s' too large for GPU storage\n", desc.folderPath);
VDBSequenceDesc newDesc = desc;
newDesc.gpuResident = false;
return AddSequence(newDesc);
}
if(gpuFrames.count + fileCount > gpuFrames.capacity)
{
ri.Printf(PRINT_WARNING, "^3WARNING: NanoVDB frame limit reached\n");
return false;
}
ri.Printf(PRINT_ALL, "NanoVDB: analyzed %s in %d ms\n", desc.folderPath, Sys_Milliseconds() - startTimeMS);
startTimeMS = Sys_Milliseconds();
gpuByteCount = AlignUp<uint32_t>(gpuByteCount, NANOVDB_GRID_BUFFER_ALIGNMENT);
BufferDesc bufferDesc("NanoVDB full sequence", gpuByteCount, ResourceStates::ComputeShaderAccessBit);
bufferDesc.shortLifeTime = true;
bufferDesc.structureByteCount = 4;
gpuBuffer = CreateBuffer(bufferDesc);
uint32_t gpuByteOffset = NANOVDB_GRID_BUFFER_ALIGNMENT;
firstFrameIndex = gpuFrames.count;
fileList = ri.FS_ListFiles(desc.folderPath, ".nvdb", &fileCount);
for(int f = 0; f < fileCount; f++)
{
GPUFrame frame = {};
FileGrid grids[FileGrid::Count] = {};
fileHandle_t fh;
const int byteCount = FS_FOpenFileRead(va("%s/%s", desc.folderPath, fileList[f]), &fh, qfalse);
if(byteCount > 0)
{
FindGrids(grids, fh, byteCount, desc);
if(grids[FileGrid::Smoke].IsValid())
{
const uint32_t gridByteCount = AlignUp<uint32_t>(grids[FileGrid::Smoke].byteCount, NANOVDB_GRID_BUFFER_ALIGNMENT);
uint8_t* const cpuBuffer = BeginBufferUpload(gpuBuffer, gpuByteOffset, gridByteCount);
FS_Seek(fh, (int)grids[FileGrid::Smoke].byteOffset, FS_SEEK_SET);
FS_Read(cpuBuffer, (int)grids[FileGrid::Smoke].byteCount, fh);
EndBufferUpload(gpuBuffer);
frame.smokeByteOffset = gpuByteOffset;
gpuByteOffset += gridByteCount;
}
if(grids[FileGrid::Fire].IsValid())
{
const uint32_t gridByteCount = AlignUp<uint32_t>(grids[FileGrid::Fire].byteCount, NANOVDB_GRID_BUFFER_ALIGNMENT);
uint8_t* const cpuBuffer = BeginBufferUpload(gpuBuffer, gpuByteOffset, gridByteCount);
FS_Seek(fh, (int)grids[FileGrid::Fire].byteOffset, FS_SEEK_SET);
FS_Read(cpuBuffer, (int)grids[FileGrid::Fire].byteCount, fh);
EndBufferUpload(gpuBuffer);
frame.fireByteOffset = gpuByteOffset;
gpuByteOffset += gridByteCount;
}
if(f == 0)
{
ReadTransform(originOffset, scale, fh, grids);
}
}
if(byteCount >= 0)
{
FS_FCloseFile(fh);
}
Q_assert(frame.fireByteOffset > 0 || frame.smokeByteOffset > 0);
gpuFrames.Add(frame);
}
ri.FS_FreeFileList(fileList);
ri.Printf(PRINT_ALL, "NanoVDB: processed %s in %d ms\n", desc.folderPath, Sys_Milliseconds() - startTimeMS);
}
else
{
const int startTimeMS = Sys_Milliseconds();
firstFrameIndex = cpuFrames.count;
char** fileList = ri.FS_ListFiles(desc.folderPath, ".nvdb", &fileCount);
for(int f = 0; f < fileCount; f++)
{
FileGrid grids[FileGrid::Count] = {};
const char* const filePath = va("%s/%s", desc.folderPath, fileList[f]);
fileHandle_t fh;
const int byteCount = FS_FOpenFileRead(filePath, &fh, qfalse);
if(byteCount > 0)
{
FindGrids(grids, fh, byteCount, desc);
CPUFrame frame = {};
Q_strncpyz(frame.filePath, filePath, sizeof(frame.filePath));
frame.fireByteOffset = grids[FileGrid::Fire].byteOffset;
frame.fireByteCount = grids[FileGrid::Fire].byteCount;
frame.smokeByteOffset = grids[FileGrid::Smoke].byteOffset;
frame.smokeByteCount = grids[FileGrid::Smoke].byteCount;
cpuFrames.Add(frame);
if(f == 0)
{
ReadTransform(originOffset, scale, fh, grids);
}
}
if(byteCount >= 0)
{
FS_FCloseFile(fh);
}
}
ri.FS_FreeFileList(fileList);
if(fileCount <= 0)
{
ri.Printf(PRINT_WARNING, "^3WARNING: invalid NanoVDB folder '%s'\n", desc.folderPath);
return false;
}
if(cpuFrames.count + fileCount > cpuFrames.capacity)
{
ri.Printf(PRINT_WARNING, "^3WARNING: NanoVDB frame limit reached\n");
return false;
}
ri.Printf(PRINT_ALL, "NanoVDB: analyzed %s in %d ms\n", desc.folderPath, Sys_Milliseconds() - startTimeMS);
}
sequenceIndex = (int)sequences.count;
Sequence sequence = {};
Q_strncpyz(sequence.folderPath, desc.folderPath, sizeof(sequence.folderPath));
sequence.frameCount = (uint32_t)fileCount;
sequence.firstFrameIndex = firstFrameIndex;
sequence.buffer = gpuBuffer;
sequence.bufferByteCount = gpuByteCount;
VectorCopy(originOffset, sequence.originOffset);
VectorCopy(scale, sequence.scale);
sequences.Add(sequence);
}
if(sequenceIndex < 0)
{
ri.Printf(PRINT_WARNING, "^3WARNING: NanoVDB sequence limit reached\n");
return false;
}
Instance instance = {};
instance.fireEmissionScale = desc.fireEmissionScale;
Q_strncpyz(instance.fireGridName, desc.fireGridName, sizeof(instance.fireGridName));
instance.fireTemperatureScale = desc.fireTemperatureScale;
instance.frameRate = desc.frameRate;
instance.smokeExtinctionScale = desc.smokeExtinctionScale;
instance.smokeAlbedo = desc.smokeAlbedo;
Q_strncpyz(instance.smokeGridName, desc.smokeGridName, sizeof(instance.smokeGridName));
instance.startTimeMS = desc.startTimeMS;
instance.startTimeUS = desc.startTimeUS;
instance.sequenceIndex = (uint32_t)sequenceIndex;
VectorMultiply(desc.scale, sequences[sequenceIndex].scale, instance.scale);
VectorCopy(desc.position, instance.position);
VectorCopy(desc.useSequenceOffset ? sequences[sequenceIndex].originOffset : desc.originOffset, instance.originOffset);
VectorCopy(desc.anglesRad, instance.anglesRad);
instance.loop = desc.loop;
instances.Add(instance);
return true;
}
void NanoVDBManager::MakeWorldToIndexMatrix(matrix3x3_t matrix, const Instance& instance)
{
matrix3x3_t scale, rot, temp, temp2;
vec3_t scaleVector;
for(int i = 0; i < 3; i++)
{
scaleVector[i] = 1.0f / instance.scale[i];
}
ScaleMatrix(scale, scaleVector);
RotationMatrixX(rot, -instance.anglesRad[0]);
MultMatrix(temp, scale, rot);
RotationMatrixY(rot, -instance.anglesRad[1]);
MultMatrix(temp2, temp, rot);
RotationMatrixZ(rot, -instance.anglesRad[2]);
MultMatrix(matrix, temp2, rot);
}
void NanoVDBManager::DrawGUI()
{
static const char* const sequencePopupTitle = "Add NanoVDB Sequence";
if(!tr.worldMapLoaded)
{
return;
}
GUI_AddMainMenuItem(GUI_MainMenu::Tools, "Edit NanoVDB", "", &windowActive);
if(!windowActive)
{
return;
}
if(ImGui::Begin("NanoVDB Settings", &windowActive, ImGuiWindowFlags_AlwaysAutoResize))
{
if(rhiInfo.forceNanoVDBPreviewMode)
{
static bool forcedPreviewMode = true;
ImGui::BeginDisabled(true);
ImGui::Checkbox("Preview mode (forced due to driver bug)", &forcedPreviewMode);
ImGui::EndDisabled();
}
else
{
ImGui::Checkbox("Preview mode", &previewMode);
if(!previewMode)
{
ImGui::Checkbox("2x super-sampling", &supersampling);
ImGui::Checkbox("Linear interpolation", &linearInterpolation);
ImGui::Checkbox("Accurate overlap test", &accurateOverlapTest);
ImGui::Checkbox("Ambient lighting: higher angular LoD", &ambientIncreasedCoverage);
ImGui::SliderInt("Ambient lighting: sub-sampling", &ambientRaymarchLOD, 1, 8);
ImGui::SliderFloat("Emissive scattering scale", &emissiveScatterScale, 0.0f, 1.0f, "%g");
}
}
const uint64_t streamByteCount = (uint64_t)ARRAY_LEN(streamBuffers) * (uint64_t)streamBufferByteCount;
uint64_t dedicatedByteCount = 0;
for(uint32_t i = 0; i < sequences.count; i++)
{
dedicatedByteCount += (uint64_t)sequences[i].bufferByteCount;
}
ImGui::Text("%d sequence%s, %s dedicated, %s stream",
(int)sequences.count, sequences.count >= 2 ? "s" : "",
Com_FormatBytes(dedicatedByteCount),
Com_FormatBytes(streamByteCount));
ImGui::Text("%d CPU frame%s, %d GPU frame%s",
(int)cpuFrames.count, cpuFrames.count >= 2 ? "s" : "",
(int)gpuFrames.count, gpuFrames.count >= 2 ? "s" : "");
ImGui::Text("%d streamed file%s", (int)streamedFrames.count, streamedFrames.count >= 2 ? "s" : "");
ImGui::Separator();
if(ImGui::Button("Purge unused sequences"))
{
Purge();
}
if(ImGui::BeginTabBar("Tabs#VDB", ImGuiTabBarFlags_AutoSelectNewTabs))
{
for(uint32_t i = 0; i < instances.count; i++)
{
if(ImGui::BeginTabItem(va("#%d", i + 1)))
{
Instance& inst = instances[i];
Sequence& seq = sequences[inst.sequenceIndex];
ImGui::Text("%s (%d frame%s, %s)", seq.folderPath, (int)seq.frameCount,
seq.frameCount >= 2 ? "s" : "",
IsNullHandle(seq.buffer) ? "streamed" : "in VRAM");
ImGui::SliderFloat("Framerate", &inst.frameRate, 15.0f, 120.0f, "%g");
ImGui::SliderFloat("Smoke extinction scale (thickness)", &inst.smokeExtinctionScale, 0.0f, 10.0f, "%g");
ImGui::SliderFloat("Smoke albedo (reflectivity)", &inst.smokeAlbedo, 0.0f, 1.0f, "%g");
ImGui::SliderFloat("Flame emission scale (brightness)", &inst.fireEmissionScale, 0.0f, 1.0f, "%g");
ImGui::SliderFloat("Flame temperature scale (color)", &inst.fireTemperatureScale, 0.0f, 20000.0f, "%g");
vec3_t angles;
for(int a = 0; a < 3; a++)
{
angles[a] = RAD2DEG(inst.anglesRad[a]);
}
ImGui::SliderFloat3("Origin offset (index space)", inst.originOffset, -1000.0f, 1000.0f, "%g");
VectorScaleGUI(inst.originOffset, "origin");
ImGui::SliderFloat3("Scale", inst.scale, 0.0f, 100.0f, "%g");
VectorScaleGUI(inst.scale, "scale");
ImGui::SliderFloat3("Position (world space)", inst.position, -100 * 1000.0f, 100 * 1000.0f, "%g");
ImGui::SliderFloat3("Angles", angles, 0.0f, 360.0f, "%g");
for(int a = 0; a < 3; a++)
{
inst.anglesRad[a] = DEG2RAD(angles[a]);
}
ImGui::Separator();
if(ImGui::Button("Remove"))
{
instances.Remove(i);
}
ImGui::EndTabItem();
}
}
if(ImGui::BeginTabItem("Add"))
{
static char sequencePath[64];
static char flamesGridName[64] = "flames";
static char smokeGridName[64] = "density";
static bool gpuResident = true;
ImGui::InputText("Folder path", sequencePath, sizeof(sequencePath));
ImGui::SameLine();
if(ImGui::Button("..."))
{
OpenFolderDialog_Open("nanovdb");
}
ImGui::InputText("Flames grid", flamesGridName, sizeof(flamesGridName));
ImGui::InputText("Smoke grid", smokeGridName, sizeof(smokeGridName));
ImGui::Checkbox("GPU resident", &gpuResident);
ImGui::Separator();
if(ImGui::Button("Add"))
{
VDBSequenceDesc desc = {};
desc.fireGridName = flamesGridName;
desc.folderPath = sequencePath;
desc.gpuResident = gpuResident;
desc.loop = true;
desc.smokeGridName = smokeGridName;
AddSequence(desc);
}
if(OpenFolderDialog_Do())
{
Q_strncpyz(sequencePath, OpenFolderDialog_GetPath(), sizeof(sequencePath));
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
ImGui::End();
}
void NanoVDBManager::Purge()
{
// build sequence reference counts
uint32_t sequenceRefCounts[ARRAY_LEN(sequences.items)] = {};
for(uint32_t i = 0; i < instances.count; i++)
{
const uint32_t s = instances[i].sequenceIndex;
sequenceRefCounts[s]++;
}
// queue GPU buffer deletions
for(uint32_t s = 0; s < sequences.count; s++)
{
if(sequenceRefCounts[s] == 0 &&
!IsNullHandle(sequences[s].buffer))
{
DestroyBufferDelayed(sequences[s].buffer);
}
}
// compact sequence array, build index map, remove frames, fix frame offsets
uint32_t sequenceRemap[ARRAY_LEN(sequences.items)] = {};
uint32_t removed = 0;
uint32_t dst = 0;
uint32_t src = 0;
for(; src < sequences.count; src++)
{
if(sequenceRefCounts[src] == 0)
{
const uint32_t first = sequences[src].firstFrameIndex;
const uint32_t count = sequences[src].frameCount;
const bool streamed = IsNullHandle(sequences[src].buffer);
if(streamed)
{
cpuFrames.RemoveRange(first, count);
}
else
{
gpuFrames.RemoveRange(first, count);
}
for(uint32_t s = 0; s < sequences.count; s++)
{
if(sequences[s].firstFrameIndex > first)
{
sequences[s].firstFrameIndex -= count;
}
}
removed++;
continue;
}
sequenceRemap[src] = dst;
if(src > dst)
{
sequences[dst] = sequences[src];
}
dst++;
}
sequences.count -= removed;
// fix sequence indices
for(uint32_t i = 0; i < instances.count; i++)
{
const uint32_t s = instances[i].sequenceIndex;
instances[i].sequenceIndex = sequenceRemap[s];
}
#if defined(_DEBUG)
for(uint32_t i = 0; i < instances.count; i++)
{
Q_assert(instances[i].sequenceIndex < sequences.count);
}
for(uint32_t s = 0; s < sequences.count; s++)
{
const Sequence& seq = sequences[s];
const uint32_t frameCount = IsNullHandle(seq.buffer) ? cpuFrames.count : gpuFrames.count;
Q_assert(seq.firstFrameIndex + seq.frameCount <= frameCount);
}
#endif
}
int NanoVDBManager::FindStreamedFrameIndex(uint32_t sequenceIndex, uint32_t frameIndex)
{
int index = -1;
for(uint32_t f = 0; f < streamedFrames.count; f++)
{
if(streamedFrames[f].sequenceIndex == sequenceIndex &&
streamedFrames[f].frameIndex == frameIndex)
{
index = (int)f;
break;
}
}
return index;
}

View file

@ -0,0 +1,255 @@
/*
===========================================================================
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 - GPU particle system
#include "crp_local.h"
#include "shaders/crp/scene_view.h.hlsli"
#include "compshaders/crp/particles_clear.h"
#include "compshaders/crp/particles_setup.h"
#include "compshaders/crp/particles_emit.h"
#include "compshaders/crp/particles_simulate.h"
// @TODO:
#if 0
static const uint32_t EmitterParticleCount = 5;
static const uint32_t EmitterEmitCount = 2;
static const float EmitterMaxSeconds = 2.0f / 60.0f;
#else
static const float EmitterMaxSeconds = 1.0f;
static const float EmitterFPS = 60.0f;
static const uint32_t EmitterParticleCount = 1024;
static const uint32_t EmitterEmitCount = EmitterParticleCount / (uint32_t)ceilf(EmitterMaxSeconds * EmitterFPS);
#endif
#pragma pack(push, 4)
struct GPSClearRC
{
uint32_t emitterBufferIndex;
uint32_t deadBufferIndex;
uint32_t emitterIndex;
uint32_t firstParticle;
uint32_t particleCount;
float maxSeconds;
};
struct GPSSetupRC
{
uint32_t emitterBufferIndex;
uint32_t indirectBufferIndex;
uint32_t emitterIndex;
uint32_t emitCount;
};
struct GPSEmitRC
{
uint32_t particleBufferIndex;
uint32_t liveBufferIndex;
uint32_t deadBufferIndex;
uint32_t emitterBufferIndex;
uint32_t emitterIndex;
};
struct GPSSimulateRC
{
uint32_t particleBufferIndex;
uint32_t liveSrcBufferIndex;
uint32_t liveDstBufferIndex;
uint32_t deadBufferIndex;
uint32_t emitterBufferIndex;
uint32_t emitterIndex;
};
#pragma pack(pop)
void ParticleSystem::Init()
{
Q_assert(EmitterParticleCount <= MAX_PARTICLES);
clearPipeline = CreateComputePipeline("GPS Clear", ShaderByteCode(g_particles_clear_cs));
setupPipeline = CreateComputePipeline("GPS Setup", ShaderByteCode(g_particles_setup_cs));
emitPipeline = CreateComputePipeline("GPS Emit", ShaderByteCode(g_particles_emit_cs));
simulatePipeline = CreateComputePipeline("GPS Simulate", ShaderByteCode(g_particles_simulate_cs));
{
BufferDesc desc("particles", MAX_PARTICLES * sizeof(Particle), ResourceStates::UnorderedAccessBit);
desc.shortLifeTime = true;
desc.structureByteCount = sizeof(Particle);
particleBuffer = CreateBuffer(desc);
}
{
BufferDesc desc("", MAX_PARTICLES * 4, ResourceStates::UnorderedAccessBit);
desc.shortLifeTime = true;
desc.structureByteCount = 4;
desc.name = "live particles #1";
liveBuffers[0] = CreateBuffer(desc);
desc.name = "live particles #2";
liveBuffers[1] = CreateBuffer(desc);
desc.name = "dead particles";
deadBuffer = CreateBuffer(desc);
}
{
BufferDesc desc("particle emitters", MAX_PARTICLE_EMITTERS * sizeof(ParticleEmitter), ResourceStates::UnorderedAccessBit);
desc.shortLifeTime = true;
desc.structureByteCount = sizeof(ParticleEmitter);
emitterBuffer = CreateBuffer(desc);
}
{
const uint32_t dispatchData[] =
{
0, 1, 1,
0, 1, 1
};
BufferDesc desc("particle dispatch", sizeof(dispatchData), ResourceStates::UnorderedAccessBit);
desc.shortLifeTime = true;
indirectBuffer = CreateBuffer(desc);
uint8_t* const mapped = BeginBufferUpload(indirectBuffer);
memcpy(mapped, dispatchData, sizeof(dispatchData));
EndBufferUpload(indirectBuffer);
}
needsClearing = true;
liveBufferReadIndex = 0;
}
void ParticleSystem::Draw()
{
#if 1 // @TODO: shouldn't be necessary once cgame is adding the emitters
if(tr.world == NULL)
{
return;
}
#endif
#if 0
static int counter = 0;
counter++;
if(counter == 4)
{
counter = 0;
needsClearing = true;
}
#endif
SCOPED_RENDER_PASS("Particles", 1.0f, 1.0f, 1.0f);
if(needsClearing)
{
SCOPED_DEBUG_LABEL("Clear", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(emitterBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(deadBuffer, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
GPSClearRC rc = {};
rc.emitterBufferIndex = GetBufferIndexUAV(emitterBuffer);
rc.deadBufferIndex = GetBufferIndexUAV(deadBuffer);
rc.emitterIndex = 0;
rc.firstParticle = 0;
rc.particleCount = EmitterParticleCount;
rc.maxSeconds = EmitterMaxSeconds;
CmdBindPipeline(clearPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((EmitterParticleCount + 63) / 64, 1, 1);
needsClearing = false;
}
{
SCOPED_DEBUG_LABEL("Setup", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(emitterBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(indirectBuffer, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
GPSSetupRC rc = {};
rc.emitterBufferIndex = GetBufferIndexUAV(emitterBuffer);
rc.indirectBufferIndex = GetBufferIndexUAV(indirectBuffer);
rc.emitterIndex = 0; // @TODO:
rc.emitCount = EmitterEmitCount; // @TODO:
CmdBindPipeline(setupPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch(1, 1, 1);
}
{
SCOPED_DEBUG_LABEL("Emit", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(indirectBuffer, ResourceStates::IndirectDispatchBit);
CmdBufferBarrier(emitterBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(deadBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(liveBuffers[liveBufferReadIndex], ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleBuffer, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
GPSEmitRC rc = {};
rc.emitterBufferIndex = GetBufferIndexUAV(emitterBuffer);
rc.deadBufferIndex = GetBufferIndexUAV(deadBuffer);
rc.liveBufferIndex = GetBufferIndexUAV(liveBuffers[liveBufferReadIndex]);
rc.particleBufferIndex = GetBufferIndexUAV(particleBuffer);
rc.emitterIndex = 0; // @TODO:
CmdBindPipeline(emitPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatchIndirect(indirectBuffer, 0);
}
{
SCOPED_DEBUG_LABEL("Simulate", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(indirectBuffer, ResourceStates::IndirectDispatchBit);
CmdBufferBarrier(emitterBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(deadBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(liveBuffers[liveBufferReadIndex], ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(liveBuffers[liveBufferReadIndex ^ 1], ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleBuffer, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
GPSSimulateRC rc = {};
rc.particleBufferIndex = GetBufferIndexUAV(particleBuffer);
rc.liveSrcBufferIndex = GetBufferIndexUAV(liveBuffers[liveBufferReadIndex]);
rc.liveDstBufferIndex = GetBufferIndexUAV(liveBuffers[liveBufferReadIndex ^ 1]);
rc.deadBufferIndex = GetBufferIndexUAV(deadBuffer);
rc.emitterBufferIndex = GetBufferIndexUAV(emitterBuffer);
rc.emitterIndex = 0; // @TODO:
CmdBindPipeline(simulatePipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatchIndirect(indirectBuffer, 12);
liveBufferReadIndex ^= 1;
}
}

View file

@ -71,7 +71,6 @@ static void LoadSunFromShader(const shader_t* skyShader)
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);
}
@ -175,7 +174,8 @@ void SunlightEditor::DrawGUI()
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::SliderFloat("Light intensity in fog/smoke", &crp.sunlightData.intensityVL, 0.0f, 200.0f);
ImGui::SliderFloat("Light intensity on surfaces", &crp.sunlightData.intensityDL, 0.0f, 10.0f);
ImGui::NewLine();
if(ImGui::Button("Save Config..."))

File diff suppressed because it is too large Load diff

View file

@ -3733,6 +3733,7 @@ namespace RHI
rhiInfo.hasInlineRaytracing = hasInlineRaytracing;
rhiInfo.hasBarycentrics = hasBarycentrics;
rhiInfo.allocatedByteCount = 0;
rhiInfo.forceNanoVDBPreviewMode = rhi.vendorId == VENDORID_AMD || rhi.vendorId == VENDORID_INTEL;
rhi.initialized = true;

View file

@ -1,3 +0,0 @@
*.exe
*.pdb
*.h

File diff suppressed because it is too large Load diff

View file

@ -48,6 +48,12 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#define UINT32_MAX 0xFFFFFFFF
#define UINT64_MAX 0xFFFFFFFFFFFFFFFF
#define FLT_INF asfloat(0x7F800000)
#define FLT_NAN asfloat(0xFFC00000)
#define FLT_MAX asfloat(0x7F7FFFFF)
#define HALF_MAX 65504.0
typedef RaytracingAccelerationStructure RTAS;
@ -69,6 +75,20 @@ float Brightness(float3 color)
return brightness;
}
float3 ColorAtBrightness(float3 color, float targetBrightness)
{
float brightness = Brightness(color);
if(brightness <= 0.000001)
{
color = float3(0.5, 0.5, 0.5);
brightness = Brightness(color);
}
float brightnessScale = targetBrightness / brightness;
float3 result = color * brightnessScale;
return result;
}
float4 MakeGreyscale(float4 input, float amount)
{
float grey = dot(input.rgb, float3(0.299, 0.587, 0.114));
@ -163,6 +183,13 @@ float EaseInQuad(float x)
return x * x;
}
float EaseOutQuad(float x)
{
float y = 1.0 - x;
return 1.0 - y * y;
}
float EaseInExp(float x)
{
return x == 0.0 ? 0.0 : pow(2.0, 10.0 * x - 10.0);
@ -404,6 +431,111 @@ float AnimateBlueNoise(float blueNoise, uint frameIndex)
return frac(blueNoise + float(frameIndex % 32) * 0.61803399);
}
// credit: David Hoskins
float Hash1To1(float p)
{
p = frac(p * .1031);
p *= p + 33.33;
p *= p + p;
return frac(p);
}
// credit: David Hoskins
float Hash2To1(float2 p)
{
float3 p3 = frac(float3(p.xyx) * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return frac((p3.x + p3.y) * p3.z);
}
// credit: David Hoskins
float Hash3To1(float3 p3)
{
p3 = frac(p3 * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return frac((p3.x + p3.y) * p3.z);
}
// credit: David Hoskins
float2 Hash1To2(float p)
{
float3 p3 = frac(float3(p, p, p) * float3(.1031, .1030, .0973));
p3 += dot(p3, p3.yzx + 33.33);
return frac((p3.xx + p3.yz) * p3.zy);
}
// credit: David Hoskins
float2 Hash2To2(float2 p)
{
float3 p3 = frac(float3(p.xyx) * float3(.1031, .1030, .0973));
p3 += dot(p3, p3.yzx + 33.33);
return frac((p3.xx + p3.yz) * p3.zy);
}
// credit: David Hoskins
float2 Hash3To2(float3 p3)
{
p3 = frac(p3 * float3(.1031, .1030, .0973));
p3 += dot(p3, p3.yzx + 33.33);
return frac((p3.xx + p3.yz) * p3.zy);
}
// credit: David Hoskins
float3 Hash1To3(float p)
{
float3 p3 = frac(float3(p, p, p) * float3(.1031, .1030, .0973));
p3 += dot(p3, p3.yzx + 33.33);
return frac((p3.xxy + p3.yzz) * p3.zyx);
}
// credit: David Hoskins
float3 Hash2To3(float2 p)
{
float3 p3 = frac(float3(p.xyx) * float3(.1031, .1030, .0973));
p3 += dot(p3, p3.yxz + 33.33);
return frac((p3.xxy + p3.yzz) * p3.zyx);
}
// credit: David Hoskins
float3 Hash3To3(float3 p3)
{
p3 = frac(p3 * float3(.1031, .1030, .0973));
p3 += dot(p3, p3.yxz + 33.33);
return frac((p3.xxy + p3.yxx) * p3.zyx);
}
// credit: David Hoskins
float4 Hash1To4(float p)
{
float4 p4 = frac(float4(p, p, p, p) * float4(.1031, .1030, .0973, .1099));
p4 += dot(p4, p4.wzxy + 33.33);
return frac((p4.xxyz + p4.yzzw) * p4.zywx);
}
// credit: David Hoskins
float4 Hash2To4(float2 p)
{
float4 p4 = frac(float4(p.xyxy) * float4(.1031, .1030, .0973, .1099));
p4 += dot(p4, p4.wzxy + 33.33);
return frac((p4.xxyz + p4.yzzw) * p4.zywx);
}
// credit: David Hoskins
float4 Hash3To4(float3 p)
{
float4 p4 = frac(float4(p.xyzx) * float4(.1031, .1030, .0973, .1099));
p4 += dot(p4, p4.wzxy + 33.33);
return frac((p4.xxyz + p4.yzzw) * p4.zywx);
}
// credit: David Hoskins
float4 Hash4To4(float4 p4)
{
p4 = frac(p4 * float4(.1031, .1030, .0973, .1099));
p4 += dot(p4, p4.wzxy + 33.33);
return frac((p4.xxyz + p4.yzzw) * p4.zywx);
}
float2 NDCToTC(float2 ndc)
{
float2 tc = ndc * float2(0.5, -0.5) + float2(0.5, 0.5);
@ -515,12 +647,13 @@ int FlattenIndex(int3 tileIndex, int3 tileResolution)
uint3 UnflattenIndex(uint flatIndex, uint3 tileResolution)
{
uint w = tileResolution.x;
uint h = tileResolution.y;
uint wh = tileResolution.x * h;
uint wh = w * h;
uint z = flatIndex / wh;
flatIndex -= z * wh;
uint y = flatIndex / h;
uint x = flatIndex - y * h;
uint y = flatIndex / w;
uint x = flatIndex - y * w;
uint3 result = uint3(x, y, z);
return result;
@ -528,12 +661,13 @@ uint3 UnflattenIndex(uint flatIndex, uint3 tileResolution)
int3 UnflattenIndex(int flatIndex, int3 tileResolution)
{
int w = tileResolution.x;
int h = tileResolution.y;
int wh = tileResolution.x * h;
int wh = w * h;
int z = flatIndex / wh;
flatIndex -= z * wh;
int y = flatIndex / h;
int x = flatIndex - y * h;
int y = flatIndex / w;
int x = flatIndex - y * w;
int3 result = int3(x, y, z);
return result;
@ -545,6 +679,12 @@ void ClearBoundingBox(out int3 boxMin, out int3 boxMax)
boxMax = int3(INT32_MIN, INT32_MIN, INT32_MIN);
}
void ClearBoundingBox(out float3 boxMin, out float3 boxMax)
{
boxMin = float3(FLT_MAX, FLT_MAX, FLT_MAX);
boxMax = float3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
}
template<typename T>
void ExpandBoundingBox(inout T boxMin, inout T boxMax, T newPoint)
{
@ -699,17 +839,103 @@ float3 DirectionFromLongLat(float longitude01, float latitude01)
return direction;
}
float3 AmbientColor(float4 payloadA, float4 payloadB, float3 normal, float3 fallbackColor)
float SphereVolume(float radius)
{
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);
float volume = ((4.0 / 3.0) * PI) * radius * radius * radius;
return color;
return volume;
}
// "2D Polyhedral Bounds of a Clipped, Perspective-Projected 3D Sphere" by Mara and McGuire
float2 ProjectedSphereExtentsAxis(float xy, float z, float r)
{
float t = sqrt(xy * xy + z * z - r * r);
float min = (t * xy - r * z) / (t * z + r * xy);
float max = (t * xy + r * z) / (t * z - r * xy);
float2 result = float2(min, max);
return result;
}
// "2D Polyhedral Bounds of a Clipped, Perspective-Projected 3D Sphere" by Mara and McGuire
float4 ProjectedSphereExtentsNDC(float3 spherePositionWS, float sphereRadius, matrix viewMatrix, matrix projMatrix)
{
float4 spherePosVSw = mul(viewMatrix, float4(spherePositionWS, 1));
float3 spherePosVS = spherePosVSw.xyz / spherePosVSw.w;
float2 extentsX = ProjectedSphereExtentsAxis(spherePosVS.x, -spherePosVS.z, sphereRadius) * projMatrix[0][0] + projMatrix[2][0];
float2 extentsY = ProjectedSphereExtentsAxis(spherePosVS.y, spherePosVS.z, sphereRadius) * projMatrix[1][1] + projMatrix[2][1];
float4 result = float4(extentsX.x, extentsY.x, extentsX.y, extentsY.y);
return result;
}
float3 Project(float3 P, matrix m)
{
float4 Qw = mul(m, float4(P, 1));
float3 Q = Qw.xyz / Qw.w;
return Q;
}
float VoxelStepSize(float3 dir, float3 voxelSize)
{
float3 stepSize3 = voxelSize / max(abs(dir), float(0.000001).xxx);
float stepSize = min3(stepSize3.x, stepSize3.y, stepSize3.z);
return stepSize;
}
float2x2 RandomRotationMatrix2x2(float3 position)
{
float angle = Hash3To1(position) * 2.0 * PI;
float sin, cos;
sincos(angle, sin, cos);
float2x2 result = float2x2(cos, -sin, sin, cos);
return result;
}
float3x3 RandomRotationMatrix3x3(float3 position)
{
float3 angles = Hash3To3(position) * 2.0 * PI;
float3 sin, cos;
sincos(angles.x, sin.x, cos.x);
sincos(angles.y, sin.y, cos.y);
sincos(angles.z, sin.z, cos.z);
float r0 = cos.x * cos.y;
float r1 = cos.x * sin.y * sin.z - sin.x * cos.z;
float r2 = cos.x * sin.y * cos.z + sin.x * sin.z;
float r3 = sin.x * cos.y;
float r4 = sin.x * sin.y * sin.z + cos.x * cos.z;
float r5 = sin.x * sin.y * cos.z - cos.x * sin.z;
float r6 = -sin.y;
float r7 = cos.y * sin.z;
float r8 = cos.y * cos.z;
float3x3 result = float3x3(r0, r1, r2, r3, r4, r5, r6, r7, r8);
return result;
}
float2x2 IdentityMatrix2x2()
{
return float2x2(
1, 0,
0, 1);
}
float3x3 IdentityMatrix3x3()
{
return float3x3(
1, 0, 0,
0, 1, 0,
0, 0, 1);
}
matrix IdentityMatrix4x4()
{
return matrix(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
}

View file

@ -0,0 +1,141 @@
/*
===========================================================================
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/>.
===========================================================================
*/
// shared structure for the Quake 3 light grid
#pragma once
#include "typedefs.h.hlsli"
#if !defined(__cplusplus)
# include "common.hlsli"
#endif
#if defined(__cplusplus)
# pragma pack(push, 4)
#endif
struct LightGridRC
{
float3 centerPosition;
uint textureAIndex;
float3 worldScale;
uint textureBIndex;
uint samplerIndex;
uint isAvailable;
};
#if defined(__cplusplus)
# pragma pack(pop)
#endif
#if defined(__cplusplus)
static_assert(sizeof(LightGridRC) == 40, "sizeof(LightGridRC) is wrong");
#endif
#if !defined(__cplusplus)
struct LightGridSample
{
float4 a;
float4 b;
float3 GetLightDirection()
{
return DirectionFromLongLat(b.z, b.w);
}
float3 GetLocalColor()
{
return float3(a.w, b.xy);
}
float3 GetGlobalColor()
{
return a.xyz;
}
float3 GetAmbientColor(float3 normal, float3 fallbackColor, float ambColorScale, float localColorScale)
{
float3 ambColor = GetGlobalColor();
float3 localColor = GetLocalColor();
float3 localDir = GetLightDirection();
float localScale = dot(localDir, normal) * 0.5 + 0.5; // wraps around
float3 interpColor = ambColor * ambColorScale + localColor * localScale * localColorScale;
float brightNew = Brightness(interpColor);
float brightFall = Brightness(fallbackColor);
float t = saturate(brightNew / max(brightFall, 0.001));
float3 color = lerp(fallbackColor, interpColor, t);
return color;
}
};
struct LightGrid
{
float3 centerPosition;
float3 worldScale;
float3 textureSize;
Texture3D textureA;
Texture3D textureB;
SamplerState sampler0;
LightGridSample SampleAtPosition(float3 positionWS)
{
float3 ambientTC = AABoxWorldSpaceToTC(positionWS, centerPosition, textureSize, worldScale);
LightGridSample sample;
sample.a = textureA.SampleLevel(sampler0, ambientTC, 0);
sample.b = textureB.SampleLevel(sampler0, ambientTC, 0);
return sample;
}
LightGridSample SampleAtIndex(int3 index)
{
LightGridSample sample;
sample.a = textureA[index];
sample.b = textureB[index];
return sample;
}
float3 IndexToWorldSpace(int3 voxelIndex)
{
float3 voxelCenter = AABoxIndexToWorldSpace(voxelIndex, centerPosition, textureSize, worldScale);
return voxelCenter;
}
};
LightGrid GetLightGrid(LightGridRC rc)
{
LightGrid grid;
grid.textureA = ResourceDescriptorHeap[rc.textureAIndex];
grid.textureB = ResourceDescriptorHeap[rc.textureBIndex];
grid.sampler0 = SamplerDescriptorHeap[rc.samplerIndex];
grid.centerPosition = rc.centerPosition;
grid.worldScale = rc.worldScale;
grid.textureSize = float3(GetTextureSize(grid.textureA));
return grid;
}
#endif

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/>.
===========================================================================
*/
// GPU particle system: emitter and particle free list initialization
#include "common.hlsli"
#include "scene_view.h.hlsli"
cbuffer RootConstants
{
uint emitterBufferIndex;
uint deadBufferIndex;
uint emitterIndex;
uint firstParticle;
uint particleCount;
float maxSeconds;
}
[numthreads(64, 1, 1)]
void cs(uint3 id : SV_DispatchThreadID)
{
if(id.x >= particleCount)
{
return;
}
if(id.x == 0)
{
RWStructuredBuffer<ParticleEmitter> emitterBuffer = ResourceDescriptorHeap[emitterBufferIndex];
ParticleEmitter e;
e.deadCount = particleCount;
e.firstIndex = firstParticle;
e.liveCount = 0;
e.liveCount2 = 0;
e.totalCount = particleCount;
e.emitCount = 0;
e.maxSeconds = maxSeconds;
emitterBuffer[emitterIndex] = e;
}
RWStructuredBuffer<uint> deadBuffer = ResourceDescriptorHeap[deadBufferIndex];
deadBuffer[firstParticle + id.x] = particleCount - 1 - id.x;
}

View file

@ -0,0 +1,74 @@
/*
===========================================================================
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/>.
===========================================================================
*/
// GPU particle system: particle emission step
#include "common.hlsli"
#include "scene_view.h.hlsli"
cbuffer RootConstants
{
uint particleBufferIndex;
uint liveBufferIndex;
uint deadBufferIndex;
uint emitterBufferIndex;
uint emitterIndex;
}
[numthreads(64, 1, 1)]
void cs(uint3 id : SV_DispatchThreadID)
{
RWStructuredBuffer<ParticleEmitter> emitterBuffer = ResourceDescriptorHeap[emitterBufferIndex];
uint emitCount = emitterBuffer[emitterIndex].emitCount;
if(id.x >= emitCount)
{
return;
}
RWStructuredBuffer<Particle> particleBuffer = ResourceDescriptorHeap[particleBufferIndex];
RWStructuredBuffer<uint> deadBuffer = ResourceDescriptorHeap[deadBufferIndex];
RWStructuredBuffer<uint> liveBuffer = ResourceDescriptorHeap[liveBufferIndex];
SceneView scene = GetSceneView();
uint firstIndex = emitterBuffer[emitterIndex].firstIndex;
uint oldDeadCount;
InterlockedAdd(emitterBuffer[emitterIndex].deadCount, -1, oldDeadCount);
uint newIndex = deadBuffer[firstIndex + oldDeadCount - 1];
uint oldLiveCount;
InterlockedAdd(emitterBuffer[emitterIndex].liveCount, 1, oldLiveCount);
liveBuffer[firstIndex + oldLiveCount] = newIndex;
Particle p;
#if 1 // @TODO: insert proper logic here
p.absorption = 1.0;
p.anisotropy = 0.5;
p.isEmissive = 0;
p.position = float3(694, -42, -300);
p.velocity = (Hash1To3(scene.frameSeed + 17.69 * float(id.x)) * 2.0 - 1.0) * 20.0;
p.radius = 10.0;
p.scattering = float3(1.0, 0.5, 0.0);
p.lifeTime = 0.0;
p.froxelMin = int3(0, 0, 0);
p.froxelMax = int3(-1, -1, -1);
#endif
particleBuffer[firstIndex + newIndex] = p;
}

View file

@ -0,0 +1,50 @@
/*
===========================================================================
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/>.
===========================================================================
*/
// GPU particle system: sets up buffers for 1 emission step and 1 simulation step
#include "common.hlsli"
#include "scene_view.h.hlsli"
cbuffer RootConstants
{
uint emitterBufferIndex;
uint indirectBufferIndex;
uint emitterIndex;
uint emitCountRequest;
}
[numthreads(1, 1, 1)]
void cs(uint3 id : SV_DispatchThreadID)
{
RWStructuredBuffer<ParticleEmitter> emitterBuffer = ResourceDescriptorHeap[emitterBufferIndex];
RWByteAddressBuffer indirectBuffer = ResourceDescriptorHeap[indirectBufferIndex];
uint deadCount = emitterBuffer[emitterIndex].deadCount;
uint liveCount = emitterBuffer[emitterIndex].liveCount2;
uint emitCount = min(deadCount, emitCountRequest);
indirectBuffer.Store(0, (emitCount + 63) / 64); // emit.x
indirectBuffer.Store(12, (liveCount + emitCount + 63) / 64); // simulate.x
emitterBuffer[emitterIndex].liveCount = liveCount;
emitterBuffer[emitterIndex].liveCount2 = 0;
emitterBuffer[emitterIndex].emitCount = emitCount;
}

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/>.
===========================================================================
*/
// GPU particle system: particle simulation step
#include "common.hlsli"
#include "scene_view.h.hlsli"
cbuffer RootConstants
{
uint particleBufferIndex;
uint liveSrcBufferIndex;
uint liveDstBufferIndex;
uint deadBufferIndex;
uint emitterBufferIndex;
uint emitterIndex;
}
[numthreads(64, 1, 1)]
void cs(uint3 id : SV_DispatchThreadID)
{
RWStructuredBuffer<ParticleEmitter> emitterBuffer = ResourceDescriptorHeap[emitterBufferIndex];
if(id.x >= emitterBuffer[emitterIndex].liveCount)
{
return;
}
RWStructuredBuffer<Particle> particleBuffer = ResourceDescriptorHeap[particleBufferIndex];
RWStructuredBuffer<uint> liveSrcBuffer = ResourceDescriptorHeap[liveSrcBufferIndex];
uint firstIndex = emitterBuffer[emitterIndex].firstIndex;
uint particleIndex = liveSrcBuffer[firstIndex + id.x];
Particle p = particleBuffer[firstIndex + particleIndex];
if(p.lifeTime >= emitterBuffer[emitterIndex].maxSeconds)
{
RWStructuredBuffer<uint> deadBuffer = ResourceDescriptorHeap[deadBufferIndex];
uint oldDeadCount;
InterlockedAdd(emitterBuffer[emitterIndex].deadCount, 1, oldDeadCount);
deadBuffer[firstIndex + oldDeadCount] = particleIndex;
return;
}
RWStructuredBuffer<uint> liveDstBuffer = ResourceDescriptorHeap[liveDstBufferIndex];
#if 1 // @TODO: insert proper logic here
float dt = 1.0 / 60.0;
float t = p.lifeTime / emitterBuffer[emitterIndex].maxSeconds;
float fadeOut = 1.0 - EaseOutQuad(t);
float3 velocityNoise = Hash3To3(p.position); // want Curl
float scatterNoise = Hash2To1(float2(particleIndex * 1337.0, p.lifeTime)); // want Simplex
float3 color0 = float3(1, 1, 1);
float3 color1 = float3(0.25, 0.25, 0.25);
float3 color = lerp(color0, color1, t);
p.lifeTime += dt;
p.position += p.velocity * dt;
p.velocity *= 0.85;
p.velocity += velocityNoise * (1.0 + 0.077 * 100.0);
p.velocity += float3(0, 0, 1) * (1.0 + 0.210 * 100.0);
p.absorption = fadeOut * 0.02;
p.scattering = fadeOut * (color + float(0.5 + scatterNoise).xxx) * 0.02;
p.radius = 5.0 + 2.0 * EaseInCubic(t);
#endif
particleBuffer[firstIndex + particleIndex] = p;
uint oldLiveCount;
InterlockedAdd(emitterBuffer[emitterIndex].liveCount2, 1, oldLiveCount);
liveDstBuffer[firstIndex + oldLiveCount] = particleIndex;
}

View file

@ -33,19 +33,39 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
# pragma pack(push, 4)
#endif
// @TODO: move out
struct ParticleEmitter
{
uint firstIndex;
uint liveCount; // post-emission, pre-simulation
uint liveCount2; // post-simulation
uint deadCount;
uint totalCount;
uint emitCount; // how many added this frame
float maxSeconds;
};
// @TODO: move out
struct Particle
{
// needed for injection, set by user
float3 position;
float radius;
float3 scattering; // or emissive
float absorption;
float anisotropy;
uint isEmissive;
// needed for injection, private
int3 froxelMin;
int3 froxelMax;
// extra data for simulation only
float3 velocity;
float lifeTime; // in seconds
};
// @TODO: move out
#define MAX_PARTICLES 8192
#define MAX_PARTICLES (1 << 20)
#define MAX_PARTICLE_EMITTERS (1 << 10)
// @TODO: move out
struct DynamicLight
@ -56,9 +76,6 @@ struct DynamicLight
float padding;
};
// @TODO: move out
#define SCENE_VIEW_MAX_LIGHTS 32
#if !defined(__cplusplus)
struct ExtinctionCascade
{
@ -230,7 +247,7 @@ struct SceneView
float4 clipPlane;
float4 debug;
float3 cameraPosition;
float sunIntensity;
float sunIntensityDL;
float3 sunDirection;
float zNear;
float3 sunColor;
@ -240,9 +257,9 @@ struct SceneView
float3 cameraForward;
float prevZFar;
float3 cameraLeft;
float padding0;
float sunIntensityVL;
float3 cameraUp;
float padding1;
float pointLightIntensityVL;
float3 linearDepthConstants;
float frameSeed;
float3 ambientColor;
@ -282,7 +299,7 @@ struct SceneView
return ray;
}
#if 1 // exponential depth distribution like The Last of Us Part II
#if 0 // exponential depth distribution like The Last of Us Part II
float TLOU2SliceToViewDepth(float slice, float C)
{
@ -295,7 +312,8 @@ struct SceneView
float TLOU2ViewDepthToSlice(float viewDepth, float C)
{
const float Q = 1.0;
float slice = C * (log2(exp2(Q) + viewDepth) - Q);
float logArg = exp2(Q) + viewDepth;
float slice = logArg <= 0.0 ? -666.0 : (C * (log2(logArg) - Q));
return slice;
}
@ -328,6 +346,24 @@ struct SceneView
return viewDepth;
}
#elif 1 // quadratic depth distribution
float FroxelViewDepthToZ01Ex(float viewDepth, float sliceCount, float zn, float zf)
{
float depth01 = (viewDepth - zn) / (zf - zn);
depth01 = sqrt(depth01);
return depth01;
}
float FroxelZ01ToViewDepth(float depth01, float sliceCount)
{
depth01 *= depth01;
float viewDepth = zNear + (zFar - zNear) * depth01;
return viewDepth;
}
#else // linear depth distribution
float FroxelViewDepthToZ01Ex(float viewDepth, float sliceCount, float zn, float zf)
@ -400,6 +436,19 @@ struct SceneView
return index;
}
int2 FroxelSphereZExtents(float3 positionWS, float radius, float3 textureSize)
{
float4 positionVSw = mul(viewMatrix, float4(positionWS, 1));
float viewDepth = -positionVSw.z / positionVSw.w;
float viewDepthMin = viewDepth - radius;
float viewDepthMax = viewDepth + radius;
float zMin01 = FroxelViewDepthToZ01(viewDepthMin, textureSize.z);
float zMax01 = FroxelViewDepthToZ01(viewDepthMax, textureSize.z);
int2 extents = int2(zMin01 * textureSize.z, ceil(zMax01 * textureSize.z));
return extents;
}
// @TODO: validate new logic
float3 FroxelReproject01(int3 currIndex, float3 textureSize)
{
@ -420,6 +469,82 @@ struct SceneView
return prevTC;
}
float FroxelVolume(uint3 index, float3 textureSize)
{
float3 tcBase = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize;
float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize;
float3 posL = FroxelTCToWorldSpace(tcBase + float3(-halfTexel.x, 0, 0), textureSize);
float3 posR = FroxelTCToWorldSpace(tcBase + float3(halfTexel.x, 0, 0), textureSize);
float w = distance(posL, posR);
float3 posU = FroxelTCToWorldSpace(tcBase + float3(0, halfTexel.y, 0), textureSize);
float3 posD = FroxelTCToWorldSpace(tcBase + float3(0, -halfTexel.y, 0), textureSize);
float h = distance(posU, posD);
float3 posF = FroxelTCToWorldSpace(tcBase + float3(0, 0, halfTexel.z), textureSize);
float3 posB = FroxelTCToWorldSpace(tcBase + float3(0, 0, -halfTexel.z), textureSize);
float d = distance(posF, posB);
float volume = w * h * d;
return volume;
}
float3 FroxelAverageDimensions(uint3 index, float3 textureSize)
{
float3 tcBase = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize;
float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize;
float3 posL = FroxelTCToWorldSpace(tcBase + float3(-halfTexel.x, 0, 0), textureSize);
float3 posR = FroxelTCToWorldSpace(tcBase + float3(halfTexel.x, 0, 0), textureSize);
float w = distance(posL, posR);
float3 posU = FroxelTCToWorldSpace(tcBase + float3(0, halfTexel.y, 0), textureSize);
float3 posD = FroxelTCToWorldSpace(tcBase + float3(0, -halfTexel.y, 0), textureSize);
float h = distance(posU, posD);
float3 posF = FroxelTCToWorldSpace(tcBase + float3(0, 0, -halfTexel.z), textureSize);
float3 posB = FroxelTCToWorldSpace(tcBase + float3(0, 0, halfTexel.z), textureSize);
float d = distance(posF, posB);
float3 dimensions = float3(w, h, d);
return dimensions;
}
float3 FroxelMaxDimensions(uint3 index, float3 textureSize)
{
float3 tcBase = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize;
float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize;
float3 posL = FroxelTCToWorldSpace(tcBase + float3(-halfTexel.x, 0, halfTexel.z), textureSize);
float3 posR = FroxelTCToWorldSpace(tcBase + float3(halfTexel.x, 0, halfTexel.z), textureSize);
float w = distance(posL, posR);
float3 posU = FroxelTCToWorldSpace(tcBase + float3(0, halfTexel.y, halfTexel.z), textureSize);
float3 posD = FroxelTCToWorldSpace(tcBase + float3(0, -halfTexel.y, halfTexel.z), textureSize);
float h = distance(posU, posD);
float3 posF = FroxelTCToWorldSpace(tcBase + float3(0, 0, -halfTexel.z), textureSize);
float3 posB = FroxelTCToWorldSpace(tcBase + float3(0, 0, halfTexel.z), textureSize);
float d = distance(posF, posB);
float3 dimensions = float3(w, h, d);
return dimensions;
}
#if 0
void FroxelAABB(out float boxMin, out float3 boxMax, int3 index, float3 textureSize)
{
float3 tc = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize;
float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize;
float3 pointWS;
ClearBoundingBox(boxMin, boxMax);
pointWS = FroxelTCToWorldSpace(tc + float3(-halfTexel.x, 0, 0), textureSize);
ExpandBoundingBox(boxMin, boxMax, pointWS);
pointWS = FroxelTCToWorldSpace(tc + float3(halfTexel.x, 0, 0), textureSize);
ExpandBoundingBox(boxMin, boxMax, pointWS);
pointWS = FroxelTCToWorldSpace(tc + float3(0, -halfTexel.y, 0), textureSize);
ExpandBoundingBox(boxMin, boxMax, pointWS);
pointWS = FroxelTCToWorldSpace(tc + float3(0, halfTexel.y, 0), textureSize);
ExpandBoundingBox(boxMin, boxMax, pointWS);
pointWS = FroxelTCToWorldSpace(tc + float3(0, 0, -halfTexel.z), textureSize);
ExpandBoundingBox(boxMin, boxMax, pointWS);
pointWS = FroxelTCToWorldSpace(tc + float3(0, 0, halfTexel.z), textureSize);
ExpandBoundingBox(boxMin, boxMax, pointWS);
}
#endif
float3 ExtinctionIndexToWorldSpace(int3 index, float3 textureSize, float worldScale)
{
return AABoxIndexToWorldSpace(index, cameraPosition, textureSize, worldScale);

View file

@ -106,12 +106,11 @@ float4 ps(VOut input) : SV_Target
}
}
// @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;
float3 color = vis * scene.sunColor * scene.sunIntensityDL * lambert;
float4 result = float4(color, 1);
return result;

View file

@ -70,13 +70,27 @@ struct FogVolume
#endif
};
struct Tile
{
uint firstParticle;
uint particleCount;
uint particleIndex;
uint pad0;
};
struct Counters
{
uint particleCount;
uint tileCount;
};
#if defined(__cplusplus)
#pragma pack(pop)
#endif
#if !defined(__cplusplus)
// defines voxel sampling offsets for super-sampled fog injection
// defines voxel sampling offsets for super-sampled fog/particle injection
#if defined(VOXEL_SUPERSAMPLING_1X)
static const int VoxelSampleCount = 1;
@ -203,4 +217,46 @@ static const float3 VoxelSamples[65] =
};
#endif
// defines sphere sampling offsets for super-sampled particle injection
#if defined(SPHERE_SUPERSAMPLING_1X)
static const int SphereSampleCount = 1;
static const float3 SphereSamples[1] =
{
float3(0, 0, 0)
};
#elif defined(SPHERE_SUPERSAMPLING_2X)
static const int SphereSampleCount = 13;
static const float3 SphereSamples[13] =
{
float3(-0.000070998815798, -0.000005560663499, -0.666666662862852),
float3(-0.184273763669020, -0.567104158597683, -0.298128324331843),
float3(0.482401099870031, -0.350493177757963, -0.298141167291191),
float3(-0.596343845795264, -0.000022877791848, -0.298024263279293),
float3(0.482326151451980, 0.350363917328204, -0.298414231403936),
float3(-0.184272548445118, 0.567103656272574, -0.298130030986924),
float3(0.000000000000000, 0.000000000000000, 0.000000000000000),
float3(0.184241177452589, -0.567097520996929, 0.298161088431179),
float3(-0.482404802195943, -0.350487719732367, 0.298141593172679),
float3(0.596236953290593, -0.000018337625111, 0.298238058669458),
float3(-0.482641375963473, 0.350268310738867, 0.298016538374417),
float3(0.184262272798761, 0.567101475888716, 0.298140529469443),
float3(-0.000135987657896, -0.000010037080285, 0.666666652721627)
};
#elif defined(SPHERE_SUPERSAMPLING_2X_OLD) // 8x with 0,0,0 added
static const int SphereSampleCount = 9;
static const float3 SphereSamples[9] =
{
float3(0, 0, 0),
float3(-0.378024926878978, -0.378024806866870, -0.317879684372474),
float3(0.378024762722181, -0.378024944670528, -0.317879715711806),
float3(-0.378024789075323, 0.378024882734286, -0.317879758027500),
float3(0.378024900525835, 0.378024744930628, -0.317879789366832),
float3(-0.000000084265328, -0.534607831462839, 0.317879788951622),
float3(-0.534607849254392, 0.000000128410024, 0.317879759029907),
float3(0.534607875607536, -0.000000066473779, 0.317879714709398),
float3(0.000000110618471, 0.534607893399083, 0.317879684787684)
};
#endif
#endif

View file

@ -23,15 +23,13 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#include "common.hlsli"
#include "scene_view.h.hlsli"
#include "light_grid.h.hlsli"
cbuffer RootConstants
{
float3 centerPosition;
LightGridRC lightGridRC;
float sphereScale;
float3 worldScale;
uint lightGridTextureAIndex;
uint lightGridTextureBIndex;
}
struct VOut
@ -44,16 +42,15 @@ struct VOut
VOut vs(uint vertexId : SV_VertexID)
{
Texture3D lightGridTexture = ResourceDescriptorHeap[lightGridTextureAIndex];
SceneView scene = GetSceneView();
LightGrid lightGrid = GetLightGrid(lightGridRC);
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);
int3 voxelIndex = int3(UnflattenIndex(flatVoxelIndex, lightGrid.textureSize));
float3 voxelCenter = lightGrid.IndexToWorldSpace(voxelIndex);
float2 quadPosition = QuadFromVertexID(vertexIndex);
float radius = 0.5 * sphereScale * min3(worldScale.x, worldScale.y, worldScale.z);
float radius = 0.5 * sphereScale * min3(lightGrid.worldScale.x, lightGrid.worldScale.y, lightGrid.worldScale.z);
float3 up = scene.cameraUp;
float3 forward = normalize(voxelCenter - scene.cameraPosition);
float3 right = normalize(cross(forward, up));
@ -79,9 +76,8 @@ VOut vs(uint vertexId : SV_VertexID)
float4 ps(VOut input) : SV_Target
{
Texture3D lightGridTextureA = ResourceDescriptorHeap[lightGridTextureAIndex];
Texture3D lightGridTextureB = ResourceDescriptorHeap[lightGridTextureBIndex];
SceneView scene = GetSceneView();
LightGrid lightGrid = GetLightGrid(lightGridRC);
float3 rayDir = normalize(input.positionWS - scene.cameraPosition);
float t = RaytraceSphere(scene.cameraPosition, rayDir, input.sphere.xyz, input.sphere.w);
@ -90,11 +86,10 @@ float4 ps(VOut input) : SV_Target
discard;
}
float4 payloadA = lightGridTextureA[input.voxelIndex];
float4 payloadB = lightGridTextureB[input.voxelIndex];
LightGridSample ambient = lightGrid.SampleAtIndex(input.voxelIndex);
float3 hitPosition = scene.cameraPosition + rayDir * t;
float3 normal = normalize(hitPosition - input.sphere.xyz);
float3 color = AmbientColor(payloadA, payloadB, normal, scene.ambientColor);
float3 color = ambient.GetAmbientColor(normal, scene.ambientColor, 1, 1);
float4 result = float4(color * 0.5, 1);
return result;

View file

@ -0,0 +1,113 @@
/*
===========================================================================
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 a NanoVDB volume into the extinction volume
#include "common.hlsli"
#include "scene_view.h.hlsli"
#include "vl_common.h.hlsli"
#include "vl_nanovdb.hlsli"
#define LOW_QUALITY_INJECTION_MODE 1
cbuffer RootConstants
{
float4 packedTransform[4];
float2 packedTransform2;
uint nanovdbBufferIndex;
uint extinctionTextureIndex;
uint densityGridByteOffset;
uint densityGridByteOffset2;
uint linearInterpolation;
float worldScale;
float densityExtinctionScale;
float t;
}
[numthreads(4, 4, 4)]
void cs(uint3 id : SV_DispatchThreadID)
{
RWTexture3D<float> extinctionTexture = ResourceDescriptorHeap[extinctionTextureIndex];
uint3 textureSize = GetTextureSize(extinctionTexture);
if(any(id >= textureSize))
{
return;
}
pnanovdb_buf_t nanovdbBuffer = ResourceDescriptorHeap[nanovdbBufferIndex];
SceneView scene = GetSceneView();
float3 textureSizeF = float3(textureSize);
float3 tcBase = (float3(id) + float3(0.5, 0.5, 0.5)) / textureSizeF;
float3 voxelPosition = scene.ExtinctionIndexToWorldSpace(id, textureSizeF, worldScale);
Transform transform = DecodeTransform(packedTransform, packedTransform2);
#if LOW_QUALITY_INJECTION_MODE
// big perf boost, tiny visual impact
transform.stepSize = 0.5 * worldScale.xxx;
#endif
if(linearInterpolation != 0u)
{
SampleResult extResult1 = CreateSampleResult();
if(densityGridByteOffset)
{
Grid grid1 = GetGrid(nanovdbBuffer, densityGridByteOffset);
if(grid1.OverlapsAxisAlignedBox(scene, id, textureSizeF, worldScale, transform))
{
extResult1 = grid1.GetAxisAlignedBoxAverage(scene, voxelPosition, worldScale.xxx, transform);
}
}
SampleResult extResult2 = CreateSampleResult();
if(densityGridByteOffset2 > 0)
{
Grid grid2 = GetGrid(nanovdbBuffer, densityGridByteOffset2);
if(grid2.OverlapsAxisAlignedBox(scene, id, textureSizeF, worldScale, transform))
{
extResult2 = grid2.GetAxisAlignedBoxAverage(scene, voxelPosition, worldScale.xxx, transform);
}
}
if(extResult1.sum > 0.0 || extResult2.sum > 0.0)
{
float extinction1 = (extResult1.sum / float(extResult1.maxSampleCount));
float extinction2 = (extResult2.sum / float(extResult2.maxSampleCount));
float extinction = lerp(extinction1, extinction2, t) * densityExtinctionScale;
extinctionTexture[id] += extinction;
}
}
else if(densityGridByteOffset > 0)
{
Grid grid = GetGrid(nanovdbBuffer, densityGridByteOffset);
if(grid.OverlapsAxisAlignedBox(scene, id, textureSizeF, worldScale, transform))
{
SampleResult extResult = CreateSampleResult();
extResult = grid.GetAxisAlignedBoxAverage(scene, voxelPosition, worldScale.xxx, transform);
if(extResult.sum > 0.0)
{
float extinction = (extResult.sum / float(extResult.maxSampleCount)) * densityExtinctionScale;
extinctionTexture[id] += extinction;
}
}
}
}

View file

@ -1,100 +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/>.
===========================================================================
*/
// 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

@ -0,0 +1,68 @@
/*
===========================================================================
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: depth test screen tiles
#include "common.hlsli"
#include "scene_view.h.hlsli"
cbuffer RootConstants
{
uint3 frustumTextureSize;
uint frustumVisTextureIndex;
uint depthMip;
}
[numthreads(8, 8, 1)]
void cs(uint3 id : SV_DispatchThreadID)
{
SceneView scene = GetSceneView();
RWTexture2D<uint> frustumVisTexture = ResourceDescriptorHeap[frustumVisTextureIndex];
uint2 visTextureSize = GetTextureSize(frustumVisTexture);
uint2 tileIndex = id.xy;
if(any(tileIndex >= visTextureSize.xy))
{
return;
}
Texture2D<float2> depthMinMaxTexture = ResourceDescriptorHeap[scene.depthMinMaxTextureIndex];
float3 frustumTextureSizeF = float3(frustumTextureSize);
// x=min is furthest with reverse Z
float tileDepthZW = depthMinMaxTexture.mips[depthMip][id.xy].x;
float tileDepth = scene.LinearDepth(tileDepthZW);
uint furthestVisibleIndex = 0;
for(uint d = 1; d < frustumTextureSize.z; d++)
{
// Z offset is 0 because we want the closest part of the froxel
float3 froxelTC = (float3(tileIndex.xy, d) + float3(0.5, 0.5, 0)) / frustumTextureSizeF;
float froxelDepth = scene.FroxelZ01ToViewDepth(froxelTC.z, frustumTextureSizeF.z);
if(froxelDepth < tileDepth)
{
furthestVisibleIndex = d;
}
else
{
break;
}
}
frustumVisTexture[tileIndex] = furthestVisibleIndex;
}

View file

@ -0,0 +1,398 @@
/*
===========================================================================
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 a NanoVDB volume into the material textures
//#define PREVIEW_MODE 1
// preview mode is a faster and less complex version of the shader
// that doesn't crash the AMD/Intel shader compilers
// this shader considers smoke to be fully isotropic (g = 0)
// in "Creating the Atmospheric World of Red Dead Redemption 2" by Fabian Bauer,
// g is 0.1 with strong backscatter for smoke
// RDR2 uses a phase function with multiple octaves of HG
// it fakes backscattering for a specific extinction range
// the phase function returns the max. of the sum of octaves and the backscatter
#include "common.hlsli"
#include "scene_view.h.hlsli"
#include "light_grid.h.hlsli"
#include "vl_common.h.hlsli"
#include "vl_nanovdb.hlsli"
cbuffer RootConstants
{
float4 packedTransform[4];
float2 packedTransform2;
uint nanovdbBufferIndex;
uint blackbodyTextureIndex;
LightGridRC lightGridRC;
uint materialTextureAIndex;
uint materialTextureBIndex;
uint materialTextureCIndex;
uint scatterExtTextureIndex;
uint frustumVisTextureIndex;
uint densityGridByteOffset;
uint flamesGridByteOffset;
uint densityGridByteOffset2;
uint flamesGridByteOffset2;
uint linearInterpolation;
uint accurateOverlapTest;
uint ambientAngularCoverage;
float densityExtinctionScale;
float densityAlbedo;
float flamesEmissionScale;
float flamesTemperatureScale;
float stepScale; // for super-sampling froxel average
float transStepScale; // for under-sampling ambient transmittance
float t;
}
static const float SqrtOneThird = sqrt(1.0 / 3.0);
#if PREVIEW_MODE
static const float3 Dirs[4] =
{
float3(-SqrtOneThird, -SqrtOneThird, SqrtOneThird),
float3(SqrtOneThird, SqrtOneThird, SqrtOneThird),
float3(-SqrtOneThird, SqrtOneThird, SqrtOneThird),
float3(SqrtOneThird, -SqrtOneThird, SqrtOneThird)
};
#else
static const float3 Dirs[6 + 8] =
{
float3(1, 0, 0),
float3(-1, 0, 0),
float3(0, 1, 0),
float3(0, -1, 0),
float3(0, 0, 1),
float3(0, 0, -1),
float3(-SqrtOneThird, -SqrtOneThird, -SqrtOneThird),
float3(-SqrtOneThird, -SqrtOneThird, SqrtOneThird),
float3(-SqrtOneThird, SqrtOneThird, -SqrtOneThird),
float3(-SqrtOneThird, SqrtOneThird, SqrtOneThird),
float3(SqrtOneThird, -SqrtOneThird, -SqrtOneThird),
float3(SqrtOneThird, -SqrtOneThird, SqrtOneThird),
float3(SqrtOneThird, SqrtOneThird, -SqrtOneThird),
float3(SqrtOneThird, SqrtOneThird, SqrtOneThird)
};
#endif
float3 BlackbodyColor(float temperatureK, Texture2D blackbodyTexture, SamplerState blackbodySampler)
{
const float minT = 800;
const float maxT = 12000;
float t = saturate((temperatureK - minT) / (maxT - minT));
float3 emission = blackbodyTexture.SampleLevel(blackbodySampler, float2(t, 0.5), 0.0).rgb;
return emission;
}
// Stefan-Boltzmann law
float BlackbodyRadiation(float temperatureK)
{
const float sigma = 5.670373e-8;
float T = temperatureK;
float T2 = T * T;
float T4 = T2 * T2;
float radiation = (T4 * sigma) / PI;
return radiation;
}
float3 BlackbodyEmission(float temperatureK, Texture2D blackbodyTexture, SamplerState blackbodySampler)
{
const float scale = 1.0e-6;
float3 color = BlackbodyColor(temperatureK, blackbodyTexture, blackbodySampler);
float radiation = BlackbodyRadiation(temperatureK);
float colorBrightness = Brightness(color);
float3 result = (color * radiation * scale) / colorBrightness;
return result;
}
struct SampleRequest
{
pnanovdb_buf_t buffer;
uint gridByteOffset;
float3 froxelPosition;
int3 froxelIndex;
float3 textureSize;
float3 froxelSize;
Transform transform;
SceneView scene;
bool ambientLight;
int3 froxelId;
LightGrid lightGrid;
};
#if PREVIEW_MODE
SampleResult SampleFroxel(SampleRequest request)
{
SampleResult result = CreateSampleResult();
if(request.gridByteOffset == 0)
{
// no grid requested
return result;
}
Grid grid = GetGrid(request.buffer, request.gridByteOffset);
float3 indexF = grid.WorldToIndex(request.froxelPosition, request.transform);
bool overlaps = IsInRange(indexF, grid.bboxMin, grid.bboxMax);
if(!overlaps)
{
// no grid/froxel intersection
return result;
}
result = grid.GetFroxelAverage(request.scene, request.froxelPosition, request.froxelSize, request.transform);
if(!request.ambientLight)
{
// no ambient light requested
return result;
}
const float inScatterScale = 2.0;
const float stepScale = 8.0;
const uint dirCount = 4;
float jitterScale = 0.5 * Hash3To1(request.froxelId);
float extScale = densityExtinctionScale;
float trans = 0.0;
[unroll]
for(uint i = 0; i < dirCount; i++)
{
float3 dir = Dirs[i];
float3 step = dir * stepScale;
trans += grid.RaymarchTransmittance(request.froxelPosition + step * jitterScale, step, request.transform, extScale);
}
result.inScatteredLight = request.scene.ambientColor * (inScatterScale * trans * request.scene.ambientIntensity);
return result;
}
#else
SampleResult SampleFroxel(SampleRequest request)
{
SampleResult result = CreateSampleResult();
if(request.gridByteOffset == 0)
{
// no grid requested
return result;
}
Grid grid = GetGrid(request.buffer, request.gridByteOffset);
bool overlaps;
if(accurateOverlapTest != 0u)
{
overlaps = grid.OverlapsFroxel(request.scene, request.froxelIndex, request.textureSize, request.transform);
}
else
{
float3 indexF = grid.WorldToIndex(request.froxelPosition, request.transform);
overlaps = IsInRange(indexF, grid.bboxMin, grid.bboxMax);
}
if(!overlaps)
{
// no grid/froxel intersection
return result;
}
result = grid.GetFroxelAverage(request.scene, request.froxelPosition, request.froxelSize, request.transform);
if(!request.ambientLight)
{
// no ambient light requested
return result;
}
float3x3 rotation = RandomRotationMatrix3x3(float3(request.froxelId));
float jitterScale = 0.5 * Hash3To1(request.froxelId);
float stepScale = transStepScale;
uint dirCount = ambientAngularCoverage != 0 ? 14 : 6;
float localWeight = ambientAngularCoverage != 0 ? 1.75 : 0.75;
if(lightGridRC.isAvailable)
{
LightGridSample ambient = request.lightGrid.SampleAtPosition(request.froxelPosition);
float extScale = densityExtinctionScale;
float3 lightDir = ambient.GetLightDirection();
float3 dirTransStep = lightDir * stepScale;
float dirTrans = grid.RaymarchTransmittance(request.froxelPosition + dirTransStep * jitterScale, dirTransStep, request.transform, extScale);
float3 globalColor = float3(0, 0, 0);
for(uint i = 0; i < dirCount; i++)
{
float3 dir = mul(rotation, Dirs[i]);
float scale = stepScale;
float3 step = dir * stepScale;
LightGridSample sample = request.lightGrid.SampleAtPosition(request.froxelPosition + dir * scale * 0.5);
float trans = grid.RaymarchTransmittance(request.froxelPosition + step * jitterScale, step, request.transform, extScale);
float3 color = ColorAtBrightness(sample.GetGlobalColor(), 0.5);
globalColor += color * trans;
}
float3 cameraDir = normalize(request.froxelPosition - request.scene.cameraPosition);
float localScale = dot(-cameraDir, lightDir) * 0.5 + 0.5; // wraps around
float3 localColor = ColorAtBrightness(ambient.GetLocalColor(), 0.5) * localScale * dirTrans;
float3 color = (globalColor + localColor * localWeight) / (float(dirCount) + localWeight);
result.inScatteredLight = color * request.scene.ambientIntensity;
}
else
{
float extScale = densityExtinctionScale;
float trans = 0.0;
for(uint i = 0; i < dirCount; i++)
{
float3 dir = Dirs[i];
float3 step = dir * stepScale;
trans += grid.RaymarchTransmittance(request.froxelPosition + step * jitterScale, step, request.transform, extScale);
}
trans /= float(dirCount);
result.inScatteredLight = request.scene.ambientColor * (trans * request.scene.ambientIntensity);
}
return result;
}
#endif
[numthreads(4, 4, 4)]
void cs(uint3 id : SV_DispatchThreadID)
{
RWTexture3D<float4> materialTextureA = ResourceDescriptorHeap[materialTextureAIndex];
uint3 textureSize = GetTextureSize(materialTextureA);
if(any(id >= textureSize))
{
return;
}
RWTexture2D<uint> frustumVisTexture = ResourceDescriptorHeap[frustumVisTextureIndex];
uint furthestVisibleFroxelZIndex = frustumVisTexture[id.xy];
if(id.z > furthestVisibleFroxelZIndex)
{
return;
}
SceneView scene = GetSceneView();
LightGrid lightGrid;
if(lightGridRC.isAvailable)
{
lightGrid = GetLightGrid(lightGridRC);
}
pnanovdb_buf_t nanovdbBuffer = ResourceDescriptorHeap[nanovdbBufferIndex];
Texture2D blackbodyTexture = ResourceDescriptorHeap[blackbodyTextureIndex];
SamplerState blackbodySampler = SamplerDescriptorHeap[scene.linearClampSamplerIndex];
RWTexture3D<float4> materialTextureB = ResourceDescriptorHeap[materialTextureBIndex];
RWTexture3D<float> materialTextureC = ResourceDescriptorHeap[materialTextureCIndex];
RWTexture3D<float4> scatterExtTexture = ResourceDescriptorHeap[scatterExtTextureIndex];
float3 textureSizeF = float3(textureSize);
float3 froxelPosition = scene.FroxelIndexToWorldSpace(int3(id), textureSizeF);
float3 froxelSize = scene.FroxelMaxDimensions(id, textureSizeF);
Transform transform = DecodeTransform(packedTransform, packedTransform2);
transform.stepSize *= stepScale;
#if !PREVIEW_MODE
if(linearInterpolation != 0u)
{
SampleRequest r;
r.buffer = nanovdbBuffer;
r.froxelIndex = id;
r.froxelPosition = froxelPosition;
r.froxelSize = froxelSize;
r.gridByteOffset = densityGridByteOffset;
r.scene = scene;
r.textureSize = textureSizeF;
r.transform = transform;
r.lightGrid = lightGrid;
r.froxelId = id;
r.ambientLight = true;
SampleResult extResult1 = SampleFroxel(r);
r.gridByteOffset = densityGridByteOffset2;
SampleResult extResult2 = SampleFroxel(r);
r.ambientLight = false;
r.gridByteOffset = flamesGridByteOffset;
SampleResult emResult1 = SampleFroxel(r);
r.gridByteOffset = flamesGridByteOffset2;
SampleResult emResult2 = SampleFroxel(r);
if(extResult1.sum > 0.0 || extResult2.sum > 0.0)
{
float extinction1 = (extResult1.sum / float(extResult1.maxSampleCount));
float extinction2 = (extResult2.sum / float(extResult2.maxSampleCount));
float extinction = lerp(extinction1, extinction2, t) * densityExtinctionScale;
float scatter = extinction * densityAlbedo;
float absorption = extinction - scatter;
float coverage1 = float(extResult1.sampleCount) / float(extResult1.maxSampleCount);
float coverage2 = float(extResult2.sampleCount) / float(extResult2.maxSampleCount);
float coverage = lerp(coverage1, coverage2, t);
float3 inScattered = lerp(extResult1.inScatteredLight, extResult2.inScatteredLight, t);
materialTextureA[id] += float4(scatter.xxx, absorption);
materialTextureC[id] += coverage;
scatterExtTexture[id] += float4(scatter * inScattered, 0);
}
if(emResult1.sum > 0.0 || emResult2.sum > 0.0)
{
float Tnorm1 = emResult1.sum / float(emResult1.maxSampleCount);
float Tnorm2 = emResult2.sum / float(emResult2.maxSampleCount);
float T = lerp(Tnorm1, Tnorm2, t) * flamesTemperatureScale;
float3 emission = BlackbodyEmission(T, blackbodyTexture, blackbodySampler) * flamesEmissionScale;
materialTextureB[id] += float4(emission, 0.0);
}
}
else
#endif
{
SampleRequest r;
r.buffer = nanovdbBuffer;
r.froxelIndex = id;
r.froxelPosition = froxelPosition;
r.froxelSize = froxelSize;
r.gridByteOffset = densityGridByteOffset;
r.scene = scene;
r.textureSize = textureSizeF;
r.transform = transform;
r.lightGrid = lightGrid;
r.froxelId = id;
r.ambientLight = true;
SampleResult extResult = SampleFroxel(r);
r.gridByteOffset = flamesGridByteOffset;
r.ambientLight = false;
SampleResult emResult = SampleFroxel(r);
if(extResult.sum > 0.0)
{
float extinction = (extResult.sum / float(extResult.maxSampleCount)) * densityExtinctionScale;
float scatter = extinction * densityAlbedo;
float absorption = extinction - scatter;
float coverage = float(extResult.sampleCount) / float(extResult.maxSampleCount);
materialTextureA[id] += float4(scatter.xxx, absorption);
materialTextureC[id] += coverage;
scatterExtTexture[id] += float4(scatter * extResult.inScatteredLight, 0);
}
if(emResult.sum > 0.0)
{
float Tnorm = emResult.sum / float(emResult.maxSampleCount);
float T = Tnorm * flamesTemperatureScale;
float3 emission = BlackbodyEmission(T, blackbodyTexture, blackbodySampler) * flamesEmissionScale;
materialTextureB[id] += float4(emission, 0.0);
}
}
}

View file

@ -18,60 +18,129 @@ 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
// volumetric lighting: inject particles into the frustum material textures
// 0 -> particle is a point
// 1 -> particle is a sphere, no super-sampling
// 2 -> particle is a sphere, 2x super-sampling
#define QUALITY 1
#include "common.hlsli"
#include "scene_view.h.hlsli"
#if QUALITY >= 2
#define VOXEL_SUPERSAMPLING_2X
#define SPHERE_SUPERSAMPLING_2X
#else
#define VOXEL_SUPERSAMPLING_1X
#define SPHERE_SUPERSAMPLING_1X
#endif
#include "vl_common.h.hlsli"
cbuffer RootConstants
{
uint3 tileScale;
uint pad0;
uint3 tileResolution;
uint particleBufferIndex;
uint particleCount;
uint materialTextureAIndex;
uint materialTextureBIndex;
uint materialTextureCIndex;
uint tileBufferIndex;
uint tileIndexBufferIndex;
uint particleIndexBufferIndex;
uint counterBufferIndex;
uint tileCount;
}
[numthreads(1024, 1, 1)]
void cs(uint3 dtid : SV_DispatchThreadID, uint gidx : SV_GroupIndex)
#define VOXEL_COUNT 512
#define THREAD_COUNT 512
groupshared uint s_scatterR[VOXEL_COUNT];
groupshared uint s_scatterG[VOXEL_COUNT];
groupshared uint s_scatterB[VOXEL_COUNT];
groupshared uint s_absorption[VOXEL_COUNT];
groupshared uint s_emissiveR[VOXEL_COUNT];
groupshared uint s_emissiveG[VOXEL_COUNT];
groupshared uint s_emissiveB[VOXEL_COUNT];
groupshared uint s_anisotropy[VOXEL_COUNT];
groupshared uint s_coverage[VOXEL_COUNT];
static const float g_materialScale = 131072.0;
static const float g_anisotropyScale = 1024.0;
static const float g_coverageScale = 1024.0;
float FroxelMinSize(SceneView scene, uint3 id, float3 textureSize)
{
uint tileIndex = dtid.x / 1024;
if(tileIndex >= tileCount)
{
return;
float3 center = scene.FroxelIndexToWorldSpace(id, textureSize);
float w = distance(center, scene.FroxelIndexToWorldSpace(id + uint3(1, 0, 0), textureSize));
float h = distance(center, scene.FroxelIndexToWorldSpace(id + uint3(0, 1, 0), textureSize));
float d = distance(center, scene.FroxelIndexToWorldSpace(id + uint3(0, 0, 1), textureSize));
float size = min3(w, h, d);
return size;
}
RWStructuredBuffer<uint3> tileBuffer = ResourceDescriptorHeap[tileBufferIndex];
[numthreads(THREAD_COUNT, 1, 1)]
void cs(uint3 dtid : SV_DispatchThreadID, uint gtidx : SV_GroupIndex)
{
uint tileIndexIndex = dtid.x / THREAD_COUNT;
#if 0
RWStructuredBuffer<Counters> counterBuffer = ResourceDescriptorHeap[counterBufferIndex];
Counters counters = counterBuffer[0];
//if(tileIndexIndex >= tileCount)
if(tileIndexIndex >= counters.tileCount)
{
return; // should never happen
}
#endif
RWStructuredBuffer<uint> tileIndexBuffer = ResourceDescriptorHeap[tileIndexBufferIndex];
RWTexture3D<float4> materialTextureA = ResourceDescriptorHeap[materialTextureAIndex];
uint3 textureSize = GetTextureSize(materialTextureA);
uint3 tileCornerIndex = tileBuffer[tileIndex];
uint3 tileThreadIndex = UnflattenIndex(gidx, tileScale);
uint tileIndex = tileIndexBuffer[tileIndexIndex];
uint3 tileCornerIndex = UnflattenIndex(tileIndex, tileResolution);
uint3 tileThreadIndex = UnflattenIndex(gtidx, tileScale);
uint3 id = tileCornerIndex * tileScale + tileThreadIndex;
if(any(id >= textureSize))
int3 froxelIndexMin = int3(tileCornerIndex * tileScale);
int3 froxelIndexMax = int3(tileCornerIndex * tileScale) + int3(tileScale) - int3(1, 1, 1);
uint smIndex = FlattenIndex(id - uint3(froxelIndexMin), tileScale);
if(smIndex < VOXEL_COUNT)
{
return;
s_scatterR[smIndex] = 0;
s_scatterG[smIndex] = 0;
s_scatterB[smIndex] = 0;
s_absorption[smIndex] = 0;
s_emissiveR[smIndex] = 0;
s_emissiveG[smIndex] = 0;
s_emissiveB[smIndex] = 0;
s_anisotropy[smIndex] = 0;
s_coverage[smIndex] = 0;
}
GroupMemoryBarrierWithGroupSync();
RWTexture3D<float4> materialTextureB = ResourceDescriptorHeap[materialTextureBIndex];
RWTexture3D<float> materialTextureC = ResourceDescriptorHeap[materialTextureCIndex];
StructuredBuffer<Particle> particleBuffer = ResourceDescriptorHeap[particleBufferIndex];
RWStructuredBuffer<Particle> particleBuffer = ResourceDescriptorHeap[particleBufferIndex];
RWStructuredBuffer<Tile> tileBuffer = ResourceDescriptorHeap[tileBufferIndex];
RWStructuredBuffer<uint> particleIndexBuffer = ResourceDescriptorHeap[particleIndexBufferIndex];
SceneView scene = GetSceneView();
Tile tile = tileBuffer[tileIndex];
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++)
#if QUALITY > 0
float3 left = scene.cameraLeft;
float3 up = scene.cameraUp;
float3 fwd = scene.cameraForward;
float froxelMinSize = FroxelMinSize(scene, (tileCornerIndex * tileScale) + (tileScale / 2) - uint3(1, 1, 1), textureSizeF);
#endif
uint particleCount = tile.particleCount;
uint firstParticle = tile.firstParticle;
for(uint i = smIndex; i < particleCount; i += THREAD_COUNT)
{
Particle particle = particleBuffer[i];
uint particleIndex = particleIndexBuffer[firstParticle + i];
Particle particle = particleBuffer[particleIndex];
float3 scattering;
float3 emissive;
[flatten]
@ -86,29 +155,124 @@ void cs(uint3 dtid : SV_DispatchThreadID, uint gidx : SV_GroupIndex)
emissive = float3(0, 0, 0);
}
#if QUALITY > 0
bool isBigParticle = particle.radius >= froxelMinSize;
bool isMediumParticle = particle.radius >= 0.125 * froxelMinSize;
int3 boxMin = particle.froxelMin - froxelIndexMin;
int3 boxMax = particle.froxelMax - froxelIndexMin;
boxMin = max(boxMin, int3(0, 0, 0));
boxMax = min(boxMax, int3(tileScale) - int3(1, 1, 1));
if(all(boxMax < boxMin))
{
continue;
}
for(int z = boxMin.z; z <= boxMax.z; z++)
{
for(int y = boxMin.y; y <= boxMax.y; y++)
{
for(int x = boxMin.x; x <= boxMax.x; x++)
{
uint3 froxelGroupThreadId = uint3(x, y, z);
uint froxelFlatIndex = FlattenIndex(froxelGroupThreadId, tileScale);
uint3 froxelThreadId = tileCornerIndex * tileScale + froxelGroupThreadId;
float particleCoverage = 0.0;
if(isBigParticle)
{
float3 tcBase = (float3(froxelThreadId) + float3(0.5, 0.5, 0.5)) / textureSizeF;
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)
float coverage = sqrt(saturate(1.0 - dist / particle.radius));
coverage *= 0.25 + 0.75 * SimplexNoise3D(0.25 * (position - particle.position));
particleCoverage += coverage;
}
particleCoverage /= float(VoxelSampleCount);
}
else if(isMediumParticle)
{
float3 basePosition = scene.FroxelIndexToWorldSpace(froxelThreadId, textureSizeF);
for(uint s = 0; s < SphereSampleCount; s++)
{
float3 position = basePosition + particle.radius * SphereSamples[s];
int3 sampleVoxelIdx = scene.FroxelWorldSpaceToIndex(position, textureSizeF);
bool isInVoxel = all(froxelThreadId == uint3(sampleVoxelIdx));
float dist = isInVoxel ? distance(position, particle.position) : 0.0;
float coverage = sqrt(saturate(1.0 - dist / particle.radius));
particleCoverage += coverage;
}
particleCoverage /= float(SphereSampleCount);
particleCoverage *= min(SphereVolume(particle.radius) / scene.FroxelVolume(froxelThreadId, textureSizeF), 1.0);
}
else
{
// assumes the sphere's density is not 1 but 1/distance
float density = 2.0 * PI * particle.radius * particle.radius;
particleCoverage = min(density / scene.FroxelVolume(froxelThreadId, textureSizeF), 1.0);
}
if(particleCoverage == 0.0)
{
continue;
}
float coverage = sqrt(saturate(1.0 - dist / particle.radius));
particleCoverage += coverage;
uint4 scatterAbs = g_materialScale * particleCoverage * float4(scattering, particle.absorption);
uint4 emissiveAniso = float4(g_materialScale.xxx, g_anisotropyScale) * particleCoverage * float4(emissive, particle.anisotropy);
uint coverage = g_coverageScale * particleCoverage;
InterlockedAdd(s_scatterR[froxelFlatIndex], scatterAbs.r);
InterlockedAdd(s_scatterG[froxelFlatIndex], scatterAbs.g);
InterlockedAdd(s_scatterB[froxelFlatIndex], scatterAbs.b);
InterlockedAdd(s_absorption[froxelFlatIndex], scatterAbs.w);
InterlockedAdd(s_emissiveR[froxelFlatIndex], emissiveAniso.r);
InterlockedAdd(s_emissiveG[froxelFlatIndex], emissiveAniso.g);
InterlockedAdd(s_emissiveB[froxelFlatIndex], emissiveAniso.b);
InterlockedAdd(s_anisotropy[froxelFlatIndex], emissiveAniso.w);
InterlockedAdd(s_coverage[froxelFlatIndex], coverage);
}
}
particleCoverage /= float(VoxelSampleCount);
accumScatterAbs += particleCoverage * float4(scattering, particle.absorption);
accumEmissiveAniso += particleCoverage * float4(emissive, particle.anisotropy);
accumCoverage += particleCoverage;
}
if(accumCoverage > 0.0)
#else
int3 froxelIndex = scene.FroxelWorldSpaceToIndex(particle.position, textureSizeF);
if(!IsInRange(froxelIndex, froxelIndexMin, froxelIndexMax))
{
continue;
}
uint froxelFlatIndex = FlattenIndex(uint3(froxelIndex) - uint3(froxelIndexMin), tileScale);
float particleCoverage = 1.0;
uint4 scatterAbs = g_materialScale * particleCoverage * float4(scattering, particle.absorption);
uint4 emissiveAniso = float4(g_materialScale.xxx, g_anisotropyScale) * particleCoverage * float4(emissive, particle.anisotropy);
uint coverage = g_coverageScale * particleCoverage;
InterlockedAdd(s_scatterR[froxelFlatIndex], scatterAbs.r);
InterlockedAdd(s_scatterG[froxelFlatIndex], scatterAbs.g);
InterlockedAdd(s_scatterB[froxelFlatIndex], scatterAbs.b);
InterlockedAdd(s_absorption[froxelFlatIndex], scatterAbs.w);
InterlockedAdd(s_emissiveR[froxelFlatIndex], emissiveAniso.r);
InterlockedAdd(s_emissiveG[froxelFlatIndex], emissiveAniso.g);
InterlockedAdd(s_emissiveB[froxelFlatIndex], emissiveAniso.b);
InterlockedAdd(s_anisotropy[froxelFlatIndex], emissiveAniso.w);
InterlockedAdd(s_coverage[froxelFlatIndex], coverage);
#endif
}
GroupMemoryBarrierWithGroupSync();
if(smIndex < VOXEL_COUNT &&
s_coverage[smIndex] > 0 &&
all(id < textureSize))
{
float4 accumScatterAbs = float4(s_scatterR[smIndex], s_scatterG[smIndex], s_scatterB[smIndex], s_absorption[smIndex]) / g_materialScale;
float4 accumEmissiveAniso = float4(s_emissiveR[smIndex], s_emissiveG[smIndex], s_emissiveB[smIndex], s_anisotropy[smIndex]) / float4(g_materialScale.xxx, g_anisotropyScale);
float accumCoverage = s_coverage[smIndex] / g_coverageScale;
materialTextureA[id] += accumScatterAbs;
materialTextureB[id] += accumEmissiveAniso;
materialTextureC[id] += accumCoverage;

View file

@ -23,18 +23,14 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#include "common.hlsli"
#include "scene_view.h.hlsli"
#include "light_grid.h.hlsli"
cbuffer RootConstants
{
float3 centerPosition;
LightGridRC lightGridRC;
uint materialTextureAIndex;
float3 worldScale;
uint scatterExtTextureIndex;
uint ambientLightTextureAIndex;
uint ambientLightTextureBIndex;
uint ambientSamplerIndex;
uint isLightGridAvailable;
}
[numthreads(4, 4, 4)]
@ -47,40 +43,28 @@ void cs(uint3 id : SV_DispatchThreadID)
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));
ExtinctionCascade cascade = scene.GetExtinctionCascade(scene.extinctionWorldScale.y);
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);
if(lightGridRC.isAvailable != 0)
{
LightGrid lightGrid = GetLightGrid(lightGridRC);
LightGridSample ambient = lightGrid.SampleAtPosition(positionWS);
float3 ambientColor = ambient.GetAmbientColor(normalWS, scene.ambientColor, 1, 1);
float3 inScattering = scattering * ambientColor * scene.ambientIntensity;
scatterExtTexture[id] = float4(inScattering, extinction);
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);
scatterExtTexture[id] += float4(inScattering, extinction);
}
}

View file

@ -70,7 +70,7 @@ void cs(uint3 id : SV_DispatchThreadID)
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:
float3 lightRaw = light.color * intensity * scene.pointLightIntensityVL;
float2 froxelTC = (float2(id.xy) + float2(0.5, 0.5)) / float2(textureSize.xy);
float2 froxelNDC = TCToNDC(froxelTC);
float3 cameraRay = scene.CamerayRay(froxelNDC);

View file

@ -34,24 +34,6 @@ cbuffer RootConstants
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)
{
@ -66,7 +48,8 @@ void cs(uint3 id : SV_DispatchThreadID)
RWTexture3D<float4> materialTextureB = ResourceDescriptorHeap[materialTextureBIndex];
RWTexture3D<float> sunlightVisTexture = ResourceDescriptorHeap[sunlightVisTextureIndex];
SceneView scene = GetSceneView();
float froxelSize = FroxelSize(id, float3(textureSize), scene);
float3 froxelSize3 = scene.FroxelAverageDimensions(id, float3(textureSize));
float froxelSize = max3(froxelSize3.x, froxelSize3.y, froxelSize3.z);
SunVShadowCascade cascade = scene.GetSunVShadowCascade(froxelSize);
float3 positionWS = scene.FroxelIndexToWorldSpace(id, textureSize);
@ -80,7 +63,7 @@ void cs(uint3 id : SV_DispatchThreadID)
float3 scattering = materialTextureA[id].rgb;
float anisotropy = materialTextureB[id].a;
float phase = HenyeyGreenstein(cosTheta, anisotropy);
float3 inScattering = vis * scene.sunColor * scene.sunIntensity * scattering * phase;
float3 inScattering = vis * scene.sunColor * scene.sunIntensityVL * scattering * phase;
scatterExtTexture[id].rgb += inScattering;
}

View file

@ -0,0 +1,157 @@
/*
===========================================================================
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 on the X/Y axis to propagate light from emissive froxels
#include "common.hlsli"
#include "scene_view.h.hlsli"
cbuffer RootConstants
{
uint materialTextureAIndex;
uint materialTextureBIndex;
float emissiveScatter; // how much of the light emitted sideways is reflected towards the camera
}
[numthreads(8, 8, 1)]
void cs(uint3 id : SV_DispatchThreadID)
{
RWTexture3D<float4> materialTextureA = ResourceDescriptorHeap[materialTextureAIndex];
uint3 textureSize = GetTextureSize(materialTextureA);
#if DIRECTION_NX || DIRECTION_PX
if(any(id.xy >= textureSize.yz))
#else
if(any(id.xy >= textureSize.xz))
#endif
{
return;
}
SceneView scene = GetSceneView();
RWTexture3D<float4> materialTextureB = ResourceDescriptorHeap[materialTextureBIndex];
#if DIRECTION_PX
uint3 coords = uint3(0, id.x, id.y);
uint3 index0 = coords;
float3 tc0 = (float3(index0) + float3(0, 0.5, 0.5)) / textureSize;
float3 prevPosition = scene.FroxelTCToWorldSpace(tc0, float3(textureSize));
float3 accumEmit = float3(0, 0, 0);
for(uint x = 1; x < textureSize.x; x++)
{
uint3 prevIndex = uint3(x - 1, coords.yz);
uint3 currIndex = uint3(x, coords.yz);
float3 tc = (float3(currIndex) + float3(0, 0.5, 0.5)) / textureSize;
float3 currPosition = scene.FroxelTCToWorldSpace(tc, float3(textureSize));
float4 scatterAbs = materialTextureA[prevIndex];
float3 emissive = materialTextureB[prevIndex].rgb;
float scatter = Brightness(scatterAbs.xyz);
float extinction = scatter + scatterAbs.w;
float dist = distance(currPosition, prevPosition);
float froxelTrans = Transmittance(dist, extinction);
accumEmit = (accumEmit + emissive) * froxelTrans;
float3 extraEmit = accumEmit * emissiveScatter * scatter;
if(any(extraEmit > 0.0))
{
materialTextureB[currIndex].rgb += extraEmit;
}
prevPosition = currPosition;
}
#elif DIRECTION_NX
uint3 coords = uint3(textureSize.x - 1, id.x, id.y);
uint3 index0 = coords;
float3 tc0 = (float3(index0) + float3(1, 0.5, 0.5)) / textureSize;
float3 prevPosition = scene.FroxelTCToWorldSpace(tc0, float3(textureSize));
float3 accumEmit = float3(0, 0, 0);
for(int x = textureSize.x - 2; x >= 0; x--)
{
uint3 prevIndex = uint3(x + 1, coords.yz);
uint3 currIndex = uint3(x, coords.yz);
float3 tc = (float3(currIndex) + float3(1, 0.5, 0.5)) / textureSize;
float3 currPosition = scene.FroxelTCToWorldSpace(tc, float3(textureSize));
float4 scatterAbs = materialTextureA[prevIndex];
float3 emissive = materialTextureB[prevIndex].rgb;
float scatter = Brightness(scatterAbs.xyz);
float extinction = scatter + scatterAbs.w;
float dist = distance(currPosition, prevPosition);
float froxelTrans = Transmittance(dist, extinction);
accumEmit = (accumEmit + emissive) * froxelTrans;
float3 extraEmit = accumEmit * emissiveScatter * scatter;
if(any(extraEmit > 0.0))
{
materialTextureB[currIndex].rgb += extraEmit;
}
prevPosition = currPosition;
}
#elif DIRECTION_PY
uint3 index0 = uint3(id.x, 0, id.y);
float3 tc0 = (float3(index0) + float3(0.5, 0, 0.5)) / textureSize;
float3 prevPosition = scene.FroxelTCToWorldSpace(tc0, float3(textureSize));
float3 accumEmit = float3(0, 0, 0);
for(uint y = 1; y < textureSize.y; y++)
{
uint3 prevIndex = uint3(id.x, y - 1, id.y);
uint3 currIndex = uint3(id.x, y + 0, id.y);
float3 tc = (float3(currIndex) + float3(0.5, 0, 0.5)) / textureSize;
float3 currPosition = scene.FroxelTCToWorldSpace(tc, float3(textureSize));
float4 scatterAbs = materialTextureA[prevIndex];
float3 emissive = materialTextureB[prevIndex].rgb;
float scatter = Brightness(scatterAbs.xyz);
float extinction = scatter + scatterAbs.w;
float dist = distance(currPosition, prevPosition);
float froxelTrans = Transmittance(dist, extinction);
accumEmit = (accumEmit + emissive) * froxelTrans;
float3 extraEmit = accumEmit * emissiveScatter * scatter;
if(any(extraEmit > 0.0))
{
materialTextureB[currIndex].rgb += extraEmit;
}
prevPosition = currPosition;
}
#elif DIRECTION_NY
uint3 index0 = uint3(id.x, textureSize.y - 1, id.y);
float3 tc0 = (float3(index0) + float3(0.5, 1, 0.5)) / textureSize;
float3 prevPosition = scene.FroxelTCToWorldSpace(tc0, float3(textureSize));
float3 accumEmit = float3(0, 0, 0);
for(int y = textureSize.y - 2; y >= 0; y--)
{
uint3 prevIndex = uint3(id.x, y + 1, id.y);
uint3 currIndex = uint3(id.x, y + 0, id.y);
float3 tc = (float3(currIndex) + float3(0.5, 1, 0.5)) / textureSize;
float3 currPosition = scene.FroxelTCToWorldSpace(tc, float3(textureSize));
float4 scatterAbs = materialTextureA[prevIndex];
float3 emissive = materialTextureB[prevIndex].rgb;
float scatter = Brightness(scatterAbs.xyz);
float extinction = scatter + scatterAbs.w;
float dist = distance(currPosition, prevPosition);
float froxelTrans = Transmittance(dist, extinction);
accumEmit = (accumEmit + emissive) * froxelTrans;
float3 extraEmit = accumEmit * emissiveScatter * scatter;
if(any(extraEmit > 0.0))
{
materialTextureB[currIndex].rgb += extraEmit;
}
prevPosition = currPosition;
}
#else
float MissingDirectionMacro[-1];
#endif
}

View file

@ -42,8 +42,19 @@ void cs(uint3 id : SV_DispatchThreadID)
return;
}
// integScatter is computed using Frostbite's analytical solution:
// Int(S * T(Z) * dZ) == S * (1 - T(Z)) / extinction
// Even if the extinction coefficient is constant all along a given line segment of length Z,
// the transmittance is different at every point.
// We compute the final scatter/emissive using an analytical solution like Frostbite does.
// Integral(T(x) * dx) == Integral(e^(-extinction*x) * dZ) [0 to Z]
// == [e^(-extinction*x) / (-extinction)] [0 to Z]
// == (-1 / extinction) * [e^(-Z*extinction)] [0 to Z]
// == (-1 / extinction) * [T(Z)] [0 to Z]
// == (-1 / extinction) * (T(Z) - T(0))
// == (-1 / extinction) * (T(Z) - 1)
// == (1 / extinction) * (1 - T(Z))
// == (1 - T(Z)) / extinction
// The scatter/emissive coefficients are considered uniform in each froxel.
// They are therefore constants that can be pulled out of the integral, hence their omission.
SceneView scene = GetSceneView();
RWTexture3D<float4> resolveTexture = ResourceDescriptorHeap[resolveTextureIndex];
@ -58,14 +69,14 @@ void cs(uint3 id : SV_DispatchThreadID)
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;
float3 froxelEmissive = materialTextureB[index].rgb;
float3 froxelScatter = froxelScatterExt.rgb;
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;
float froxelTransInteg = (1.0 - froxelTrans) / (froxelExtinction == 0.0 ? 1.0 : froxelExtinction);
accumScatter += (accumTrans * froxelTransInteg) * (froxelScatter + froxelEmissive);
accumTrans *= froxelTrans;
resolveTexture[index] = float4(accumScatter, accumTrans);
prevPosition = currPosition;

View file

@ -30,6 +30,7 @@ cbuffer RootConstants
{
float3 jitter;
uint visTextureIndex;
uint frustumVisTextureIndex;
uint depthMip;
}
@ -43,9 +44,17 @@ void cs(uint3 id : SV_DispatchThreadID)
return;
}
RWTexture2D<uint> frustumVisTexture = ResourceDescriptorHeap[frustumVisTextureIndex];
uint furthestVisibleFroxelZIndex = frustumVisTexture[id.xy];
if(id.z > furthestVisibleFroxelZIndex)
{
// this helps fix issues like dark spots around opaque geometry set against the skybox
visTexture[id] = 0.0;
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);
@ -57,14 +66,5 @@ void cs(uint3 id : SV_DispatchThreadID)
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

@ -25,6 +25,15 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
#include "scene_view.h.hlsli"
#if defined(TYPE_FLOAT4)
typedef float4 Type;
#elif defined(TYPE_FLOAT)
typedef float Type;
#else
#pragma message "define TYPE_FLOAT4 or TYPE_FLOAT"
#endif
cbuffer RootConstants
{
uint currTextureIndex;
@ -36,7 +45,7 @@ cbuffer RootConstants
[numthreads(4, 4, 4)]
void cs(uint3 id : SV_DispatchThreadID)
{
RWTexture3D<float> currTexture = ResourceDescriptorHeap[currTextureIndex];
RWTexture3D<Type> currTexture = ResourceDescriptorHeap[currTextureIndex];
uint3 textureSize = GetTextureSize(currTexture);
if(any(id >= textureSize))
{
@ -44,17 +53,17 @@ void cs(uint3 id : SV_DispatchThreadID)
}
SceneView scene = GetSceneView();
Texture3D<float> prevTexture = ResourceDescriptorHeap[prevTextureIndex];
Texture3D<Type> prevTexture = ResourceDescriptorHeap[prevTextureIndex];
SamplerState prevTextureSampler = SamplerDescriptorHeap[prevTextureSamplerIndex];
float3 tc = scene.FroxelReproject01(id, float3(textureSize));
float currValue = currTexture[id];
Type 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)
Type prevValue = prevTexture.SampleLevel(prevTextureSampler, tc, 0);
Type finalValue = lerp(currValue, prevValue, alpha);
if(any(finalValue != currValue))
{
currTexture[id] = finalValue;
}

View file

@ -0,0 +1,279 @@
/*
===========================================================================
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: sampling NanoVDB volumes over oriented froxels and axis-aligned boxes
#pragma once
#include "common.hlsli"
#include "scene_view.h.hlsli"
#define PNANOVDB_HLSL
#include "PNanoVDB.h"
struct Transform
{
float3x3 worldToIndex;
float3 originOffset;
float3 translation;
float3 stepSize;
void DecodeTransform(float4 transform[4], float2 transform2)
{
worldToIndex = float3x3(
transform[0].x, transform[0].y, transform[0].z,
transform[0].w, transform[1].x, transform[1].y,
transform[1].z, transform[1].w, transform[2].x);
originOffset = float3(transform[2].yzw);
translation = float3(transform[3].xyz);
stepSize = float3(transform[3].w, transform2.x, transform2.y);
}
};
Transform DecodeTransform(float4 packed[4], float2 packed2)
{
Transform transform;
transform.DecodeTransform(packed, packed2);
return transform;
}
struct SampleResult
{
float sum;
int maxSampleCount;
int sampleCount;
float transmittance;
float3 inScatteredLight;
void Clear()
{
sum = 0.0;
maxSampleCount = 1;
sampleCount = 0;
transmittance = 1.0;
inScatteredLight = float3(0, 0, 0);
}
};
SampleResult CreateSampleResult()
{
SampleResult result;
result.Clear();
return result;
}
struct Grid
{
pnanovdb_buf_t buffer;
pnanovdb_grid_handle_t grid;
pnanovdb_tree_handle_t tree;
pnanovdb_root_handle_t root;
pnanovdb_vec3_t bboxMin;
pnanovdb_vec3_t bboxMax;
pnanovdb_uint32_t gridType;
pnanovdb_readaccessor_t accessor;
void Init(pnanovdb_buf_t gridBuffer, uint gridByteOffset)
{
buffer = gridBuffer;
grid.address.byte_offset = gridByteOffset;
pnanovdb_vec3_t extBboxMin;
pnanovdb_vec3_t extBboxMax;
pnanovdb_tree_handle_t tree = pnanovdb_grid_get_tree(buffer, grid);
pnanovdb_root_handle_t root = pnanovdb_tree_get_root(buffer, tree);
bboxMin = pnanovdb_coord_to_vec3(pnanovdb_root_get_bbox_min(buffer, root));
bboxMax = pnanovdb_coord_to_vec3(pnanovdb_root_get_bbox_max(buffer, root));
gridType = pnanovdb_grid_get_grid_type(buffer, grid);
pnanovdb_readaccessor_init(accessor, root);
}
float3 WorldToIndex(float3 worldPosition, Transform transform)
{
float3 index = mul(transform.worldToIndex, worldPosition - transform.translation) - transform.originOffset;
return index;
}
float ReadFloat(pnanovdb_coord_t coords)
{
pnanovdb_uint32_t level;
pnanovdb_address_t address = pnanovdb_readaccessor_get_value_address_and_level(gridType, buffer, accessor, coords, level);
float result;
if(level == 0u && gridType != PNANOVDB_GRID_TYPE_FLOAT)
{
if(gridType == PNANOVDB_GRID_TYPE_FPN)
{
result = pnanovdb_leaf_fpn_read_float(buffer, address, coords);
}
else
{
result = pnanovdb_leaf_fp_read_float(buffer, address, coords, gridType - PNANOVDB_GRID_TYPE_FP4 + 2u);
}
}
else
{
result = pnanovdb_read_float(buffer, address);
}
return result;
}
SampleResult GetFroxelAverage(SceneView scene, float3 froxelPosition, float3 froxelSize, Transform transform)
{
int3 steps = int3(ceil(froxelSize / (2.0 * transform.stepSize)));
SampleResult result;
result.maxSampleCount = (2 * steps.x + 1) * (2 * steps.y + 1) * (2 * steps.z + 1);
result.sampleCount = 0;
result.sum = 0.0;
for(int z = -steps.z; z <= steps.z; z++)
{
float3 offZ = float(z) * transform.stepSize.z * scene.cameraForward;
for(int y = -steps.y; y <= steps.y; y++)
{
float3 offY = float(y) * transform.stepSize.y * scene.cameraUp;
for(int x = -steps.x; x <= steps.x; x++)
{
float3 offX = float(x) * transform.stepSize.x * scene.cameraLeft;
float3 worldPosition = froxelPosition + offX + offY + offZ;
float3 indexF = WorldToIndex(worldPosition, transform);
if(IsInRange(indexF, bboxMin, bboxMax))
{
int3 index = int3(indexF);
float value = ReadFloat(index);
result.sum += value;
result.sampleCount += value > 0.0 ? 1.0 : 0.0;
}
}
}
}
return result;
}
SampleResult GetAxisAlignedBoxAverage(SceneView scene, float3 voxelPosition, float3 voxelSize, Transform transform)
{
int3 steps = int3(voxelSize / (2.0 * transform.stepSize));
SampleResult result;
result.maxSampleCount = (2 * steps.x + 1) * (2 * steps.y + 1) * (2 * steps.z + 1);
result.sampleCount = 0;
result.sum = 0.0;
for(int z = -steps.z; z <= steps.z; z++)
{
for(int y = -steps.y; y <= steps.y; y++)
{
for(int x = -steps.x; x <= steps.x; x++)
{
float3 worldPosition = voxelPosition + float3(x, y, z) * transform.stepSize;
float3 indexF = WorldToIndex(worldPosition, transform);
int3 index = int3(indexF);
float value = ReadFloat(index);
result.sum += value;
result.sampleCount += value > 0.0 ? 1.0 : 0.0;
}
}
}
return result;
}
void ExpandFroxelBoundingBox(inout float3 boxMin, inout float3 boxMax, SceneView scene, float3 tc, float3 textureSize, Transform transform)
{
float3 pointWS = scene.FroxelTCToWorldSpace(tc, textureSize);
float3 indexF = WorldToIndex(pointWS, transform);
ExpandBoundingBox(boxMin, boxMax, indexF);
}
bool OverlapsFroxel(SceneView scene, int3 index, float3 textureSize, Transform transform)
{
float3 tc = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize;
float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize;
float3 froxelMin, froxelMax;
ClearBoundingBox(froxelMin, froxelMax);
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(-halfTexel.x, 0, 0), textureSize, transform);
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(halfTexel.x, 0, 0), textureSize, transform);
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(0, -halfTexel.y, 0), textureSize, transform);
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(0, halfTexel.y, 0), textureSize, transform);
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(0, 0, -halfTexel.z), textureSize, transform);
ExpandFroxelBoundingBox(froxelMin, froxelMax, scene, tc + float3(0, 0, halfTexel.z), textureSize, transform);
bool overlaps = all(froxelMax >= bboxMin) && all(froxelMin <= bboxMax);
return overlaps;
}
void ExpandAABB(inout float3 boxMin, inout float3 boxMax, SceneView scene, float3 tc, float3 textureSize, float worldScale, Transform transform)
{
float3 pointWS = scene.ExtinctionTCToWorldSpace(tc, textureSize, worldScale);
float3 indexF = WorldToIndex(pointWS, transform);
ExpandBoundingBox(boxMin, boxMax, indexF);
}
bool OverlapsAxisAlignedBox(SceneView scene, int3 index, float3 textureSize, float worldScale, Transform transform)
{
float3 tc = (float3(index) + float3(0.5, 0.5, 0.5)) / textureSize;
float3 halfTexel = float3(0.5, 0.5, 0.5) / textureSize;
float3 voxelMin, voxelMax;
ClearBoundingBox(voxelMin, voxelMax);
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(-halfTexel.x, 0, 0), textureSize, worldScale, transform);
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(halfTexel.x, 0, 0), textureSize, worldScale, transform);
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(0, -halfTexel.y, 0), textureSize, worldScale, transform);
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(0, halfTexel.y, 0), textureSize, worldScale, transform);
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(0, 0, -halfTexel.z), textureSize, worldScale, transform);
ExpandAABB(voxelMin, voxelMax, scene, tc + float3(0, 0, halfTexel.z), textureSize, worldScale, transform);
bool overlaps = all(voxelMax >= bboxMin) && all(voxelMin <= bboxMax);
return overlaps;
}
float RaymarchTransmittance(float3 position, float3 step, Transform transform, float extinctionScale)
{
float stepDist = length(step);
float accumTrans = 1.0;
while(true)
{
position += step;
float3 indexF = WorldToIndex(position, transform);
if(!IsInRange(indexF, bboxMin, bboxMax))
{
break;
}
int3 index = int3(indexF);
float ext = ReadFloat(index) * extinctionScale;
float sampleTrans = Transmittance(stepDist, ext);
accumTrans *= saturate(sampleTrans);
}
return accumTrans;
}
};
Grid GetGrid(pnanovdb_buf_t buffer, uint gridByteOffset)
{
Grid grid;
grid.Init(buffer, gridByteOffset);
return grid;
}

View file

@ -18,38 +18,42 @@ 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: update indirect dispatch buffer for particle injection
// volumetric lighting particles: clear all froxel tiles and global counters
#include "common.hlsli"
#include "vl_common.h.hlsli"
#include "scene_view.h.hlsli"
cbuffer RootConstants
{
uint3 tileResolution;
uint counterBufferIndex;
uint tileBufferIndex;
uint dispatchBufferIndex;
uint particleTileBufferIndex;
uint tileCount;
}
[numthreads(4, 4, 4)]
[numthreads(64, 1, 1)]
void cs(uint3 id : SV_DispatchThreadID)
{
if(any(id >= tileResolution))
if(id.x >= tileCount)
{
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)
RWStructuredBuffer<Tile> tileBuffer= ResourceDescriptorHeap[tileBufferIndex];
if(id.x == 0)
{
uint workIndex;
dispatchBuffer.InterlockedAdd(0, 1, workIndex);
tileWorkBuffer[workIndex] = id;
RWStructuredBuffer<Counters> counterBuffer = ResourceDescriptorHeap[counterBufferIndex];
Counters counters;
counters.particleCount = 0;
counters.tileCount = 0;
counterBuffer[0] = counters;
}
Tile tile;
tile.firstParticle = 0;
tile.particleCount = 0;
tile.particleIndex = 0;
tile.pad0 = 0;
tileBuffer[id.x] = tile;
}

View file

@ -0,0 +1,88 @@
/*
===========================================================================
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 particles: count the number of particles in each froxel tile
#include "common.hlsli"
#include "vl_common.h.hlsli"
#include "scene_view.h.hlsli"
cbuffer RootConstants
{
uint3 fullResolution;
uint tileBufferIndex;
uint3 tileResolution;
uint pad0;
uint3 tileScale;
uint pad1;
uint particleBufferIndex;
uint emitterBufferIndex;
uint liveBufferIndex;
uint emitterIndex;
}
[numthreads(64, 1, 1)]
void cs(uint3 id : SV_DispatchThreadID)
{
RWStructuredBuffer<ParticleEmitter> emitterBuffer = ResourceDescriptorHeap[emitterBufferIndex];
ParticleEmitter emitter = emitterBuffer[emitterIndex];
if(id.x >= emitter.liveCount2)
{
return;
}
RWStructuredBuffer<Particle> particleBuffer = ResourceDescriptorHeap[particleBufferIndex];
RWStructuredBuffer<Tile> tileBuffer= ResourceDescriptorHeap[tileBufferIndex];
RWStructuredBuffer<uint> liveBuffer = ResourceDescriptorHeap[liveBufferIndex];
SceneView scene = GetSceneView();
uint firstIndex = emitter.firstIndex;
uint particleIndex = liveBuffer[firstIndex + id.x];
Particle particle = particleBuffer[firstIndex + particleIndex];
float3 P = particle.position;
float r = particle.radius;
float3 fwd = scene.cameraForward;
float4 extentsXYNDC = ProjectedSphereExtentsNDC(particle.position, particle.radius, scene.viewMatrix, scene.projectionMatrix);
float4 extentsXYFroxel = (extentsXYNDC * 0.5 + float(0.5).xxxx) * float4(fullResolution.xy, fullResolution.xy);
int2 extentsZFroxel = scene.FroxelSphereZExtents(particle.position, particle.radius, fullResolution);
int3 boxMin = int3(extentsXYFroxel.x, extentsXYFroxel.y, extentsZFroxel.x);
int3 boxMax = int3(ceil(extentsXYFroxel.z), ceil(extentsXYFroxel.w), extentsZFroxel.y);
particleBuffer[firstIndex + particleIndex].froxelMin = max(boxMin, int3(0, 0, 0));
particleBuffer[firstIndex + particleIndex].froxelMax = min(boxMax, int3(fullResolution) - int3(1, 1, 1));
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 flatTileIndex = FlattenIndex(tileIndex, tileResolution);
InterlockedAdd(tileBuffer[flatTileIndex].particleCount, 1);
}
}
}
}

View file

@ -18,53 +18,49 @@ 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
// volumetric lighting particles: create final live particle index array
#include "common.hlsli"
#include "vl_common.h.hlsli"
#include "scene_view.h.hlsli"
cbuffer RootConstants
{
uint3 fullResolution;
uint tileBufferIndex;
uint emitterIndex;
uint3 tileResolution;
uint particleBufferIndex;
uint maxParticleIndexCount;
uint3 tileScale;
uint particleCount;
uint tileBufferIndex;
uint emitterBufferIndex;
uint particleBufferIndex;
uint liveBufferIndex;
uint indexBufferIndex;
}
[numthreads(64, 1, 1)]
void cs(uint3 id : SV_DispatchThreadID)
{
uint particleIndex = id.x;
if(particleIndex >= particleCount)
RWStructuredBuffer<ParticleEmitter> emitterBuffer = ResourceDescriptorHeap[emitterBufferIndex];
ParticleEmitter emitter = emitterBuffer[emitterIndex];
if(id.x >= emitter.liveCount2)
{
return;
}
StructuredBuffer<Particle> particleBuffer = ResourceDescriptorHeap[particleBufferIndex];
RWByteAddressBuffer tileBuffer = ResourceDescriptorHeap[tileBufferIndex];
RWStructuredBuffer<Particle> particleBuffer = ResourceDescriptorHeap[particleBufferIndex];
RWStructuredBuffer<Tile> tileBuffer = ResourceDescriptorHeap[tileBufferIndex];
RWStructuredBuffer<uint> liveBuffer = ResourceDescriptorHeap[liveBufferIndex];
RWStructuredBuffer<uint> indexBuffer = ResourceDescriptorHeap[indexBufferIndex];
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);
uint firstIndex = emitter.firstIndex;
uint particleIndex = liveBuffer[firstIndex + id.x];
Particle particle = particleBuffer[firstIndex + particleIndex];
int3 boxMin = particle.froxelMin / int3(tileScale);
int3 boxMax = particle.froxelMax / 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++)
@ -74,8 +70,15 @@ void cs(uint3 id : SV_DispatchThreadID)
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);
uint flatTileIndex = FlattenIndex(tileIndex, tileResolution);
uint particleWriteOffset;
InterlockedAdd(tileBuffer[flatTileIndex].particleIndex, 1, particleWriteOffset);
uint particleWriteIndex = tileBuffer[flatTileIndex].firstParticle + particleWriteOffset;
if(particleWriteOffset < tileBuffer[flatTileIndex].particleCount &&
particleWriteIndex < maxParticleIndexCount)
{
indexBuffer[particleWriteIndex] = firstIndex + particleIndex;
}
}
}
}

View file

@ -1,73 +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/>.
===========================================================================
*/
// 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,59 @@
/*
===========================================================================
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 particles: create compacted froxel tile array and compute per-tile particle offset
#include "common.hlsli"
#include "vl_common.h.hlsli"
#include "scene_view.h.hlsli"
cbuffer RootConstants
{
uint counterBufferIndex;
uint tileBufferIndex;
uint tileIndexBufferIndex;
uint tileCount;
}
[numthreads(64, 1, 1)]
void cs(uint3 id : SV_DispatchThreadID)
{
uint tileIndex = id.x;
if(tileIndex >= tileCount)
{
return;
}
RWStructuredBuffer<Tile> tileBuffer = ResourceDescriptorHeap[tileBufferIndex];
if(tileBuffer[tileIndex].particleCount == 0)
{
return;
}
RWStructuredBuffer<Counters> counterBuffer = ResourceDescriptorHeap[counterBufferIndex];
RWStructuredBuffer<uint> tileIndexBuffer = ResourceDescriptorHeap[tileIndexBufferIndex];
InterlockedAdd(counterBuffer[0].particleCount, tileBuffer[tileIndex].particleCount, tileBuffer[tileIndex].firstParticle);
uint tileIndexIndex;
InterlockedAdd(counterBuffer[0].tileCount, 1, tileIndexIndex);
tileIndexBuffer[tileIndexIndex] = tileIndex;
}

View file

@ -337,6 +337,7 @@ void RE_BeginFrame( stereoFrame_t stereoFrame )
tr.renderMode = RM_NONE;
tr.sceneCounterRT = 0;
tr.numRTSurfs = 0;
tr.hasWorldRender = qfalse;
// delayed screenshot
if ( r_delayedScreenshotPending ) {

View file

@ -1008,6 +1008,11 @@ typedef struct {
renderMode_t renderMode;
// world rendering info for the current frame
qbool hasWorldRender;
int worldRenderTimeMS;
int worldRenderTimeUS; // [0;999]
// the following are only to be used after calling R_UpdateShader()
// the save state boolean is needed because otherwise, a delayed shader load
// could change the error and warning messages of the edited shader
@ -1669,6 +1674,7 @@ void R_TransposeMatrix( const matrix4x4_t in, matrix4x4_t out );
void R_CameraPositionFromMatrix( const matrix4x4_t modelView, vec3_t cameraPos );
void R_CameraAxisVectorsFromMatrix( const matrix4x4_t modelView, vec3_t axisX, vec3_t axisY, vec3_t axisZ );
void R_MakeIdentityMatrix( matrix4x4_t m );
void R_MakeIdentityMatrix3x3( matrix3x3_t m );
void R_MakeOrthoProjectionMatrix( matrix4x4_t m, float w, float h );
// LinearDepth(depthZW, A, B, C) -> A / (B + depthZW * C)
@ -1774,6 +1780,7 @@ struct RHIInfo
qbool isCacheCoherentUMA;
qbool hasInlineRaytracing;
qbool hasBarycentrics;
qbool forceNanoVDBPreviewMode; // work-around for driver crashes (shader compiler)
};
extern RHIInfo rhiInfo;

View file

@ -456,6 +456,20 @@ void R_MakeIdentityMatrix( matrix4x4_t m )
}
void R_MakeIdentityMatrix3x3( matrix3x3_t m )
{
m[0] = 1.0f;
m[1] = 0.0f;
m[2] = 0.0f;
m[3] = 0.0f;
m[4] = 1.0f;
m[5] = 0.0f;
m[6] = 0.0f;
m[7] = 0.0f;
m[8] = 1.0f;
}
void R_MakeOrthoProjectionMatrix( matrix4x4_t m, float w, float h )
{
// 2/(r-l) 0 0 0

View file

@ -292,6 +292,11 @@ void RE_RenderScene( const refdef_t* fd, int us )
if ((tr.refdef.rdflags & RDF_NOWORLDMODEL) == 0) {
tr.sceneCounterRT++;
}
if (!tr.hasWorldRender && (tr.refdef.rdflags & RDF_NOWORLDMODEL) == 0) {
tr.hasWorldRender = qtrue;
tr.worldRenderTimeMS = tr.refdef.time;
tr.worldRenderTimeUS = tr.refdef.microSeconds;
}
// setup view parms for the initial view
//

View file

@ -268,20 +268,31 @@ void CompilePixelShader(const char* headerPath, const char* shaderPath, const ch
CompileShader(args, psExtraCount, psExtras);
}
void CompileCompute(const char* headerPath, const char* shaderPath, const char* varName)
void CompileCompute(const char* headerPath, const char* shaderPath, const char* varName, int csOptionCount = 0, ...)
{
const char* extras[] =
int csExtraCount = 4;
const char* csExtras[64] =
{
"-D", "COMPUTE_SHADER=1",
"-Vn", HeaderVariable(va("g_%s_cs", varName))
};
assert(csExtraCount + csOptionCount <= _countof(csExtras));
va_list argPtr;
va_start(argPtr, csOptionCount);
for(int i = 0; i < csOptionCount; i++)
{
csExtras[csExtraCount++] = va_arg(argPtr, const char*);
}
va_end(argPtr);
ShaderArgs args;
args.entryPoint = "cs";
args.headerPath = headerPath;
args.shaderPath = shaderPath;
args.targetProfile = targetCS;
CompileShader(args, _countof(extras), extras);
CompileShader(args, csExtraCount, csExtras);
}
void CompileUberVS(const char* headerPath, const char* shaderPath, int stageCount)
@ -455,22 +466,23 @@ void ProcessCRP()
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_extinction_injection_nanovdb",
//"vl_extinction_injection_particles",
"vl_frustum_anisotropy_average",
"vl_frustum_depth_test",
"vl_frustum_injection_fog",
"vl_frustum_injection_nanovdb",
"vl_frustum_injection_particles",
"vl_frustum_inscatter_ambient",
"vl_frustum_inscatter_point_light",
"vl_frustum_inscatter_sunlight",
"vl_frustum_raymarch",
"vl_frustum_sunlight_visibility",
"vl_frustum_temporal",
"vl_particles_clear",
"vl_particles_hit",
"vl_particles_list",
"vl_particles_tiles",
"vl_shadow_point_light",
"vl_shadow_sun"
};
@ -479,10 +491,21 @@ void ProcessCRP()
const char* const s = vlComputeShaders[i];
CompileCompute(va("%s.h", s), va("%s.hlsl", s), s);
}
CompileCompute("vl_frustum_temporal_float4.h", "vl_frustum_temporal.hlsl", "vl_frustum_temporal_float4", 1, "-D TYPE_FLOAT4=1");
CompileCompute("vl_frustum_temporal_float.h", "vl_frustum_temporal.hlsl", "vl_frustum_temporal_float", 1, "-D TYPE_FLOAT=1");
CompileCompute("vl_frustum_injection_nanovdb_lq.h", "vl_frustum_injection_nanovdb.hlsl", "vl_frustum_injection_nanovdb_lq", 1, "-D PREVIEW_MODE=1");
CompileCompute("vl_frustum_light_propagation_nx.h", "vl_frustum_light_propagation.hlsl", "vl_frustum_light_propagation_nx", 1, "-D DIRECTION_NX=1");
CompileCompute("vl_frustum_light_propagation_ny.h", "vl_frustum_light_propagation.hlsl", "vl_frustum_light_propagation_ny", 1, "-D DIRECTION_NY=1");
CompileCompute("vl_frustum_light_propagation_px.h", "vl_frustum_light_propagation.hlsl", "vl_frustum_light_propagation_px", 1, "-D DIRECTION_PX=1");
CompileCompute("vl_frustum_light_propagation_py.h", "vl_frustum_light_propagation.hlsl", "vl_frustum_light_propagation_py", 1, "-D DIRECTION_PY=1");
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");
CompileCompute("particles_clear.h", "particles_clear.hlsl", "particles_clear");
CompileCompute("particles_setup.h", "particles_setup.hlsl", "particles_setup");
CompileCompute("particles_emit.h", "particles_emit.hlsl", "particles_emit");
CompileCompute("particles_simulate.h", "particles_simulate.hlsl", "particles_simulate");
}
int main(int /*argc*/, const char** argv)

View file

@ -650,6 +650,7 @@ solution "cnq3"
kind "StaticLib"
language "C++"
AddSourcesAndHeaders("renderer")
includedirs { string.format("%s", path_src) }
if os.istarget("bsd") then
includedirs { "/usr/local/include" }
end
@ -668,6 +669,7 @@ solution "cnq3"
flags { "ExcludeFromBuild" }
filter { }
end
files { string.format("%s/renderer/shaders/**.h", path_src) }
ApplyLibProjectSettings()
includedirs { path_src.."/imgui" }
filter "action:gmake"

View file

@ -59,7 +59,7 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<PreprocessorDefinitions>DEBUG;_DEBUG;_CRT_SECURE_NO_WARNINGS;WIN32;_WIN32;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\..\cnq3tools\aftermath;..\..\..\cnq3tools\nvapi;..\..\code\imgui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\code;..\..\..\cnq3tools\aftermath;..\..\..\cnq3tools\nvapi;..\..\code\imgui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@ -83,7 +83,7 @@
<WarningLevel>Level4</WarningLevel>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<PreprocessorDefinitions>NDEBUG;_CRT_SECURE_NO_WARNINGS;WIN32;_WIN32;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\..\cnq3tools\aftermath;..\..\..\cnq3tools\nvapi;..\..\code\imgui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\code;..\..\..\cnq3tools\aftermath;..\..\..\cnq3tools\nvapi;..\..\code\imgui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<Optimization>MinSpace</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
@ -117,6 +117,7 @@
<ClInclude Include="..\..\code\renderer\grp_uber_shaders.h" />
<ClInclude Include="..\..\code\renderer\rhi_local.h" />
<ClInclude Include="..\..\code\renderer\rhi_public.h" />
<ClInclude Include="..\..\code\renderer\shaders\crp\PNanoVDB.h" />
<ClInclude Include="..\..\code\renderer\smaa_area_texture.h" />
<ClInclude Include="..\..\code\renderer\smaa_search_texture.h" />
<ClInclude Include="..\..\code\renderer\srp_local.h" />
@ -135,7 +136,9 @@
<ClCompile Include="..\..\code\renderer\crp_magnifier.cpp" />
<ClCompile Include="..\..\code\renderer\crp_main.cpp" />
<ClCompile Include="..\..\code\renderer\crp_motion_blur.cpp" />
<ClCompile Include="..\..\code\renderer\crp_nano_vdb.cpp" />
<ClCompile Include="..\..\code\renderer\crp_opaque.cpp" />
<ClCompile Include="..\..\code\renderer\crp_particles.cpp" />
<ClCompile Include="..\..\code\renderer\crp_prepass.cpp" />
<ClCompile Include="..\..\code\renderer\crp_raytracing.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sun_editor.cpp" />
@ -273,6 +276,18 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\opaque.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_clear.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_emit.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_setup.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_simulate.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\prepass.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -315,15 +330,21 @@
<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">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_nanovdb.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_depth_test.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_nanovdb.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_particles.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -336,6 +357,9 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_sunlight.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_light_propagation.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_raymarch.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -345,13 +369,16 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_temporal.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_dispatch.hlsl">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_clear.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_extinction.hlsl">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_hit.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_frustum.hlsl">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_list.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_tiles.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_point_light.hlsl">
@ -421,12 +448,14 @@
<None Include="..\..\code\renderer\shaders\crp\dof.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\fullscreen.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\gatherdof.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\light_grid.h.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\crp\vl_nanovdb.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

@ -21,6 +21,9 @@
<ClInclude Include="..\..\code\renderer\grp_uber_shaders.h" />
<ClInclude Include="..\..\code\renderer\rhi_local.h" />
<ClInclude Include="..\..\code\renderer\rhi_public.h" />
<ClInclude Include="..\..\code\renderer\shaders\crp\PNanoVDB.h">
<Filter>shaders\crp</Filter>
</ClInclude>
<ClInclude Include="..\..\code\renderer\smaa_area_texture.h" />
<ClInclude Include="..\..\code\renderer\smaa_search_texture.h" />
<ClInclude Include="..\..\code\renderer\srp_local.h" />
@ -39,7 +42,9 @@
<ClCompile Include="..\..\code\renderer\crp_magnifier.cpp" />
<ClCompile Include="..\..\code\renderer\crp_main.cpp" />
<ClCompile Include="..\..\code\renderer\crp_motion_blur.cpp" />
<ClCompile Include="..\..\code\renderer\crp_nano_vdb.cpp" />
<ClCompile Include="..\..\code\renderer\crp_opaque.cpp" />
<ClCompile Include="..\..\code\renderer\crp_particles.cpp" />
<ClCompile Include="..\..\code\renderer\crp_prepass.cpp" />
<ClCompile Include="..\..\code\renderer\crp_raytracing.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sun_editor.cpp" />
@ -177,6 +182,18 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\opaque.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_clear.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_emit.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_setup.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_simulate.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\prepass.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -219,15 +236,21 @@
<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">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_nanovdb.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_depth_test.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_nanovdb.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_particles.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -240,6 +263,9 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_sunlight.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_light_propagation.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_raymarch.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -249,13 +275,16 @@
<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">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_clear.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_extinction.hlsl">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_hit.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_frustum.hlsl">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_list.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_tiles.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_point_light.hlsl">
@ -341,6 +370,9 @@
<None Include="..\..\code\renderer\shaders\crp\gatherdof.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\light_grid.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\oit.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
@ -359,6 +391,9 @@
<None Include="..\..\code\renderer\shaders\crp\vl_common.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\vl_nanovdb.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\grp\fog.hlsli">
<Filter>shaders\grp</Filter>
</None>

View file

@ -59,7 +59,7 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<PreprocessorDefinitions>DEBUG;_DEBUG;_CRT_SECURE_NO_WARNINGS;WIN32;_WIN32;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\..\cnq3tools\aftermath;..\..\..\cnq3tools\nvapi;..\..\code\imgui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\code;..\..\..\cnq3tools\aftermath;..\..\..\cnq3tools\nvapi;..\..\code\imgui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@ -84,7 +84,7 @@
<WarningLevel>Level4</WarningLevel>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<PreprocessorDefinitions>NDEBUG;_CRT_SECURE_NO_WARNINGS;WIN32;_WIN32;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\..\cnq3tools\aftermath;..\..\..\cnq3tools\nvapi;..\..\code\imgui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\code;..\..\..\cnq3tools\aftermath;..\..\..\cnq3tools\nvapi;..\..\code\imgui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<Optimization>MinSpace</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
@ -119,6 +119,7 @@
<ClInclude Include="..\..\code\renderer\grp_uber_shaders.h" />
<ClInclude Include="..\..\code\renderer\rhi_local.h" />
<ClInclude Include="..\..\code\renderer\rhi_public.h" />
<ClInclude Include="..\..\code\renderer\shaders\crp\PNanoVDB.h" />
<ClInclude Include="..\..\code\renderer\smaa_area_texture.h" />
<ClInclude Include="..\..\code\renderer\smaa_search_texture.h" />
<ClInclude Include="..\..\code\renderer\srp_local.h" />
@ -137,7 +138,9 @@
<ClCompile Include="..\..\code\renderer\crp_magnifier.cpp" />
<ClCompile Include="..\..\code\renderer\crp_main.cpp" />
<ClCompile Include="..\..\code\renderer\crp_motion_blur.cpp" />
<ClCompile Include="..\..\code\renderer\crp_nano_vdb.cpp" />
<ClCompile Include="..\..\code\renderer\crp_opaque.cpp" />
<ClCompile Include="..\..\code\renderer\crp_particles.cpp" />
<ClCompile Include="..\..\code\renderer\crp_prepass.cpp" />
<ClCompile Include="..\..\code\renderer\crp_raytracing.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sun_editor.cpp" />
@ -275,6 +278,18 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\opaque.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_clear.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_emit.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_setup.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_simulate.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\prepass.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -317,15 +332,21 @@
<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">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_nanovdb.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_depth_test.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_nanovdb.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_particles.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -338,6 +359,9 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_sunlight.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_light_propagation.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_raymarch.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
@ -347,13 +371,16 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_temporal.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_dispatch.hlsl">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_clear.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_extinction.hlsl">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_hit.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_frustum.hlsl">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_list.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_tiles.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_point_light.hlsl">
@ -423,12 +450,14 @@
<None Include="..\..\code\renderer\shaders\crp\dof.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\fullscreen.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\gatherdof.hlsli" />
<None Include="..\..\code\renderer\shaders\crp\light_grid.h.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\crp\vl_nanovdb.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

@ -21,6 +21,9 @@
<ClInclude Include="..\..\code\renderer\grp_uber_shaders.h" />
<ClInclude Include="..\..\code\renderer\rhi_local.h" />
<ClInclude Include="..\..\code\renderer\rhi_public.h" />
<ClInclude Include="..\..\code\renderer\shaders\crp\PNanoVDB.h">
<Filter>shaders\crp</Filter>
</ClInclude>
<ClInclude Include="..\..\code\renderer\smaa_area_texture.h" />
<ClInclude Include="..\..\code\renderer\smaa_search_texture.h" />
<ClInclude Include="..\..\code\renderer\srp_local.h" />
@ -39,7 +42,9 @@
<ClCompile Include="..\..\code\renderer\crp_magnifier.cpp" />
<ClCompile Include="..\..\code\renderer\crp_main.cpp" />
<ClCompile Include="..\..\code\renderer\crp_motion_blur.cpp" />
<ClCompile Include="..\..\code\renderer\crp_nano_vdb.cpp" />
<ClCompile Include="..\..\code\renderer\crp_opaque.cpp" />
<ClCompile Include="..\..\code\renderer\crp_particles.cpp" />
<ClCompile Include="..\..\code\renderer\crp_prepass.cpp" />
<ClCompile Include="..\..\code\renderer\crp_raytracing.cpp" />
<ClCompile Include="..\..\code\renderer\crp_sun_editor.cpp" />
@ -177,6 +182,18 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\opaque.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_clear.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_emit.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_setup.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\particles_simulate.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\prepass.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -219,15 +236,21 @@
<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">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_extinction_injection_nanovdb.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_depth_test.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_nanovdb.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_injection_particles.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -240,6 +263,9 @@
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_inscatter_sunlight.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_light_propagation.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_frustum_raymarch.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
@ -249,13 +275,16 @@
<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">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_clear.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_extinction.hlsl">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_hit.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_preprocess_frustum.hlsl">
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_list.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_particles_tiles.hlsl">
<Filter>shaders\crp</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\shaders\crp\vl_shadow_point_light.hlsl">
@ -341,6 +370,9 @@
<None Include="..\..\code\renderer\shaders\crp\gatherdof.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\light_grid.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\oit.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
@ -359,6 +391,9 @@
<None Include="..\..\code\renderer\shaders\crp\vl_common.h.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\crp\vl_nanovdb.hlsli">
<Filter>shaders\crp</Filter>
</None>
<None Include="..\..\code\renderer\shaders\grp\fog.hlsli">
<Filter>shaders\grp</Filter>
</None>