cnq3/code/renderer/crp_volumetric_light.cpp
myT 30150e889e added sunlight and volumetric lighting
fixed depth linearization
2024-03-29 04:19:38 +01:00

1545 lines
49 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 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);
}
}