cnq3/code/renderer/crp_volumetric_light.cpp
2024-11-04 02:45:22 +01:00

1891 lines
62 KiB
C++

/*
===========================================================================
Copyright (C) 2024 Gian 'myT' Schellenbaum
This file is part of Challenge Quake 3 (CNQ3).
Challenge Quake 3 is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Challenge Quake 3 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
===========================================================================
*/
// Cinematic Rendering Pipeline - fog, NanoVDB volumes and particles lit by the sun and local lights
#include "crp_local.h"
#include "../client/cl_imgui.h"
#include "../im3d/im3d.h"
#include "shaders/crp/scene_view.h.hlsli"
#include "shaders/crp/light_grid.h.hlsli"
#include "shaders/crp/vl_common.h.hlsli"
#include "compshaders/crp/fullscreen.h"
#include "compshaders/crp/vl_extinction_injection_fog.h"
#include "compshaders/crp/vl_extinction_injection_nanovdb.h"
//#include "compshaders/crp/vl_extinction_injection_particles.h"
#include "compshaders/crp/vl_frustum_anisotropy_average.h"
#include "compshaders/crp/vl_frustum_depth_test.h"
#include "compshaders/crp/vl_frustum_injection_fog.h"
#include "compshaders/crp/vl_frustum_injection_nanovdb.h"
#include "compshaders/crp/vl_frustum_injection_nanovdb_lq.h"
#include "compshaders/crp/vl_frustum_injection_particles.h"
#include "compshaders/crp/vl_frustum_inscatter_ambient.h"
#include "compshaders/crp/vl_frustum_inscatter_point_light.h"
#include "compshaders/crp/vl_frustum_inscatter_sunlight.h"
#include "compshaders/crp/vl_frustum_light_propagation_nx.h"
#include "compshaders/crp/vl_frustum_light_propagation_ny.h"
#include "compshaders/crp/vl_frustum_light_propagation_px.h"
#include "compshaders/crp/vl_frustum_light_propagation_py.h"
#include "compshaders/crp/vl_frustum_raymarch.h"
#include "compshaders/crp/vl_frustum_sunlight_visibility.h"
#include "compshaders/crp/vl_frustum_temporal_float.h"
#include "compshaders/crp/vl_frustum_temporal_float4.h"
#include "compshaders/crp/vl_particles_clear.h"
#include "compshaders/crp/vl_particles_hit.h"
#include "compshaders/crp/vl_particles_list.h"
#include "compshaders/crp/vl_particles_tiles.h"
#include "compshaders/crp/vl_shadow_point_light.h"
#include "compshaders/crp/vl_shadow_sun.h"
#include "compshaders/crp/vl_debug_ambient.h"
#include "compshaders/crp/vl_debug_extinction.h"
#include "compshaders/crp/vl_debug_shadow_sun.h"
#pragma pack(push, 4)
struct VLGlobalFogRC
{
FogVolume fog;
float time;
uint32_t materialTextureAIndex;
uint32_t materialTextureBIndex;
uint32_t materialTextureCIndex;
};
struct VLAnisotropyRC
{
uint32_t materialTextureBIndex;
uint32_t materialTextureCIndex;
};
struct VLSunlightVisRC
{
vec3_t jitter;
uint32_t visTextureIndex;
uint32_t frustumVisTextureIndex;
uint32_t depthMip;
};
struct VLSunlightRC
{
uint32_t materialTextureAIndex;
uint32_t materialTextureBIndex;
uint32_t scatterExtTextureIndex;
uint32_t sunlightVisTextureIndex;
};
struct VLAmbientRC
{
LightGridRC lightGrid;
uint32_t materialTextureAIndex;
uint32_t scatterExtTextureIndex;
};
struct VLRaymarchRC
{
uint32_t scatterTextureIndex;
uint32_t resolveTextureIndex;
uint32_t materialTextureBIndex;
};
struct VLExtinctionFogRC
{
FogVolume fog;
float time;
uint32_t extinctionTextureIndex;
float worldScale;
};
struct VLPointLightShadowRC
{
vec3_t lightPosition;
float extinctionWorldScale;
float shadowWorldScale;
uint32_t shadowTextureIndex;
};
struct VLSunlightShadowRC
{
uint32_t shadowTextureIndex;
uint32_t sourceTextureIndex;
float shadowWorldScale;
float sourceWorldScale;
};
struct VLPointLightScatterRC
{
DynamicLight light;
uint32_t materialTextureAIndex;
uint32_t materialTextureBIndex;
uint32_t scatterExtTextureIndex;
uint32_t transmittanceTextureIndex;
uint32_t transmittanceSamplerIndex;
float shadowWorldScale;
};
struct VLExtinctionVizRC
{
vec3_t color;
float worldScale;
vec3_t cameraPosition;
float boxScale; // 1 for full size
float extinctionScale;
uint32_t extinctionTextureIndex;
};
struct VLSunShadowVizRC
{
vec3_t color;
float worldScale;
vec3_t cameraPosition;
float boxScale; // 1 for full size
uint32_t shadowTextureIndex;
};
struct VLAmbientVizRC
{
LightGridRC lightGrid;
float sphereScale;
};
struct VLTemporalRC
{
uint32_t currTextureIndex;
uint32_t prevTextureIndex;
uint32_t prevTextureSamplerIndex;
float alpha;
};
struct VLParticleClearRC
{
uint32_t counterBufferIndex;
uint32_t tileBufferIndex;
uint32_t tileCount;
};
struct VLParticleHitRC
{
uvec3_t fullResolution;
uint32_t tileBufferIndex;
uvec3_t tileResolution;
uint32_t pad0;
uvec3_t tileScale;
uint32_t pad1;
uint32_t particleBufferIndex;
uint32_t emitterBufferIndex;
uint32_t liveBufferIndex;
uint32_t emitterIndex;
};
struct VLParticleTilesRC
{
uint32_t counterBufferIndex;
uint32_t tileBufferIndex;
uint32_t tileIndexBufferIndex;
uint32_t tileCount;
};
struct VLParticleListRC
{
uvec3_t fullResolution;
uint32_t emitterIndex;
uvec3_t tileResolution;
uint32_t maxParticleIndexCount;
uvec3_t tileScale;
uint32_t tileBufferIndex;
uint32_t emitterBufferIndex;
uint32_t particleBufferIndex;
uint32_t liveBufferIndex;
uint32_t indexBufferIndex;
};
struct VLParticleInjectionRC
{
uvec3_t tileScale;
uint32_t pad0;
uvec3_t tileResolution;
uint32_t particleBufferIndex;
uint32_t materialTextureAIndex;
uint32_t materialTextureBIndex;
uint32_t materialTextureCIndex;
uint32_t tileBufferIndex;
uint32_t tileIndexBufferIndex;
uint32_t particleIndexBufferIndex;
uint32_t counterBufferIndex;
uint32_t tileCount;
};
struct NanoVDBTransform
{
matrix3x3_t worldToIndex;
vec3_t originOffset;
vec3_t translation;
vec3_t stepSize;
};
struct VLNanoVDBFrustInjectionRC
{
NanoVDBTransform transform;
uint32_t nanovdbBufferIndex;
uint32_t blackbodyTextureIndex;
LightGridRC lightGrid;
uint32_t materialTextureAIndex;
uint32_t materialTextureBIndex;
uint32_t materialTextureCIndex;
uint32_t scatterExtTextureIndex;
uint32_t frustumVisTextureIndex;
uint32_t densityGridByteOffset;
uint32_t flamesGridByteOffset;
uint32_t densityGridByteOffset2;
uint32_t flamesGridByteOffset2;
uint32_t linearInterpolation;
uint32_t accurateOverlapTest;
uint32_t ambientAngularCoverage;
float densityExtinctionScale;
float densityAlbedo;
float flamesEmissionScale;
float flamesTemperatureScale;
float stepScale;
float transStepScale;
float t;
};
struct VLNanoVDBExtInjectionRC
{
NanoVDBTransform transform;
uint32_t nanovdbBufferIndex;
uint32_t extinctionTextureIndex;
uint32_t densityGridByteOffset;
uint32_t densityGridByteOffset2;
uint32_t linearInterpolation;
float worldScale;
float densityExtinctionScale;
float t;
};
struct VLNanoVDBLightPropRC
{
uint32_t materialTextureAIndex;
uint32_t materialTextureBIndex;
float emissiveScatter;
};
struct VLFrustumDepthTestRC
{
uvec3_t frustumTextureSize;
uint32_t frustumVisTextureIndex;
uint32_t depthMip;
};
#pragma pack(pop)
const float MaxFogCoordinate = 69420.0f;
static uint32_t ReverseBits32(uint32_t n)
{
n = (n << 16) | (n >> 16);
n = ((n & 0x00ff00ff) << 8) | ((n & 0xff00ff00) >> 8);
n = ((n & 0x0f0f0f0f) << 4) | ((n & 0xf0f0f0f0) >> 4);
n = ((n & 0x33333333) << 2) | ((n & 0xcccccccc) >> 2);
n = ((n & 0x55555555) << 1) | ((n & 0xaaaaaaaa) >> 1);
return n;
}
static float RadicalInverseBase2(uint32_t seqIndex)
{
return ReverseBits32(seqIndex) * 0x1p-32;
}
static float RadicalInverse(uint64_t seqIndex, uint32_t base)
{
const float oneMinusEpsilon = 0x1.fffffep-1;
const float invBase = 1.0f / (float)base;
uint32_t reversedDigits = 0;
float invBaseN = 1.0f;
while(seqIndex)
{
uint32_t next = seqIndex / base;
uint32_t digit = seqIndex - next * base;
reversedDigits = reversedDigits * base + digit;
invBaseN *= invBase;
seqIndex = next;
}
return fminf(reversedDigits * invBaseN, oneMinusEpsilon);
}
static float VanDerCorputSequence(uint32_t seqIndex)
{
return RadicalInverseBase2(seqIndex);
}
static void Halton23Sequence(vec2_t values01, uint32_t seqIndex)
{
values01[0] = RadicalInverseBase2(seqIndex);
values01[1] = RadicalInverse(seqIndex, 3);
}
static float Brightness(const vec3_t color)
{
return
color[0] * 0.299f +
color[1] * 0.587f +
color[2] * 0.114f;
}
static void ConvertFog(FogVolume& dst, const VolumetricLight::Fog& src, const VolumetricLight& vl)
{
const float scatter = src.albedo * src.extinction / Brightness(src.scatterColor);
VectorScale(src.scatterColor, scatter, dst.scatter);
dst.absorption = src.extinction - Brightness(dst.scatter);
VectorScale(src.emissiveColor, src.emissive, dst.emissive);
dst.anisotropy = src.anisotropy;
if(src.isGlobalFog)
{
VectorCopy(vl.mapBoxMin, dst.boxMin);
VectorCopy(vl.mapBoxMax, dst.boxMax);
}
else
{
for(int a = 0; a < 3; a++)
{
dst.boxMin[a] = src.boxCenter[a] - 0.5f * src.boxSize[a];
dst.boxMax[a] = src.boxCenter[a] + 0.5f * src.boxSize[a];
}
}
dst.noiseMin = 1.0f;
dst.noiseMax = src.noiseStrength;
dst.noiseScale = 1.0f / src.noiseSpatialPeriod;
dst.noiseTimeScale = 1.0f / src.noiseTimePeriod;
dst.isHeightFog = src.isHeightFog;
}
static void TransformPosition(vec3_t out, const matrix3x3_t m, const vec3_t v)
{
out[0] = v[0] * m[0] + v[1] * m[3] + v[2] * m[6];
out[1] = v[0] * m[1] + v[1] * m[4] + v[2] * m[7];
out[2] = v[0] * m[2] + v[1] * m[5] + v[2] * m[8];
}
static float VoxelStepSize(const vec3_t dir, const vec3_t voxelSize)
{
vec3_t stepSize3;
for(int i = 0; i < 3; i++)
{
stepSize3[i] = voxelSize[i] / max(fabsf(dir[i]), 0.000001f);
}
const float stepSize = min(stepSize3[0], min(stepSize3[1], stepSize3[2]));
return stepSize;
}
static const float OpaqueTransmittanceThreshold = 1.0f / 256.0f;
static const float LnOpaqueTransmittanceThreshold = logf(OpaqueTransmittanceThreshold);
static float OpaqueDistanceToExtinction(float opaqueDistance)
{
return -LnOpaqueTransmittanceThreshold / opaqueDistance;
}
static float ExtinctionToOpaqueDistance(float extinction)
{
return -LnOpaqueTransmittanceThreshold / extinction;
}
void VolumetricLight::Init()
{
if(srp.firstInit)
{
VectorSet(ambientColorGUI, 1.0f, 1.0f, 1.0f);
VectorSet(ambientColor, 0.5f, 0.5f, 0.5f);
ambientIntensity = 0.1f;
}
int xyDivisor = 1;
for(int i = 0; i < xySubsampling; i++)
{
xyDivisor *= 2;
}
VectorSet(mapBoxMin, -MaxFogCoordinate, -MaxFogCoordinate, -MaxFogCoordinate); // patched on world load
VectorSet(mapBoxMax, MaxFogCoordinate, MaxFogCoordinate, MaxFogCoordinate);
VectorSet(frustumSize, glConfig.vidWidth / xyDivisor, glConfig.vidHeight / xyDivisor, zResolution);
depthMip = (uint32_t)xySubsampling;
VectorSet(frustumTileScale, 8, 8, 8); // x*y*z == 512, must match the shader
VectorSet(frustumTileSize,
(frustumSize[0] + frustumTileScale[0] - 1) / frustumTileScale[0],
(frustumSize[1] + frustumTileScale[1] - 1) / frustumTileScale[1],
(frustumSize[2] + frustumTileScale[2] - 1) / frustumTileScale[2]);
VectorSet(extinctionSize, extinctionResolution, extinctionResolution, extinctionResolution);
VectorSet(extinctionTileScale, 8, 8, 8); // 8*8*8 == 512, must match the shader
VectorSet(extinctionTileSize,
(extinctionSize[0] + extinctionTileScale[0] - 1) / extinctionTileScale[0],
(extinctionSize[1] + extinctionTileScale[1] - 1) / extinctionTileScale[1],
(extinctionSize[2] + extinctionTileScale[2] - 1) / extinctionTileScale[2]);
Vector4Set(extinctionVolumeScale, 8, 16, 32, 64); // patched on world load
VectorSet(sunShadowSize, sunShadowResolution, sunShadowResolution, sunShadowResolution);
Vector4Set(sunShadowVolumeScale, 8, 16, 32, 64); // patched on world load
shadowPixelCount = pointShadowResolution;
pointShadowVolumeScale = 8.0f;
jitterCounter = 0;
extinctionFogPipeline = CreateComputePipeline("VL Extinction Fog", ShaderByteCode(g_vl_extinction_injection_fog_cs));
extinctionVDBPipeline = CreateComputePipeline("VL Extinction VDB", ShaderByteCode(g_vl_extinction_injection_nanovdb_cs));
frustumAmbientPipeline = CreateComputePipeline("VL Frustum Ambient Light Scatter", ShaderByteCode(g_vl_frustum_inscatter_ambient_cs));
frustumAnisotropyPipeline = CreateComputePipeline("VL Frustum Finalize Material", ShaderByteCode(g_vl_frustum_anisotropy_average_cs));
frustumFogPipeline = CreateComputePipeline("VL Frustum Fog", ShaderByteCode(g_vl_frustum_injection_fog_cs));
frustumLightPropNXPipeline = CreateComputePipeline("VL Frustum Light Prop -X", ShaderByteCode(g_vl_frustum_light_propagation_nx_cs));
frustumLightPropNYPipeline = CreateComputePipeline("VL Frustum Light Prop -Y", ShaderByteCode(g_vl_frustum_light_propagation_ny_cs));
frustumLightPropPXPipeline = CreateComputePipeline("VL Frustum Light Prop +X", ShaderByteCode(g_vl_frustum_light_propagation_px_cs));
frustumLightPropPYPipeline = CreateComputePipeline("VL Frustum Light Prop +Y", ShaderByteCode(g_vl_frustum_light_propagation_py_cs));
frustumParticlePipeline = CreateComputePipeline("VL Frustum Particles", ShaderByteCode(g_vl_frustum_injection_particles_cs));
frustumRaymarchPipeline = CreateComputePipeline("VL Frustum Raymarch", ShaderByteCode(g_vl_frustum_raymarch_cs));
frustumTemporalFloatPipeline = CreateComputePipeline("VL Frustum Temporal Reprojection float", ShaderByteCode(g_vl_frustum_temporal_float_cs));
frustumTemporalFloat4Pipeline = CreateComputePipeline("VL Frustum Temporal Reprojection float4", ShaderByteCode(g_vl_frustum_temporal_float4_cs));
frustumVDBLQPipeline = CreateComputePipeline("VL Frustum VDB LQ", ShaderByteCode(g_vl_frustum_injection_nanovdb_lq_cs));
if(rhiInfo.forceNanoVDBPreviewMode)
{
frustumVDBPipeline = frustumVDBLQPipeline;
}
else
{
frustumVDBPipeline = CreateComputePipeline("VL Frustum VDB", ShaderByteCode(g_vl_frustum_injection_nanovdb_cs));
}
frustumDepthTestPipeline = CreateComputePipeline("VL Frustum Z Test", ShaderByteCode(g_vl_frustum_depth_test_cs));
particleClearPipeline = CreateComputePipeline("VL Particle Clear", ShaderByteCode(g_vl_particles_clear_cs));
particleHitPipeline = CreateComputePipeline("VL Particle Hit", ShaderByteCode(g_vl_particles_hit_cs));
particleListPipeline = CreateComputePipeline("VL Particle List Build", ShaderByteCode(g_vl_particles_list_cs));
particleTilesPipeline = CreateComputePipeline("VL Particle Tile List Build", ShaderByteCode(g_vl_particles_tiles_cs));
pointLightShadowPipeline = CreateComputePipeline("VL Shadow Raymarch Point Light", ShaderByteCode(g_vl_shadow_point_light_cs));
sunlightScatterPipeline = CreateComputePipeline("VL Frustum Sunlight Scatter", ShaderByteCode(g_vl_frustum_inscatter_sunlight_cs));
sunlightShadowPipeline = CreateComputePipeline("VL Shadow Raymarch Sun", ShaderByteCode(g_vl_shadow_sun_cs));
if(rhiInfo.hasInlineRaytracing)
{
frustumSunlightVisPipeline = CreateComputePipeline("VL Frustum Sunlight Visibility", ShaderByteCode(g_vl_frustum_sunlight_visibility_cs));
frustumPointLightScatterPipeline = CreateComputePipeline("VL Frustum Point Light Scatter", ShaderByteCode(g_vl_frustum_inscatter_point_light_cs));
}
else
{
frustumSunlightVisPipeline = RHI_MAKE_NULL_HANDLE();
frustumPointLightScatterPipeline = RHI_MAKE_NULL_HANDLE();
}
{
GraphicsPipelineDesc desc("VL Extinction Viz");
desc.shortLifeTime = true;
desc.rootSignature = RHI_MAKE_NULL_HANDLE();
desc.vertexShader.Set(g_vl_debug_extinction_vs);
desc.pixelShader.Set(g_vl_debug_extinction_ps);
desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float;
desc.depthStencil.depthComparison = ComparisonFunction::GreaterEqual;
desc.depthStencil.enableDepthTest = true;
desc.depthStencil.enableDepthWrites = true;
desc.rasterizer.cullMode = CT_BACK_SIDED;
desc.AddRenderTarget(0, crp.renderTargetFormat);
extinctionVizPipeline = CreateGraphicsPipeline(desc);
}
{
GraphicsPipelineDesc desc("VL Sun Shadow Viz");
desc.shortLifeTime = true;
desc.rootSignature = RHI_MAKE_NULL_HANDLE();
desc.vertexShader.Set(g_vl_debug_shadow_sun_vs);
desc.pixelShader.Set(g_vl_debug_shadow_sun_ps);
desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float;
desc.depthStencil.depthComparison = ComparisonFunction::GreaterEqual;
desc.depthStencil.enableDepthTest = true;
desc.depthStencil.enableDepthWrites = true;
desc.rasterizer.cullMode = CT_BACK_SIDED;
desc.AddRenderTarget(0, crp.renderTargetFormat);
sunShadowVizPipeline = CreateGraphicsPipeline(desc);
}
{
GraphicsPipelineDesc desc("VL Ambient Viz");
desc.shortLifeTime = true;
desc.rootSignature = RHI_MAKE_NULL_HANDLE();
desc.vertexShader.Set(g_vl_debug_ambient_vs);
desc.pixelShader.Set(g_vl_debug_ambient_ps);
desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float;
desc.depthStencil.depthComparison = ComparisonFunction::GreaterEqual;
desc.depthStencil.enableDepthTest = true;
desc.depthStencil.enableDepthWrites = true;
desc.rasterizer.cullMode = CT_FRONT_SIDED;
desc.AddRenderTarget(0, crp.renderTargetFormat);
ambientVizPipeline = CreateGraphicsPipeline(desc);
}
{
TextureDesc desc("VL", frustumSize[0], frustumSize[1]);
desc.shortLifeTime = true;
desc.committedResource = true;
desc.initialState = ResourceStates::UnorderedAccessBit;
desc.allowedState = ResourceStates::UnorderedAccessBit | ResourceStates::ComputeShaderAccessBit | ResourceStates::PixelShaderAccessBit;
desc.depth = frustumSize[2];
desc.name = "VL scatter/absorption";
desc.format = TextureFormat::R16G16B16A16_Float;
materialTextureA = CreateTexture(desc);
desc.name = "VL emissive/anisotropy";
desc.format = TextureFormat::R16G16B16A16_Float;
materialTextureB = CreateTexture(desc);
desc.name = "VL anisotropy counter";
desc.format = TextureFormat::R16_Float;
materialTextureC = CreateTexture(desc);
desc.name = "VL sunlight vis";
desc.format = TextureFormat::R8_UNorm;
sunlightVisTexture = CreateTexture(desc);
desc.name = "VL sunlight vis temporal";
desc.format = TextureFormat::R8_UNorm;
prevSunlightVisTexture = CreateTexture(desc);
desc.name = "VL in-scatter/ext";
desc.format = TextureFormat::R16G16B16A16_Float;
scatterExtTexture = CreateTexture(desc);
desc.name = "VL in-scatter/trans";
desc.format = TextureFormat::R16G16B16A16_Float;
scatterTransTexture = CreateTexture(desc);
desc.width = extinctionSize[0];
desc.height = extinctionSize[1];
desc.depth = extinctionSize[2];
desc.format = TextureFormat::R16_Float;
for(int i = 0; i < 4; i++)
{
desc.name = va("VL extinction #%d", i + 1);
extinctionTextures[i] = CreateTexture(desc);
}
desc.width = sunShadowSize[0];
desc.height = sunShadowSize[1];
desc.depth = sunShadowSize[2];
desc.format = TextureFormat::R16_Float;
for(int i = 0; i < 4; i++)
{
desc.name = va("VL sun shadow #%d", i + 1);
sunShadowTextures[i] = CreateTexture(desc);
}
desc.width = shadowPixelCount;
desc.height = shadowPixelCount;
desc.depth = shadowPixelCount;
desc.name = "VL point light shadow";
desc.format = TextureFormat::R16_Float;
pointShadowTexture = CreateTexture(desc);
desc.width = (glConfig.vidWidth + xyDivisor - 1) / xyDivisor;
desc.height = (glConfig.vidHeight + xyDivisor - 1) / xyDivisor;
desc.depth = 1;
desc.name = "VL frustum visibility";
desc.format = TextureFormat::R16_UInt;
frustumVisTexture = CreateTexture(desc);
ambientLightTextureA = RHI_MAKE_NULL_HANDLE(); // created on world load when available
ambientLightTextureB = RHI_MAKE_NULL_HANDLE(); // created on world load when available
}
const uint32_t tileCountF = frustumTileSize[0] * frustumTileSize[1] * frustumTileSize[2];
const uint32_t tileCountE = extinctionTileSize[0] * extinctionTileSize[1] * extinctionTileSize[2];
const uint32_t maxTileCount = max(tileCountF, tileCountE);
{
const uint32_t tileSize = sizeof(Tile);
const uint32_t byteCount = maxTileCount * tileSize;
BufferDesc desc("VL particle voxel tile", byteCount, ResourceStates::UnorderedAccessBit);
desc.shortLifeTime = true;
desc.structureByteCount = tileSize;
particleTileBuffer = CreateBuffer(desc);
}
{
BufferDesc desc("VL particle global counters", sizeof(Counters), ResourceStates::UnorderedAccessBit);
desc.shortLifeTime = true;
desc.structureByteCount = sizeof(Counters);
particleCounterBuffer = CreateBuffer(desc);
}
{
BufferDesc desc("VL particle tile index", maxTileCount * 4, ResourceStates::UnorderedAccessBit);
desc.shortLifeTime = true;
desc.structureByteCount = 4;
particleTileIndexBuffer = CreateBuffer(desc);
}
{
const uint32_t Magic = 64; // @TODO: adjust or use particle groups
BufferDesc desc("VL particle index", Magic * MAX_PARTICLES * 4, ResourceStates::UnorderedAccessBit);
desc.shortLifeTime = true;
desc.structureByteCount = 4;
particleIndexBuffer = CreateBuffer(desc);
maxParticleIndexCount = Magic * MAX_PARTICLES;
}
{
BufferDesc desc("VL particle dispatch", 12, ResourceStates::UnorderedAccessBit);
desc.shortLifeTime = true;
particleDispatchBuffer = CreateBuffer(desc);
uint32_t* const mapped = (uint32_t*)BeginBufferUpload(particleDispatchBuffer);
mapped[0] = 0;
mapped[1] = 1;
mapped[2] = 1;
EndBufferUpload(particleDispatchBuffer);
}
}
void VolumetricLight::ProcessWorld(world_t& world)
{
Q_assert(world.nodes != NULL);
Q_assert(world.numnodes > 0);
if(world.nodes == NULL || world.numnodes <= 0)
{
VectorSet(mapBoxMin, -MaxFogCoordinate, -MaxFogCoordinate, -MaxFogCoordinate);
VectorSet(mapBoxMax, MaxFogCoordinate, MaxFogCoordinate, MaxFogCoordinate);
return;
}
const mnode_t& node = world.nodes[0];
vec3_t mapDimensions;
VectorSubtract(node.maxs, node.mins, mapDimensions);
VectorCopy(node.mins, mapBoxMin);
VectorCopy(node.maxs, mapBoxMax);
if(world.lightGridData != NULL)
{
for(int i = 0; i < 3; i++)
{
lightGridCenter[i] =
world.lightGridOrigin[i] +
(world.lightGridBounds[i] * 0.5f - 0.5f) * world.lightGridSize[i];
}
TextureDesc desc("VL", world.lightGridBounds[0], world.lightGridBounds[1]);
desc.shortLifeTime = true;
desc.committedResource = true;
desc.initialState = ResourceStates::ComputeShaderAccessBit;
desc.allowedState = ResourceStates::ComputeShaderAccessBit | ResourceStates::PixelShaderAccessBit;
desc.depth = world.lightGridBounds[2];
desc.format = TextureFormat::R8G8B8A8_UNorm;
desc.name = "VL ambient light A";
ambientLightTextureA = CreateTexture(desc);
desc.name = "VL ambient light B";
ambientLightTextureB = CreateTexture(desc);
MappedTexture texture;
BeginTextureUpload(texture, ambientLightTextureA);
const uint32_t rowCount = texture.rowCount * texture.sliceCount;
const uint32_t columnCount = texture.columnCount;
const uint32_t srcRowByteCount = world.lightGridBounds[0] * 8;
for(uint32_t r = 0; r < rowCount; r++)
{
uint32_t* dst = (uint32_t*)(texture.mappedData + r * texture.dstRowByteCount);
const uint32_t* src = (const uint32_t*)(world.lightGridData + r * srcRowByteCount);
for(uint32_t c = 0; c < columnCount; c++)
{
*dst = src[0];
dst += 1;
src += 2;
}
}
EndTextureUpload();
BeginTextureUpload(texture, ambientLightTextureB);
for(uint32_t r = 0; r < rowCount; r++)
{
uint32_t* dst = (uint32_t*)(texture.mappedData + r * texture.dstRowByteCount);
const uint32_t* src = (const uint32_t*)(world.lightGridData + r * srcRowByteCount);
for(uint32_t c = 0; c < columnCount; c++)
{
*dst = src[1];
dst += 1;
src += 2;
}
}
EndTextureUpload();
}
{
const float largest = max(max(mapDimensions[0], mapDimensions[1]), mapDimensions[2]);
const float scale3 = largest / (float)extinctionSize[0];
const float scale2 = extinctionVolumeScale[2];
float downScale = 1.0f;
while(scale3 <= 1.5f * scale2 * downScale)
{
downScale *= 0.5f;
}
extinctionVolumeScale[0] *= downScale;
extinctionVolumeScale[1] *= downScale;
extinctionVolumeScale[2] *= downScale;
extinctionVolumeScale[3] = scale3;
}
{
const float length = VectorLength(mapDimensions);
const float scale3 = length / (float)sunShadowSize[0];
const float scale2 = sunShadowVolumeScale[2];
float downScale = 1.0f;
while(scale3 <= 1.5f * scale2 * downScale)
{
downScale *= 0.5f;
}
sunShadowVolumeScale[0] *= downScale;
sunShadowVolumeScale[1] *= downScale;
sunShadowVolumeScale[2] *= downScale;
sunShadowVolumeScale[3] = scale3;
}
if(!LoadFogFile(va("fogs/%s.fogs", world.baseName)))
{
fogCount = 0;
// @NOTE: fog 0 is invalid
for(int f = 1; f < world.numfogs && fogCount < ARRAY_LEN(fogs); f++)
{
const float transmittanceThreshold = 1.0 / 256.0f;
const float lnThreshold = logf(transmittanceThreshold);
const fog_t& q3fog = world.fogs[f];
const float distance = q3fog.parms.depthForOpaque;
const float extinction = -lnThreshold / distance;
Fog& fog = fogs[fogCount++];
fog = {};
for(int a = 0; a < 3; a++)
{
fog.boxCenter[a] = 0.5f * (q3fog.bounds[0][a] + q3fog.bounds[1][a]);
fog.boxSize[a] = q3fog.bounds[1][a] - q3fog.bounds[0][a];
}
fog.extinction = extinction;
fog.albedo = 0.75f;
VectorCopy(q3fog.parms.color, fog.scatterColor);
fog.emissive = 0.0f;
VectorSet(fog.emissiveColor, 1, 1, 1);
fog.anisotropy = 0.0f;
fog.noiseStrength = 2.0f;
fog.noiseSpatialPeriod = 128.0f;
fog.noiseTimePeriod = 8.0f;
}
}
}
void VolumetricLight::DrawBegin()
{
{
SCOPED_RENDER_PASS("VL Clear", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit);
for(int i = 0; i < 4; i++)
{
CmdTextureBarrier(extinctionTextures[i], ResourceStates::UnorderedAccessBit);
}
CmdEndBarrier();
const uint32_t values[4] = {};
CmdClearTextureUAV(materialTextureA, 0, values);
CmdClearTextureUAV(materialTextureB, 0, values);
CmdClearTextureUAV(materialTextureC, 0, values);
CmdClearTextureUAV(scatterExtTexture, 0, values);
for(int i = 0; i < 4; i++)
{
CmdClearTextureUAV(extinctionTextures[i], 0, values);
}
}
{
SCOPED_RENDER_PASS("VL Frustum Depth", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(crp.depthMinMaxTexture, ResourceStates::ComputeShaderAccessBit);
CmdTextureBarrier(frustumVisTexture, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLFrustumDepthTestRC rc = {};
rc.frustumVisTextureIndex = GetTextureIndexUAV(frustumVisTexture, 0);
rc.depthMip = depthMip;
VectorCopy(frustumSize, rc.frustumTextureSize);
CmdBindPipeline(frustumDepthTestPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 7) / 8, (frustumSize[1] + 7) / 8, 1);
}
{
SCOPED_RENDER_PASS("VL Fog", 1.0f, 1.0f, 1.0f);
for(int f = 0; f < fogCount; f++)
{
SCOPED_DEBUG_LABEL("Frustum", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLGlobalFogRC rc = {};
ConvertFog(rc.fog, fogs[f], *this);
rc.time = backEnd.refdef.floatTime;
rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0);
rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0);
rc.materialTextureCIndex = GetTextureIndexUAV(materialTextureC, 0);
CmdBindPipeline(frustumFogPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4);
}
for(int f = 0; f < fogCount; f++)
{
VLExtinctionFogRC rc = {};
ConvertFog(rc.fog, fogs[f], *this);
rc.time = backEnd.refdef.floatTime;
for(int c = 0; c < 4; c++)
{
SCOPED_DEBUG_LABEL("Extinction", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(extinctionTextures[c], ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
rc.extinctionTextureIndex = GetTextureIndexUAV(extinctionTextures[c], 0);
rc.worldScale = extinctionVolumeScale[c];
CmdBindPipeline(extinctionFogPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((extinctionSize[0] + 3) / 4, (extinctionSize[1] + 3) / 4, (extinctionSize[2] + 3) / 4);
}
}
}
if(0) // @TODO:
{
SCOPED_RENDER_PASS("VL Frustum Particles", 1.0f, 1.0f, 1.0f);
const ParticleSystem& ps = crp.particleSystem;
const uint32_t tileCount = frustumTileSize[0] * frustumTileSize[1] * frustumTileSize[2];
{
SCOPED_DEBUG_LABEL("Clear", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(particleCounterBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleTileBuffer, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLParticleClearRC rc = {};
rc.counterBufferIndex = GetBufferIndexUAV(particleCounterBuffer);
rc.tileBufferIndex = GetBufferIndexUAV(particleTileBuffer);
rc.tileCount = tileCount;
CmdBindPipeline(particleClearPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((tileCount + 63) / 64, 1, 1);
}
{
SCOPED_DEBUG_LABEL("Hit", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(ps.indirectBuffer, ResourceStates::IndirectDispatchBit);
CmdBufferBarrier(ps.particleBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(ps.emitterBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(ps.liveBuffers[ps.liveBufferReadIndex], ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleTileBuffer, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLParticleHitRC rc = {};
rc.particleBufferIndex = GetBufferIndexUAV(ps.particleBuffer);
rc.emitterBufferIndex = GetBufferIndexUAV(ps.emitterBuffer);
rc.liveBufferIndex = GetBufferIndexUAV(ps.liveBuffers[ps.liveBufferReadIndex]);
rc.emitterIndex = 0; // @TODO:
rc.tileBufferIndex = GetBufferIndexUAV(particleTileBuffer);
VectorCopy(frustumSize, rc.fullResolution);
VectorCopy(frustumTileSize, rc.tileResolution);
VectorCopy(frustumTileScale, rc.tileScale);
CmdBindPipeline(particleHitPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatchIndirect(ps.indirectBuffer, 12);
}
{
SCOPED_DEBUG_LABEL("Tile List Build", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(particleCounterBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleTileBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleTileIndexBuffer, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLParticleTilesRC rc = {};
rc.counterBufferIndex = GetBufferIndexUAV(particleCounterBuffer);
rc.tileBufferIndex = GetBufferIndexUAV(particleTileBuffer);
rc.tileIndexBufferIndex = GetBufferIndexUAV(particleTileIndexBuffer);
rc.tileCount = tileCount;
CmdBindPipeline(particleTilesPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((tileCount + 63) / 64, 1, 1);
}
{
SCOPED_DEBUG_LABEL("Particle List Build", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(ps.indirectBuffer, ResourceStates::IndirectDispatchBit);
CmdBufferBarrier(ps.particleBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(ps.emitterBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(ps.liveBuffers[ps.liveBufferReadIndex], ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleTileBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleIndexBuffer, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLParticleListRC rc = {};
rc.emitterBufferIndex = GetBufferIndexUAV(ps.emitterBuffer);
rc.indexBufferIndex = GetBufferIndexUAV(particleIndexBuffer);
rc.liveBufferIndex = GetBufferIndexUAV(ps.liveBuffers[ps.liveBufferReadIndex]);
rc.particleBufferIndex = GetBufferIndexUAV(ps.particleBuffer);
rc.tileBufferIndex = GetBufferIndexUAV(particleTileBuffer);
rc.emitterIndex = 0; // @TODO:
rc.maxParticleIndexCount = maxParticleIndexCount;
VectorCopy(frustumSize, rc.fullResolution);
VectorCopy(frustumTileSize, rc.tileResolution);
VectorCopy(frustumTileScale, rc.tileScale);
CmdBindPipeline(particleListPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatchIndirect(ps.indirectBuffer, 12);
}
{
SCOPED_DEBUG_LABEL("Set Dispatch Tile Count", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(particleCounterBuffer, ResourceStates::CopySourceBit);
CmdBufferBarrier(particleDispatchBuffer, ResourceStates::CopyDestinationBit);
CmdEndBarrier();
CmdCopyBuffer(particleDispatchBuffer, 0, particleCounterBuffer, 4, 4);
}
{
SCOPED_DEBUG_LABEL("Injection", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(ps.particleBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleTileBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleTileIndexBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleIndexBuffer, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleCounterBuffer, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit);
CmdBufferBarrier(particleDispatchBuffer, ResourceStates::IndirectDispatchBit);
CmdEndBarrier();
VLParticleInjectionRC rc = {};
rc.particleBufferIndex = GetBufferIndexUAV(ps.particleBuffer);
rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0);
rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0);
rc.materialTextureCIndex = GetTextureIndexUAV(materialTextureC, 0);
rc.tileBufferIndex = GetBufferIndexUAV(particleTileBuffer);
rc.tileIndexBufferIndex = GetBufferIndexUAV(particleTileIndexBuffer);
rc.particleIndexBufferIndex = GetBufferIndexUAV(particleIndexBuffer);
rc.counterBufferIndex = GetBufferIndexUAV(particleCounterBuffer);
rc.tileCount = frustumTileSize[0] * frustumTileSize[1] * frustumTileSize[2];
VectorCopy(frustumTileScale, rc.tileScale);
VectorCopy(frustumTileSize, rc.tileResolution);
CmdBindPipeline(frustumParticlePipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatchIndirect(particleDispatchBuffer, 0);
}
}
// NanoVDB: integrated GPUs are too slow and will trigger a TDR
if(!rhiInfo.isUMA)
{
SCOPED_RENDER_PASS("VL NanoVDB", 1.0f, 1.0f, 1.0f);
NanoVDBManager& vdb = crp.vdbManager;
for(uint32_t i = 0; i < vdb.drawInstances.count; i++)
{
const NanoVDBManager::Instance& instance = vdb.instances[i];
const NanoVDBManager::DrawInstance& drawInstance = vdb.drawInstances[i];
NanoVDBTransform transform = {};
vdb.MakeWorldToIndexMatrix(transform.worldToIndex, instance);
VectorCopy(instance.originOffset, transform.originOffset);
VectorCopy(instance.position, transform.translation);
if(drawInstance.smokeByteOffset > 0 || drawInstance.fireByteOffset > 0)
{
SCOPED_DEBUG_LABEL("Frustum", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(drawInstance.buffer, ResourceStates::ComputeShaderAccessBit);
if(tr.world->lightGridData != NULL)
{
CmdTextureBarrier(ambientLightTextureA, ResourceStates::ComputeShaderAccessBit);
CmdTextureBarrier(ambientLightTextureB, ResourceStates::ComputeShaderAccessBit);
}
CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
// order change: fw, left, up -> left, up, fw
const orientationr_t& orient = backEnd.viewParms.orient;
vec3_t forward, left, up;
TransformPosition(forward, transform.worldToIndex, orient.axis[0]);
TransformPosition(left, transform.worldToIndex, orient.axis[1]);
TransformPosition(up, transform.worldToIndex, orient.axis[2]);
VectorNormalize(forward);
VectorNormalize(left);
VectorNormalize(up);
transform.stepSize[0] = VoxelStepSize(left, instance.scale);
transform.stepSize[1] = VoxelStepSize(up, instance.scale);
transform.stepSize[2] = VoxelStepSize(forward, instance.scale);
const float transStepScale =
max3(instance.scale[0], instance.scale[1], instance.scale[2]) * (float)vdb.ambientRaymarchLOD;
VLNanoVDBFrustInjectionRC rc = {};
SetLightGridRootConstants(rc.lightGrid);
rc.nanovdbBufferIndex = GetBufferIndexSRV(drawInstance.buffer);
rc.blackbodyTextureIndex = GetTextureIndexSRV(crp.blackbodyTexture);
rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0);
rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0);
rc.materialTextureCIndex = GetTextureIndexUAV(materialTextureC, 0);
rc.scatterExtTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0);
rc.frustumVisTextureIndex = GetTextureIndexUAV(frustumVisTexture, 0);
rc.densityGridByteOffset = drawInstance.smokeByteOffset;
rc.flamesGridByteOffset = drawInstance.fireByteOffset;
rc.densityGridByteOffset2 = drawInstance.smokeByteOffset2;
rc.flamesGridByteOffset2 = drawInstance.fireByteOffset2;
rc.linearInterpolation = vdb.linearInterpolation ? 1 : 0;
rc.accurateOverlapTest = vdb.accurateOverlapTest ? 1 : 0;
rc.ambientAngularCoverage = vdb.ambientIncreasedCoverage ? 1 : 0;
rc.densityExtinctionScale = instance.smokeExtinctionScale;
rc.densityAlbedo = instance.smokeAlbedo;
rc.flamesEmissionScale = instance.fireEmissionScale;
rc.flamesTemperatureScale = instance.fireTemperatureScale;
rc.transform = transform;
rc.stepScale = vdb.supersampling ? 0.5f : 1.0f;
rc.transStepScale = transStepScale;
rc.t = drawInstance.t;
const bool previewMode = rhiInfo.forceNanoVDBPreviewMode != qfalse || vdb.previewMode;
const HPipeline pipeline = previewMode ? frustumVDBLQPipeline : frustumVDBPipeline;
CmdBindPipeline(pipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4);
}
if(drawInstance.smokeByteOffset > 0 || drawInstance.smokeByteOffset2 > 0)
{
vec3_t axisX, axisY, axisZ;
VectorSet(axisX, 1, 0, 0);
VectorSet(axisY, 0, 1, 0);
VectorSet(axisZ, 0, 0, 1);
transform.stepSize[0] = VoxelStepSize(axisX, instance.scale);
transform.stepSize[1] = VoxelStepSize(axisY, instance.scale);
transform.stepSize[2] = VoxelStepSize(axisZ, instance.scale);
for(int c = 0; c < 4; c++)
{
SCOPED_DEBUG_LABEL("Extinction", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdBufferBarrier(drawInstance.buffer, ResourceStates::ComputeShaderAccessBit);
CmdTextureBarrier(extinctionTextures[c], ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLNanoVDBExtInjectionRC rc = {};
rc.nanovdbBufferIndex = GetBufferIndexSRV(drawInstance.buffer);
rc.extinctionTextureIndex = GetTextureIndexUAV(extinctionTextures[c], 0);
rc.worldScale = extinctionVolumeScale[c];
rc.densityGridByteOffset = drawInstance.smokeByteOffset;
rc.densityGridByteOffset2 = drawInstance.smokeByteOffset2;
rc.linearInterpolation = vdb.linearInterpolation ? 1 : 0;
rc.densityExtinctionScale = instance.smokeExtinctionScale;
rc.transform = transform;
rc.t = drawInstance.t;
CmdBindPipeline(extinctionVDBPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((extinctionSize[0] + 3) / 4, (extinctionSize[1] + 3) / 4, (extinctionSize[2] + 3) / 4);
}
}
}
uint32_t fireOffsetMask = 0;
for(uint32_t i = 0; i < vdb.drawInstances.count; i++)
{
fireOffsetMask |= vdb.drawInstances[i].fireByteOffset;
}
const bool hasFire = fireOffsetMask > 0;
if(hasFire && !vdb.previewMode && vdb.emissiveScatterScale > 0.0f)
{
VLNanoVDBLightPropRC rc = {};
rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0);
rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0);
rc.emissiveScatter = vdb.emissiveScatterScale;
{
SCOPED_DEBUG_LABEL("Light Propagation +X", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
CmdBindPipeline(frustumLightPropPXPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[1] + 7) / 8, (frustumSize[2] + 7) / 8, 1);
}
{
SCOPED_DEBUG_LABEL("Light Propagation -X", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
CmdBindPipeline(frustumLightPropNXPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[1] + 7) / 8, (frustumSize[2] + 7) / 8, 1);
}
{
SCOPED_DEBUG_LABEL("Light Propagation +Y", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
CmdBindPipeline(frustumLightPropPYPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 7) / 8, (frustumSize[2] + 7) / 8, 1);
}
{
SCOPED_DEBUG_LABEL("Light Propagation -Y", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
CmdBindPipeline(frustumLightPropNYPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 7) / 8, (frustumSize[2] + 7) / 8, 1);
}
}
}
{
SCOPED_RENDER_PASS("VL Anisotropy Avg", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLAnisotropyRC rc = {};
rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0);
rc.materialTextureCIndex = GetTextureIndexUAV(materialTextureC, 0);
CmdBindPipeline(frustumAnisotropyPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4);
}
{
SCOPED_RENDER_PASS("VL Ambient", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit);
if(tr.world->lightGridData != NULL)
{
CmdTextureBarrier(ambientLightTextureA, ResourceStates::ComputeShaderAccessBit);
CmdTextureBarrier(ambientLightTextureB, ResourceStates::ComputeShaderAccessBit);
}
CmdEndBarrier();
VLAmbientRC rc = {};
rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0);
rc.scatterExtTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0);
SetLightGridRootConstants(rc.lightGrid);
CmdBindPipeline(frustumAmbientPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4);
}
firstFrame = false;
}
void VolumetricLight::DrawPointLight(const dlight_t& light)
{
{
SCOPED_DEBUG_LABEL("VL DL Shadow", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
for(int c = 0; c < 4; c++)
{
CmdTextureBarrier(extinctionTextures[c], ResourceStates::ComputeShaderAccessBit);
}
CmdTextureBarrier(pointShadowTexture, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLPointLightShadowRC rc = {};
rc.shadowTextureIndex = GetTextureIndexUAV(pointShadowTexture, 0);
rc.extinctionWorldScale = extinctionVolumeScale[0]; // pick the highest resolution possible
rc.shadowWorldScale = pointShadowVolumeScale;
VectorCopy(light.origin, rc.lightPosition);
const uint32_t groupCount = (shadowPixelCount + 3) / 4;
CmdBindPipeline(pointLightShadowPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch(groupCount, groupCount, groupCount);
}
{
SCOPED_DEBUG_LABEL("VL DL Scatter", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(pointShadowTexture, ResourceStates::ComputeShaderAccessBit);
CmdEndBarrier();
VLPointLightScatterRC rc = {};
rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0);
rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0);
rc.scatterExtTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0);
rc.transmittanceTextureIndex = GetTextureIndexSRV(pointShadowTexture);
rc.transmittanceSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear);
rc.shadowWorldScale = pointShadowVolumeScale;
VectorCopy(light.origin, rc.light.position);
rc.light.radius = light.radius;
VectorCopy(light.color, rc.light.color);
CmdBindPipeline(frustumPointLightScatterPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4);
}
}
void VolumetricLight::DrawSunlight()
{
if(!drawSunlight)
{
return;
}
SCOPED_RENDER_PASS("VL Sunlight", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
for(int c = 0; c < 4; c++)
{
CmdTextureBarrier(extinctionTextures[c], ResourceStates::ComputeShaderAccessBit);
}
for(int c = 0; c < 3; c++)
{
CmdTextureBarrier(sunShadowTextures[c], ResourceStates::UnorderedAccessBit);
}
CmdTextureBarrier(sunShadowTextures[3], ResourceStates::ComputeShaderAccessBit);
CmdEndBarrier();
// each cascade needs to sample the higher level for setting the initial transmittance value
for(int c = 3; c >= 0; c--)
{
SCOPED_DEBUG_LABEL("Shadow", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(sunShadowTextures[c], ResourceStates::UnorderedAccessBit);
if(c < 3)
{
CmdTextureBarrier(sunShadowTextures[c + 1], ResourceStates::ComputeShaderAccessBit);
}
CmdEndBarrier();
VLSunlightShadowRC rc = {};
rc.shadowTextureIndex = GetTextureIndexUAV(sunShadowTextures[c], 0);
rc.shadowWorldScale = sunShadowVolumeScale[c];
if(c < 3)
{
rc.sourceTextureIndex = GetTextureIndexSRV(sunShadowTextures[c + 1]);
rc.sourceWorldScale = sunShadowVolumeScale[c + 1];
}
CmdBindPipeline(sunlightShadowPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((sunShadowSize[0] + 7) / 8, (sunShadowSize[1] + 7) / 8, 1);
}
{
SCOPED_DEBUG_LABEL("Vis", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(crp.depthMinMaxTexture, ResourceStates::ComputeShaderAccessBit);
CmdTextureBarrier(sunlightVisTexture, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
const float maxJitterDepthDistance = 16.0f;
const float maxJitterRadiusXY = 4.0f;
vec2_t point01;
Halton23Sequence(point01, jitterCounter);
// 6 points on the circle, rotated by 15 degrees
// ordered such that it oscillates around (0, 0)
const float jitterXY[] =
{
0.965926f, 0.258819f,
-0.965926f, -0.258819f,
0.707107f, -0.707107f,
-0.707107f, 0.707107f,
0.258819f, 0.965926f,
-0.258819f, -0.965926f
};
const int jitterXYSampleIndex = jitterCounter % 6;
VLSunlightVisRC rc = {};
rc.visTextureIndex = GetTextureIndexUAV(sunlightVisTexture, 0);
rc.frustumVisTextureIndex = GetTextureIndexUAV(frustumVisTexture, 0);
rc.depthMip = depthMip;
rc.jitter[0] = (0.5f * jitterXY[2 * jitterXYSampleIndex + 0]) * maxJitterRadiusXY;
rc.jitter[1] = (0.5f * jitterXY[2 * jitterXYSampleIndex + 1]) * maxJitterRadiusXY;
rc.jitter[2] = VanDerCorputSequence(jitterCounter) * maxJitterDepthDistance;
CmdBindPipeline(frustumSunlightVisPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4);
}
if(!firstFrame)
{
SCOPED_DEBUG_LABEL("Vis Temporal", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(prevSunlightVisTexture, ResourceStates::ComputeShaderAccessBit);
CmdTextureBarrier(sunlightVisTexture, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLTemporalRC rc = {};
rc.currTextureIndex = GetTextureIndexUAV(sunlightVisTexture, 0);
rc.prevTextureIndex = GetTextureIndexSRV(prevSunlightVisTexture);
rc.prevTextureSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear);
rc.alpha = 0.9f;
CmdBindPipeline(frustumTemporalFloatPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4);
}
{
SCOPED_DEBUG_LABEL("Vis Copy", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(sunlightVisTexture, ResourceStates::CopySourceBit);
CmdTextureBarrier(prevSunlightVisTexture, ResourceStates::CopyDestinationBit);
CmdEndBarrier();
CmdCopyTexture(prevSunlightVisTexture, sunlightVisTexture);
}
{
SCOPED_DEBUG_LABEL("Sunlight", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
for(int c = 0; c < 4; c++)
{
CmdTextureBarrier(sunShadowTextures[c], ResourceStates::ComputeShaderAccessBit);
}
CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(sunlightVisTexture, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLSunlightRC rc = {};
rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0);
rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0);
rc.sunlightVisTextureIndex = GetTextureIndexUAV(sunlightVisTexture, 0);
rc.scatterExtTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0);
CmdBindPipeline(sunlightScatterPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4);
}
}
void VolumetricLight::DrawEnd()
{
SCOPED_RENDER_PASS("VL Raymarch", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit);
CmdTextureBarrier(scatterTransTexture, ResourceStates::UnorderedAccessBit);
CmdEndBarrier();
VLRaymarchRC rc = {};
rc.scatterTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0);
rc.resolveTextureIndex = GetTextureIndexUAV(scatterTransTexture, 0);
rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0);
CmdBindPipeline(frustumRaymarchPipeline);
CmdSetComputeRootConstants(0, sizeof(rc), &rc);
CmdDispatch((frustumSize[0] + 7) / 8, (frustumSize[1] + 7) / 8, 1);
jitterCounter++;
}
void VolumetricLight::DrawIm3d()
{
if((uint32_t)activeFogIndex < fogCount &&
crp.im3d.ShouldDrawGizmos())
{
Fog& fog = fogs[activeFogIndex];
if(!fog.isGlobalFog)
{
const char* const id = va("Fog #%d", (int)activeFogIndex + 1);
matrix3x3_t rotation;
R_MakeIdentityMatrix3x3(rotation);
Im3d::Gizmo(id, fog.boxCenter, rotation, fog.boxSize);
}
}
}
void VolumetricLight::DrawDebug()
{
if(!ShouldDrawDebug())
{
return;
}
if(drawExtinctionDebug)
{
SCOPED_RENDER_PASS("VL Extinction Viz", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
for(int c = 0; c < 4; c++)
{
CmdTextureBarrier(extinctionTextures[c], ResourceStates::UnorderedAccessBit);
}
CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit);
CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit);
CmdEndBarrier();
const int c = debugExtinctionCascadeIndex;
VLExtinctionVizRC rc = {};
rc.extinctionTextureIndex = GetTextureIndexUAV(extinctionTextures[c], 0);
rc.worldScale = extinctionVolumeScale[c];
rc.boxScale = debugBoxScale;
rc.extinctionScale = debugExtinctionScale;
VectorCopy(debugCameraPosition, rc.cameraPosition);
VectorCopy(colorRed, rc.color);
const uint32_t voxelCount = extinctionSize[0] * extinctionSize[1] * extinctionSize[2];
const uint32_t vertexCount = voxelCount * 36;
CmdBindRenderTargets(1, &crp.renderTarget, &crp.depthTexture);
CmdBindPipeline(extinctionVizPipeline);
CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
CmdDraw(vertexCount, 0);
}
if(drawSunShadowDebug)
{
SCOPED_RENDER_PASS("VL Sun Shadow Viz", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
for(int c = 0; c < 4; c++)
{
CmdTextureBarrier(sunShadowTextures[c], ResourceStates::UnorderedAccessBit);
}
CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit);
CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit);
CmdEndBarrier();
const int c = debugSunShadowCascadeIndex;
VLSunShadowVizRC rc = {};
rc.shadowTextureIndex = GetTextureIndexUAV(sunShadowTextures[c], 0);
rc.worldScale = sunShadowVolumeScale[c];
rc.boxScale = debugBoxScale;
VectorCopy(debugCameraPosition, rc.cameraPosition);
VectorCopy(colorRed, rc.color);
const uint32_t voxelCount = sunShadowSize[0] * sunShadowSize[1] * sunShadowSize[2];
const uint32_t vertexCount = voxelCount * 36;
CmdBindRenderTargets(1, &crp.renderTarget, &crp.depthTexture);
CmdBindPipeline(sunShadowVizPipeline);
CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
CmdDraw(vertexCount, 0);
}
if(drawAmbientDebug && tr.world->lightGridData != NULL)
{
SCOPED_RENDER_PASS("VL Ambient Viz", 1.0f, 1.0f, 1.0f);
CmdBeginBarrier();
CmdTextureBarrier(ambientLightTextureA, ResourceStates::PixelShaderAccessBit);
CmdTextureBarrier(ambientLightTextureB, ResourceStates::PixelShaderAccessBit);
CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit);
CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit);
CmdEndBarrier();
VLAmbientVizRC rc = {};
SetLightGridRootConstants(rc.lightGrid);
rc.sphereScale = debugSphereScale;
const uint32_t voxelCount = tr.world->lightGridBounds[0] * tr.world->lightGridBounds[1] * tr.world->lightGridBounds[2];
const uint32_t vertexCount = voxelCount * 6;
CmdBindRenderTargets(1, &crp.renderTarget, &crp.depthTexture);
CmdBindPipeline(ambientVizPipeline);
CmdSetGraphicsRootConstants(0, sizeof(rc), &rc);
CmdDraw(vertexCount, 0);
}
}
void VolumetricLight::DrawGUI()
{
activeFogIndex = -1;
if(!tr.worldMapLoaded)
{
return;
}
GUI_AddMainMenuItem(GUI_MainMenu::Tools, "Edit Volumetrics", "", &windowActive);
if(!lockCameraPosition)
{
VectorCopy(backEnd.viewParms.world.viewOrigin, debugCameraPosition);
}
if(!windowActive)
{
return;
}
if(ImGui::Begin("Volumetric Data", &windowActive, ImGuiWindowFlags_AlwaysAutoResize))
{
const ImGuiColorEditFlags colorEditFlags =
ImGuiColorEditFlags_Float |
ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_DisplayRGB |
ImGuiColorEditFlags_InputRGB;
if(fogCount < ARRAY_LEN(fogs) && ImGui::Button("Add Fog"))
{
const float fogSize = 40.0f;
Fog& fog = fogs[fogCount++];
fog = {};
fog.extinction = OpaqueDistanceToExtinction(5000.0f);
fog.albedo = 0.7f;
VectorSet(fog.scatterColor, 1, 1, 1);
fog.emissive = 0.0f;
VectorSet(fog.emissiveColor, 1, 1, 1);
fog.anisotropy = 0.0f;
fog.noiseStrength = 1.0f;
fog.noiseSpatialPeriod = 50.0f;
fog.noiseTimePeriod = 8.0f;
fog.isHeightFog = false;
fog.isGlobalFog = true;
for(int a = 0; a < 3; a++)
{
VectorCopy(backEnd.refdef.vieworg, fog.boxCenter);
VectorSet(fog.boxSize, fogSize, fogSize, fogSize);
}
}
ImGui::SameLine();
if(fogCount > 0 && ImGui::Button("Save Config..."))
{
SaveFileDialog_Open("fogs", ".fogs");
}
ImGui::SameLine();
if(ImGui::Button("Open Config..."))
{
OpenFileDialog_Open("fogs", ".fogs");
}
const char* axisNames[3] = { "X", "Y", "Z" };
if(ImGui::BeginTabBar("Tabs#Fogs", ImGuiTabBarFlags_AutoSelectNewTabs))
{
for(int i = 0; i < fogCount; i++)
{
if(ImGui::BeginTabItem(va("#%d", i + 1)))
{
activeFogIndex = i;
Fog& fog = fogs[i];
ImGui::Checkbox("Global fog", &fog.isGlobalFog);
if(!fog.isGlobalFog)
{
for(int a = 0; a < 3; a++)
{
ImGui::SliderFloat(va("Box center %s", axisNames[a]), &fog.boxCenter[a], mapBoxMin[a], mapBoxMax[a], "%g");
}
for(int a = 0; a < 3; a++)
{
ImGui::SliderFloat(va("Box size %s", axisNames[a]), &fog.boxSize[a], 0.0f, mapBoxMax[a] - mapBoxMin[a], "%g");
}
}
float opaqueDistance = ExtinctionToOpaqueDistance(fog.extinction);
ImGui::SliderFloat("Distance to opaque", &opaqueDistance, 1.0f, 10000.0f, "%g");
fog.extinction = OpaqueDistanceToExtinction(opaqueDistance);
ImGui::ColorEdit3("Reflective (scatter) color", fog.scatterColor, colorEditFlags);
ImGui::SliderFloat("Reflectivity (albedo)", &fog.albedo, 0.0f, 1.0f, "%g");
ImGui::SliderFloat("Directionality (anisotropy)", &fog.anisotropy, 0.0f, 1.0f, "%g");
ImGui::SliderFloat("Emissive strength", &fog.emissive, 0.0f, 0.001f, "%g");
ImGui::ColorEdit3("Emissive color", fog.emissiveColor, colorEditFlags);
ImGui::SliderFloat("Noise strength", &fog.noiseStrength, 1.0f, 10.0f, "%g");
ImGui::SliderFloat("Noise space period", &fog.noiseSpatialPeriod, 0.0f, 200.0f, "%g");
ImGui::SliderFloat("Noise time period", &fog.noiseTimePeriod, 0.0f, 20.0f, "%g");
ImGui::Checkbox("Height fog", &fog.isHeightFog);
ImGui::EndTabItem();
ImGui::Separator();
if(ImGui::Button("Remove"))
{
if(i < fogCount - 1)
{
memmove(&fogs[i], &fogs[i + 1], (fogCount - 1 - i) * sizeof(fogs[0]));
}
fogCount--;
}
}
}
if(ImGui::BeginTabItem("Main"))
{
bool enableVL = crp_volLight->integer != 0;
if(ImGui::Checkbox("Enable volumetric lighting", &enableVL))
{
Cvar_Set(crp_volLight->name, enableVL ? "1" : "0");
}
ImGui::Checkbox("Enable sunlight", &drawSunlight);
ImGui::Separator();
ImGui::Checkbox("Enable light grid", &enableLightGrid);
ImGui::ColorEdit3("Ambient light color", ambientColorGUI, colorEditFlags);
ImGui::SliderFloat("Ambient light intensity", &ambientIntensity, 0.0f, 1.0f);
ImGui::Separator();
ImGui::SliderFloat("Sunlight intensity", &crp.sunlightData.intensityVL, 0.0f, 200.0f);
ImGui::SliderFloat("Point light intensity", &pointLightIntensity, 0.0f, 200.0f);
const float brightness = Brightness(ambientColorGUI);
if(brightness > 0.000001f)
{
const float scale = 0.5f / brightness;
for(int i = 0; i < 3; i++)
{
ambientColor[i] = ambientColorGUI[i] * scale;
}
}
else
{
VectorSet(ambientColor, 0.5f, 0.5f, 0.5f);
}
ImGui::EndTabItem();
}
if(ImGui::BeginTabItem("Resolution"))
{
const float titleWidth = 14.0f * ImGui::GetFontSize();
RadioButton(&xySubsampling, titleWidth, "X/Y sub-sampling", 3, "2x", 1, "4x", 2, "8x", 3);
RadioButton(&zResolution, titleWidth, "Z resolution", 3, "128", 128, "256", 256, "512", 512);
RadioButton(&extinctionResolution, titleWidth, "Extinction resolution", 3, "64", 64, "128", 128, "256", 256);
RadioButton(&sunShadowResolution, titleWidth, "Sun shadow resolution", 3, "64", 64, "128", 128, "256", 256);
RadioButton(&pointShadowResolution, titleWidth, "Point shadow resolution", 3, "32", 32, "64", 64, "128", 128);
int divisor = 1;
for(int i = 0; i < xySubsampling; i++)
{
divisor *= 2;
}
if((glConfig.vidWidth / divisor) != frustumSize[0] ||
(glConfig.vidHeight / divisor) != frustumSize[1] ||
zResolution != frustumSize[2] ||
extinctionResolution != extinctionSize[0] ||
sunShadowResolution != sunShadowSize[0] ||
pointShadowResolution != shadowPixelCount)
{
if(ImGui::Button("Video restart"))
{
Cbuf_AddText("vid_restart\n");
}
}
ImGui::EndTabItem();
}
if(ImGui::BeginTabItem("Debug"))
{
if(tr.world->lightGridData != NULL)
{
ImGui::Checkbox("Draw ambient light grid", &drawAmbientDebug);
if(drawAmbientDebug)
{
ImGui::SliderFloat("Voxel sphere scale", &debugSphereScale, 0.25f, 1.0f);
}
}
else
{
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "No valid light grid was found for this map");
}
ImGui::Separator();
ImGui::Checkbox("Draw extinction volume", &drawExtinctionDebug);
if(drawExtinctionDebug)
{
ImGui::SliderInt("Extinction cascade", &debugExtinctionCascadeIndex, 0, 3);
ImGui::SliderFloat("Extinction value scale", &debugExtinctionScale, 1.0f, 1000.0f);
}
ImGui::Separator();
ImGui::Checkbox("Draw sun shadow volume", &drawSunShadowDebug);
if(drawSunShadowDebug)
{
ImGui::SliderInt("Sun shadow cascade", &debugSunShadowCascadeIndex, 0, 3);
}
ImGui::Separator();
ImGui::Checkbox("Lock camera position", &lockCameraPosition);
ImGui::SliderFloat("Voxel box scale", &debugBoxScale, 0.25f, 1.0f);
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
if(SaveFileDialog_Do())
{
SaveFogFile(SaveFileDialog_GetPath());
}
if(OpenFileDialog_Do())
{
LoadFogFile(OpenFileDialog_GetPath());
}
}
ImGui::End();
}
bool VolumetricLight::WantRTASUpdate(const trRefdef_t& scene)
{
return crp_volLight->integer != 0;
}
bool VolumetricLight::ShouldDraw()
{
if(crp_volLight->integer == 0 ||
(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) != 0 ||
!IsViewportFullscreen(backEnd.viewParms) ||
lockCameraPosition)
{
return false;
}
return true;
}
bool VolumetricLight::ShouldDrawDebug()
{
if(crp_volLight->integer == 0 ||
(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) != 0 ||
!IsViewportFullscreen(backEnd.viewParms))
{
return false;
}
return drawExtinctionDebug || drawSunShadowDebug || drawAmbientDebug;
}
bool VolumetricLight::LoadFogFile(const char* filePath)
{
bool success = false;
void* data = NULL;
const int byteCount = ri.FS_ReadFile(filePath, &data);
if(data != NULL &&
byteCount > 0 &&
byteCount <= sizeof(fogs) &&
(byteCount % sizeof(fogs[0])) == 0)
{
memcpy(&fogs[0], data, byteCount);
fogCount = byteCount / sizeof(fogs[0]);
success = true;
}
if(data != NULL)
{
ri.FS_FreeFile(data);
}
return success;
}
void VolumetricLight::SaveFogFile(const char* filePath)
{
if(fogCount > 0)
{
FS_EnableCNQ3FolderWrites(qtrue);
ri.FS_WriteFile(filePath, &fogs[0], fogCount * sizeof(fogs[0]));
FS_EnableCNQ3FolderWrites(qfalse);
}
}
void VolumetricLight::SetLightGridRootConstants(LightGridRC& rc)
{
rc.isAvailable = tr.world->lightGridData != NULL && enableLightGrid;
if(rc.isAvailable)
{
rc.textureAIndex = GetTextureIndexSRV(ambientLightTextureA);
rc.textureBIndex = GetTextureIndexSRV(ambientLightTextureB);
VectorCopy(lightGridCenter, rc.centerPosition);
VectorCopy(tr.world->lightGridSize, rc.worldScale);
rc.samplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear);
}
}