From c795aba899e6eacd77b1414dc691e04f7b8659a4 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Thu, 28 May 2026 22:16:09 -0700 Subject: [PATCH] Spec fixes --- neo/engine/opengl/gl_d3d12raylight.cpp | 163 ++++++++++++++++++------- neo/engine/opengl/gl_d3d12shim.cpp | 4 + neo/engine/opengl/opengl.h | 2 + neo/engine/renderer/draw_common.cpp | 93 ++++++++++++++ 4 files changed, 217 insertions(+), 45 deletions(-) diff --git a/neo/engine/opengl/gl_d3d12raylight.cpp b/neo/engine/opengl/gl_d3d12raylight.cpp index 2948990d..d642be4f 100644 --- a/neo/engine/opengl/gl_d3d12raylight.cpp +++ b/neo/engine/opengl/gl_d3d12raylight.cpp @@ -2983,6 +2983,13 @@ float4 LoadSceneNormal(uint2 pixel) return nSample; } +float LoadSceneSurfaceRoughness(float4 normalSample) +{ + // The raster G-buffer stores authored/material roughness in normal.w. + // Keep a small floor so glossy materials do not become perfect mirrors. + return clamp(normalSample.w, 0.05, 1.0); +} + float SafeLengthSq(float3 v) { return max(dot(v, v), 1e-8); @@ -3234,7 +3241,8 @@ void BounceClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectio payload.meshIndex = InstanceID() & GL_RAYTRACING_INSTANCE_USER_ID_MASK; DecodeRayMaterialAtHit(payload.meshIndex, attr, payload.hitNormal, payload.hitTexCoord); } - +)" +R"( [shader("closesthit")] void ReflectionClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr) { @@ -4014,6 +4022,17 @@ float EstimateSpecularNormalVariance(uint2 pixel, float3 centerN) return saturate(v * 1.70); } +float StabilizeSpecularSurfaceRoughness(uint2 pixel, float3 N, float surfaceRoughness) +{ + float normalVariance = EstimateSpecularNormalVariance(pixel, N); + + // Doom 3 normal maps can be extremely high contrast. Let clean painted metal + // and wet floors stay glossy, but force rougher lobes on noisy pixels so GGX + // does not turn texel-scale normal detail into broken sparkles. + float varianceFloor = lerp(0.12, 0.46, smoothstep(0.08, 0.42, normalVariance)); + return clamp(max(surfaceRoughness, varianceFloor), 0.05, 1.0); +} + float3 Doom3SyntheticSpecularMask(uint2 pixel, float3 baseAlbedo) { // Missing Doom 3 specular maps used to get a bright Phong-era fallback. Build @@ -4034,8 +4053,8 @@ float3 Doom3SyntheticSpecularMask(uint2 pixel, float3 baseAlbedo) float cavityDamping = lerp(1.0, 0.48, smoothstep(0.03, 0.32, normalVariance)); float roughSurfaceDamping = lerp(1.0, 0.58, smoothstep(0.08, 0.45, normalVariance)); - float f0 = 0.04 * cleanDiffuse * cavityDamping * roughSurfaceDamping; - f0 = clamp(f0, 0.010, 0.055); + float f0 = 0.045 * cleanDiffuse * cavityDamping * roughSurfaceDamping; + f0 = clamp(f0, 0.012, 0.070); float tintAmount = 0.10 * saturate(saturation); float3 neutralSpec = float3(f0, f0, f0); @@ -4058,17 +4077,17 @@ float3 ShapeAuthoredDoom3Specular(uint2 pixel, float3 baseAlbedo, float3 authore // Doom/idTech spec maps were painted for Phong, often as broad intensity masks. // Treat them as strength/tint hints over a physical dielectric baseline instead // of feeding their RGB straight into F0. - float authoredStrength = smoothstep(0.025, 0.72, authoredPeak); - float strengthScale = lerp(0.65, 2.85, authoredStrength); + float authoredStrength = smoothstep(0.018, 0.68, authoredPeak); + float strengthScale = lerp(0.85, 4.65, authoredStrength); float3 shaped = synthetic * strengthScale; float3 authoredTint = authored / max(authoredPeak, 1.0e-4); - float tintTrust = saturate((authoredPeak - 0.08) * 0.55); + float tintTrust = saturate((authoredPeak - 0.05) * 0.85); shaped = lerp(shaped, authoredTint * SpecularPeak3(shaped), tintTrust); float peak = SpecularPeak3(shaped); - if (peak > 0.16 && peak > 1.0e-5) - shaped *= 0.16 / peak; + if (peak > 0.30 && peak > 1.0e-5) + shaped *= 0.30 / peak; return max(shaped, float3(0.004, 0.004, 0.004)); } @@ -4108,15 +4127,17 @@ float ComputeDiffuseLightingTerm(float3 N, float3 L) return saturate(rawNoL); } -float EstimateSpecularRoughness(float3 specularAlbedo) +float EstimateSpecularRoughness(float3 specularAlbedo, float surfaceRoughness) { - // 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, but keep a floor high enough that POM/normal-map - // detail cannot collapse into one-pixel GGX fireflies. + // Doom 3 spec maps are art-directed intensity/tint masks, not roughness maps. + // Use the authored/material roughness packed by the raster path, with spec + // strength only nudging the lobe sharper for bright painted highlights. float peak = SpecularPeak3(saturate(specularAlbedo)); float strength = smoothstep(0.010, 0.145, peak); - return clamp(lerp(0.70, 0.42, strength), 0.38, 0.76); + float specDrivenRoughness = lerp(0.68, 0.22, strength); + float materialRoughness = clamp(surfaceRoughness, 0.05, 1.0); + float roughness = lerp(specDrivenRoughness, min(specDrivenRoughness, materialRoughness), 0.72); + return clamp(roughness, 0.18, 0.86); } float3 LimitSpecularPeak(float3 c, float peakLimit) @@ -4142,7 +4163,8 @@ float3 ComputeSpecularShaped( float f0Blend, float fresnelToWhiteScale, float energyScale, - float peakLimit) + float peakLimit, + float surfaceRoughness) { if (gEnableSpecular == 0) return 0.0; @@ -4175,7 +4197,7 @@ float3 ComputeSpecularShaped( if (specPeak <= 0.001) return 0.0; - float roughness = clamp(EstimateSpecularRoughness(specMask) + roughnessBias, 0.38, 0.90); + float roughness = clamp(EstimateSpecularRoughness(specMask, surfaceRoughness) + roughnessBias, 0.18, 0.92); float a = roughness * roughness; float a2 = max(a * a, 1.0e-4); @@ -4197,9 +4219,9 @@ float3 ComputeSpecularShaped( float3 F = F0 + (1.0 - F0) * (pow(1.0 - VoH, 5.0) * saturate(fresnelToWhiteScale)); float specTerm = (D * G) / max(4.0 * NoL * NoV, 1.0e-4); - specTerm = min(specTerm, 3.6); + specTerm = min(specTerm, 2.85); - const float SPECULAR_ENERGY_SCALE = 2.35; + const float SPECULAR_ENERGY_SCALE = 3.15; float3 specular = lightColor * @@ -4211,6 +4233,42 @@ float3 ComputeSpecularShaped( specTerm * (SPECULAR_ENERGY_SCALE * max(energyScale, 0.0)); + float legacyLobe = Doom3SpecularLookup(NoH) * saturate(1.0 - roughness * 0.55); + specular += + lightColor * + lightIntensity * + atten * + shadow * + NoL * + specMask * + legacyLobe * + (0.72 * max(energyScale, 0.0)); + + float clearcoatMask = smoothstep(0.075, 0.22, specPeak) * saturate((0.58 - roughness) / 0.42); + if (clearcoatMask > 0.0) + { + float coatRoughness = clamp(roughness * 0.55, 0.14, 0.36); + float coatA = coatRoughness * coatRoughness; + float coatA2 = max(coatA * coatA, 1.0e-4); + float coatDenom = NoH * NoH * (coatA2 - 1.0) + 1.0; + float coatD = coatA2 / max(PI * coatDenom * coatDenom, 1.0e-4); + float coatK = ((coatRoughness + 1.0) * (coatRoughness + 1.0)) * 0.125; + float coatGv = NoV / max(NoV * (1.0 - coatK) + coatK, 1.0e-4); + float coatGl = NoL / max(NoL * (1.0 - coatK) + coatK, 1.0e-4); + float coatF = 0.04 + 0.96 * pow(1.0 - VoH, 5.0); + float coatTerm = min((coatD * coatGv * coatGl) / max(4.0 * NoL * NoV, 1.0e-4), 2.75); + + specular += + lightColor * + lightIntensity * + atten * + shadow * + NoL * + coatF * + coatTerm * + (0.30 * clearcoatMask * max(energyScale, 0.0)); + } + return LimitSpecularPeak(specular, peakLimit); } @@ -4222,7 +4280,8 @@ float3 ComputeSpecular( float lightIntensity, float atten, float shadow, - float3 specularAlbedo) + float3 specularAlbedo, + float surfaceRoughness) { return ComputeSpecularShaped( N, @@ -4237,8 +4296,9 @@ float3 ComputeSpecular( 0.12, 0.82, 1.0, - 1.42, - 4.8); + 1.70, + 6.5, + surfaceRoughness); } float EstimateSkeletalSubsurfaceMask(float3 baseAlbedo, float3 specularAlbedo) @@ -4256,7 +4316,8 @@ float EstimateSkeletalSubsurfaceMask(float3 baseAlbedo, float3 specularAlbedo) return saturate((0.08 + warm * 0.42 + redBias * 0.38) * fleshRange * lerp(0.55, 1.0, matte)); } - +)" +R"( float3 ComputeSkeletalSpecularAlbedo(float3 baseAlbedo, float3 specularAlbedo) { float subsurfaceMask = EstimateSkeletalSubsurfaceMask(baseAlbedo, specularAlbedo); @@ -4269,7 +4330,8 @@ float3 ComputeSkeletalSpecularAlbedo(float3 baseAlbedo, float3 specularAlbedo) float cap = lerp(0.16, 0.075, subsurfaceMask); return min(softSpec, float3(cap, cap, cap)); } - +)" +R"( float3 ComputeSkeletalSpecular( float3 N, float3 V, @@ -4279,7 +4341,8 @@ float3 ComputeSkeletalSpecular( float lightIntensity, float atten, float shadow, - float3 specularAlbedo) + float3 specularAlbedo, + float surfaceRoughness) { float subsurfaceMask = EstimateSkeletalSubsurfaceMask(baseAlbedo, specularAlbedo); float3 characterSpec = ComputeSkeletalSpecularAlbedo(baseAlbedo, specularAlbedo); @@ -4301,7 +4364,8 @@ float3 ComputeSkeletalSpecular( 0.58, lerp(0.32, 0.14, subsurfaceMask), lerp(0.40, 0.24, subsurfaceMask), - lerp(0.55, 0.18, subsurfaceMask)); + lerp(0.55, 0.18, subsurfaceMask), + max(surfaceRoughness, 0.48)); } float ComputeSubsurfaceShape(float3 N, float3 V, float3 L) @@ -4817,7 +4881,7 @@ float3 EstimatePathTracedSky(float3 worldPos, float3 N, inout uint rng) } )" R"( -float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, bool isSkeletal, float subsurfaceMask, Light Lgt, inout uint rng, out float3 specularOut, out float3 subsurfaceOut) +float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, float surfaceRoughness, bool isSkeletal, float subsurfaceMask, Light Lgt, inout uint rng, out float3 specularOut, out float3 subsurfaceOut) { specularOut = 0.0; subsurfaceOut = 0.0; @@ -4870,8 +4934,8 @@ float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3 shadow = TraceVisibilityBiased(worldPos, N, L, dist); specAccum += isSkeletal - ? ComputeSkeletalSpecular(N, V, L, baseAlbedo, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo) - : ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo); + ? ComputeSkeletalSpecular(N, V, L, baseAlbedo, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo, surfaceRoughness) + : ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo, surfaceRoughness); sssAccum += ComputeSkeletalSubsurfaceRadiance(N, V, L, baseAlbedo, Lgt.color, Lgt.intensity, atten, shadow, subsurfaceMask); diffuseAccum += Lgt.color * (Lgt.intensity * atten * diffuseNoL * shadow); @@ -4884,7 +4948,7 @@ float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3 } )" R"( -float3 PathTraceDirectSpotLight(float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, bool isSkeletal, float subsurfaceMask, Light Lgt, inout uint rng, out float3 specularOut, out float3 subsurfaceOut) +float3 PathTraceDirectSpotLight(float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, float surfaceRoughness, bool isSkeletal, float subsurfaceMask, Light Lgt, inout uint rng, out float3 specularOut, out float3 subsurfaceOut) { specularOut = 0.0; subsurfaceOut = 0.0; @@ -4904,15 +4968,15 @@ float3 PathTraceDirectSpotLight(float3 worldPos, float3 N, float3 V, float3 base shadow = TraceVisibilityBiased(worldPos, N, L, dist); specularOut = isSkeletal - ? ComputeSkeletalSpecular(N, V, L, baseAlbedo, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo) - : ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo); + ? ComputeSkeletalSpecular(N, V, L, baseAlbedo, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo, surfaceRoughness) + : ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo, surfaceRoughness); subsurfaceOut = ComputeSkeletalSubsurfaceRadiance(N, V, L, baseAlbedo, Lgt.color, Lgt.intensity, atten, shadow, subsurfaceMask); return Lgt.color * (Lgt.intensity * atten * diffuseNoL * shadow); } )" R"( -float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, bool isSkeletal, float subsurfaceMask, Light Lgt, inout uint rng, out float3 specularOut, out float3 subsurfaceOut) +float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, float surfaceRoughness, bool isSkeletal, float subsurfaceMask, Light Lgt, inout uint rng, out float3 specularOut, out float3 subsurfaceOut) { specularOut = 0.0; subsurfaceOut = 0.0; @@ -4981,7 +5045,8 @@ float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V Lgt.intensity * faceTerm, 1.0, shadow, - specularAlbedo) + specularAlbedo, + surfaceRoughness) : ComputeSpecular( N, V, @@ -4990,7 +5055,8 @@ float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V Lgt.intensity * faceTerm, 1.0, shadow, - specularAlbedo)) * atten; + specularAlbedo, + surfaceRoughness)) * atten; sssAccum += ComputeSkeletalSubsurfaceRadiance( N, @@ -5294,6 +5360,7 @@ float3 EstimateShadowedBounceLight(uint2 hitPixel, float3 hitPos, float3 hitN, f float3 sss = 0.0; float3 diffuse = 0.0; float3 hitSpecularAlbedo = LoadSceneSpecularAlbedo(hitPixel, hitAlbedo); + float hitSurfaceRoughness = StabilizeSpecularSurfaceRoughness(hitPixel, hitN, LoadSceneSurfaceRoughness(LoadSceneNormal(hitPixel))); float subsurfaceMask = hitIsSkeletal ? EstimateSkeletalSubsurfaceMask(hitAlbedo, hitSpecularAlbedo) * 0.45 : 0.0; // Use the same visibility-capable direct-light samplers as the primary hit. @@ -5301,15 +5368,15 @@ float3 EstimateShadowedBounceLight(uint2 hitPixel, float3 hitPos, float3 hitN, f // old unoccluded light-list approximation. if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT) { - diffuse = PathTraceDirectPointLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss); + diffuse = PathTraceDirectPointLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitSurfaceRoughness, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss); } else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT) { - diffuse = PathTraceDirectSpotLight(hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss); + diffuse = PathTraceDirectSpotLight(hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitSurfaceRoughness, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss); } else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT) { - diffuse = PathTraceDirectRectLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss); + diffuse = PathTraceDirectRectLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitSurfaceRoughness, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss); } return clamp(diffuse + sss * 0.35, 0.0, 12.0); @@ -5848,7 +5915,7 @@ float3 ApplyPrimarySpecularPost(float3 specularAccum, float ao, bool isSkeletal) // 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)); + float specAo = lerp(0.76, 1.0, saturate(ao)); if (isSkeletal) { specAo = lerp(specAo, 1.0, 0.50); @@ -5869,6 +5936,7 @@ float3 PathTraceDeterministicLighting( float3 V, float3 baseAlbedo, float3 specularAlbedo, + float surfaceRoughness, bool isSkeletal, float cavity, float ao, @@ -5915,15 +5983,15 @@ float3 PathTraceDeterministicLighting( if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT) { - diffuse = PathTraceDirectPointLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss); + diffuse = PathTraceDirectPointLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, surfaceRoughness, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss); } else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT) { - diffuse = PathTraceDirectSpotLight(worldPos, N, V, baseAlbedo, specularAlbedo, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss); + diffuse = PathTraceDirectSpotLight(worldPos, N, V, baseAlbedo, specularAlbedo, surfaceRoughness, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss); } else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT) { - diffuse = PathTraceDirectRectLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss); + diffuse = PathTraceDirectRectLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, surfaceRoughness, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss); } lightingAccum += diffuse; @@ -6057,6 +6125,7 @@ float3 EstimateRayTracedSpecularReflection( float3 V, float3 baseAlbedo, float3 specularAlbedo, + float surfaceRoughness, float cavity, bool isSkeletal, inout uint rng) @@ -6097,8 +6166,8 @@ float3 EstimateRayTracedSpecularReflection( ? 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 roughness = EstimateSpecularRoughness(specularAlbedo); - roughness = clamp(lerp(roughness, 0.78, smoothstep(0.12, 0.46, normalVariance)), 0.38, 0.82); + float roughness = EstimateSpecularRoughness(specularAlbedo, surfaceRoughness); + roughness = clamp(lerp(roughness, 0.78, smoothstep(0.12, 0.46, normalVariance)), 0.16, 0.84); // Do not multiply by specPeak again here. specularAlbedo is already F0, and // double-penalizing it made normal dielectric floors fall below the ray gate. @@ -6273,9 +6342,12 @@ void RayGen() uint geoFlag = DecodeGeometryFlag(positionSample.w); bool isSkeletal = (geoFlag & GEOMETRY_FLAG_SKELETAL) != 0u; bool isUnlit = (geoFlag & GEOMETRY_FLAG_UNLIT) != 0u; + float4 normalSample = LoadSceneNormal(pixel); + float surfaceRoughness = LoadSceneSurfaceRoughness(normalSample); float3 N = EnhanceBumpNormal(pixel, worldPos, baseAlbedo, isSkeletal); if (isSkeletal) N = StabilizeSkeletalNormal(pixel, worldPos, N); + surfaceRoughness = StabilizeSpecularSurfaceRoughness(pixel, N, surfaceRoughness); float3 V = normalize(gCameraPos.xyz - worldPos); if (isUnlit) @@ -6325,6 +6397,7 @@ void RayGen() V, baseAlbedo, specularAlbedo, + surfaceRoughness, isSkeletal, cavity, ao, @@ -6370,6 +6443,7 @@ void RayGen() V, baseAlbedo, specularAlbedo, + surfaceRoughness, cavity, isSkeletal, reflectionRng); @@ -6384,7 +6458,6 @@ void RayGen() finalColor += baseAlbedo * reactiveFinalGather * gGiAmount; } - float outputAo = isSkeletal ? lerp(ao, 1.0, 0.55) : ao; finalColor += subsurfaceAccum; // Volumetric light scattering is radiance in the camera ray, not surface @@ -6400,7 +6473,7 @@ void RayGen() // the eventual swap-chain/backbuffer is LDR. finalColor += emissiveSurface + emissiveBloom; - gOutputTex[pixel] = float4(PreparePathTraceOutputColor(finalColor) * outputAo, primaryHitDistance); + gOutputTex[pixel] = float4(PreparePathTraceOutputColor(finalColor), primaryHitDistance); } )"; diff --git a/neo/engine/opengl/gl_d3d12shim.cpp b/neo/engine/opengl/gl_d3d12shim.cpp index d51410b7..7b01bb11 100644 --- a/neo/engine/opengl/gl_d3d12shim.cpp +++ b/neo/engine/opengl/gl_d3d12shim.cpp @@ -292,6 +292,8 @@ void APIENTRY glTextureSpecularMap(GLuint texture, GLboolean isSpecularMap); void APIENTRY glBindSpecularMapTexture(GLuint texture); void APIENTRY glSpecularMapTexture(GLuint texture); void APIENTRY glSpecularMapStrengthf(GLfloat strength); +void APIENTRY glSurfaceRoughnessf(GLfloat roughness); +void APIENTRY glMaterialTypef(GLfloat materialType); void APIENTRY glTextureAverageColorQD3D12(GLuint texture, GLfloat r, GLfloat g, GLfloat b, GLfloat a); void APIENTRY glTerrainBlendEnableQD3D12(GLboolean enable); void APIENTRY glEnableTerrainBlendQD3D12(GLboolean enable); @@ -18725,6 +18727,8 @@ PROC WINAPI qd3d12_wglGetProcAddress(LPCSTR name) { { "glBindSpecularMapTexture", (PROC)glBindSpecularMapTexture }, { "glSpecularMapTexture", (PROC)glSpecularMapTexture }, { "glSpecularMapStrengthf", (PROC)glSpecularMapStrengthf }, + { "glSurfaceRoughnessf", (PROC)glSurfaceRoughnessf }, + { "glMaterialTypef", (PROC)glMaterialTypef }, { "glTerrainBlendEnableQD3D12", (PROC)glTerrainBlendEnableQD3D12 }, { "glEnableTerrainBlendQD3D12", (PROC)glEnableTerrainBlendQD3D12 }, { "glTerrainLayerTextureQD3D12", (PROC)glTerrainLayerTextureQD3D12 }, diff --git a/neo/engine/opengl/opengl.h b/neo/engine/opengl/opengl.h index 75d2d09e..308d824e 100644 --- a/neo/engine/opengl/opengl.h +++ b/neo/engine/opengl/opengl.h @@ -2329,6 +2329,8 @@ void APIENTRY glTextureSpecularMap(GLuint texture, GLboolean isSpecularMap); void APIENTRY glBindSpecularMapTexture(GLuint texture); void APIENTRY glSpecularMapTexture(GLuint texture); void APIENTRY glSpecularMapStrengthf(GLfloat strength); +void APIENTRY glSurfaceRoughnessf(GLfloat roughness); +void APIENTRY glMaterialTypef(GLfloat materialType); void APIENTRY glTextureAverageColorQD3D12(GLuint texture, GLfloat r, GLfloat g, GLfloat b, GLfloat a); void glUpdateTopLevelAceelStructure( diff --git a/neo/engine/renderer/draw_common.cpp b/neo/engine/renderer/draw_common.cpp index 6b60c440..260dc722 100644 --- a/neo/engine/renderer/draw_common.cpp +++ b/neo/engine/renderer/draw_common.cpp @@ -32,6 +32,92 @@ If you have questions concerning this license or the applicable additional terms idCVar r_renderNeuralMaterial("r_renderNeuralMaterial", "0", CVAR_BOOL, ""); +static bool RB_MaterialNameHasToken( const idMaterial *shader, const char *token ) { + if ( !shader || !token || !token[0] ) { + return false; + } + + idStr name = shader->GetName(); + name.ToLower(); + return name.Find( token ) >= 0; +} + +static float RB_EstimatePathTraceRoughness( const idMaterial *shader, const idImage *specImage, const idImage *glowMapImage ) { + const bool hasSpec = ( specImage != NULL ); + const bool hasGlow = ( glowMapImage != NULL ); + float roughness = hasSpec ? 0.44f : 0.72f; + + if ( RB_MaterialNameHasToken( shader, "glass" ) || + RB_MaterialNameHasToken( shader, "window" ) || + RB_MaterialNameHasToken( shader, "screen" ) || + RB_MaterialNameHasToken( shader, "monitor" ) || + RB_MaterialNameHasToken( shader, "computer" ) ) { + roughness = hasGlow ? 0.22f : 0.28f; + } + else if ( RB_MaterialNameHasToken( shader, "blood" ) || + RB_MaterialNameHasToken( shader, "slime" ) || + RB_MaterialNameHasToken( shader, "water" ) || + RB_MaterialNameHasToken( shader, "wet" ) ) { + roughness = 0.24f; + } + else if ( RB_MaterialNameHasToken( shader, "floor" ) || + RB_MaterialNameHasToken( shader, "tile" ) || + RB_MaterialNameHasToken( shader, "plate" ) ) { + roughness = hasSpec ? 0.34f : 0.52f; + } + else if ( RB_MaterialNameHasToken( shader, "metal" ) || + RB_MaterialNameHasToken( shader, "panel" ) || + RB_MaterialNameHasToken( shader, "pipe" ) || + RB_MaterialNameHasToken( shader, "door" ) || + RB_MaterialNameHasToken( shader, "trim" ) || + RB_MaterialNameHasToken( shader, "machine" ) ) { + roughness = hasSpec ? 0.36f : 0.58f; + } + else if ( RB_MaterialNameHasToken( shader, "skin" ) || + RB_MaterialNameHasToken( shader, "flesh" ) || + RB_MaterialNameHasToken( shader, "monster" ) || + RB_MaterialNameHasToken( shader, "char" ) ) { + roughness = 0.62f; + } + else if ( RB_MaterialNameHasToken( shader, "wall" ) || + RB_MaterialNameHasToken( shader, "ceil" ) || + RB_MaterialNameHasToken( shader, "concrete" ) || + RB_MaterialNameHasToken( shader, "rock" ) || + RB_MaterialNameHasToken( shader, "stone" ) ) { + roughness = hasSpec ? 0.56f : 0.78f; + } + + return idMath::ClampFloat( 0.08f, 0.92f, roughness ); +} + +static float RB_EstimatePathTraceSpecularStrength( const idMaterial *shader, const idImage *specImage, const idImage *glowMapImage ) { + if ( !specImage ) { + return 1.0f; + } + + float strength = 1.35f; + if ( RB_MaterialNameHasToken( shader, "floor" ) || + RB_MaterialNameHasToken( shader, "tile" ) || + RB_MaterialNameHasToken( shader, "metal" ) || + RB_MaterialNameHasToken( shader, "panel" ) || + RB_MaterialNameHasToken( shader, "door" ) ) { + strength = 1.85f; + } + if ( glowMapImage || + RB_MaterialNameHasToken( shader, "glass" ) || + RB_MaterialNameHasToken( shader, "screen" ) || + RB_MaterialNameHasToken( shader, "monitor" ) ) { + strength = 2.10f; + } + if ( RB_MaterialNameHasToken( shader, "skin" ) || + RB_MaterialNameHasToken( shader, "flesh" ) || + RB_MaterialNameHasToken( shader, "monster" ) ) { + strength = 0.75f; + } + + return idMath::ClampFloat( 0.5f, 2.4f, strength ); +} + static idDrawVert *RB_BindAmbientBuffer( const srfTriangles_t *tri ) { if ( tri->ambientVbo ) { glBindBufferARB( GL_ARRAY_BUFFER_ARB, tri->ambientVbo ); @@ -498,6 +584,8 @@ void RB_T_FillDepthBuffer(const drawSurf_t* surf) { idImage* bumpImage = shader->GetBumpImage(); idImage* specImage = shader->GetSpecImage(); idImage* glowMapImage = shader->GetGlowImage(regs); + const float pathTraceRoughness = RB_EstimatePathTraceRoughness( shader, specImage, glowMapImage ); + const float pathTraceSpecularStrength = RB_EstimatePathTraceSpecularStrength( shader, specImage, glowMapImage ); // Make sure the material has tried to load/upload its NeuralPOM payload. // If no neural files exist, this should leave the handle as 0. @@ -542,11 +630,14 @@ void RB_T_FillDepthBuffer(const drawSurf_t* surf) { if (specImage) { specImage->Bind(); glBindSpecularMapTexture(specImage->texnum); + glSpecularMapStrengthf(pathTraceSpecularStrength); } else { globalImages->BindNull(); glBindSpecularMapTexture(0); + glSpecularMapStrengthf(1.0f); } + glSurfaceRoughnessf(pathTraceRoughness); // glow fallback if (glowMapImage) { @@ -580,6 +671,8 @@ void RB_T_FillDepthBuffer(const drawSurf_t* surf) { GL_SelectTexture(2); glBindSpecularMapTexture(0); + glSpecularMapStrengthf(1.0f); + glSurfaceRoughnessf(0.5f); globalImages->BindNull(); GL_SelectTexture(1);