/* =========================================================================== Copyright (C) 2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). Challenge Quake 3 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Challenge Quake 3 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Challenge Quake 3. If not, see . =========================================================================== */ // Cinematic Rendering Pipeline - fog and particles lit by the sun and local lights #include "crp_local.h" #include "../client/cl_imgui.h" #include "shaders/crp/scene_view.h.hlsli" #include "shaders/crp/vl_common.h.hlsli" #include "compshaders/crp/fullscreen.h" #if defined(VL_CPU_PARTICLES) #include "compshaders/crp/vl_extinction_injection_particles.h" #include "compshaders/crp/vl_frustum_injection_particles.h" #include "compshaders/crp/vl_particles_dispatch.h" #include "compshaders/crp/vl_particles_preprocess_extinction.h" #include "compshaders/crp/vl_particles_preprocess_frustum.h" #endif #include "compshaders/crp/vl_extinction_injection_fog.h" #include "compshaders/crp/vl_frustum_anisotropy_average.h" #include "compshaders/crp/vl_frustum_injection_fog.h" #include "compshaders/crp/vl_frustum_inscatter_ambient.h" #include "compshaders/crp/vl_frustum_inscatter_point_light.h" #include "compshaders/crp/vl_frustum_inscatter_sunlight.h" #include "compshaders/crp/vl_frustum_raymarch.h" #include "compshaders/crp/vl_frustum_sunlight_visibility.h" #include "compshaders/crp/vl_frustum_temporal.h" #include "compshaders/crp/vl_shadow_point_light.h" #include "compshaders/crp/vl_shadow_sun.h" #include "compshaders/crp/vl_debug_ambient.h" #include "compshaders/crp/vl_debug_extinction.h" #include "compshaders/crp/vl_debug_shadow_sun.h" #pragma pack(push, 4) struct VLGlobalFogRC { FogVolume fog; float time; uint32_t materialTextureAIndex; uint32_t materialTextureBIndex; uint32_t materialTextureCIndex; }; struct VLParticlePreProcessRC { uvec3_t fullResolution; uint32_t tileBufferIndex; uvec3_t tileResolution; uint32_t particleBufferIndex; uvec3_t tileScale; uint32_t particleCount; }; struct VLParticlePreProcessExtinctionRC { uvec3_t fullResolution; uint32_t tileBufferIndex; uvec3_t tileResolution; uint32_t particleBufferIndex; uvec3_t tileScale; uint32_t particleCount; float extinctionWorldScale; }; struct VLParticleDispatchRC { uvec3_t tileResolution; uint32_t tileBufferIndex; uint32_t dispatchBufferIndex; uint32_t particleTileBufferIndex; }; struct VLParticleRC { uvec3_t tileScale; uint32_t particleBufferIndex; uint32_t particleCount; uint32_t materialTextureAIndex; uint32_t materialTextureBIndex; uint32_t materialTextureCIndex; uint32_t tileBufferIndex; uint32_t tileCount; }; struct VLParticleExtinctionRC { uvec3_t tileScale; uint32_t particleBufferIndex; uint32_t particleCount; uint32_t extinctionTextureIndex; uint32_t tileBufferIndex; uint32_t tileCount; float extinctionWorldScale; }; struct VLAnisotropyRC { uint32_t materialTextureBIndex; uint32_t materialTextureCIndex; }; struct VLSunlightVisRC { vec3_t jitter; uint32_t visTextureIndex; uint32_t depthMip; }; struct VLSunlightRC { uint32_t materialTextureAIndex; uint32_t materialTextureBIndex; uint32_t scatterExtTextureIndex; uint32_t sunlightVisTextureIndex; }; struct VLAmbientRC { vec3_t centerPosition; uint32_t materialTextureAIndex; vec3_t worldScale; uint32_t scatterExtTextureIndex; uint32_t ambientLightTextureAIndex; uint32_t ambientLightTextureBIndex; uint32_t ambientSamplerIndex; uint32_t isLightGridAvailable; }; struct VLRaymarchRC { uint32_t scatterTextureIndex; uint32_t resolveTextureIndex; uint32_t materialTextureBIndex; }; struct VLExtinctionFogRC { FogVolume fog; float time; uint32_t extinctionTextureIndex; float worldScale; }; struct VLPointLightShadowRC { vec3_t lightPosition; float extinctionWorldScale; float shadowWorldScale; uint32_t shadowTextureIndex; }; struct VLSunlightShadowRC { uint32_t shadowTextureIndex; uint32_t sourceTextureIndex; float shadowWorldScale; float sourceWorldScale; }; struct VLPointLightScatterRC { DynamicLight light; uint32_t materialTextureAIndex; uint32_t materialTextureBIndex; uint32_t scatterExtTextureIndex; uint32_t transmittanceTextureIndex; uint32_t transmittanceSamplerIndex; float shadowWorldScale; }; struct VLExtinctionVizRC { vec3_t color; float worldScale; vec3_t cameraPosition; float boxScale; // 1 for full size float extinctionScale; uint32_t extinctionTextureIndex; }; struct VLSunShadowVizRC { vec3_t color; float worldScale; vec3_t cameraPosition; float boxScale; // 1 for full size uint32_t shadowTextureIndex; }; struct VLAmbientVizRC { vec3_t centerPosition; float sphereScale; vec3_t worldScale; uint32_t lightGridTextureAIndex; uint32_t lightGridTextureBIndex; }; struct VLTemporalRC { uint32_t currTextureIndex; uint32_t prevTextureIndex; uint32_t prevTextureSamplerIndex; float alpha; }; #pragma pack(pop) const float MaxFogCoordinate = 69420.0f; static uint32_t ReverseBits32(uint32_t n) { n = (n << 16) | (n >> 16); n = ((n & 0x00ff00ff) << 8) | ((n & 0xff00ff00) >> 8); n = ((n & 0x0f0f0f0f) << 4) | ((n & 0xf0f0f0f0) >> 4); n = ((n & 0x33333333) << 2) | ((n & 0xcccccccc) >> 2); n = ((n & 0x55555555) << 1) | ((n & 0xaaaaaaaa) >> 1); return n; } static float RadicalInverseBase2(uint32_t seqIndex) { return ReverseBits32(seqIndex) * 0x1p-32; } static float RadicalInverse(uint64_t seqIndex, uint32_t base) { const float oneMinusEpsilon = 0x1.fffffep-1; const float invBase = 1.0f / (float)base; uint32_t reversedDigits = 0; float invBaseN = 1.0f; while(seqIndex) { uint32_t next = seqIndex / base; uint32_t digit = seqIndex - next * base; reversedDigits = reversedDigits * base + digit; invBaseN *= invBase; seqIndex = next; } return fminf(reversedDigits * invBaseN, oneMinusEpsilon); } static float VanDerCorputSequence(uint32_t seqIndex) { return RadicalInverseBase2(seqIndex); } static void Halton23Sequence(vec2_t values01, uint32_t seqIndex) { values01[0] = RadicalInverseBase2(seqIndex); values01[1] = RadicalInverse(seqIndex, 3); } static float Brightness(const vec3_t color) { return color[0] * 0.299f + color[1] * 0.587f + color[2] * 0.114f; } static void ConvertFog(FogVolume& dst, const VolumetricLight::Fog& src, const VolumetricLight& vl) { const float scatter = src.albedo * src.extinction / Brightness(src.scatterColor); VectorScale(src.scatterColor, scatter, dst.scatter); dst.absorption = src.extinction - Brightness(dst.scatter); VectorScale(src.emissiveColor, src.emissive, dst.emissive); dst.anisotropy = src.anisotropy; if(src.isGlobalFog) { VectorCopy(vl.mapBoxMin, dst.boxMin); VectorCopy(vl.mapBoxMax, dst.boxMax); } else { VectorCopy(src.boxMin, dst.boxMin); VectorCopy(src.boxMax, dst.boxMax); } dst.noiseMin = 1.0f; dst.noiseMax = src.noiseStrength; dst.noiseScale = 1.0f / src.noiseSpatialPeriod; dst.noiseTimeScale = 1.0f / src.noiseTimePeriod; dst.isHeightFog = src.isHeightFog; } static const float OpaqueTransmittanceThreshold = 1.0f / 256.0f; static const float LnOpaqueTransmittanceThreshold = logf(OpaqueTransmittanceThreshold); static float OpaqueDistanceToExtinction(float opaqueDistance) { return -LnOpaqueTransmittanceThreshold / opaqueDistance; } static float ExtinctionToOpaqueDistance(float extinction) { return -LnOpaqueTransmittanceThreshold / extinction; } #if defined(VL_CPU_PARTICLES) void CRP_AddParticle(const vec3_t position, float radius, float alpha) { uint32_t& particleCount = crp.volumetricLight.particleCount; if(particleCount >= MAX_PARTICLES) { return; } Particle p = {}; VectorCopy(position, p.position); p.radius = radius; p.scattering[0] = 0.1f * alpha; p.scattering[1] = 0.1f * alpha; p.scattering[2] = 0.1f * alpha; p.absorption = 0.1f * alpha; p.anisotropy = 0.0f; p.isEmissive = 0; HBuffer buffer = crp.volumetricLight.particleBuffer; Particle* const particle = (Particle*)MapBuffer(buffer) + particleCount; memcpy(particle, &p, sizeof(p)); UnmapBuffer(buffer); particleCount++; } // for use with billboarded quads such as CPMA's rocket smoke void CRP_AddPolygonAsParticle(const polyVert_t* vertices, int vertexCount) { vec3_t bounds[2]; VectorCopy(vertices[0].xyz, bounds[0]); VectorCopy(vertices[0].xyz, bounds[1]); for(int i = 1; i < vertexCount; i++) { AddPointToBounds(vertices[i].xyz, bounds[0], bounds[1]); } vec3_t position; VectorAdd(bounds[0], bounds[1], position); VectorScale(position, 0.5f, position); vec3_t extents; VectorSubtract(bounds[1], bounds[0], extents); const float radius = max(max(extents[0], extents[1]), extents[2]) * 0.5f; const float alpha = (float)vertices[0].modulate[3] / 255.0f; CRP_AddParticle(position, radius, alpha); } #endif void VolumetricLight::Init() { if(srp.firstInit) { VectorSet(ambientColor, 0.125f, 0.125f, 0.125f); ambientIntensity = 1.0f; } // patched on world load VectorSet(mapBoxMin, -MaxFogCoordinate, -MaxFogCoordinate, -MaxFogCoordinate); VectorSet(mapBoxMax, MaxFogCoordinate, MaxFogCoordinate, MaxFogCoordinate); VectorSet(frustumSize, glConfig.vidWidth / 8, glConfig.vidHeight / 8, 256); depthMip = 3; VectorSet(frustumTileScale, 8, 8, 16); // x*y*z == 1024, must match the shader VectorSet(frustumTileSize, (frustumSize[0] + frustumTileScale[0] - 1) / frustumTileScale[0], (frustumSize[1] + frustumTileScale[1] - 1) / frustumTileScale[1], (frustumSize[2] + frustumTileScale[2] - 1) / frustumTileScale[2]); VectorSet(extinctionSize, 128, 128, 128); VectorSet(extinctionTileScale, 8, 8, 8); // 8*8*8 == 512, must match the shader VectorSet(extinctionTileSize, (extinctionSize[0] + extinctionTileScale[0] - 1) / extinctionTileScale[0], (extinctionSize[1] + extinctionTileScale[1] - 1) / extinctionTileScale[1], (extinctionSize[2] + extinctionTileScale[2] - 1) / extinctionTileScale[2]); Vector4Set(extinctionVolumeScale, 8, 16, 32, 64); // patched on world load VectorSet(sunShadowSize, 128, 128, 128); Vector4Set(sunShadowVolumeScale, 8, 16, 32, 64); // patched on world load shadowPixelCount = 64; pointShadowVolumeScale = 8.0f; jitterCounter = 0; #if defined(VL_CPU_PARTICLES) extinctionParticlePipeline = CreateComputePipeline("VL Extinction Particles", ShaderByteCode(g_vl_extinction_injection_particles_cs)); frustumParticlePipeline = CreateComputePipeline("VL Frustum Particles", ShaderByteCode(g_vl_frustum_injection_particles_cs)); particleDispatchPipeline = CreateComputePipeline("VL Particles Dispatch", ShaderByteCode(g_vl_particles_dispatch_cs)); particlePreProcessExtinctionPipeline = CreateComputePipeline("VL Particles Extinction Pre-process", ShaderByteCode(g_vl_particles_preprocess_extinction_cs)); particlePreProcessFrustumPipeline = CreateComputePipeline("VL Particles Frustum Pre-process", ShaderByteCode(g_vl_particles_preprocess_frustum_cs)); #endif extinctionFogPipeline = CreateComputePipeline("VL Extinction Fog", ShaderByteCode(g_vl_extinction_injection_fog_cs)); frustumAmbientPipeline = CreateComputePipeline("VL Frustum Ambient Light Scatter", ShaderByteCode(g_vl_frustum_inscatter_ambient_cs)); frustumAnisotropyPipeline = CreateComputePipeline("VL Frustum Finalize Material", ShaderByteCode(g_vl_frustum_anisotropy_average_cs)); frustumFogPipeline = CreateComputePipeline("VL Frustum Fog", ShaderByteCode(g_vl_frustum_injection_fog_cs)); frustumRaymarchPipeline = CreateComputePipeline("VL Frustum Raymarch", ShaderByteCode(g_vl_frustum_raymarch_cs)); frustumTemporalPipeline = CreateComputePipeline("VL Frustum Temporal Reprojection", ShaderByteCode(g_vl_frustum_temporal_cs)); pointLightShadowPipeline = CreateComputePipeline("VL Shadow Raymarch Point Light", ShaderByteCode(g_vl_shadow_point_light_cs)); sunlightScatterPipeline = CreateComputePipeline("VL Frustum Sunlight Scatter", ShaderByteCode(g_vl_frustum_inscatter_sunlight_cs)); sunlightShadowPipeline = CreateComputePipeline("VL Shadow Raymarch Sun", ShaderByteCode(g_vl_shadow_sun_cs)); if(rhiInfo.hasInlineRaytracing) { frustumSunlightVisPipeline = CreateComputePipeline("VL Frustum Sunlight Visibility", ShaderByteCode(g_vl_frustum_sunlight_visibility_cs)); frustumPointLightScatterPipeline = CreateComputePipeline("VL Frustum Point Light Scatter", ShaderByteCode(g_vl_frustum_inscatter_point_light_cs)); } else { frustumSunlightVisPipeline = RHI_MAKE_NULL_HANDLE(); frustumPointLightScatterPipeline = RHI_MAKE_NULL_HANDLE(); } { GraphicsPipelineDesc desc("VL Extinction Viz"); desc.rootSignature = RHI_MAKE_NULL_HANDLE(); desc.vertexShader.Set(g_vl_debug_extinction_vs); desc.pixelShader.Set(g_vl_debug_extinction_ps); desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float; desc.depthStencil.depthComparison = ComparisonFunction::GreaterEqual; desc.depthStencil.enableDepthTest = true; desc.depthStencil.enableDepthWrites = true; desc.rasterizer.cullMode = CT_TWO_SIDED; // @TODO: desc.AddRenderTarget(0, crp.renderTargetFormat); extinctionVizPipeline = CreateGraphicsPipeline(desc); } { GraphicsPipelineDesc desc("VL Sun Shadow Viz"); desc.rootSignature = RHI_MAKE_NULL_HANDLE(); desc.vertexShader.Set(g_vl_debug_shadow_sun_vs); desc.pixelShader.Set(g_vl_debug_shadow_sun_ps); desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float; desc.depthStencil.depthComparison = ComparisonFunction::GreaterEqual; desc.depthStencil.enableDepthTest = true; desc.depthStencil.enableDepthWrites = true; desc.rasterizer.cullMode = CT_TWO_SIDED; // @TODO: desc.AddRenderTarget(0, crp.renderTargetFormat); sunShadowVizPipeline = CreateGraphicsPipeline(desc); } { GraphicsPipelineDesc desc("VL Ambient Viz"); desc.rootSignature = RHI_MAKE_NULL_HANDLE(); desc.vertexShader.Set(g_vl_debug_ambient_vs); desc.pixelShader.Set(g_vl_debug_ambient_ps); desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float; desc.depthStencil.depthComparison = ComparisonFunction::GreaterEqual; desc.depthStencil.enableDepthTest = true; desc.depthStencil.enableDepthWrites = true; desc.rasterizer.cullMode = CT_TWO_SIDED; // @TODO: desc.AddRenderTarget(0, crp.renderTargetFormat); ambientVizPipeline = CreateGraphicsPipeline(desc); } { TextureDesc desc("VL", frustumSize[0], frustumSize[1]); desc.shortLifeTime = true; desc.committedResource = true; desc.initialState = ResourceStates::UnorderedAccessBit; desc.allowedState = ResourceStates::UnorderedAccessBit | ResourceStates::ComputeShaderAccessBit | ResourceStates::PixelShaderAccessBit; desc.depth = frustumSize[2]; desc.name = "VL scatter/absorption"; desc.format = TextureFormat::R16G16B16A16_Float; materialTextureA = CreateTexture(desc); desc.name = "VL emissive/anisotropy"; desc.format = TextureFormat::R16G16B16A16_Float; materialTextureB = CreateTexture(desc); desc.name = "VL anisotropy counter"; desc.format = TextureFormat::R16_Float; materialTextureC = CreateTexture(desc); desc.name = "VL sunlight vis"; desc.format = TextureFormat::R8_UNorm; sunlightVisTexture = CreateTexture(desc); desc.name = "VL sunlight vis temporal"; desc.format = TextureFormat::R8_UNorm; prevSunlightVisTexture = CreateTexture(desc); desc.name = "VL in-scatter/ext"; desc.format = TextureFormat::R16G16B16A16_Float; scatterExtTexture = CreateTexture(desc); desc.name = "VL in-scatter/trans"; desc.format = TextureFormat::R16G16B16A16_Float; scatterTransTexture = CreateTexture(desc); desc.width = extinctionSize[0]; desc.height = extinctionSize[1]; desc.depth = extinctionSize[2]; desc.format = TextureFormat::R16_Float; for(int i = 0; i < 4; i++) { desc.name = va("VL extinction #%d", i + 1); extinctionTextures[i] = CreateTexture(desc); } desc.width = sunShadowSize[0]; desc.height = sunShadowSize[1]; desc.depth = sunShadowSize[2]; desc.format = TextureFormat::R16_Float; for(int i = 0; i < 4; i++) { desc.name = va("VL sun shadow #%d", i + 1); sunShadowTextures[i] = CreateTexture(desc); } desc.width = shadowPixelCount; desc.height = shadowPixelCount; desc.depth = shadowPixelCount; desc.name = "VL point light shadow"; desc.format = TextureFormat::R16_Float; pointShadowTexture = CreateTexture(desc); ambientLightTextureA = RHI_MAKE_NULL_HANDLE(); // created on world load when available ambientLightTextureB = RHI_MAKE_NULL_HANDLE(); // created on world load when available } #if defined(VL_CPU_PARTICLES) const uint32_t tileCountF = frustumTileSize[0] * frustumTileSize[1] * frustumTileSize[2]; const uint32_t tileCountE = extinctionTileSize[0] * extinctionTileSize[1] * extinctionTileSize[2]; const uint32_t maxTileCount = max(tileCountF, tileCountE); { BufferDesc desc("particle", MAX_PARTICLES * sizeof(Particle), ResourceStates::ComputeShaderAccessBit); desc.shortLifeTime = true; desc.memoryUsage = MemoryUsage::Upload; desc.structureByteCount = sizeof(Particle); particleBuffer = CreateBuffer(desc); } { const uint32_t tileSize = 4; // 1 uint const uint32_t byteCount = maxTileCount * tileSize; BufferDesc desc("particle hit", byteCount, ResourceStates::UnorderedAccessBit); desc.shortLifeTime = true; particleHitBuffer = CreateBuffer(desc); } { const uint32_t tileSize = 12; // 3 uint const uint32_t byteCount = maxTileCount * tileSize; BufferDesc desc("particle tile", byteCount, ResourceStates::UnorderedAccessBit); desc.shortLifeTime = true; desc.structureByteCount = tileSize; particleTileBuffer = CreateBuffer(desc); } { BufferDesc desc("particle dispatch", 12, ResourceStates::UnorderedAccessBit); desc.shortLifeTime = true; particleDispatchBuffer = CreateBuffer(desc); } { BufferDesc desc("particle dispatch clear", 12, ResourceStates::CopySourceBit); desc.shortLifeTime = true; desc.memoryUsage = MemoryUsage::Upload; // @TODO: not ideal... particleDispatchClearBuffer = CreateBuffer(desc); uint32_t* const groupCounts = (uint32_t*)MapBuffer(particleDispatchClearBuffer); groupCounts[0] = 0; groupCounts[1] = 1; groupCounts[2] = 1; UnmapBuffer(particleDispatchClearBuffer); } #endif } void VolumetricLight::ProcessWorld(world_t& world) { Q_assert(world.nodes != NULL); Q_assert(world.numnodes > 0); if(world.nodes == NULL || world.numnodes <= 0) { VectorSet(mapBoxMin, -MaxFogCoordinate, -MaxFogCoordinate, -MaxFogCoordinate); VectorSet(mapBoxMax, MaxFogCoordinate, MaxFogCoordinate, MaxFogCoordinate); return; } const mnode_t& node = world.nodes[0]; vec3_t mapDimensions; VectorSubtract(node.maxs, node.mins, mapDimensions); VectorCopy(node.mins, mapBoxMin); VectorCopy(node.maxs, mapBoxMax); if(world.lightGridData != NULL) { for(int i = 0; i < 3; i++) { lightGridCenter[i] = world.lightGridOrigin[i] + (world.lightGridBounds[i] * 0.5f - 0.5f) * world.lightGridSize[i]; } TextureDesc desc("VL", world.lightGridBounds[0], world.lightGridBounds[1]); desc.shortLifeTime = true; desc.committedResource = true; desc.initialState = ResourceStates::ComputeShaderAccessBit; desc.allowedState = ResourceStates::ComputeShaderAccessBit | ResourceStates::PixelShaderAccessBit; desc.depth = world.lightGridBounds[2]; desc.format = TextureFormat::R8G8B8A8_UNorm; desc.name = "VL ambient light A"; ambientLightTextureA = CreateTexture(desc); desc.name = "VL ambient light B"; ambientLightTextureB = CreateTexture(desc); MappedTexture texture; BeginTextureUpload(texture, ambientLightTextureA); const uint32_t rowCount = texture.rowCount * texture.sliceCount; const uint32_t columnCount = texture.columnCount; const uint32_t srcRowByteCount = world.lightGridBounds[0] * 8; for(uint32_t r = 0; r < rowCount; r++) { uint32_t* dst = (uint32_t*)(texture.mappedData + r * texture.dstRowByteCount); const uint32_t* src = (const uint32_t*)(world.lightGridData + r * srcRowByteCount); for(uint32_t c = 0; c < columnCount; c++) { *dst = src[0]; dst += 1; src += 2; } } EndTextureUpload(); BeginTextureUpload(texture, ambientLightTextureB); for(uint32_t r = 0; r < rowCount; r++) { uint32_t* dst = (uint32_t*)(texture.mappedData + r * texture.dstRowByteCount); const uint32_t* src = (const uint32_t*)(world.lightGridData + r * srcRowByteCount); for(uint32_t c = 0; c < columnCount; c++) { *dst = src[1]; dst += 1; src += 2; } } EndTextureUpload(); } { const float largest = max(max(mapDimensions[0], mapDimensions[1]), mapDimensions[2]); const float scale3 = largest / (float)extinctionSize[0]; const float scale2 = extinctionVolumeScale[2]; float downScale = 1.0f; while(scale3 <= 1.5f * scale2 * downScale) { downScale *= 0.5f; } extinctionVolumeScale[0] *= downScale; extinctionVolumeScale[1] *= downScale; extinctionVolumeScale[2] *= downScale; extinctionVolumeScale[3] = scale3; } { const float length = VectorLength(mapDimensions); const float scale3 = length / (float)sunShadowSize[0]; const float scale2 = sunShadowVolumeScale[2]; float downScale = 1.0f; while(scale3 <= 1.5f * scale2 * downScale) { downScale *= 0.5f; } sunShadowVolumeScale[0] *= downScale; sunShadowVolumeScale[1] *= downScale; sunShadowVolumeScale[2] *= downScale; sunShadowVolumeScale[3] = scale3; } if(!LoadFogFile(va("fogs/%s.fogs", world.baseName))) { fogCount = 0; // @NOTE: fog 0 is invalid for(int f = 1; f < world.numfogs && fogCount < ARRAY_LEN(fogs); f++) { const float transmittanceThreshold = 1.0 / 256.0f; const float lnThreshold = logf(transmittanceThreshold); const fog_t& q3fog = world.fogs[f]; const float distance = q3fog.parms.depthForOpaque; const float extinction = -lnThreshold / distance; Fog& fog = fogs[fogCount++]; fog = {}; VectorCopy(q3fog.bounds[0], fog.boxMin); VectorCopy(q3fog.bounds[1], fog.boxMax); fog.extinction = extinction; fog.albedo = 0.75f; VectorCopy(q3fog.parms.color, fog.scatterColor); fog.emissive = 0.0f; VectorSet(fog.emissiveColor, 1, 1, 1); fog.anisotropy = 0.0f; fog.noiseStrength = 2.0f; fog.noiseSpatialPeriod = 128.0f; fog.noiseTimePeriod = 8.0f; } } } void VolumetricLight::DrawBegin() { { SCOPED_RENDER_PASS("VL Clear", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit); for(int i = 0; i < 4; i++) { CmdTextureBarrier(extinctionTextures[i], ResourceStates::UnorderedAccessBit); } CmdEndBarrier(); const uint32_t values[4] = {}; CmdClearTextureUAV(materialTextureA, 0, values); CmdClearTextureUAV(materialTextureB, 0, values); CmdClearTextureUAV(materialTextureC, 0, values); for(int i = 0; i < 4; i++) { CmdClearTextureUAV(extinctionTextures[i], 0, values); } } { SCOPED_RENDER_PASS("VL Fog", 1.0f, 1.0f, 1.0f); for(int f = 0; f < fogCount; f++) { SCOPED_DEBUG_LABEL("VL Frustum Fog", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); VLGlobalFogRC rc = {}; ConvertFog(rc.fog, fogs[f], *this); rc.time = backEnd.refdef.floatTime; rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0); rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); rc.materialTextureCIndex = GetTextureIndexUAV(materialTextureC, 0); CmdBindPipeline(frustumFogPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); } for(int f = 0; f < fogCount; f++) { VLExtinctionFogRC rc = {}; ConvertFog(rc.fog, fogs[f], *this); rc.time = backEnd.refdef.floatTime; for(int c = 0; c < 4; c++) { SCOPED_DEBUG_LABEL("VL Extinction Fog", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(extinctionTextures[c], ResourceStates::UnorderedAccessBit); CmdEndBarrier(); rc.extinctionTextureIndex = GetTextureIndexUAV(extinctionTextures[c], 0); rc.worldScale = extinctionVolumeScale[c]; CmdBindPipeline(extinctionFogPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((extinctionSize[0] + 3) / 4, (extinctionSize[1] + 3) / 4, (extinctionSize[2] + 3) / 4); } } } #if defined(VL_CPU_PARTICLES) if(particleCount > 0) { SCOPED_RENDER_PASS("VL Frustum Particles", 1.0f, 1.0f, 1.0f); { SCOPED_DEBUG_LABEL("Pre-pass", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdBufferBarrier(particleBuffer, ResourceStates::ComputeShaderAccessBit); CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); CmdClearBufferUAV(particleHitBuffer, 0); CmdBeginBarrier(); CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); VLParticlePreProcessRC rc = {}; rc.tileBufferIndex = GetBufferIndexUAV(particleHitBuffer); rc.particleBufferIndex = GetBufferIndexSRV(particleBuffer); rc.particleCount = particleCount; VectorCopy(frustumSize, rc.fullResolution); VectorCopy(frustumTileSize, rc.tileResolution); VectorCopy(frustumTileScale, rc.tileScale); CmdBindPipeline(particlePreProcessFrustumPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((particleCount + 63) / 64, 1, 1); } { SCOPED_DEBUG_LABEL("Clear Dispatch", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdBufferBarrier(particleDispatchClearBuffer, ResourceStates::CopySourceBit); CmdBufferBarrier(particleDispatchBuffer, ResourceStates::CopyDestinationBit); CmdEndBarrier(); CmdCopyBuffer(particleDispatchBuffer, particleDispatchClearBuffer); } { SCOPED_DEBUG_LABEL("Prepare Dispatch", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); CmdBufferBarrier(particleDispatchBuffer, ResourceStates::UnorderedAccessBit); CmdBufferBarrier(particleTileBuffer, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); VLParticleDispatchRC rc = {}; rc.tileBufferIndex = GetBufferIndexUAV(particleHitBuffer); rc.dispatchBufferIndex = GetBufferIndexUAV(particleDispatchBuffer); rc.particleTileBufferIndex = GetBufferIndexUAV(particleTileBuffer); VectorCopy(frustumTileSize, rc.tileResolution); CmdBindPipeline(particleDispatchPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((frustumTileSize[0] + 3) / 4, (frustumTileSize[1] + 3) / 4, (frustumTileSize[2] + 3) / 4); } { SCOPED_DEBUG_LABEL("Injection", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdBufferBarrier(particleBuffer, ResourceStates::ComputeShaderAccessBit); CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit); CmdBufferBarrier(particleDispatchBuffer, ResourceStates::IndirectDispatchBit); CmdEndBarrier(); VLParticleRC rc = {}; rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0); rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); rc.materialTextureCIndex = GetTextureIndexUAV(materialTextureC, 0); rc.tileBufferIndex = GetBufferIndexUAV(particleTileBuffer); rc.particleBufferIndex = GetBufferIndexSRV(particleBuffer); rc.particleCount = particleCount; rc.tileCount = frustumTileSize[0] * frustumTileSize[1] * frustumTileSize[2]; VectorCopy(frustumTileScale, rc.tileScale); CmdBindPipeline(frustumParticlePipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatchIndirect(particleDispatchBuffer, 0); } } if(particleCount > 0) { SCOPED_RENDER_PASS("VL Extinction Particles", 1.0f, 1.0f, 1.0f); for(int c = 0; c < 4; c++) { { SCOPED_DEBUG_LABEL("Pre-pass", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdBufferBarrier(particleBuffer, ResourceStates::ComputeShaderAccessBit); CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); CmdClearBufferUAV(particleHitBuffer, 0); CmdBeginBarrier(); CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); VLParticlePreProcessExtinctionRC rc = {}; rc.tileBufferIndex = GetBufferIndexUAV(particleHitBuffer); rc.particleBufferIndex = GetBufferIndexSRV(particleBuffer); rc.particleCount = particleCount; VectorCopy(extinctionSize, rc.fullResolution); VectorCopy(extinctionTileSize, rc.tileResolution); VectorCopy(extinctionTileScale, rc.tileScale); rc.extinctionWorldScale = extinctionVolumeScale[c]; CmdBindPipeline(particlePreProcessExtinctionPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((particleCount + 63) / 64, 1, 1); } { SCOPED_DEBUG_LABEL("Clear Dispatch", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdBufferBarrier(particleDispatchClearBuffer, ResourceStates::CopySourceBit); CmdBufferBarrier(particleDispatchBuffer, ResourceStates::CopyDestinationBit); CmdEndBarrier(); CmdCopyBuffer(particleDispatchBuffer, particleDispatchClearBuffer); } { SCOPED_DEBUG_LABEL("Prepare Dispatch", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdBufferBarrier(particleHitBuffer, ResourceStates::UnorderedAccessBit); CmdBufferBarrier(particleDispatchBuffer, ResourceStates::UnorderedAccessBit); CmdBufferBarrier(particleTileBuffer, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); VLParticleDispatchRC rc = {}; rc.tileBufferIndex = GetBufferIndexUAV(particleHitBuffer); rc.dispatchBufferIndex = GetBufferIndexUAV(particleDispatchBuffer); rc.particleTileBufferIndex = GetBufferIndexUAV(particleTileBuffer); VectorCopy(extinctionTileSize, rc.tileResolution); CmdBindPipeline(particleDispatchPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((extinctionTileSize[0] + 3) / 4, (extinctionTileSize[1] + 3) / 4, (extinctionTileSize[2] + 3) / 4); } { SCOPED_DEBUG_LABEL("Injection", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdBufferBarrier(particleBuffer, ResourceStates::ComputeShaderAccessBit); CmdTextureBarrier(extinctionTextures[c], ResourceStates::UnorderedAccessBit); CmdBufferBarrier(particleDispatchBuffer, ResourceStates::IndirectDispatchBit); CmdEndBarrier(); VLParticleExtinctionRC rc = {}; rc.extinctionTextureIndex = GetTextureIndexUAV(extinctionTextures[c], 0); rc.tileBufferIndex = GetBufferIndexUAV(particleTileBuffer); rc.particleBufferIndex = GetBufferIndexSRV(particleBuffer); rc.particleCount = particleCount; rc.tileCount = extinctionTileSize[0] * extinctionTileSize[1] * extinctionTileSize[2]; rc.extinctionWorldScale = extinctionVolumeScale[c]; VectorCopy(extinctionTileScale, rc.tileScale); CmdBindPipeline(extinctionParticlePipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatchIndirect(particleDispatchBuffer, 0); } } } #endif { SCOPED_RENDER_PASS("VL Anisotropy Avg", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(materialTextureC, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); VLAnisotropyRC rc = {}; rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); rc.materialTextureCIndex = GetTextureIndexUAV(materialTextureC, 0); CmdBindPipeline(frustumAnisotropyPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); } { SCOPED_RENDER_PASS("VL Ambient", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit); if(tr.world->lightGridData != NULL) { CmdTextureBarrier(ambientLightTextureA, ResourceStates::ComputeShaderAccessBit); CmdTextureBarrier(ambientLightTextureB, ResourceStates::ComputeShaderAccessBit); } CmdEndBarrier(); VLAmbientRC rc = {}; rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0); rc.scatterExtTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0); rc.isLightGridAvailable = tr.world->lightGridData != NULL; if(rc.isLightGridAvailable) { rc.ambientLightTextureAIndex = GetTextureIndexSRV(ambientLightTextureA); rc.ambientLightTextureBIndex = GetTextureIndexSRV(ambientLightTextureB); VectorCopy(lightGridCenter, rc.centerPosition); VectorCopy(tr.world->lightGridSize, rc.worldScale); rc.ambientSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear); } CmdBindPipeline(frustumAmbientPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); } firstFrame = false; } void VolumetricLight::DrawPointLight(const dlight_t& light) { { SCOPED_DEBUG_LABEL("VL DL Shadow", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); for(int c = 0; c < 4; c++) { CmdTextureBarrier(extinctionTextures[c], ResourceStates::ComputeShaderAccessBit); } CmdTextureBarrier(pointShadowTexture, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); VLPointLightShadowRC rc = {}; rc.shadowTextureIndex = GetTextureIndexUAV(pointShadowTexture, 0); rc.extinctionWorldScale = extinctionVolumeScale[0]; // pick the highest resolution possible rc.shadowWorldScale = pointShadowVolumeScale; VectorCopy(light.origin, rc.lightPosition); const uint32_t groupCount = (shadowPixelCount + 3) / 4; CmdBindPipeline(pointLightShadowPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch(groupCount, groupCount, groupCount); } { SCOPED_DEBUG_LABEL("VL DL Scatter", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(pointShadowTexture, ResourceStates::ComputeShaderAccessBit); CmdEndBarrier(); VLPointLightScatterRC rc = {}; rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0); rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); rc.scatterExtTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0); rc.transmittanceTextureIndex = GetTextureIndexSRV(pointShadowTexture); rc.transmittanceSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear); rc.shadowWorldScale = pointShadowVolumeScale; VectorCopy(light.origin, rc.light.position); rc.light.radius = light.radius; VectorCopy(light.color, rc.light.color); CmdBindPipeline(frustumPointLightScatterPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); } } void VolumetricLight::DrawSunlight() { SCOPED_RENDER_PASS("VL Sunlight", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); for(int c = 0; c < 4; c++) { CmdTextureBarrier(extinctionTextures[c], ResourceStates::ComputeShaderAccessBit); } for(int c = 0; c < 3; c++) { CmdTextureBarrier(sunShadowTextures[c], ResourceStates::UnorderedAccessBit); } CmdTextureBarrier(sunShadowTextures[3], ResourceStates::ComputeShaderAccessBit); CmdEndBarrier(); // each cascade needs to sample the higher level for setting the initial transmittance value for(int c = 3; c >= 0; c--) { SCOPED_DEBUG_LABEL("VL Sunlight Shadow", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(sunShadowTextures[c], ResourceStates::UnorderedAccessBit); if(c < 3) { CmdTextureBarrier(sunShadowTextures[c + 1], ResourceStates::ComputeShaderAccessBit); } CmdEndBarrier(); VLSunlightShadowRC rc = {}; rc.shadowTextureIndex = GetTextureIndexUAV(sunShadowTextures[c], 0); rc.shadowWorldScale = sunShadowVolumeScale[c]; if(c < 3) { rc.sourceTextureIndex = GetTextureIndexSRV(sunShadowTextures[c + 1]); rc.sourceWorldScale = sunShadowVolumeScale[c + 1]; } CmdBindPipeline(sunlightShadowPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((sunShadowSize[0] + 7) / 8, (sunShadowSize[1] + 7) / 8, 1); } { SCOPED_DEBUG_LABEL("VL Sunlight Vis", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(crp.depthMinMaxTexture, ResourceStates::ComputeShaderAccessBit); CmdTextureBarrier(sunlightVisTexture, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); const float maxJitterDepthDistance = 16.0f; const float maxJitterRadiusXY = 4.0f; vec2_t point01; Halton23Sequence(point01, jitterCounter); // 6 points on the circle, rotated by 15 degrees // ordered such that it oscillates around (0, 0) const float jitterXY[] = { 0.965926f, 0.258819f, -0.965926f, -0.258819f, 0.707107f, -0.707107f, -0.707107f, 0.707107f, 0.258819f, 0.965926f, -0.258819f, -0.965926f }; const int jitterXYSampleIndex = jitterCounter % 6; VLSunlightVisRC rc = {}; rc.visTextureIndex = GetTextureIndexUAV(sunlightVisTexture, 0); rc.depthMip = depthMip; rc.jitter[0] = (0.5f * jitterXY[2 * jitterXYSampleIndex + 0]) * maxJitterRadiusXY; rc.jitter[1] = (0.5f * jitterXY[2 * jitterXYSampleIndex + 1]) * maxJitterRadiusXY; rc.jitter[2] = VanDerCorputSequence(jitterCounter) * maxJitterDepthDistance; CmdBindPipeline(frustumSunlightVisPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); } if(!firstFrame) { SCOPED_DEBUG_LABEL("VL Sunlight Vis Temporal", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(prevSunlightVisTexture, ResourceStates::ComputeShaderAccessBit); CmdTextureBarrier(sunlightVisTexture, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); VLTemporalRC rc = {}; rc.currTextureIndex = GetTextureIndexUAV(sunlightVisTexture, 0); rc.prevTextureIndex = GetTextureIndexSRV(prevSunlightVisTexture); rc.prevTextureSamplerIndex = GetSamplerIndex(TW_CLAMP_TO_EDGE, TextureFilter::Linear); rc.alpha = 0.9f; CmdBindPipeline(frustumTemporalPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); } { SCOPED_DEBUG_LABEL("VL Sunlight Vis Copy", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(sunlightVisTexture, ResourceStates::CopySourceBit); CmdTextureBarrier(prevSunlightVisTexture, ResourceStates::CopyDestinationBit); CmdEndBarrier(); CmdCopyTexture(prevSunlightVisTexture, sunlightVisTexture); } { SCOPED_DEBUG_LABEL("VL Sunlight", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); for(int c = 0; c < 4; c++) { CmdTextureBarrier(sunShadowTextures[c], ResourceStates::ComputeShaderAccessBit); } CmdTextureBarrier(materialTextureA, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(materialTextureB, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(sunlightVisTexture, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); VLSunlightRC rc = {}; rc.materialTextureAIndex = GetTextureIndexUAV(materialTextureA, 0); rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); rc.sunlightVisTextureIndex = GetTextureIndexUAV(sunlightVisTexture, 0); rc.scatterExtTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0); CmdBindPipeline(sunlightScatterPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((frustumSize[0] + 3) / 4, (frustumSize[1] + 3) / 4, (frustumSize[2] + 3) / 4); } } void VolumetricLight::DrawEnd() { SCOPED_RENDER_PASS("VL Raymarch", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(scatterExtTexture, ResourceStates::UnorderedAccessBit); CmdTextureBarrier(scatterTransTexture, ResourceStates::UnorderedAccessBit); CmdEndBarrier(); VLRaymarchRC rc = {}; rc.scatterTextureIndex = GetTextureIndexUAV(scatterExtTexture, 0); rc.resolveTextureIndex = GetTextureIndexUAV(scatterTransTexture, 0); rc.materialTextureBIndex = GetTextureIndexUAV(materialTextureB, 0); CmdBindPipeline(frustumRaymarchPipeline); CmdSetComputeRootConstants(0, sizeof(rc), &rc); CmdDispatch((frustumSize[0] + 7) / 8, (frustumSize[1] + 7) / 8, 1); jitterCounter++; } void VolumetricLight::DrawDebug() { if(!ShouldDrawDebug()) { return; } if(drawExtinctionDebug) { SCOPED_RENDER_PASS("VL Extinction Viz", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); for(int c = 0; c < 4; c++) { CmdTextureBarrier(extinctionTextures[c], ResourceStates::UnorderedAccessBit); } CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit); CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit); CmdEndBarrier(); const int c = debugExtinctionCascadeIndex; VLExtinctionVizRC rc = {}; rc.extinctionTextureIndex = GetTextureIndexUAV(extinctionTextures[c], 0); rc.worldScale = extinctionVolumeScale[c]; rc.boxScale = debugBoxScale; rc.extinctionScale = debugExtinctionScale; VectorCopy(debugCameraPosition, rc.cameraPosition); VectorCopy(colorRed, rc.color); const uint32_t voxelCount = extinctionSize[0] * extinctionSize[1] * extinctionSize[2]; const uint32_t vertexCount = voxelCount * 36; CmdBindRenderTargets(1, &crp.renderTarget, &crp.depthTexture); CmdBindPipeline(extinctionVizPipeline); CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); CmdDraw(vertexCount, 0); } if(drawSunShadowDebug) { SCOPED_RENDER_PASS("VL Sun Shadow Viz", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); for(int c = 0; c < 4; c++) { CmdTextureBarrier(sunShadowTextures[c], ResourceStates::UnorderedAccessBit); } CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit); CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit); CmdEndBarrier(); const int c = debugSunShadowCascadeIndex; VLSunShadowVizRC rc = {}; rc.shadowTextureIndex = GetTextureIndexUAV(sunShadowTextures[c], 0); rc.worldScale = sunShadowVolumeScale[c]; rc.boxScale = debugBoxScale; VectorCopy(debugCameraPosition, rc.cameraPosition); VectorCopy(colorRed, rc.color); const uint32_t voxelCount = sunShadowSize[0] * sunShadowSize[1] * sunShadowSize[2]; const uint32_t vertexCount = voxelCount * 36; CmdBindRenderTargets(1, &crp.renderTarget, &crp.depthTexture); CmdBindPipeline(sunShadowVizPipeline); CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); CmdDraw(vertexCount, 0); } if(drawAmbientDebug && tr.world->lightGridData != NULL) { SCOPED_RENDER_PASS("VL Ambient Viz", 1.0f, 1.0f, 1.0f); CmdBeginBarrier(); CmdTextureBarrier(ambientLightTextureA, ResourceStates::PixelShaderAccessBit); CmdTextureBarrier(ambientLightTextureB, ResourceStates::PixelShaderAccessBit); CmdTextureBarrier(crp.renderTarget, ResourceStates::RenderTargetBit); CmdTextureBarrier(crp.depthTexture, ResourceStates::DepthWriteBit); CmdEndBarrier(); VLAmbientVizRC rc = {}; rc.lightGridTextureAIndex = GetTextureIndexSRV(ambientLightTextureA); rc.lightGridTextureBIndex = GetTextureIndexSRV(ambientLightTextureB); VectorCopy(tr.world->lightGridSize, rc.worldScale); rc.sphereScale = debugSphereScale; VectorCopy(lightGridCenter, rc.centerPosition); const uint32_t voxelCount = tr.world->lightGridBounds[0] * tr.world->lightGridBounds[1] * tr.world->lightGridBounds[2]; const uint32_t vertexCount = voxelCount * 6; CmdBindRenderTargets(1, &crp.renderTarget, &crp.depthTexture); CmdBindPipeline(ambientVizPipeline); CmdSetGraphicsRootConstants(0, sizeof(rc), &rc); CmdDraw(vertexCount, 0); } } void VolumetricLight::DrawGUI() { if(!tr.worldMapLoaded) { return; } GUI_AddMainMenuItem(GUI_MainMenu::Tools, "Edit Volumetrics", "", &windowActive); if(!lockCameraPosition) { VectorCopy(backEnd.viewParms.world.viewOrigin, debugCameraPosition); } if(!windowActive) { return; } if(ImGui::Begin("Volumetric Data", &windowActive, ImGuiWindowFlags_AlwaysAutoResize)) { const ImGuiColorEditFlags colorEditFlags = ImGuiColorEditFlags_Float | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputRGB; ImGui::ColorEdit3("Ambient light color", ambientColor, colorEditFlags); ImGui::SliderFloat("Ambient light intensity", &ambientIntensity, 0.0f, 10.0f); ImGui::Separator(); if(fogCount < ARRAY_LEN(fogs) && ImGui::Button("Add Fog")) { const float fogSize = 40.0f; Fog& fog = fogs[fogCount++]; fog = {}; fog.extinction = 0.1f; fog.albedo = 0.5f; VectorSet(fog.scatterColor, 1, 1, 1); fog.emissive = 0.0f; VectorSet(fog.emissiveColor, 1, 1, 1); fog.anisotropy = 0.0f; fog.noiseStrength = 1.0f; fog.noiseSpatialPeriod = 50.0f; fog.noiseTimePeriod = 8.0f; fog.isHeightFog = false; fog.isGlobalFog = false; for(int a = 0; a < 3; a++) { fog.boxMin[a] = backEnd.refdef.vieworg[a] - fogSize; fog.boxMax[a] = backEnd.refdef.vieworg[a] + fogSize; } } ImGui::SameLine(); if(fogCount > 0 && ImGui::Button("Save Config...")) { OpenSaveFileDialog("fogs", ".fogs"); } ImGui::SameLine(); if(ImGui::Button("Open Config...")) { OpenOpenFileDialog("fogs", ".fogs"); } const char* axisNames[3] = { "X", "Y", "Z" }; ImGui::BeginTabBar("Tabs#Fogs", ImGuiTabBarFlags_AutoSelectNewTabs); for(int i = 0; i < fogCount; i++) { if(ImGui::BeginTabItem(va("#%d", i + 1))) { Fog& fog = fogs[i]; ImGui::Checkbox("Global fog", &fog.isGlobalFog); if(!fog.isGlobalFog) { for(int a = 0; a < 3; a++) { ImGui::SliderFloat(va("Box min %s", axisNames[a]), &fog.boxMin[a], mapBoxMin[a], mapBoxMax[a], "%g"); ImGui::SliderFloat(va("Box max %s", axisNames[a]), &fog.boxMax[a], mapBoxMin[a], mapBoxMax[a], "%g"); } } float opaqueDistance = ExtinctionToOpaqueDistance(fog.extinction); ImGui::SliderFloat("Distance to opaque", &opaqueDistance, 1.0f, 10000.0f, "%g"); fog.extinction = OpaqueDistanceToExtinction(opaqueDistance); ImGui::ColorEdit3("Reflective (scatter) color", fog.scatterColor, colorEditFlags); ImGui::SliderFloat("Reflectivity (albedo)", &fog.albedo, 0.0f, 1.0f, "%g"); ImGui::SliderFloat("Directionality (anisotropy)", &fog.anisotropy, 0.0f, 1.0f, "%g"); ImGui::SliderFloat("Emissive strength", &fog.emissive, 0.0f, 0.001f, "%g"); ImGui::ColorEdit3("Emissive color", fog.emissiveColor, colorEditFlags); ImGui::SliderFloat("Noise strength", &fog.noiseStrength, 1.0f, 10.0f, "%g"); ImGui::SliderFloat("Noise space period", &fog.noiseSpatialPeriod, 0.0f, 200.0f, "%g"); ImGui::SliderFloat("Noise time period", &fog.noiseTimePeriod, 0.0f, 20.0f, "%g"); ImGui::Checkbox("Height fog", &fog.isHeightFog); ImGui::EndTabItem(); ImGui::Separator(); if(ImGui::Button("Remove")) { if(i < fogCount - 1) { memmove(&fogs[i], &fogs[i + 1], (fogCount - 1 - i) * sizeof(fogs[0])); } fogCount--; } } } if(ImGui::BeginTabItem("Debug")) { if(tr.world->lightGridData != NULL) { ImGui::Checkbox("Draw ambient light grid", &drawAmbientDebug); ImGui::SliderFloat("Voxel sphere scale", &debugSphereScale, 0.25f, 1.0f); } else { ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "No valid light grid was found for this map"); } ImGui::NewLine(); ImGui::SliderInt("Extinction cascade", &debugExtinctionCascadeIndex, 0, 3); ImGui::Checkbox("Draw extinction volume", &drawExtinctionDebug); ImGui::SliderFloat("Extinction value scale", &debugExtinctionScale, 1.0f, 1000.0f); ImGui::NewLine(); ImGui::SliderInt("Sun shadow cascade", &debugSunShadowCascadeIndex, 0, 3); ImGui::Checkbox("Draw sun shadow volume", &drawSunShadowDebug); ImGui::NewLine(); ImGui::Checkbox("Lock camera position", &lockCameraPosition); ImGui::SliderFloat("Voxel box scale", &debugBoxScale, 0.25f, 1.0f); ImGui::EndTabItem(); } ImGui::EndTabBar(); if(SaveFileDialog()) { SaveFogFile(GetSaveFileDialogPath()); } if(OpenFileDialog()) { LoadFogFile(GetOpenFileDialogPath()); } } ImGui::End(); } bool VolumetricLight::WantRTASUpdate(const trRefdef_t& scene) { return crp_volLight->integer != 0; } bool VolumetricLight::ShouldDraw() { if(crp_volLight->integer == 0 || (backEnd.refdef.rdflags & RDF_NOWORLDMODEL) != 0 || !IsViewportFullscreen(backEnd.viewParms) || lockCameraPosition) { return false; } return true; } bool VolumetricLight::ShouldDrawDebug() { if(crp_volLight->integer == 0 || (backEnd.refdef.rdflags & RDF_NOWORLDMODEL) != 0 || !IsViewportFullscreen(backEnd.viewParms)) { return false; } return drawExtinctionDebug || drawSunShadowDebug || drawAmbientDebug; } bool VolumetricLight::LoadFogFile(const char* filePath) { bool success = false; void* data = NULL; const int byteCount = ri.FS_ReadFile(filePath, &data); if(data != NULL && byteCount > 0 && byteCount <= sizeof(fogs) && (byteCount % sizeof(fogs[0])) == 0) { memcpy(&fogs[0], data, byteCount); fogCount = byteCount / sizeof(fogs[0]); success = true; } if(data != NULL) { ri.FS_FreeFile(data); } return success; } void VolumetricLight::SaveFogFile(const char* filePath) { if(fogCount > 0) { FS_EnableCNQ3FolderWrites(qtrue); ri.FS_WriteFile(filePath, &fogs[0], fogCount * sizeof(fogs[0])); FS_EnableCNQ3FolderWrites(qfalse); } }