mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
Switched tp PBR
This commit is contained in:
@@ -2930,8 +2930,7 @@ 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
|
||||
@@ -2940,7 +2939,8 @@ float Doom3ProjectedDepthFalloff(float depth, float nearClip, float farClip)
|
||||
float depth01 = saturate((depth - nearClip) / max(farClip - nearClip, 1e-4));
|
||||
return Doom3QuadraticFalloffImage(0.5 + depth01 * 0.5);
|
||||
}
|
||||
|
||||
)"
|
||||
R"(
|
||||
float ComputePointLightAttenuation(float3 worldPos, Light Lgt)
|
||||
{
|
||||
float3 radii = GetPointLightRadius(Lgt);
|
||||
@@ -3305,29 +3305,25 @@ float ComputeCavity(uint2 pixel, float3 worldPos, float3 N)
|
||||
return 1.0 - cavity * 0.18;
|
||||
}
|
||||
|
||||
float Doom3SpecularLookup(float x)
|
||||
{
|
||||
// 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);
|
||||
|
||||
float broad = pow(x, 12.0);
|
||||
float tight = pow(x, 48.0);
|
||||
|
||||
return saturate(broad * 0.55 + tight * 0.85);
|
||||
}
|
||||
static const float PBR_PI = 3.14159265;
|
||||
|
||||
float SpecularPeak3(float3 c)
|
||||
{
|
||||
return max(max(c.r, c.g), c.b);
|
||||
}
|
||||
|
||||
float3 PbrDefaultSpecularF0()
|
||||
{
|
||||
// Missing legacy spec maps become a standard dielectric F0 instead of the
|
||||
// previous Doom/Phong pseudo-spec mask. Authored black specular maps still
|
||||
// resolve to black because the alpha-valid bit is handled below.
|
||||
return float3(0.04, 0.04, 0.04);
|
||||
}
|
||||
|
||||
bool LooksLikeAuthoredSpecularSample(float4 specSample, float3 baseAlbedo)
|
||||
{
|
||||
// Normal path: alpha is the raster G-buffer validity bit.
|
||||
// Normal path: alpha is the raster G-buffer validity bit. This preserves
|
||||
// authored black Phong spec maps: black+alpha means intentionally no specular.
|
||||
if (specSample.a > 0.5)
|
||||
return true;
|
||||
|
||||
@@ -3342,36 +3338,75 @@ bool LooksLikeAuthoredSpecularSample(float4 specSample, float3 baseAlbedo)
|
||||
return rgbPeak > 0.025 && rgbDiff > 0.035;
|
||||
}
|
||||
|
||||
float3 Doom3PseudoSpecularMask(float3 baseAlbedo)
|
||||
{
|
||||
// Doom 3 normally uses a dedicated specular map.
|
||||
// This fallback is only used for pixels whose specular G-buffer says no
|
||||
// specular map was written by the raster pass.
|
||||
float lum = dot(saturate(baseAlbedo), float3(0.299, 0.587, 0.114));
|
||||
|
||||
float specStrength = lerp(0.22, 0.72, saturate(lum * 1.35));
|
||||
|
||||
// Slight warm/colored contribution from the diffuse texture, but mostly neutral
|
||||
// like a missing/default specular map.
|
||||
float3 neutralSpec = float3(specStrength, specStrength, specStrength);
|
||||
float3 tintedSpec = saturate(baseAlbedo) * 0.35 + neutralSpec * 0.65;
|
||||
|
||||
return max(tintedSpec, float3(0.18, 0.18, 0.18));
|
||||
}
|
||||
|
||||
float3 LoadSceneSpecularAlbedo(uint2 pixel, float3 baseAlbedo)
|
||||
{
|
||||
// Name kept for compatibility with the rest of this shader. The value is
|
||||
// now interpreted as legacy Phong specular input that is remapped to PBR F0
|
||||
// and roughness by the helpers below.
|
||||
float4 specSample = gSpecularTex.Load(int3(pixel, 0));
|
||||
|
||||
// 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".
|
||||
// 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);
|
||||
return PbrDefaultSpecularF0();
|
||||
}
|
||||
|
||||
float3 PbrF0FromPhongSpecularInput(float3 phongSpecular)
|
||||
{
|
||||
// Legacy Phong/specular-workflow maps are already artist-authored F0-like
|
||||
// colors. Keep the color rather than converting through metalness, because
|
||||
// there is no metallic/roughness texture bound in this pass yet.
|
||||
return saturate(phongSpecular);
|
||||
}
|
||||
|
||||
float PbrRoughnessFromPhongSpecularInput(float3 phongSpecular)
|
||||
{
|
||||
// There is no roughness channel in the current input. Use spec intensity as
|
||||
// a compatibility heuristic: bright Phong spec usually meant a tighter,
|
||||
// smoother highlight; dark maps become rougher. This gives old content a
|
||||
// stable GGX lobe without requiring new material assets immediately.
|
||||
float specPeak = SpecularPeak3(saturate(phongSpecular));
|
||||
float glossHint = saturate((specPeak - 0.02) / 0.98);
|
||||
glossHint = pow(glossHint, 0.55);
|
||||
|
||||
float perceptualRoughness = lerp(0.86, 0.18, glossHint);
|
||||
return clamp(perceptualRoughness, 0.3, 0.92);
|
||||
}
|
||||
|
||||
float3 PbrDiffuseAlbedoFromPhongSpecularInput(float3 baseAlbedo, float3 phongSpecular)
|
||||
{
|
||||
// Without a metallic channel, keep diffuse mostly intact. Apply only a mild
|
||||
// energy-conservation term so very reflective legacy materials do not also
|
||||
// receive the full diffuse response.
|
||||
float f0Peak = SpecularPeak3(PbrF0FromPhongSpecularInput(phongSpecular));
|
||||
float diffuseEnergy = 1.0 - saturate(f0Peak);
|
||||
return saturate(baseAlbedo) * diffuseEnergy;
|
||||
}
|
||||
|
||||
float DistributionGGX(float NdotH, float roughness)
|
||||
{
|
||||
float a = max(roughness * roughness, 0.001);
|
||||
float a2 = a * a;
|
||||
float denom = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
||||
return a2 / max(PBR_PI * denom * denom, 1.0e-6);
|
||||
}
|
||||
|
||||
float GeometrySchlickGGX(float NdotX, float roughness)
|
||||
{
|
||||
float r = roughness + 1.0;
|
||||
float k = (r * r) * 0.125;
|
||||
return NdotX / max(NdotX * (1.0 - k) + k, 1.0e-6);
|
||||
}
|
||||
|
||||
float GeometrySmithGGX(float NdotV, float NdotL, float roughness)
|
||||
{
|
||||
return GeometrySchlickGGX(NdotV, roughness) * GeometrySchlickGGX(NdotL, roughness);
|
||||
}
|
||||
|
||||
float3 FresnelSchlick(float cosTheta, float3 F0)
|
||||
{
|
||||
float f = pow(saturate(1.0 - cosTheta), 5.0);
|
||||
return F0 + (1.0 - F0) * f;
|
||||
}
|
||||
|
||||
float3 ComputeSpecular(
|
||||
@@ -3387,47 +3422,40 @@ float3 ComputeSpecular(
|
||||
if (gEnableSpecular == 0)
|
||||
return 0.0;
|
||||
|
||||
if (atten <= 0.0 || shadow <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
N = normalize(N);
|
||||
V = normalize(V);
|
||||
L = normalize(L);
|
||||
|
||||
float NdotL = dot(N, L);
|
||||
float NdotV = dot(N, V);
|
||||
|
||||
if (atten <= 0.0 || shadow <= 0.0)
|
||||
float NdotL = saturate(dot(N, L));
|
||||
float NdotV = saturate(dot(N, V));
|
||||
if (NdotL <= 1.0e-4 || NdotV <= 1.0e-4)
|
||||
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 = Doom3SafeNormalizeOr(L + V, N);
|
||||
float NdotH = saturate(dot(N, H));
|
||||
float VdotH = saturate(dot(V, H));
|
||||
|
||||
float specTerm = Doom3SpecularLookup(NdotH) * lightFacing * viewFacing;
|
||||
if (specTerm <= 1.0e-5)
|
||||
float3 F0 = PbrF0FromPhongSpecularInput(specularAlbedo);
|
||||
if (SpecularPeak3(F0) <= 1.0e-5)
|
||||
return 0.0;
|
||||
|
||||
float3 specMask = saturate(specularAlbedo);
|
||||
float roughness = PbrRoughnessFromPhongSpecularInput(specularAlbedo);
|
||||
float D = DistributionGGX(NdotH, roughness);
|
||||
float G = GeometrySmithGGX(NdotV, NdotL, roughness);
|
||||
float3 F = FresnelSchlick(VdotH, F0);
|
||||
|
||||
// Doom 3's interaction pass is strongly additive. Keep it punchy,
|
||||
// but clamp enough to avoid fireflies with stochastic light sampling.
|
||||
const float DOOM3_SPECULAR_SCALE = 4.75;
|
||||
float3 specularBRDF = (D * G) * F / max(4.0 * NdotV * NdotL, 1.0e-4);
|
||||
|
||||
float3 specular =
|
||||
lightColor *
|
||||
lightIntensity *
|
||||
atten *
|
||||
shadow *
|
||||
specMask *
|
||||
specTerm *
|
||||
DOOM3_SPECULAR_SCALE;
|
||||
// The renderer's light intensities are authored for the older additive
|
||||
// Phong/Doom path, not calibrated physical lux. This small unit bridge keeps
|
||||
// PBR highlights readable without returning to a hand-shaped Phong lobe.
|
||||
const float LEGACY_LIGHT_UNIT_TO_PBR_SPECULAR = 1.35;
|
||||
|
||||
float3 incidentRadiance = lightColor * (lightIntensity * atten * shadow);
|
||||
float3 specular = incidentRadiance * specularBRDF * NdotL * LEGACY_LIGHT_UNIT_TO_PBR_SPECULAR;
|
||||
|
||||
return clamp(specular, 0.0, 8.0);
|
||||
}
|
||||
@@ -4192,7 +4220,9 @@ float3 EstimateDirectLightingForBounceHit(uint2 hitPixel, float3 hitPos, float3
|
||||
// Outgoing diffuse radiance from the bounce surface. The primary surface's
|
||||
// albedo is applied later in RayGen, so only the secondary hit albedo belongs
|
||||
// here.
|
||||
return clamp(hitAlbedo * max(lighting, 0.0), 0.0, 16.0);
|
||||
float3 hitSpecularInput = LoadSceneSpecularAlbedo(hitPixel, hitAlbedo);
|
||||
float3 hitDiffuseAlbedo = PbrDiffuseAlbedoFromPhongSpecularInput(hitAlbedo, hitSpecularInput);
|
||||
return clamp(hitDiffuseAlbedo * max(lighting, 0.0), 0.0, 16.0);
|
||||
}
|
||||
|
||||
)"
|
||||
@@ -4593,30 +4623,36 @@ float3 EstimateRayTracedSpecularReflection(
|
||||
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)
|
||||
float3 F0 = PbrF0FromPhongSpecularInput(specularAlbedo);
|
||||
float f0Peak = SpecularPeak3(F0);
|
||||
if (f0Peak <= 1.0e-5)
|
||||
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)
|
||||
float3 mirrorR = SafeNormalizeOr(reflect(-V, N), N);
|
||||
float NoMirrorR = saturate(dot(N, mirrorR));
|
||||
if (NoMirrorR <= 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 roughness = PbrRoughnessFromPhongSpecularInput(specularAlbedo);
|
||||
|
||||
// The current material path has no prefiltered environment map or roughness
|
||||
// mip chain, so trace one glossy ray. Roughness widens the ray cone and then
|
||||
// fades the result so rough materials read as broad/subtle reflections rather
|
||||
// than sharp mirrors.
|
||||
float3 R = mirrorR;
|
||||
if (roughness > 0.08)
|
||||
{
|
||||
float coneRadius = roughness * roughness * 0.42;
|
||||
float3 glossyR = SampleConeWorld(mirrorR, coneRadius, rng);
|
||||
R = SafeNormalizeOr(lerp(mirrorR, glossyR, saturate(roughness * 0.85)), mirrorR);
|
||||
|
||||
if (dot(N, R) <= 0.001)
|
||||
R = mirrorR;
|
||||
}
|
||||
|
||||
float NoR = saturate(dot(N, R));
|
||||
float3 fresnel = FresnelSchlick(NoV, F0);
|
||||
|
||||
float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NoR);
|
||||
float3 reflectionOrigin = worldPos + N * normalBias + R * (gShadowBias * 0.5);
|
||||
@@ -4678,16 +4714,13 @@ float3 EstimateRayTracedSpecularReflection(
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
float cavityFade = lerp(0.48, 1.0, saturate(cavity));
|
||||
float roughReflectionVisibility = saturate(1.0 - roughness * 0.68);
|
||||
|
||||
if (reflectionStrength <= 0.001)
|
||||
if (roughReflectionVisibility <= 0.001)
|
||||
return 0.0;
|
||||
|
||||
return clamp(reflectedRadiance * fresnel * reflectionStrength * cavityFade * reflectionScale, 0.0, 8.0);
|
||||
return clamp(reflectedRadiance * fresnel * cavityFade * roughReflectionVisibility, 0.0, 8.0);
|
||||
}
|
||||
)"
|
||||
R"(
|
||||
@@ -4810,14 +4843,15 @@ void RayGen()
|
||||
cavity,
|
||||
reflectionRng);
|
||||
|
||||
float3 albedo = baseAlbedo * cavity;
|
||||
float3 pbrDiffuseAlbedo = PbrDiffuseAlbedoFromPhongSpecularInput(baseAlbedo, specularAlbedo);
|
||||
float3 albedo = pbrDiffuseAlbedo * cavity;
|
||||
float3 finalColor = (albedo * lightingAccum) + specularAccum + reflectedSpecular;
|
||||
|
||||
if (gMaxBounces > 1u)
|
||||
{
|
||||
// reactiveFinalGather is incoming indirect radiance. Apply the primary
|
||||
// diffuse albedo here, matching the regular lighting path.
|
||||
finalColor += baseAlbedo * reactiveFinalGather;
|
||||
finalColor += pbrDiffuseAlbedo * reactiveFinalGather;
|
||||
}
|
||||
|
||||
// Volumetric light scattering is radiance in the camera ray, not surface
|
||||
|
||||
@@ -2843,7 +2843,7 @@ void idMaterial::ParseMaterial(idLexer& src) {
|
||||
}
|
||||
else if (!token.Icmp("materialType")) {
|
||||
src.ReadTokenOnLine(&token);
|
||||
materialType = static_cast<const rvDeclMatType*>(declManager->FindType(static_cast<declType_t>(6), token.c_str(), true));
|
||||
materialType = static_cast<const rvDeclMatType*>(declManager->FindType(static_cast<declType_t>(6), token.c_str(), false));
|
||||
if (materialType && materialType->IsImplicit()) {
|
||||
common->Warning("UNKNOWN: materialType '%s' in '%s'", token.c_str(), GetName());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user