Added lit particle emissive support.

This commit is contained in:
Justin Marshall
2026-05-24 14:07:55 -07:00
parent 56bb2f004f
commit 4e6d4a281f
49 changed files with 34640 additions and 87 deletions
+115 -54
View File
@@ -469,7 +469,7 @@ static const UINT QD3D12_UploadBufferSize = 128 * 1024 * 1024;
static const UINT QD3D12_RequestedGBufferSampleCount = 1;
static constexpr UINT QD3D12_PostSrvCount = 16;
static constexpr UINT QD3D12_PostRootConstants = QD3D12_PostSrvCount;
static constexpr UINT QD3D12_PostConstantDwords = 24;
static constexpr UINT QD3D12_PostConstantDwords = 28;
static constexpr UINT QD3D12_ToneMapTileDim = 64;
static constexpr UINT QD3D12_MaxScreenSpaceDecals = 64;
static constexpr UINT QD3D12_MaxScreenSpaceParticleLights = GL_RAYTRACING_MAX_LIGHTS;
@@ -551,6 +551,7 @@ struct QD3D12ScreenSpaceParticleBatch
uint32_t textureId = 0;
uint32_t blendMode = 0;
float softDepth = 16.0f;
float emissiveScale = 0.0f;
};
enum PipelineMode
@@ -1719,6 +1720,7 @@ struct GLState
ComPtr<ID3D12PipelineState> postScreenDecalStainPSO;
ComPtr<ID3D12PipelineState> postScreenParticlePSO;
ComPtr<ID3D12PipelineState> postScreenParticleAdditivePSO;
ComPtr<ID3D12PipelineState> postScreenParticleEmissivePSO;
ComPtr<ID3DBlob> vsMainBlob;
ComPtr<ID3DBlob> hsMainBlob;
@@ -3891,11 +3893,10 @@ float4 QD3D12_SampleGlowAtUV(VSOut i, float2 uv0)
float glowMask = alphaLooksForcedOpaque ? rgbMask : saturate(max(glow.a, rgbMask));
float emissionMask = alphaLooksForcedOpaque ? 1.0 : glowMask;
// Store bright HDR radiance in the FP16 emissive G-buffer. This is still
// direct/bloom-only; emissive lighting has intentionally stayed disabled.
// The higher baseline lets normal idTech glow maps read as obvious emission
// without every material having to call glGlowMapStrengthf(3+).
const float kDefaultGlowBloomRadiance = 3.25;
// Store cinematic HDR radiance in the FP16 emissive G-buffer. The bloom pass
// and particle-light gather both expect a strong source value here so small
// Doom 3 glow sheets survive tone mapping and denoise.
const float kDefaultGlowBloomRadiance = 7.25;
float strength = max(gGlowMapStrength, 0.0);
// Low-value glow maps looked too weak after the no-light hotfix. Give the
@@ -4513,6 +4514,7 @@ cbuffer PostCB : register(b0)
float4 gPostParams3;
float4 gPostFogColor;
float4 gPostFogCameraWorldPos;
float4 gPostParticleParams;
};
#define gPostSharpness gPostParams0.x
@@ -4531,6 +4533,8 @@ cbuffer PostCB : register(b0)
#define gScreenParticleSoftDepth gPostParams3.z
#define gScreenParticleLightCount gPostParams3.w
#define gScreenDecalCount gPostFogCameraWorldPos.w
#define gScreenParticleEmissiveScale gPostParticleParams.x
#define gScreenParticleOutputMode gPostParticleParams.y
#define QD3D12_TONEMAP_TILE_DIM 64u
static const uint GL_RAYTRACING_LIGHT_TYPE_POINT = 0u;
@@ -4814,12 +4818,17 @@ float4 PSScreenParticles(ParticleVSOut i) : SV_Target0
if (alpha <= 0.0001)
discard;
if (gPostFogCameraValid > 0.5)
uint2 pixel = uint2(i.pos.xy);
float sceneDepth = gFogDepth.Load(int3(pixel, 0));
float particleDepth = saturate(i.pos.z);
if (sceneDepth < 0.99999)
{
uint2 pixel = uint2(i.pos.xy);
float sceneDepth = gFogDepth.Load(int3(pixel, 0));
float particleDepth = saturate(i.pos.z);
if (sceneDepth < 0.99999)
if (particleDepth > sceneDepth + 0.00005)
{
discard;
}
if (gPostFogCameraValid > 0.5)
{
float4 positionSample = gScreenDecalPosition.Load(int3(pixel, 0));
uint geometryBits = (uint)round(max(positionSample.w, 0.0));
@@ -4832,13 +4841,16 @@ float4 PSScreenParticles(ParticleVSOut i) : SV_Target0
if (alpha <= 0.0001)
discard;
}
else if (particleDepth > sceneDepth + 0.00005)
{
discard;
}
}
}
if (gScreenParticleOutputMode > 0.5)
{
const float kScreenParticleEmissionBoost = 3.0;
float emissiveAlpha = alpha;
return float4(texel.rgb * (emissiveAlpha * max(gScreenParticleEmissiveScale, 0.0) * kScreenParticleEmissionBoost), 2.0 + particleDepth);
}
texel.rgb *= ScreenParticleDirectLighting(i.world);
return float4(texel.rgb, alpha);
@@ -5035,10 +5047,10 @@ float4 PSTAA(VSOut i) : SV_Target0
float3 QD3D12_LoadPostRadiance(float2 uv)
{
// Clamp bloom taps so a single bad HDR edge sample cannot flood the LDR
// scene-color composite. The direct center emissive value is still added by
// PSAdd below, so this only limits the halo.
return min(max(gTex0.Sample(gSamp0, saturate(uv)).rgb, 0.0), 12.0);
// Keep bloom HDR enough to look cinematic, but use a soft lift instead of an
// unbounded add so one malformed texel cannot bleach the whole frame.
float3 radiance = min(max(gTex0.Sample(gSamp0, saturate(uv)).rgb, 0.0), 32.0);
return min(radiance + sqrt(radiance) * 0.45, 40.0);
}
float3 QD3D12_EmissiveBloom(float2 uv)
@@ -5047,38 +5059,48 @@ float3 QD3D12_EmissiveBloom(float2 uv)
gTex0.GetDimensions(srcW, srcH);
float2 texel = 1.0 / max(float2((float)srcW, (float)srcH), float2(1.0, 1.0));
// Strong threshold-free bloom kernel. Direct emissive is still added by
// PSAdd; this wider halo makes glow maps read even when the final color
// buffer is LDR and clamps the swap-chain output.
// Wide threshold-free bloom kernel. Direct emissive is still added by PSAdd;
// these outer taps make fire and plasma read as real camera bloom instead of
// a thin one-pixel glow.
float3 bloom = 0.0;
bloom += QD3D12_LoadPostRadiance(uv) * 0.180;
bloom += QD3D12_LoadPostRadiance(uv) * 0.320;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 1.0, 0.0)) * 0.220;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-1.0, 0.0)) * 0.220;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 1.0)) * 0.220;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -1.0)) * 0.220;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 1.0, 0.0)) * 0.420;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-1.0, 0.0)) * 0.420;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 1.0)) * 0.420;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -1.0)) * 0.420;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 2.0, 2.0)) * 0.135;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-2.0, 2.0)) * 0.135;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 2.0, -2.0)) * 0.135;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-2.0, -2.0)) * 0.135;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 2.0, 2.0)) * 0.260;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-2.0, 2.0)) * 0.260;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 2.0, -2.0)) * 0.260;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-2.0, -2.0)) * 0.260;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 4.0, 0.0)) * 0.090;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-4.0, 0.0)) * 0.090;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 4.0)) * 0.090;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -4.0)) * 0.090;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 4.0, 0.0)) * 0.180;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-4.0, 0.0)) * 0.180;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 4.0)) * 0.180;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -4.0)) * 0.180;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 8.0, 0.0)) * 0.060;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-8.0, 0.0)) * 0.060;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 8.0)) * 0.060;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -8.0)) * 0.060;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 8.0, 0.0)) * 0.120;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-8.0, 0.0)) * 0.120;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 8.0)) * 0.120;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -8.0)) * 0.120;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 14.0, 0.0)) * 0.035;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-14.0, 0.0)) * 0.035;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 14.0)) * 0.035;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -14.0)) * 0.035;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 14.0, 0.0)) * 0.075;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-14.0, 0.0)) * 0.075;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 14.0)) * 0.075;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -14.0)) * 0.075;
return min(bloom * 0.62, 5.0);
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 24.0, 0.0)) * 0.045;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-24.0, 0.0)) * 0.045;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 24.0)) * 0.045;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -24.0)) * 0.045;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 42.0, 0.0)) * 0.025;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2(-42.0, 0.0)) * 0.025;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, 42.0)) * 0.025;
bloom += QD3D12_LoadPostRadiance(uv + texel * float2( 0.0, -42.0)) * 0.025;
return min(bloom * 1.15, 24.0);
}
)HLSL"
R"HLSL(
@@ -5088,7 +5110,7 @@ float4 PSAdd(VSOut i) : SV_Target0
float3 direct = max(center.rgb, 0.0);
float3 bloom = QD3D12_EmissiveBloom(i.uv);
return float4(direct + bloom, saturate(center.a));
return float4(direct * 1.08 + bloom, saturate(center.a));
}
float PSDepthCopy(VSOut i) : SV_Depth
@@ -8055,7 +8077,9 @@ static void QD3D12_SetPostConstants(
float screenDecalIndex = 0.0f,
bool forceCameraPosition = false,
float screenParticleSoftDepth = 16.0f,
float screenParticleLightCount = 0.0f)
float screenParticleLightCount = 0.0f,
float screenParticleEmissiveScale = 0.0f,
float screenParticleOutputMode = 0.0f)
{
if (!cl)
return;
@@ -8094,6 +8118,8 @@ static void QD3D12_SetPostConstants(
constants[14] = screenParticleSoftDepth;
constants[15] = screenParticleLightCount;
constants[23] = screenDecalCount;
constants[24] = screenParticleEmissiveScale;
constants[25] = screenParticleOutputMode;
cl->SetGraphicsRoot32BitConstants(QD3D12_PostRootConstants, QD3D12_PostConstantDwords, constants, 0);
}
@@ -8539,22 +8565,27 @@ void APIENTRY glDrawScreenSpaceDecalsQD3D12(void)
}
}
static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleBatch& batch, ID3D12Resource* tlas)
static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleBatch& batch, ID3D12Resource* tlas, bool emissiveOnly)
{
const glScreenSpaceParticleVertex_t* verts = batch.vertices.empty() ? nullptr : batch.vertices.data();
const uint32_t vertexCount = (uint32_t)batch.vertices.size();
const uint32_t textureId = batch.textureId;
const uint32_t blendMode = batch.blendMode;
const float softDepth = batch.softDepth;
const float emissiveScale = batch.emissiveScale;
if (!verts || vertexCount == 0)
return;
if (emissiveOnly && emissiveScale <= 0.0f)
return;
QD3D12Window* window = g_currentWindow;
if (!window || !g_gl.device || !g_gl.cmdList || !g_gl.postScreenParticlePSO)
return;
if (!window->sceneColorBuffers[window->frameIndex] || !window->positionBuffers[window->frameIndex] || !window->depthBuffer)
return;
if (emissiveOnly && !window->emissiveBuffers[window->frameIndex])
return;
if (!QD3D12_EnsureScreenSpaceParticleLightBuffer() || !QD3D12_UpdateScreenSpaceParticleTlasSrv(tlas))
return;
@@ -8565,7 +8596,14 @@ static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleB
const UINT frame = window->frameIndex;
ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get();
QD3D12_TransitionResource(cl, window->sceneColorBuffers[frame].Get(), window->sceneColorState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET);
if (emissiveOnly)
{
QD3D12_TransitionResource(cl, window->emissiveBuffers[frame].Get(), window->emissiveBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET);
}
else
{
QD3D12_TransitionResource(cl, window->sceneColorBuffers[frame].Get(), window->sceneColorState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET);
}
QD3D12_TransitionResource(cl, window->depthBuffer.Get(), window->depthState, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
QD3D12_TransitionResource(cl, window->positionBuffers[frame].Get(), window->positionBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
@@ -8607,17 +8645,19 @@ static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleB
}
}
ID3D12PipelineState* particlePSO = (blendMode == 1 && g_gl.postScreenParticleAdditivePSO.Get() != nullptr)
ID3D12PipelineState* particlePSO = emissiveOnly && g_gl.postScreenParticleEmissivePSO.Get()
? g_gl.postScreenParticleEmissivePSO.Get()
: (blendMode == 1 && g_gl.postScreenParticleAdditivePSO.Get() != nullptr)
? g_gl.postScreenParticleAdditivePSO.Get()
: g_gl.postScreenParticlePSO.Get();
ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() };
cl->SetDescriptorHeaps(_countof(heaps), heaps);
cl->SetGraphicsRootSignature(g_gl.postRootSig.Get());
QD3D12_SetPostConstants(cl, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, false, 0.0f, 0.0f, true, softDepth, (float)g_gl.screenSpaceParticleLightCount);
QD3D12_SetPostConstants(cl, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, false, 0.0f, 0.0f, true, softDepth, (float)g_gl.screenSpaceParticleLightCount, emissiveScale, emissiveOnly ? 1.0f : 0.0f);
cl->SetPipelineState(particlePSO);
D3D12_CPU_DESCRIPTOR_HANDLE sceneColorRtv = CurrentResolvedSceneColorRTV();
cl->OMSetRenderTargets(1, &sceneColorRtv, FALSE, nullptr);
D3D12_CPU_DESCRIPTOR_HANDLE particleRtv = emissiveOnly ? CurrentResolvedEmissiveRTV() : CurrentResolvedSceneColorRTV();
cl->OMSetRenderTargets(1, &particleRtv, FALSE, nullptr);
cl->RSSetViewports(1, &viewport);
cl->RSSetScissorRects(1, &scissor);
cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
@@ -8630,7 +8670,7 @@ static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleB
cl->DrawInstanced(vertexCount, 1, 0, 0);
}
void APIENTRY glDrawScreenSpaceParticlesQD3D12(const glScreenSpaceParticleVertex_t* verts, uint32_t vertexCount, uint32_t textureId, uint32_t blendMode, float softDepth)
void APIENTRY glDrawScreenSpaceParticlesQD3D12(const glScreenSpaceParticleVertex_t* verts, uint32_t vertexCount, uint32_t textureId, uint32_t blendMode, float softDepth, float emissiveScale)
{
if (!verts || vertexCount == 0)
return;
@@ -8640,6 +8680,7 @@ void APIENTRY glDrawScreenSpaceParticlesQD3D12(const glScreenSpaceParticleVertex
batch.textureId = textureId;
batch.blendMode = blendMode;
batch.softDepth = softDepth;
batch.emissiveScale = emissiveScale;
g_gl.screenSpaceParticleBatches.push_back(std::move(batch));
}
@@ -8663,11 +8704,22 @@ static void QD3D12_DrawQueuedScreenSpaceParticles(ID3D12Resource* tlas)
for (const QD3D12ScreenSpaceParticleBatch& batch : g_gl.screenSpaceParticleBatches)
{
QD3D12_DrawScreenSpaceParticleBatch(batch, tlas);
QD3D12_DrawScreenSpaceParticleBatch(batch, tlas, false);
}
g_gl.screenSpaceParticleBatches.clear();
}
static void QD3D12_DrawQueuedScreenSpaceParticleEmissive(ID3D12Resource* tlas)
{
if (g_gl.screenSpaceParticleBatches.empty())
return;
for (const QD3D12ScreenSpaceParticleBatch& batch : g_gl.screenSpaceParticleBatches)
{
QD3D12_DrawScreenSpaceParticleBatch(batch, tlas, true);
}
}
static ID3D12Resource* QD3D12_PrepareStreamlineOutputForWrite(QD3D12Window& w)
{
ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get();
@@ -10421,6 +10473,14 @@ static void QD3D12_CreatePSOs()
particleDesc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE;
QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&particleDesc, IID_PPV_ARGS(&g_gl.postScreenParticleAdditivePSO)));
particleDesc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_ONE;
particleDesc.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_ONE;
particleDesc.BlendState.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD;
particleDesc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE;
particleDesc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE;
particleDesc.BlendState.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD;
QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&particleDesc, IID_PPV_ARGS(&g_gl.postScreenParticleEmissivePSO)));
D3D12_GRAPHICS_PIPELINE_STATE_DESC depthDesc{};
depthDesc.pRootSignature = g_gl.postRootSig.Get();
depthDesc.VS = { g_gl.postVsBlob->GetBufferPointer(), g_gl.postVsBlob->GetBufferSize() };
@@ -17860,6 +17920,7 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle)
QD3D12_FlushQueuedBatches();
QD3D12_ResolveGBufferForCurrentFrame(*window);
QD3D12_DrawQueuedScreenSpaceParticleEmissive(tlas);
ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get();
ID3D12Resource* sceneColor = window->sceneColorBuffers[frameIndex].Get();