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
+2
View File
@@ -3868,6 +3868,7 @@ static classVariableInfo_t idParticleStage_typeInfo[] = {
{ "idParticleParm", "aspect", (intptr_t)(&((idParticleStage *)0)->aspect), sizeof( ((idParticleStage *)0)->aspect ) },
{ "idVec4", "color", (intptr_t)(&((idParticleStage *)0)->color), sizeof( ((idParticleStage *)0)->color ) },
{ "idVec4", "fadeColor", (intptr_t)(&((idParticleStage *)0)->fadeColor), sizeof( ((idParticleStage *)0)->fadeColor ) },
{ "float", "emissive", (intptr_t)(&((idParticleStage *)0)->emissive), sizeof( ((idParticleStage *)0)->emissive ) },
{ "float", "fadeInFraction", (intptr_t)(&((idParticleStage *)0)->fadeInFraction), sizeof( ((idParticleStage *)0)->fadeInFraction ) },
{ "float", "fadeOutFraction", (intptr_t)(&((idParticleStage *)0)->fadeOutFraction), sizeof( ((idParticleStage *)0)->fadeOutFraction ) },
{ "float", "fadeIndexFraction", (intptr_t)(&((idParticleStage *)0)->fadeIndexFraction), sizeof( ((idParticleStage *)0)->fadeIndexFraction ) },
@@ -4371,6 +4372,7 @@ static classVariableInfo_t srfTriangles_t_typeInfo[] = {
{ "unsigned int", "ambientVbo", (intptr_t)(&((srfTriangles_t *)0)->ambientVbo), sizeof( ((srfTriangles_t *)0)->ambientVbo ) },
{ "unsigned int", "indexVbo", (intptr_t)(&((srfTriangles_t *)0)->indexVbo), sizeof( ((srfTriangles_t *)0)->indexVbo ) },
{ "bool", "isSkeletal", (intptr_t)(&((srfTriangles_t *)0)->isSkeletal), sizeof( ((srfTriangles_t *)0)->isSkeletal ) },
{ "float", "particleEmissiveScale", (intptr_t)(&((srfTriangles_t *)0)->particleEmissiveScale), sizeof( ((srfTriangles_t *)0)->particleEmissiveScale ) },
{ "int", "numAllocedVerts", (intptr_t)(&((srfTriangles_t *)0)->numAllocedVerts), sizeof( ((srfTriangles_t *)0)->numAllocedVerts ) },
{ "int", "numAllocedIndices", (intptr_t)(&((srfTriangles_t *)0)->numAllocedIndices), sizeof( ((srfTriangles_t *)0)->numAllocedIndices ) },
{ NULL, 0 }
+19
View File
@@ -252,6 +252,9 @@ static bool IsParticleStageToken(const idToken& token) {
if (!token.Icmp("fadeColor")) {
return true;
}
if (!token.Icmp("emissive") || !token.Icmp("emsive")) {
return true;
}
if (!token.Icmp("offset")) {
return true;
}
@@ -582,6 +585,16 @@ idParticleStage* idDeclParticle::ParseParticleStage(idLexer& src) {
stage->fadeColor[3] = src.ParseFloat();
continue;
}
if (!token.Icmp("emissive") || !token.Icmp("emsive")) {
if (src.ReadTokenOnLine(&token)) {
token.StripQuotes();
stage->emissive = atof(token.c_str());
}
else {
stage->emissive = 1.0f;
}
continue;
}
if (!token.Icmp("offset")) {
stage->offset[0] = src.ParseFloat();
stage->offset[1] = src.ParseFloat();
@@ -874,6 +887,9 @@ void idDeclParticle::WriteStage( idFile *f, idParticleStage *stage ) {
f->WriteFloatString( "\t\tcolor \t\t\t\t%.3f %.3f %.3f %.3f\n", stage->color.x, stage->color.y, stage->color.z, stage->color.w );
f->WriteFloatString( "\t\tfadeColor \t\t\t%.3f %.3f %.3f %.3f\n", stage->fadeColor.x, stage->fadeColor.y, stage->fadeColor.z, stage->fadeColor.w );
if ( stage->emissive > 0.0f ) {
f->WriteFloatString( "\t\temissive \t\t\t%.3f\n", stage->emissive );
}
f->WriteFloatString( "\t\toffset \t\t\t\t%.3f %.3f %.3f\n", stage->offset.x, stage->offset.y, stage->offset.z );
f->WriteFloatString( "\t\tgravity \t\t\t" );
@@ -996,6 +1012,7 @@ idParticleStage::idParticleStage( void ) {
// idParticleParm aspect
color.Zero();
fadeColor.Zero();
emissive = 0.0f;
fadeInFraction = 0.0f;
fadeOutFraction = 0.0f;
fadeIndexFraction = 0.0f;
@@ -1071,6 +1088,7 @@ void idParticleStage::Default() {
fadeColor.y = 0.0f;
fadeColor.z = 0.0f;
fadeColor.w = 0.0f;
emissive = 0.0f;
fadeInFraction = 0.1f;
fadeOutFraction = 0.25f;
fadeIndexFraction = 0.0f;
@@ -1698,6 +1716,7 @@ void idParticleStage::operator=( const idParticleStage &src ) {
aspect = src.aspect;
color = src.color;
fadeColor = src.fadeColor;
emissive = src.emissive;
fadeInFraction = src.fadeInFraction;
fadeOutFraction = src.fadeOutFraction;
fadeIndexFraction = src.fadeIndexFraction;
+1
View File
@@ -183,6 +183,7 @@ public:
idVec4 color;
idVec4 fadeColor; // either 0 0 0 0 for additive, or 1 1 1 0 for blended materials
float emissive; // screen-space particle emissive radiance scale, 0 disables
float fadeInFraction; // in 0.0 to 1.0 range
float fadeOutFraction; // in 0.0 to 1.0 range
float fadeIndexFraction; // in 0.0 to 1.0 range, causes later index smokes to be more faded
+1
View File
@@ -142,6 +142,7 @@ typedef struct srfTriangles_s {
unsigned int indexVbo; // model-owned index GL buffer
bool isSkeletal;
float particleEmissiveScale;
int numAllocedVerts;
int numAllocedIndices;
} srfTriangles_t;
+1
View File
@@ -236,6 +236,7 @@ idRenderModel *idRenderModelPrt::InstantiateDynamicModel( const struct renderEnt
surf->geometry->numVerts = numVerts;
surf->geometry->numIndexes = numIndexes;
surf->geometry->bounds = stage->bounds; // just always draw the particles
surf->geometry->particleEmissiveScale = stage->emissive;
}
return staticModel;
+138 -28
View File
@@ -3899,45 +3899,55 @@ float3 LoadEmissiveRadianceClamped(int2 p)
{
int2 maxPixel = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
p = clamp(p, int2(0, 0), maxPixel);
return CompressEmissiveRadiance(gEmissiveTex.Load(int3(p, 0)).rgb, 7.50);
float3 radiance = CompressEmissiveRadiance(gEmissiveTex.Load(int3(p, 0)).rgb, 18.0);
return min(radiance + sqrt(max(radiance, 0.0)) * 0.35, 22.0);
}
float3 EstimateEmissiveBloomAtPixel(uint2 pixel)
{
int2 p = int2(pixel);
// Strong threshold-free bloom. This stays direct/bloom-only: no extra
// TraceRay calls, and no emissive lighting contribution. The goal is to make
// visible glow maps read as hot emissive surfaces in the DXR output.
// Strong threshold-free bloom in the ray output. Particle emissive lighting
// is injected separately below; this is the visible camera halo.
float3 bloom = 0.0;
bloom += LoadEmissiveRadianceClamped(p) * 0.180;
bloom += LoadEmissiveRadianceClamped(p) * 0.320;
bloom += LoadEmissiveRadianceClamped(p + int2( 1, 0)) * 0.220;
bloom += LoadEmissiveRadianceClamped(p + int2(-1, 0)) * 0.220;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 1)) * 0.220;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -1)) * 0.220;
bloom += LoadEmissiveRadianceClamped(p + int2( 1, 0)) * 0.420;
bloom += LoadEmissiveRadianceClamped(p + int2(-1, 0)) * 0.420;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 1)) * 0.420;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -1)) * 0.420;
bloom += LoadEmissiveRadianceClamped(p + int2( 2, 2)) * 0.135;
bloom += LoadEmissiveRadianceClamped(p + int2(-2, 2)) * 0.135;
bloom += LoadEmissiveRadianceClamped(p + int2( 2, -2)) * 0.135;
bloom += LoadEmissiveRadianceClamped(p + int2(-2, -2)) * 0.135;
bloom += LoadEmissiveRadianceClamped(p + int2( 2, 2)) * 0.260;
bloom += LoadEmissiveRadianceClamped(p + int2(-2, 2)) * 0.260;
bloom += LoadEmissiveRadianceClamped(p + int2( 2, -2)) * 0.260;
bloom += LoadEmissiveRadianceClamped(p + int2(-2, -2)) * 0.260;
bloom += LoadEmissiveRadianceClamped(p + int2( 4, 0)) * 0.090;
bloom += LoadEmissiveRadianceClamped(p + int2(-4, 0)) * 0.090;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 4)) * 0.090;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -4)) * 0.090;
bloom += LoadEmissiveRadianceClamped(p + int2( 4, 0)) * 0.180;
bloom += LoadEmissiveRadianceClamped(p + int2(-4, 0)) * 0.180;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 4)) * 0.180;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -4)) * 0.180;
bloom += LoadEmissiveRadianceClamped(p + int2( 8, 0)) * 0.060;
bloom += LoadEmissiveRadianceClamped(p + int2(-8, 0)) * 0.060;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 8)) * 0.060;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -8)) * 0.060;
bloom += LoadEmissiveRadianceClamped(p + int2( 8, 0)) * 0.120;
bloom += LoadEmissiveRadianceClamped(p + int2(-8, 0)) * 0.120;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 8)) * 0.120;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -8)) * 0.120;
bloom += LoadEmissiveRadianceClamped(p + int2( 14, 0)) * 0.035;
bloom += LoadEmissiveRadianceClamped(p + int2(-14, 0)) * 0.035;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 14)) * 0.035;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -14)) * 0.035;
bloom += LoadEmissiveRadianceClamped(p + int2( 14, 0)) * 0.075;
bloom += LoadEmissiveRadianceClamped(p + int2(-14, 0)) * 0.075;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 14)) * 0.075;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -14)) * 0.075;
return bloom * 0.78;
bloom += LoadEmissiveRadianceClamped(p + int2( 24, 0)) * 0.045;
bloom += LoadEmissiveRadianceClamped(p + int2(-24, 0)) * 0.045;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 24)) * 0.045;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -24)) * 0.045;
bloom += LoadEmissiveRadianceClamped(p + int2( 42, 0)) * 0.025;
bloom += LoadEmissiveRadianceClamped(p + int2(-42, 0)) * 0.025;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, 42)) * 0.025;
bloom += LoadEmissiveRadianceClamped(p + int2( 0, -42)) * 0.025;
return min(bloom * 1.45, 28.0);
}
float3 SafeNormalizeOr(float3 v, float3 fallback)
@@ -3948,6 +3958,103 @@ float3 SafeNormalizeOr(float3 v, float3 fallback)
return v * rsqrt(lenSq);
}
bool TryScreenParticleWorldCandidate(float4 clip, float3 receiverPos, inout float bestDistSq, inout float3 bestWorld)
{
float4 worldA = mul(clip, gInvViewProj);
if (abs(worldA.w) > 1e-6)
{
float3 candidate = worldA.xyz / worldA.w;
float distSq = dot(candidate - receiverPos, candidate - receiverPos);
if (distSq < bestDistSq && distSq > 1e-4 && distSq < 147456.0)
{
bestDistSq = distSq;
bestWorld = candidate;
}
}
float4 worldB = mul(gInvViewProj, clip);
if (abs(worldB.w) > 1e-6)
{
float3 candidate = worldB.xyz / worldB.w;
float distSq = dot(candidate - receiverPos, candidate - receiverPos);
if (distSq < bestDistSq && distSq > 1e-4 && distSq < 147456.0)
{
bestDistSq = distSq;
bestWorld = candidate;
}
}
return bestDistSq < 147456.0;
}
bool TryReconstructScreenParticleWorld(uint2 particlePixel, float particleDepth, float3 receiverPos, out float3 particleWorld)
{
float2 uv = ((float2)particlePixel + 0.5) / max(gScreenSize.xy, float2(1.0, 1.0));
float x = uv.x * 2.0 - 1.0;
float yUp = 1.0 - uv.y * 2.0;
float yDown = uv.y * 2.0 - 1.0;
float bestDistSq = 1.0e30;
particleWorld = receiverPos;
TryScreenParticleWorldCandidate(float4(x, yUp, particleDepth, 1.0), receiverPos, bestDistSq, particleWorld);
TryScreenParticleWorldCandidate(float4(x, yDown, particleDepth, 1.0), receiverPos, bestDistSq, particleWorld);
return bestDistSq < 147456.0;
}
float3 EstimateScreenSpaceEmissiveParticleLighting(uint2 pixel, float3 worldPos, float3 N)
{
static const int2 kParticleLightTaps[17] =
{
int2( 0, 0),
int2( 7, 0), int2( -7, 0), int2( 0, 7), int2( 0, -7),
int2( 10, 10), int2(-10, 10), int2( 10, -10), int2(-10, -10),
int2( 18, 4), int2(-18, -4), int2( 4, 18), int2( -4, -18),
int2( 30, 0), int2(-30, 0), int2( 0, 30), int2( 0, -30)
};
int2 maxPixel = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1);
float pixelScale = max(1.0, round(min(gScreenSize.x, gScreenSize.y) / 720.0));
float3 lighting = 0.0;
[unroll]
for (uint i = 0u; i < 17u; ++i)
{
int2 sp = int2(pixel) + int2(round((float2)kParticleLightTaps[i] * pixelScale));
sp = clamp(sp, int2(0, 0), maxPixel);
float4 particleEmission = gEmissiveTex.Load(int3(sp, 0));
if (particleEmission.a <= 1.5)
continue;
float particleDepth = saturate(particleEmission.a - 2.0);
float3 particleWorld;
if (!TryReconstructScreenParticleWorld(uint2(sp), particleDepth, worldPos, particleWorld))
continue;
float3 toParticle = particleWorld - worldPos;
float distSq = dot(toParticle, toParticle);
if (distSq <= 1e-4 || distSq >= 147456.0)
continue;
float dist = sqrt(distSq);
float3 L = toParticle / dist;
float NoL = saturate(dot(N, L));
if (NoL <= 0.01)
continue;
float visibility = TraceVisibilityBiased(worldPos, N, L, dist);
if (visibility <= 0.0)
continue;
float distanceFade = saturate(1.0 - dist / 640.0);
distanceFade *= distanceFade;
float attenuation = distanceFade / (1.0 + distSq * 0.000075);
lighting += CompressEmissiveRadiance(particleEmission.rgb, 24.0) * (NoL * visibility * attenuation);
}
return lighting * 9.0;
}
bool ProjectClipToGBufferCandidate(
float4 clipPos,
bool flipY,
@@ -4151,7 +4258,8 @@ float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3
specularOut = specAccum * invSamples;
return diffuseAccum * invSamples;
}
)"
R"(
float3 PathTraceDirectSpotLight(float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, Light Lgt, inout uint rng, out float3 specularOut)
{
specularOut = 0.0;
@@ -4175,7 +4283,8 @@ float3 PathTraceDirectSpotLight(float3 worldPos, float3 N, float3 V, float3 base
return Lgt.color * (Lgt.intensity * atten * diffuseNoL * shadow);
}
)"
R"(
float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, Light Lgt, inout uint rng, out float3 specularOut)
{
specularOut = 0.0;
@@ -5304,6 +5413,7 @@ void RayGen()
skyVis,
ambientSkyVis,
specularAccum);
lightingAccum += EstimateScreenSpaceEmissiveParticleLighting(pixel, worldPos, N);
// Only the indirect bounce path uses per-SPP randomness now. Direct lights,
// AO, sky visibility, cavity, and final gather are deterministic and should
+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();
+1 -1
View File
@@ -1853,7 +1853,7 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle);
void glSetScreenSpaceDecalsQD3D12(const glScreenSpaceDecal_t* decals, uint32_t decalCount);
void glDrawScreenSpaceDecalsQD3D12(void);
void glSetScreenSpaceParticleLightsQD3D12(const glRaytracingLight_t* lights, uint32_t lightCount);
void glDrawScreenSpaceParticlesQD3D12(const glScreenSpaceParticleVertex_t* verts, uint32_t vertexCount, uint32_t textureId, uint32_t blendMode, float softDepth);
void glDrawScreenSpaceParticlesQD3D12(const glScreenSpaceParticleVertex_t* verts, uint32_t vertexCount, uint32_t textureId, uint32_t blendMode, float softDepth, float emissiveScale);
ID3D12Device* QD3D12_GetDevice(void);
ID3D12CommandQueue* QD3D12_GetQueue(void);
+2 -1
View File
@@ -1610,7 +1610,8 @@ static void RB_SubmitScreenSpaceParticleSurf( const drawSurf_t *surf ) {
RB_FillScreenSpaceParticleVertex( particleVerts[i], surf->geo->verts[vertIndex], surf->space, projectionMatrix, stageColor );
}
glDrawScreenSpaceParticlesQD3D12( particleVerts.Ptr(), particleVerts.Num(), image->texnum, blendMode, 16.0f );
const float emissiveScale = ( surf->dsFlags & DSF_SCREEN_SPACE_PARTICLE_EMISSIVE ) ? surf->geo->particleEmissiveScale : 0.0f;
glDrawScreenSpaceParticlesQD3D12( particleVerts.Ptr(), particleVerts.Num(), image->texnum, blendMode, 16.0f, emissiveScale );
}
static void RB_STD_DrawScreenSpaceParticles( drawSurf_t **drawSurfs, int numDrawSurfs ) {
+6 -1
View File
@@ -1093,6 +1093,7 @@ static void R_ParticleDeform( drawSurf_t *surf, bool useArea ) {
// just always draw the particles
tri->bounds = stage->bounds;
tri->particleEmissiveScale = stage->emissive;
tri->numVerts = 0;
@@ -1211,7 +1212,11 @@ static void R_ParticleDeform( drawSurf_t *surf, bool useArea ) {
tri->ambientCache = vertexCache.AllocFrameTemp( tri->verts, tri->numVerts * sizeof( idDrawVert ) );
if ( tri->ambientCache ) {
// add the drawsurf
R_AddDrawSurf( tri, surf->space, renderEntity, stage->material, surf->scissorRect, DSF_SCREEN_SPACE_PARTICLE );
int dsFlags = DSF_SCREEN_SPACE_PARTICLE;
if ( stage->emissive > 0.0f ) {
dsFlags |= DSF_SCREEN_SPACE_PARTICLE_EMISSIVE;
}
R_AddDrawSurf( tri, surf->space, renderEntity, stage->material, surf->scissorRect, dsFlags );
}
}
}
+7 -2
View File
@@ -1351,8 +1351,13 @@ static void R_AddAmbientDrawsurfs( viewEntity_t *vEntity ) {
R_CreateIndexCache( tri );
}
const int particleFlags = ( def->dynamicModel &&
idStr::Icmp( model->Name(), "_ParametricParticle_Snapshot_" ) == 0 ) ? DSF_SCREEN_SPACE_PARTICLE : 0;
int particleFlags = 0;
if ( def->dynamicModel && idStr::Icmp( model->Name(), "_ParametricParticle_Snapshot_" ) == 0 ) {
particleFlags = DSF_SCREEN_SPACE_PARTICLE;
if ( tri->particleEmissiveScale > 0.0f ) {
particleFlags |= DSF_SCREEN_SPACE_PARTICLE_EMISSIVE;
}
}
// add the surface for drawing
R_AddDrawSurf( tri, vEntity, &vEntity->entityDef->parms, shader, vEntity->scissorRect, particleFlags );
+1
View File
@@ -105,6 +105,7 @@ SURFACES
// drawSurf_t are always allocated and freed every frame, they are never cached
static const int DSF_VIEW_INSIDE_SHADOW = 1;
static const int DSF_SCREEN_SPACE_PARTICLE = 2;
static const int DSF_SCREEN_SPACE_PARTICLE_EMISSIVE = 4;
typedef struct drawSurf_s {
const srfTriangles_t *geo;