mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-11-10 06:31:48 +00:00
added NanoVDB support
- added the foundation for a GPU particle system - reworked volumetric particle injection
This commit is contained in:
parent
385a75c9cd
commit
afc81437c3
50 changed files with 7938 additions and 742 deletions
|
@ -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
|
||||
|
|
|
@ -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 };
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
941
code/renderer/crp_nano_vdb.cpp
Normal file
941
code/renderer/crp_nano_vdb.cpp
Normal 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;
|
||||
}
|
255
code/renderer/crp_particles.cpp
Normal file
255
code/renderer/crp_particles.cpp
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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;
|
||||
|
||||
|
|
3
code/renderer/shaders/.gitignore
vendored
3
code/renderer/shaders/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
*.exe
|
||||
*.pdb
|
||||
*.h
|
3384
code/renderer/shaders/crp/PNanoVDB.h
Normal file
3384
code/renderer/shaders/crp/PNanoVDB.h
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
}
|
||||
|
|
141
code/renderer/shaders/crp/light_grid.h.hlsli
Normal file
141
code/renderer/shaders/crp/light_grid.h.hlsli
Normal 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
|
62
code/renderer/shaders/crp/particles_clear.hlsl
Normal file
62
code/renderer/shaders/crp/particles_clear.hlsl
Normal 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;
|
||||
}
|
74
code/renderer/shaders/crp/particles_emit.hlsl
Normal file
74
code/renderer/shaders/crp/particles_emit.hlsl
Normal 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;
|
||||
}
|
50
code/renderer/shaders/crp/particles_setup.hlsl
Normal file
50
code/renderer/shaders/crp/particles_setup.hlsl
Normal 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;
|
||||
}
|
86
code/renderer/shaders/crp/particles_simulate.hlsl
Normal file
86
code/renderer/shaders/crp/particles_simulate.hlsl
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
113
code/renderer/shaders/crp/vl_extinction_injection_nanovdb.hlsl
Normal file
113
code/renderer/shaders/crp/vl_extinction_injection_nanovdb.hlsl
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
68
code/renderer/shaders/crp/vl_frustum_depth_test.hlsl
Normal file
68
code/renderer/shaders/crp/vl_frustum_depth_test.hlsl
Normal 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;
|
||||
}
|
398
code/renderer/shaders/crp/vl_frustum_injection_nanovdb.hlsl
Normal file
398
code/renderer/shaders/crp/vl_frustum_injection_nanovdb.hlsl
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
157
code/renderer/shaders/crp/vl_frustum_light_propagation.hlsl
Normal file
157
code/renderer/shaders/crp/vl_frustum_light_propagation.hlsl
Normal 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
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
279
code/renderer/shaders/crp/vl_nanovdb.hlsli
Normal file
279
code/renderer/shaders/crp/vl_nanovdb.hlsli
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
88
code/renderer/shaders/crp/vl_particles_hit.hlsl
Normal file
88
code/renderer/shaders/crp/vl_particles_hit.hlsl
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
code/renderer/shaders/crp/vl_particles_tiles.hlsl
Normal file
59
code/renderer/shaders/crp/vl_particles_tiles.hlsl
Normal 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;
|
||||
}
|
|
@ -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 ) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue