Quality enhacements.

This commit is contained in:
Justin Marshall
2026-05-10 03:41:48 -07:00
parent b441556af8
commit 4a53e6a186
2 changed files with 282 additions and 27 deletions
+281 -26
View File
@@ -2713,6 +2713,18 @@ void BounceClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectio
payload.pad0 = 0;
}
[shader("closesthit")]
void ReflectionClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr)
{
// Specular rays are view rays, not diffuse/visibility rays. They should
// report the first reflected surface even when that surface was tagged as
// glass/alpha, otherwise glass panes/sprites vanish from mirror-like hits.
payload.hit = 1;
payload.hitT = RayTCurrent();
payload.materialFlags = DecodeInstanceMaterialFlags();
payload.pad0 = 0;
}
float TraceShadow(float3 origin, float3 dir, float maxT)
{
RayDesc ray;
@@ -2766,6 +2778,35 @@ bool TraceBounce(float3 origin, float3 dir, float maxT, out float hitT, out uint
return payload.hit != 0;
}
bool TraceSpecularReflection(float3 origin, float3 dir, float maxT, out float hitT, out uint materialFlags)
{
RayDesc ray;
ray.Origin = origin;
ray.Direction = dir;
ray.TMin = 0.001;
ray.TMax = maxT;
BouncePayload payload;
payload.hit = 0;
payload.hitT = 0.0;
payload.materialFlags = 0;
payload.pad0 = 0;
TraceRay(
gSceneBVH,
RAY_FLAG_NONE,
0xFF,
2,
0,
1,
ray,
payload);
hitT = payload.hitT;
materialFlags = payload.materialFlags;
return payload.hit != 0;
}
float Hash12(float2 p)
{
float3 p3 = frac(float3(p.xyx) * 0.1031);
@@ -2889,7 +2930,8 @@ float Doom3ProjectionTexture2D(float2 centeredCoord)
return Doom3QuadraticCentered(centeredCoord.x) * Doom3QuadraticCentered(centeredCoord.y);
}
)"
R"(
float Doom3ProjectedDepthFalloff(float depth, float nearClip, float farClip)
{
// For spot/projected lights, the renderer API supplies a conventional near/far
@@ -3265,13 +3307,39 @@ float ComputeCavity(uint2 pixel, float3 worldPos, float3 N)
float Doom3SpecularLookup(float x)
{
// Doom 3 used a lookup table for specular falloff.
// This approximates the classic broad idTech4 highlight.
// Doom 3 used a lookup table for specular falloff. A single high-power
// lobe is too binary with this G-buffer path: small normal-map/grazing
// differences make some materials lose specular completely. Use a broad
// plastic lobe plus a tighter hot spot so highlights stay readable without
// turning into a flat additive wash.
x = saturate(x);
// Broad enough to read like Doom 3 plastic/metal,
// not razor-sharp PBR.
return pow(x, 16.0);
float broad = pow(x, 12.0);
float tight = pow(x, 48.0);
return saturate(broad * 0.55 + tight * 0.85);
}
float SpecularPeak3(float3 c)
{
return max(max(c.r, c.g), c.b);
}
bool LooksLikeAuthoredSpecularSample(float4 specSample, float3 baseAlbedo)
{
// Normal path: alpha is the raster G-buffer validity bit.
if (specSample.a > 0.5)
return true;
// Tolerant path: some raster paths/specular inputs write RGB but leave the
// validity alpha at zero. Do not treat the fallback albedo descriptor as a
// spec map; when no specular texture is bound, specSample.rgb == baseAlbedo.
float3 specRgb = saturate(specSample.rgb);
float3 baseRgb = saturate(baseAlbedo);
float rgbPeak = SpecularPeak3(specRgb);
float rgbDiff = length(specRgb - baseRgb);
return rgbPeak > 0.025 && rgbDiff > 0.035;
}
float3 Doom3PseudoSpecularMask(float3 baseAlbedo)
@@ -3297,7 +3365,10 @@ float3 LoadSceneSpecularAlbedo(uint2 pixel, float3 baseAlbedo)
// The raster G-buffer writer stores alpha as a validity bit. This matters
// for black specular maps: black should mean zero specular, not "missing map".
if (specSample.a > 0.5)
// Also accept RGB-only specular inputs when they are clearly not the fallback
// albedo descriptor, which fixes materials whose specular buffer forgot to
// set the alpha-valid bit.
if (LooksLikeAuthoredSpecularSample(specSample, baseAlbedo))
return saturate(specSample.rgb);
return Doom3PseudoSpecularMask(baseAlbedo);
@@ -3323,18 +3394,25 @@ float3 ComputeSpecular(
float NdotL = dot(N, L);
float NdotV = dot(N, V);
// Doom 3 specular should not show on the wrong side of the surface,
// but once it passes this gate, do not multiply the final spec by NdotL again.
// The old code did that and made highlights collapse too aggressively.
if (NdotL <= 0.0 || NdotV <= 0.0 || atten <= 0.0 || shadow <= 0.0)
if (atten <= 0.0 || shadow <= 0.0)
return 0.0;
// Keep Doom/idTech's front-side behavior, but make the gate soft. A hard
// NdotL/NdotV cutoff was making normal-mapped and grazing surfaces randomly
// lose all specular even when the half-angle lobe should still be visible.
float lightFacing = smoothstep(-0.08, 0.18, NdotL);
float viewFacing = smoothstep(-0.04, 0.14, NdotV);
if (lightFacing <= 0.0 || viewFacing <= 0.0)
return 0.0;
// idTech4/Doom 3 interaction shader uses half-angle style specular,
// not the reflect(-L,N) Phong vector used in the old code here.
float3 H = normalize(L + V);
float3 H = Doom3SafeNormalizeOr(L + V, N);
float NdotH = saturate(dot(N, H));
float specTerm = Doom3SpecularLookup(NdotH);
float specTerm = Doom3SpecularLookup(NdotH) * lightFacing * viewFacing;
if (specTerm <= 1.0e-5)
return 0.0;
float3 specMask = saturate(specularAlbedo);
@@ -4391,7 +4469,11 @@ float3 ApplyPrimaryDiffusePost(float3 lightingAccum, float ao, float microShadow
float3 ApplyPrimarySpecularPost(float3 specularAccum, float ao, bool isSkeletal)
{
specularAccum *= ao;
// AO is a diffuse/ambient visibility term here. Multiplying specular by AO
// directly made highlights disappear on creases, props, and normal-mapped
// surfaces. Keep a mild occlusion tint, but let direct-light specular read.
float specAo = lerp(0.58, 1.0, saturate(ao));
specularAccum *= specAo;
//if (isSkeletal)
// specularAccum *= 1.15;
@@ -4467,6 +4549,148 @@ float3 PathTraceDeterministicLighting(
return ApplyPrimaryDiffusePost(lightingAccum, ao, microShadow, isSkeletal);
}
float3 EstimateFallbackReflectionHitRadiance(float3 hitPos, float3 hitNormal, float3 incomingViewDir)
{
// Reflection rays can hit off-screen or camera-hidden TLAS geometry. In that
// case this pass has no per-triangle material/normal table, so use a neutral
// lit card instead of returning black. This keeps reflected objects visible
// without pretending every unknown hit is a perfect emissive surface.
incomingViewDir = SafeNormalizeOr(incomingViewDir, -hitNormal);
hitNormal = SafeNormalizeOr(hitNormal, incomingViewDir);
if (dot(hitNormal, incomingViewDir) < 0.0)
hitNormal = -hitNormal;
float upness = saturate(hitNormal.z * 0.5 + 0.5);
float3 lighting = gAmbientColor.rgb * (gAmbientColor.a * 0.035);
lighting += GetSkyRadiance(hitNormal) * (0.12 + 0.08 * upness);
lighting += GetSkyRadiance(SafeNormalizeOr(reflect(-incomingViewDir, hitNormal), hitNormal)) * 0.055;
[loop]
for (uint i = 0; i < gLightCount; ++i)
{
lighting += EstimateFastBounceLight(hitPos, hitNormal, gLights[i]) * 0.78;
}
const float3 NEUTRAL_UNKNOWN_ALBEDO = float3(0.58, 0.58, 0.58);
return clamp(NEUTRAL_UNKNOWN_ALBEDO * max(lighting, 0.0), 0.0, 10.0);
}
float3 EstimateRayTracedSpecularReflection(
uint2 pixel,
float3 worldPos,
float3 N,
float3 V,
float3 baseAlbedo,
float3 specularAlbedo,
float cavity,
inout uint rng)
{
if (gEnableSpecular == 0u || gMaxBounces <= 1u)
return 0.0;
N = SafeNormalizeOr(N, float3(0.0, 0.0, 1.0));
V = SafeNormalizeOr(V, -N);
// Prefer authored specular maps, but do not hard-disable reflections on
// missing/RGB-only specular inputs. Missing spec maps get a muted fallback
// reflection from the pseudo-spec mask; authored black spec maps still return
// zero because LooksLikeAuthoredSpecularSample() preserves them.
float4 rawSpecular = gSpecularTex.Load(int3(pixel, 0));
bool hasSpecularMap = LooksLikeAuthoredSpecularSample(rawSpecular, baseAlbedo);
float specPeak = SpecularPeak3(specularAlbedo);
if (specPeak <= 0.015)
return 0.0;
float NoV = saturate(dot(N, V));
float3 R = SafeNormalizeOr(reflect(-V, N), N);
float NoR = saturate(dot(N, R));
if (NoR <= 0.001)
return 0.0;
// Schlick fresnel keeps reflections strongest at grazing angles while still
// honoring the artist-authored Doom/idTech-style specular map color. Missing
// spec maps use a much lower F0 so the fallback is glossy, not mirror-like.
float3 F0 = hasSpecularMap
? saturate(specularAlbedo)
: saturate(lerp(float3(0.02, 0.02, 0.02), specularAlbedo, 0.38));
float3 fresnel = F0 + (1.0 - F0) * pow(1.0 - NoV, 5.0);
float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NoR);
float3 reflectionOrigin = worldPos + N * normalBias + R * (gShadowBias * 0.5);
float hitT = 0.0;
uint materialFlags = 0u;
bool hit = TraceSpecularReflection(reflectionOrigin, R, 1000000.0, hitT, materialFlags);
float3 reflectedRadiance = 0.0;
if (!hit)
{
reflectedRadiance = GetSkyRadiance(R);
}
else
{
float3 rayHitPos = reflectionOrigin + R * hitT;
uint2 hitPixel = pixel;
float3 hitPos = rayHitPos;
float3 hitNormal = SafeNormalizeOr(-R, N);
float3 hitAlbedo = float3(0.55, 0.55, 0.55);
uint hitGeoFlag = GEOMETRY_FLAG_NONE;
bool hasGBufferMaterial = TryFetchGBufferAtRayHit(
rayHitPos,
hitPixel,
hitPos,
hitNormal,
hitAlbedo,
hitGeoFlag);
if (dot(hitNormal, -R) < 0.0)
hitNormal = -hitNormal;
if (hasGBufferMaterial)
{
float3 hitV = SafeNormalizeOr(-R, V);
reflectedRadiance = EstimateDirectLightingForBounceHit(
hitPixel,
hitPos,
hitNormal,
hitV,
hitAlbedo,
hitGeoFlag,
rng);
// Reflected glow maps should be visible in the reflection, but still
// remain direct radiance; this does not turn emissive into GI.
float3 hitEmissive = CompressEmissiveRadiance(gEmissiveTex.Load(int3(hitPixel, 0)).rgb, 6.50);
reflectedRadiance += hitEmissive;
}
else
{
// Off-screen/hidden TLAS hits lack material data in this G-buffer-only
// material path. Use a neutral lit fallback instead of a dim sky-only
// tint, otherwise many real reflected objects vanish completely.
reflectedRadiance = EstimateFallbackReflectionHitRadiance(rayHitPos, hitNormal, -R);
}
}
float cavityFade = lerp(0.42, 1.0, saturate(cavity));
float reflectionStrength = hasSpecularMap
? saturate(specPeak * 1.35)
: saturate((specPeak - 0.10) * 0.65);
float reflectionScale = hasSpecularMap ? 0.90 : 0.55;
if (reflectionStrength <= 0.001)
return 0.0;
return clamp(reflectedRadiance * fresnel * reflectionStrength * cavityFade * reflectionScale, 0.0, 8.0);
}
)"
R"(
[shader("raygeneration")]
void RayGen()
{
@@ -4513,7 +4737,7 @@ void RayGen()
// then reuse them for every stochastic GI sample.
float cavity = ComputeCavity(pixel, worldPos, N);
float ao = ComputeAmbientOcclusion(worldPos, N, pixel);
float skyVis = 0; // ComputeSkyVisibility(worldPos, N, pixel); // jmarshall - fix me later
float skyVis = 0; //ComputeSkyVisibility(worldPos, N, pixel); // jmarshall - fix me later
float ambientSkyVis = TraceStraightUpToSky(worldPos, N);
float microShadow = lerp(0.75, 1.0, cavity);
@@ -4575,8 +4799,19 @@ void RayGen()
lightingAccum += ApplyPrimaryDiffusePost(indirectLighting, ao, microShadow, isSkeletal);
}
uint reflectionRng = InitRng(pixel, gFrameIndex, 0x5EECu);
float3 reflectedSpecular = EstimateRayTracedSpecularReflection(
pixel,
worldPos,
N,
V,
baseAlbedo,
specularAlbedo,
cavity,
reflectionRng);
float3 albedo = baseAlbedo * cavity;
float3 finalColor = (albedo * lightingAccum) + specularAccum;
float3 finalColor = (albedo * lightingAccum) + specularAccum + reflectedSpecular;
if (gMaxBounces > 1u)
{
@@ -5447,7 +5682,7 @@ static int glRaytracingLightingCreateStateObject(void)
if (!dxil)
return 0;
D3D12_EXPORT_DESC exports[7] = {};
D3D12_EXPORT_DESC exports[8] = {};
exports[0].Name = L"RayGen";
exports[1].Name = L"ShadowMiss";
exports[2].Name = L"ShadowAnyHit";
@@ -5455,6 +5690,7 @@ static int glRaytracingLightingCreateStateObject(void)
exports[4].Name = L"BounceMiss";
exports[5].Name = L"BounceAnyHit";
exports[6].Name = L"BounceClosestHit";
exports[7].Name = L"ReflectionClosestHit";
D3D12_DXIL_LIBRARY_DESC libDesc = {};
D3D12_SHADER_BYTECODE libBytecode = {};
@@ -5464,7 +5700,7 @@ static int glRaytracingLightingCreateStateObject(void)
libDesc.NumExports = _countof(exports);
libDesc.pExports = exports;
D3D12_HIT_GROUP_DESC hitGroups[2] = {};
D3D12_HIT_GROUP_DESC hitGroups[3] = {};
hitGroups[0].HitGroupExport = L"ShadowHitGroup";
hitGroups[0].AnyHitShaderImport = L"ShadowAnyHit";
hitGroups[0].ClosestHitShaderImport = L"ShadowClosestHit";
@@ -5475,6 +5711,10 @@ static int glRaytracingLightingCreateStateObject(void)
hitGroups[1].ClosestHitShaderImport = L"BounceClosestHit";
hitGroups[1].Type = D3D12_HIT_GROUP_TYPE_TRIANGLES;
hitGroups[2].HitGroupExport = L"ReflectionHitGroup";
hitGroups[2].ClosestHitShaderImport = L"ReflectionClosestHit";
hitGroups[2].Type = D3D12_HIT_GROUP_TYPE_TRIANGLES;
D3D12_RAYTRACING_SHADER_CONFIG shaderConfig = {};
shaderConfig.MaxPayloadSizeInBytes = 16; // BouncePayload: uint + float + uint + uint.
shaderConfig.MaxAttributeSizeInBytes = 8;
@@ -5485,7 +5725,7 @@ static int glRaytracingLightingCreateStateObject(void)
D3D12_LOCAL_ROOT_SIGNATURE localRS = {};
localRS.pLocalRootSignature = g_glRaytracingLighting.localRootSig.Get();
D3D12_STATE_SUBOBJECT subobjects[8] = {};
D3D12_STATE_SUBOBJECT subobjects[9] = {};
UINT sub = 0;
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY;
@@ -5500,6 +5740,10 @@ static int glRaytracingLightingCreateStateObject(void)
subobjects[sub].pDesc = &hitGroups[1];
++sub;
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP;
subobjects[sub].pDesc = &hitGroups[2];
++sub;
subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG;
subobjects[sub].pDesc = &shaderConfig;
++sub;
@@ -5518,11 +5762,12 @@ static int glRaytracingLightingCreateStateObject(void)
L"ShadowMiss",
L"ShadowHitGroup",
L"BounceMiss",
L"BounceHitGroup"
L"BounceHitGroup",
L"ReflectionHitGroup"
};
D3D12_SUBOBJECT_TO_EXPORTS_ASSOCIATION assoc = {};
assoc.pSubobjectToAssociate = &subobjects[5];
assoc.pSubobjectToAssociate = &subobjects[6];
assoc.NumExports = _countof(localExports);
assoc.pExports = localExports;
@@ -5554,8 +5799,9 @@ static int glRaytracingLightingCreateShaderTables(void)
void* bounceMissId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"BounceMiss");
void* shadowHitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ShadowHitGroup");
void* bounceHitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"BounceHitGroup");
void* reflectionHitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ReflectionHitGroup");
if (!raygenId || !shadowMissId || !bounceMissId || !shadowHitId || !bounceHitId)
if (!raygenId || !shadowMissId || !bounceMissId || !shadowHitId || !bounceHitId || !reflectionHitId)
{
glRaytracingFatal("Failed to fetch shader identifiers");
return 0;
@@ -5564,7 +5810,7 @@ static int glRaytracingLightingCreateShaderTables(void)
const UINT shaderIdSize = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES;
const UINT recordSize = (UINT)glRaytracingAlignUp(shaderIdSize, D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT);
const UINT missTableSize = recordSize * 2u;
const UINT hitTableSize = recordSize * 2u;
const UINT hitTableSize = recordSize * 3u;
g_glRaytracingLighting.raygenTable = glRaytracingCreateBuffer(
g_glRaytracingCmd.device.Get(),
@@ -5609,6 +5855,7 @@ static int glRaytracingLightingCreateShaderTables(void)
memset(temp.data(), 0, temp.size());
memcpy(temp.data(), shadowHitId, shaderIdSize);
memcpy(temp.data() + recordSize, bounceHitId, shaderIdSize);
memcpy(temp.data() + recordSize * 2u, reflectionHitId, shaderIdSize);
glRaytracingMapCopy(g_glRaytracingLighting.hitTable.resource.Get(), temp.data(), hitTableSize);
return 1;
@@ -5981,7 +6228,7 @@ static bool glRaytracingLightingExecuteInternal(
rays.MissShaderTable.SizeInBytes = shaderRecordSize * 2u;
rays.MissShaderTable.StrideInBytes = shaderRecordSize;
rays.HitGroupTable.StartAddress = g_glRaytracingLighting.hitTable.gpuVA;
rays.HitGroupTable.SizeInBytes = shaderRecordSize * 2u;
rays.HitGroupTable.SizeInBytes = shaderRecordSize * 3u;
rays.HitGroupTable.StrideInBytes = shaderRecordSize;
rays.Width = pass->width;
rays.Height = pass->height;
@@ -6495,10 +6742,18 @@ void glRaytracingLightingSetSpecularInput(ID3D12Resource* texture, DXGI_FORMAT f
{
std::lock_guard<std::mutex> lock(g_glRaytracingMutex);
g_glRaytracingLighting.specularTexture = texture;
g_glRaytracingLighting.specularFormat = (format == DXGI_FORMAT_UNKNOWN)
DXGI_FORMAT newFormat = (format == DXGI_FORMAT_UNKNOWN)
? DXGI_FORMAT_R8G8B8A8_UNORM
: format;
if (g_glRaytracingLighting.specularTexture != texture ||
g_glRaytracingLighting.specularFormat != newFormat)
{
glRaytracingLightingResetDenoiseHistory();
}
g_glRaytracingLighting.specularTexture = texture;
g_glRaytracingLighting.specularFormat = newFormat;
}
bool glRaytracingLightingExecuteForScene(const glRaytracingLightingPassDesc_t* pass, glRaytracingSceneHandle_t worldHandle)
+1 -1
View File
@@ -12707,7 +12707,7 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle)
raySpp,
activeMaxBounces,
useDLSSRayReconstruction ? 0 : 1,
useDLSSRayReconstruction ? 0.0f : 1.0f);
useDLSSRayReconstruction ? 0.0f : 0.45f);
glRaytracingLightingSetEmissiveInput(sceneEmissive, QD3D12_EmissiveFormat);
glRaytracingLightingSetSpecularInput(sceneSpecular, QD3D12_SpecularAlbedoFormat);