diff --git a/neo/IceTech.sln b/neo/IceTech.sln
index d2d38fd0..10f3bfd3 100644
--- a/neo/IceTech.sln
+++ b/neo/IceTech.sln
@@ -135,7 +135,6 @@ Global
{6EA6406F-3E65-47D9-8246-D6660A81606F}.Dedicated Release|x64.ActiveCfg = Dedicated Release|Win32
{6EA6406F-3E65-47D9-8246-D6660A81606F}.Dedicated Release|x64.Build.0 = Dedicated Release|Win32
{6EA6406F-3E65-47D9-8246-D6660A81606F}.Prey Debug|x64.ActiveCfg = Debug with inlines|Win32
- {6EA6406F-3E65-47D9-8246-D6660A81606F}.Prey Debug|x64.Build.0 = Debug with inlines|Win32
{6EA6406F-3E65-47D9-8246-D6660A81606F}.Q4 Debug|x64.ActiveCfg = Debug with inlines|Win32
{6EA6406F-3E65-47D9-8246-D6660A81606F}.Q4 Debug|x64.Build.0 = Debug with inlines|Win32
{6EA6406F-3E65-47D9-8246-D6660A81606F}.Release|x64.ActiveCfg = Release|Win32
diff --git a/neo/engine/doomdll.vcxproj.user b/neo/engine/doomdll.vcxproj.user
index a02f32d6..20aa55cb 100644
--- a/neo/engine/doomdll.vcxproj.user
+++ b/neo/engine/doomdll.vcxproj.user
@@ -31,7 +31,7 @@
WindowsLocalDebugger
+set r_fullscreen 1 +set r_mode 9
D:\projects\Doom3
- +set r_fullscreen 0 +set r_mode 6|+set r_fullscreen 1 +set r_mode 6|+set r_fullscreen 1 +set r_mode 1|+set r_fullscreen 0 +set r_mode 1|+set r_fullscreen 0 +set r_mode 9|
+ +set r_fullscreen 1 +set r_mode 6|+set r_fullscreen 1 +set r_mode 1|+set r_fullscreen 0 +set r_mode 1|+set r_fullscreen 0 +set r_mode 9|+set r_fullscreen 1 +set r_mode 9|
D:\projects\Doom3\Doom3.exe
diff --git a/neo/engine/opengl/gl_d3d12raylight.cpp b/neo/engine/opengl/gl_d3d12raylight.cpp
index 71e0e740..96f49eef 100644
--- a/neo/engine/opengl/gl_d3d12raylight.cpp
+++ b/neo/engine/opengl/gl_d3d12raylight.cpp
@@ -3398,9 +3398,19 @@ float EstimateSpecularRoughness(float3 specularAlbedo)
{
// No roughness map is available in this G-buffer path, so infer a stable
// perceptual roughness from the specular map strength. Brighter spec maps
- // get tighter highlights; dark/missing maps stay broad and subdued.
+ // get tighter highlights, but keep a floor high enough that POM/normal-map
+ // detail cannot collapse into one-pixel GGX fireflies.
float peak = SpecularPeak3(saturate(specularAlbedo));
- return clamp(lerp(0.68, 0.34, peak), 0.28, 0.72);
+ return clamp(lerp(0.70, 0.42, peak), 0.38, 0.74);
+}
+
+float3 LimitSpecularPeak(float3 c, float peakLimit)
+{
+ c = max(c, 0.0);
+ float peak = SpecularPeak3(c);
+ if (peak > peakLimit && peak > 1.0e-5)
+ c *= peakLimit / peak;
+ return c;
}
float3 ComputeSpecular(
@@ -3463,7 +3473,7 @@ float3 ComputeSpecular(
float3 F = F0 + (1.0 - F0) * pow(1.0 - VoH, 5.0);
float specTerm = (D * G) / max(4.0 * NoL * NoV, 1.0e-4);
- specTerm = min(specTerm, 5.0);
+ specTerm = min(specTerm, 3.0);
const float SPECULAR_ENERGY_SCALE = 2.35;
@@ -3477,7 +3487,7 @@ float3 ComputeSpecular(
specTerm *
SPECULAR_ENERGY_SCALE;
- return clamp(specular, 0.0, 8.0);
+ return LimitSpecularPeak(specular, 4.0);
}
)"
R"(
@@ -3535,6 +3545,18 @@ float3 SampleCosineWorld(float3 N, inout uint rng)
N * localDir.z);
}
+float3 SampleCosineWorldXi(float3 N, float2 xi)
+{
+ float3 tangent, bitangent;
+ BuildOrthonormalBasis(N, tangent, bitangent);
+
+ float3 localDir = CosineSampleHemisphere(xi);
+ return normalize(
+ tangent * localDir.x +
+ bitangent * localDir.y +
+ N * localDir.z);
+}
+
float3 SampleConeWorld(float3 centerDir, float coneRadius, inout uint rng)
{
float3 tangent, bitangent;
@@ -4374,7 +4396,21 @@ float3 EstimateReactiveScreenSpaceFinalGather(uint2 pixel, float3 worldPos, floa
}
)"
R"(
-float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, inout uint rng)
+float2 GetStratifiedBounceXi(uint2 pixel, float3 worldPos, float3 pathNormal, uint sampleIndex)
+{
+ uint spp = max(gSamplesPerPixel, 1u);
+ uint sequenceLength = max(spp * 64u, 64u);
+ uint sequenceIndex = (sampleIndex + (gFrameIndex & 63u) * spp) % sequenceLength;
+
+ float rand = Hash12(
+ (float2)pixel +
+ worldPos.xy * 0.013 +
+ float2(pathNormal.x * 19.19, pathNormal.y * 37.37));
+
+ return Hammersley2D(sequenceIndex, sequenceLength, rand);
+}
+
+float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, uint sampleIndex, inout uint rng)
{
const float BOUNCE_TMAX = 1000000.0;
@@ -4393,7 +4429,10 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3
[loop]
for (uint depth = 0; depth < maxIndirectDepth; ++depth)
{
- float3 bounceDir = SampleCosineWorld(pathNormal, rng);
+ float2 bounceXi = (depth == 0u)
+ ? GetStratifiedBounceXi(pixel, pathPos, pathNormal, sampleIndex)
+ : Rand2(rng);
+ float3 bounceDir = SampleCosineWorldXi(pathNormal, bounceXi);
float NoD = saturate(dot(pathNormal, bounceDir));
float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NoD);
@@ -4456,6 +4495,7 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3
bouncedRadiance += skyTint * 0.045;
}
+ bouncedRadiance = CompressEmissiveRadiance(bouncedRadiance, depth == 0u ? 8.0 : 5.0);
accum += throughput * bouncedRadiance;
if ((hitGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u)
@@ -4487,14 +4527,14 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3
return accum;
}
-float3 EstimatePathTracedIndirectBounce(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, inout uint rng)
+float3 EstimatePathTracedIndirectBounce(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, uint sampleIndex, inout uint rng)
{
if (gMaxBounces <= 1u)
return 0.0;
// One stochastic diffuse path per lighting sample. The path can contain
// several diffuse depths, while the temporal pass below handles convergence.
- float3 accum = TraceOneIndirectBouncePath(pixel, worldPos, N, V, baseAlbedo, rng);
+ float3 accum = TraceOneIndirectBouncePath(pixel, worldPos, N, V, baseAlbedo, sampleIndex, rng);
const float INDIRECT_STRENGTH = 0.72;
return accum * INDIRECT_STRENGTH;
@@ -4714,7 +4754,8 @@ float ComputeSpecularReflectionDistanceFade(float hitT, float maxT)
return fade * fade;
}
-
+)"
+R"(
float3 EstimateRayTracedSpecularReflection(
uint2 pixel,
float3 worldPos,
@@ -4804,6 +4845,7 @@ float3 EstimateRayTracedSpecularReflection(
if (hasGBufferMaterial)
{
float3 hitV = SafeNormalizeOr(-R, V);
+ uint stableHitRng = InitRng(hitPixel, 0u, 0x5EECu);
reflectedRadiance = EstimateDirectLightingForBounceHit(
hitPixel,
hitPos,
@@ -4811,7 +4853,7 @@ float3 EstimateRayTracedSpecularReflection(
hitV,
hitAlbedo,
hitGeoFlag,
- rng);
+ stableHitRng);
// Reflected glow maps should be visible in the reflection, but still
// remain direct radiance; this does not turn emissive into GI.
@@ -4827,6 +4869,8 @@ float3 EstimateRayTracedSpecularReflection(
}
}
+ reflectedRadiance = CompressEmissiveRadiance(reflectedRadiance, hasSpecularMap ? 5.0 : 3.5);
+
float cavityFade = lerp(0.42, 1.0, saturate(cavity));
float reflectionStrength = hasSpecularMap
? saturate(specPeak * 1.35)
@@ -4968,6 +5012,7 @@ void RayGen()
N,
V,
baseAlbedo,
+ s,
rng);
}
@@ -4975,7 +5020,7 @@ void RayGen()
lightingAccum += ApplyPrimaryDiffusePost(indirectLighting, ao, microShadow, isSkeletal);
}
- uint reflectionRng = InitRng(pixel, gFrameIndex, 0x5EECu);
+ uint reflectionRng = InitRng(pixel, 0u, 0x5EECu);
float3 reflectedSpecular = EstimateRayTracedSpecularReflection(
pixel,
worldPos,
@@ -5334,9 +5379,9 @@ float3 SafeNormalTemporal(float3 n)
float3 ClampHistoryToCurrent(float3 history, float3 current)
{
- // Loose temporal clamp: it removes GI fireflies and old lighting while still
- // allowing bright muzzle-flash/door-light changes to appear in a few frames.
- float3 radius = 0.20 + abs(current) * 0.55;
+ // Clamp enough to kill rare GI/volume fireflies before they enter history,
+ // while still allowing real dynamic light changes to pull the accumulator.
+ float3 radius = 0.12 + abs(current) * 0.42;
return clamp(history, current - radius, current + radius);
}
@@ -5359,7 +5404,7 @@ void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID)
}
float4 history = gHistoryTex.Load(int3(pixel, 0));
- float historyCount = (gFrameIndex == 0u) ? 0.0 : clamp(history.a, 0.0, 31.0);
+ float historyCount = (gFrameIndex == 0u) ? 0.0 : clamp(history.a, 0.0, 63.0);
if (historyCount <= 0.0)
{
@@ -5374,14 +5419,14 @@ void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID)
float histLum = LuminanceTemporal(historyColor);
float relChange = abs(rawLum - histLum) / max(max(rawLum, histLum), 0.08);
- // Base accumulation approaches 32 frames, but large lighting changes raise
+ // Base accumulation approaches 64 frames, but large lighting changes raise
// current-frame weight so the accumulator does not leave obvious trails.
- float currentWeight = max(1.0 / (historyCount + 1.0), 0.055);
- currentWeight = max(currentWeight, saturate(relChange * 0.28));
+ float currentWeight = max(1.0 / (historyCount + 1.0), 0.035);
+ currentWeight = max(currentWeight, saturate(relChange * 0.24));
currentWeight = saturate(currentWeight);
float3 resolved = lerp(historyColor, max(raw.rgb, 0.0), currentWeight);
- float nextCount = min(historyCount + 1.0, 31.0);
+ float nextCount = min(historyCount + 1.0, 63.0);
gTemporalOutTex[pixel] = float4(resolved, raw.a);
gHistoryOutTex[pixel] = float4(resolved, nextCount);
@@ -7323,4 +7368,4 @@ void glRaytracingShowAllInstances(void)
for (int i = 0; i < GL_RAYTRACING_MAX_RENDER_WORLDS; ++i)
glRaytracingSetAllInstancesVisibleUnlocked(&g_glRaytracingScene.worlds[i], 1);
-}
\ No newline at end of file
+}
diff --git a/neo/engine/opengl/gl_d3d12shim.cpp b/neo/engine/opengl/gl_d3d12shim.cpp
index 86b8a442..bc1ac044 100644
--- a/neo/engine/opengl/gl_d3d12shim.cpp
+++ b/neo/engine/opengl/gl_d3d12shim.cpp
@@ -1298,7 +1298,7 @@ struct GLState
bool motionHistoryReset = true;
QD3D12UpscalerBackend upscalerBackend = QD3D12_UPSCALER_DLSS;
- QD3D12UpscalerQuality upscalerQuality = QD3D12_QUALITY_PERFORMANCE;
+ QD3D12UpscalerQuality upscalerQuality = QD3D12_QUALITY_DLAA;
bool enableInternalTAA = true;
bool enableRayAIDenoise = false;
bool enableDLSSRayReconstruction = true;
@@ -2513,7 +2513,7 @@ cbuffer DrawCB : register(b0)
// Pixel-shader POM depth in UV space. Keep this conservative: legacy idTech
// normal maps often do not have a true height channel, so the POM path below
// gates fallback height by local detail before applying any offset.
-#define gParallaxScale (clamp(gNormalMapStrength, 0.0, 4.0) * 0.026)
+#define gParallaxScale (clamp(gNormalMapStrength, 0.0, 4.0) * 0.020)
#define gCameraWorldPos gCameraPomPad.xyz
#define gCameraPomValid gCameraPomPad.w
#define gUseNeuralPOM gNeuralPomPad.x
@@ -2777,6 +2777,14 @@ float3 QD3D12_DecodeTangentSpaceNormal(float3 encodedNormal)
return QD3D12_SafeNormalize(n, float3(0.0, 0.0, 1.0));
}
+float3 QD3D12_DecodeTangentSpaceNormalScaled(float3 encodedNormal, float strengthScale)
+{
+ float3 n = encodedNormal * 2.0 - 1.0;
+ n.y *= gNormalMapYSign;
+ n.xy *= max(gNormalMapStrength, 0.0) * saturate(strengthScale);
+ return QD3D12_SafeNormalize(n, float3(0.0, 0.0, 1.0));
+}
+
float QD3D12_HeightFromNormalSample(float4 nm)
{
// Many legacy normal maps are RGB/DXT uploads with an implicitly forced
@@ -2883,7 +2891,8 @@ void QD3D12_BuildPixelTBN(VSOut i, out float3 n, out float3 t, out float3 b)
t = derivedT;
b = derivedB;
}
-
+)HLSL"
+R"HLSL(
float QD3D12_AuthoredPomAlphaWeight(float alphaValue)
{
// Alpha == 1 is frequently forced by RGB/DXT uploads in this shim. Treat only
@@ -2993,6 +3002,29 @@ float QD3D12_GetPomConfidence(float2 uv)
return confidence * confidence * (3.0 - 2.0 * confidence);
}
+float QD3D12_GetRegularPomFade(VSOut i, float2 baseUv)
+{
+ if (gUseNormalMap < 0.5)
+ return 0.0;
+
+ float confidence = QD3D12_GetPomConfidence(baseUv);
+ if (confidence <= 0.03)
+ return 0.0;
+
+ float3 n, t, b;
+ QD3D12_BuildPixelTBN(i, n, t, b);
+
+ float3 rawViewWS = (gCameraPomValid >= 0.5) ? (gCameraWorldPos - i.worldPos) : (-i.worldPos);
+ float3 viewWS = QD3D12_SafeNormalize(rawViewWS, n);
+ float NoV = saturate(dot(n, viewWS));
+
+ float viewDistance = (gCameraPomValid >= 0.5) ? max(length(gCameraWorldPos - i.worldPos), 1.0) : max(abs(i.currClip.w), 1.0);
+ float distanceFade = 1.0 - smoothstep(QD3D12_POM_DISTANCE_NEAR, QD3D12_POM_DISTANCE_FAR, viewDistance);
+ float grazingFade = smoothstep(0.10, 0.26, NoV);
+
+ return saturate(confidence * distanceFade * grazingFade);
+}
+
// Keep the runtime network bounded. Most payloads do not need the full 128-wide
// MLP in a fixed-function material pass; clamping evaluation here prevents a
@@ -3269,7 +3301,7 @@ float2 QD3D12_ComputeParallaxUVWithNeural(VSOut i, float2 baseUv, QD3D12NeuralPO
return baseUv;
float confidence = QD3D12_GetPomConfidence(baseUv);
- if (confidence <= 0.03)
+ if (confidence <= 0.05)
return baseUv;
float parallaxScale = gParallaxScale * confidence;
@@ -3320,7 +3352,7 @@ float2 QD3D12_ComputeParallaxUVWithNeural(VSOut i, float2 baseUv, QD3D12NeuralPO
// authored alpha height.
float2 parallaxVector = (viewTS.xy / vz) * scale;
float parallaxLen = length(parallaxVector);
- float maxParallaxShift = lerp(0.024, 0.058, confidence);
+ float maxParallaxShift = lerp(0.016, 0.040, confidence);
if (parallaxLen > maxParallaxShift && parallaxLen > 1e-6)
parallaxVector *= maxParallaxShift / parallaxLen;
@@ -3358,9 +3390,10 @@ float2 QD3D12_ComputeParallaxUVWithNeural(VSOut i, float2 baseUv, QD3D12NeuralPO
// Blend the final result in instead of applying a manual half-vector center
// correction. The center correction was the source of several texture-offset
// bugs on flat/low-confidence areas.
- return lerp(baseUv, refinedUv, saturate(distanceFade * grazingFade));
+ return lerp(baseUv, refinedUv, saturate(distanceFade * grazingFade * confidence));
}
-
+)HLSL"
+R"HLSL(
float2 QD3D12_ComputeParallaxUV(VSOut i, float2 baseUv)
{
QD3D12NeuralPOMResult nr = QD3D12_EvaluateNeuralPOM(i, baseUv);
@@ -3515,7 +3548,11 @@ float4 BuildSpecularAlbedoCached(VSOut i, QD3D12MaterialEval m)
{
if (gUseSpecularMap > 0.0)
{
- float4 spec = gSpecularMap.Sample(gSamp4, m.uv0);
+ float regularPomFade = (gUseNeuralPOM <= 0.5 || m.neural.active <= 0.5)
+ ? QD3D12_GetRegularPomFade(i, i.uv0)
+ : 0.0;
+ float2 specUv = lerp(i.uv0, m.uv0, regularPomFade);
+ float4 spec = gSpecularMap.Sample(gSamp4, specUv);
float strength = max(gSpecularMapStrength, 0.0);
// RGB-only legacy spec maps are uploaded with forced opaque alpha. For real
@@ -3526,6 +3563,8 @@ float4 BuildSpecularAlbedoCached(VSOut i, QD3D12MaterialEval m)
bool alphaLooksForcedOpaque = (spec.a >= 0.999);
float alphaMask = alphaLooksForcedOpaque ? 1.0 : saturate(spec.a);
float3 specRgb = saturate(spec.rgb) * alphaMask * strength;
+ if (regularPomFade > 0.0)
+ specRgb = min(specRgb, float3(0.82, 0.82, 0.82));
return float4(specRgb, 1.0);
}
@@ -3573,7 +3612,12 @@ float3 BuildGBufferNormalCached(VSOut i, QD3D12MaterialEval m)
float3 t, b;
QD3D12_BuildPixelTBN(i, n, t, b);
- float3 tangentNormal = QD3D12_DecodeTangentSpaceNormal(gNormalMap.Sample(gSamp2, m.uv0).xyz);
+ float regularPomFade = (gUseNeuralPOM <= 0.5 || m.neural.active <= 0.5)
+ ? QD3D12_GetRegularPomFade(i, i.uv0)
+ : 0.0;
+ float3 tangentBase = QD3D12_DecodeTangentSpaceNormalScaled(gNormalMap.Sample(gSamp2, i.uv0).xyz, 1.0);
+ float3 tangentPom = QD3D12_DecodeTangentSpaceNormalScaled(gNormalMap.Sample(gSamp2, m.uv0).xyz, lerp(0.70, 1.0, regularPomFade));
+ float3 tangentNormal = QD3D12_SafeNormalize(lerp(tangentBase, tangentPom, regularPomFade), tangentBase);
return QD3D12_SafeNormalize(
tangentNormal.x * t +
diff --git a/neo/prey/game/anim/Anim.h b/neo/prey/game/anim/Anim.h
index 6f1374e9..524c3260 100644
--- a/neo/prey/game/anim/Anim.h
+++ b/neo/prey/game/anim/Anim.h
@@ -49,12 +49,6 @@ typedef struct {
int firstComponent;
} jointAnimInfo_t;
-typedef struct {
- jointHandle_t num;
- jointHandle_t parentNum;
- int channel;
-} jointInfo_t;
-
//
// joint modifier modes. make sure to change script/doom_defs.script if you add any, or change their order.
//