mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 00:01:49 +02:00
POM Relief Mapping
This commit is contained in:
@@ -2643,7 +2643,7 @@ struct Light
|
||||
// The scalar radius above is still kept as a max/fallback range for point lights,
|
||||
// as the influence range for rect lights, and as the far clip distance for spot lights.
|
||||
float3 pointRadius;
|
||||
float pointRadiusPad; // non-zero disables specular for this light
|
||||
float pointRadiusPad;
|
||||
|
||||
uint iesTextureIndex; // 0 = none, 1..8 = gIesTextures index + 1
|
||||
float iesStrength;
|
||||
@@ -3745,7 +3745,7 @@ float EstimateSpecularRoughness(float3 specularAlbedo)
|
||||
// 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.70, 0.42, peak), 0.38, 0.74);
|
||||
return clamp(lerp(0.66, 0.40, peak), 0.36, 0.72);
|
||||
}
|
||||
|
||||
float3 LimitSpecularPeak(float3 c, float peakLimit)
|
||||
@@ -3757,7 +3757,7 @@ float3 LimitSpecularPeak(float3 c, float peakLimit)
|
||||
return c;
|
||||
}
|
||||
|
||||
float3 ComputeSpecular(
|
||||
float3 ComputeSpecularShaped(
|
||||
float3 N,
|
||||
float3 V,
|
||||
float3 L,
|
||||
@@ -3765,7 +3765,13 @@ float3 ComputeSpecular(
|
||||
float lightIntensity,
|
||||
float atten,
|
||||
float shadow,
|
||||
float3 specularAlbedo)
|
||||
float3 specularAlbedo,
|
||||
float roughnessBias,
|
||||
float specularWrap,
|
||||
float f0Blend,
|
||||
float fresnelToWhiteScale,
|
||||
float energyScale,
|
||||
float peakLimit)
|
||||
{
|
||||
if (gEnableSpecular == 0)
|
||||
return 0.0;
|
||||
@@ -3777,7 +3783,10 @@ float3 ComputeSpecular(
|
||||
V = normalize(V);
|
||||
L = normalize(L);
|
||||
|
||||
float NoL = saturate(dot(N, L));
|
||||
float rawNoL = dot(N, L);
|
||||
float NoL = (specularWrap > 0.0)
|
||||
? saturate((rawNoL + specularWrap) / (1.0 + specularWrap))
|
||||
: saturate(rawNoL);
|
||||
float NoV = saturate(dot(N, V));
|
||||
|
||||
if (NoL <= 1.0e-4 || NoV <= 1.0e-4)
|
||||
@@ -3795,7 +3804,7 @@ float3 ComputeSpecular(
|
||||
if (specPeak <= 0.001)
|
||||
return 0.0;
|
||||
|
||||
float roughness = EstimateSpecularRoughness(specMask);
|
||||
float roughness = clamp(EstimateSpecularRoughness(specMask) + roughnessBias, 0.38, 0.90);
|
||||
float a = roughness * roughness;
|
||||
float a2 = max(a * a, 1.0e-4);
|
||||
|
||||
@@ -3813,11 +3822,11 @@ float3 ComputeSpecular(
|
||||
float Gl = NoL / max(NoL * (1.0 - k) + k, 1.0e-4);
|
||||
float G = Gv * Gl;
|
||||
|
||||
float3 F0 = saturate(lerp(float3(0.025, 0.025, 0.025), specMask, 0.58));
|
||||
float3 F = F0 + (1.0 - F0) * pow(1.0 - VoH, 5.0);
|
||||
float3 F0 = saturate(lerp(float3(0.025, 0.025, 0.025), specMask, saturate(f0Blend)));
|
||||
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.0);
|
||||
specTerm = min(specTerm, 3.6);
|
||||
|
||||
const float SPECULAR_ENERGY_SCALE = 2.35;
|
||||
|
||||
@@ -3829,9 +3838,155 @@ float3 ComputeSpecular(
|
||||
NoL *
|
||||
F *
|
||||
specTerm *
|
||||
SPECULAR_ENERGY_SCALE;
|
||||
(SPECULAR_ENERGY_SCALE * max(energyScale, 0.0));
|
||||
|
||||
return LimitSpecularPeak(specular, 4.0);
|
||||
return LimitSpecularPeak(specular, peakLimit);
|
||||
}
|
||||
|
||||
float3 ComputeSpecular(
|
||||
float3 N,
|
||||
float3 V,
|
||||
float3 L,
|
||||
float3 lightColor,
|
||||
float lightIntensity,
|
||||
float atten,
|
||||
float shadow,
|
||||
float3 specularAlbedo)
|
||||
{
|
||||
return ComputeSpecularShaped(
|
||||
N,
|
||||
V,
|
||||
L,
|
||||
lightColor,
|
||||
lightIntensity,
|
||||
atten,
|
||||
shadow,
|
||||
specularAlbedo,
|
||||
-0.04,
|
||||
0.12,
|
||||
0.82,
|
||||
1.0,
|
||||
1.42,
|
||||
4.8);
|
||||
}
|
||||
|
||||
float EstimateSkeletalSubsurfaceMask(float3 baseAlbedo, float3 specularAlbedo)
|
||||
{
|
||||
// Doom 3 characters have no authored SSS/thickness maps. Use a conservative
|
||||
// skin/flesh proxy from the visible albedo and damp it on shiny/metal-like
|
||||
// pieces so armor and gear do not glow like wax.
|
||||
baseAlbedo = saturate(baseAlbedo);
|
||||
|
||||
float luma = dot(baseAlbedo, float3(0.299, 0.587, 0.114));
|
||||
float warm = saturate((baseAlbedo.r - baseAlbedo.b * 0.55) * 2.35);
|
||||
float redBias = saturate((baseAlbedo.r - max(baseAlbedo.g, baseAlbedo.b)) * 3.25 + 0.18);
|
||||
float fleshRange = smoothstep(0.035, 0.18, luma) * (1.0 - smoothstep(0.82, 1.0, luma));
|
||||
float matte = 1.0 - saturate(SpecularPeak3(specularAlbedo) * 0.85);
|
||||
|
||||
return saturate((0.08 + warm * 0.42 + redBias * 0.38) * fleshRange * lerp(0.55, 1.0, matte));
|
||||
}
|
||||
|
||||
float3 ComputeSkeletalSpecularAlbedo(float3 baseAlbedo, float3 specularAlbedo)
|
||||
{
|
||||
float subsurfaceMask = EstimateSkeletalSubsurfaceMask(baseAlbedo, specularAlbedo);
|
||||
|
||||
// Old character spec maps were authored for Doom 3's stylized interaction
|
||||
// lights. In the path tracer they behave like wet plastic unless we make the
|
||||
// BRDF much softer and cap the F0 on skin/flesh-like areas.
|
||||
float3 spec = saturate(specularAlbedo);
|
||||
float3 softSpec = spec * lerp(0.48, 0.24, subsurfaceMask);
|
||||
float cap = lerp(0.16, 0.075, subsurfaceMask);
|
||||
return min(softSpec, float3(cap, cap, cap));
|
||||
}
|
||||
|
||||
float3 ComputeSkeletalSpecular(
|
||||
float3 N,
|
||||
float3 V,
|
||||
float3 L,
|
||||
float3 baseAlbedo,
|
||||
float3 lightColor,
|
||||
float lightIntensity,
|
||||
float atten,
|
||||
float shadow,
|
||||
float3 specularAlbedo)
|
||||
{
|
||||
float subsurfaceMask = EstimateSkeletalSubsurfaceMask(baseAlbedo, specularAlbedo);
|
||||
float3 characterSpec = ComputeSkeletalSpecularAlbedo(baseAlbedo, specularAlbedo);
|
||||
|
||||
// Character MD5 specular maps often encode art-directed shine, not material
|
||||
// F0. Shape the whole BRDF, including grazing Fresnel, so faces and bodies
|
||||
// keep a soft oily skin response instead of reflecting like sealed plastic.
|
||||
return ComputeSpecularShaped(
|
||||
N,
|
||||
V,
|
||||
L,
|
||||
lightColor,
|
||||
lightIntensity,
|
||||
atten,
|
||||
shadow,
|
||||
characterSpec,
|
||||
lerp(0.10, 0.20, subsurfaceMask),
|
||||
0.0,
|
||||
0.58,
|
||||
lerp(0.32, 0.14, subsurfaceMask),
|
||||
lerp(0.40, 0.24, subsurfaceMask),
|
||||
lerp(0.55, 0.18, subsurfaceMask));
|
||||
}
|
||||
|
||||
float ComputeSubsurfaceShape(float3 N, float3 V, float3 L)
|
||||
{
|
||||
N = normalize(N);
|
||||
V = normalize(V);
|
||||
L = normalize(L);
|
||||
|
||||
float NoL = dot(N, L);
|
||||
float wrap = saturate((NoL + 0.58) / 1.58);
|
||||
wrap = wrap * wrap;
|
||||
|
||||
// Transmission becomes visible around the terminator and when the light is
|
||||
// behind the viewed surface, which is the bit missing most painfully on MD5s.
|
||||
float back = saturate((-NoL + 0.22) / 1.22);
|
||||
float forward = pow(saturate(dot(-L, V)), 2.25);
|
||||
float rim = pow(saturate(1.0 - abs(dot(N, V))), 1.60);
|
||||
|
||||
return saturate(wrap * 0.42 + back * (0.55 + forward * 0.85) + rim * wrap * 0.18);
|
||||
}
|
||||
|
||||
float3 ComputeSkeletalSubsurfaceRadiance(
|
||||
float3 N,
|
||||
float3 V,
|
||||
float3 L,
|
||||
float3 baseAlbedo,
|
||||
float3 lightColor,
|
||||
float lightIntensity,
|
||||
float atten,
|
||||
float shadow,
|
||||
float subsurfaceMask)
|
||||
{
|
||||
if (subsurfaceMask <= 0.001 || atten <= 0.0 || lightIntensity <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
float shape = ComputeSubsurfaceShape(N, V, L);
|
||||
if (shape <= 0.001)
|
||||
return 0.0;
|
||||
|
||||
float softShadow = lerp(shadow, 1.0, 0.18 * subsurfaceMask);
|
||||
float3 scatterTint = saturate(lerp(
|
||||
float3(1.0, 0.30, 0.18),
|
||||
saturate(baseAlbedo) * float3(1.22, 0.68, 0.52) + float3(0.10, 0.03, 0.02),
|
||||
0.58));
|
||||
|
||||
const float SUBSURFACE_DIRECT_SCALE = 0.34;
|
||||
return clamp(
|
||||
lightColor *
|
||||
lightIntensity *
|
||||
atten *
|
||||
softShadow *
|
||||
scatterTint *
|
||||
shape *
|
||||
(subsurfaceMask * SUBSURFACE_DIRECT_SCALE),
|
||||
0.0,
|
||||
2.25);
|
||||
}
|
||||
)"
|
||||
R"(
|
||||
@@ -4255,10 +4410,12 @@ float3 EstimatePathTracedSky(float3 worldPos, float3 N, inout uint rng)
|
||||
accum /= (float)bounceSamples;
|
||||
return accum * 0.55;
|
||||
}
|
||||
|
||||
float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, float3 specularAlbedo, Light Lgt, inout uint rng, out float3 specularOut)
|
||||
)"
|
||||
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)
|
||||
{
|
||||
specularOut = 0.0;
|
||||
subsurfaceOut = 0.0;
|
||||
|
||||
float3 toCenter = Lgt.position - worldPos;
|
||||
float centerDist = length(toCenter);
|
||||
@@ -4284,6 +4441,7 @@ float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3
|
||||
|
||||
float3 diffuseAccum = 0.0;
|
||||
float3 specAccum = 0.0;
|
||||
float3 sssAccum = 0.0;
|
||||
|
||||
[loop]
|
||||
for (uint s = 0u; s < sampleCount; ++s)
|
||||
@@ -4303,24 +4461,28 @@ float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3
|
||||
float diffuseNoL = ComputeDiffuseLightingTerm(N, L);
|
||||
|
||||
float shadow = 1.0;
|
||||
if (Lgt.samples != 0u && diffuseNoL > 0.0001)
|
||||
if (Lgt.samples != 0u && (diffuseNoL > 0.0001 || subsurfaceMask > 0.001))
|
||||
shadow = TraceVisibilityBiased(worldPos, N, L, dist);
|
||||
|
||||
if (Lgt.pointRadiusPad <= 0.5)
|
||||
specAccum += ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo);
|
||||
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);
|
||||
|
||||
sssAccum += ComputeSkeletalSubsurfaceRadiance(N, V, L, baseAlbedo, Lgt.color, Lgt.intensity, atten, shadow, subsurfaceMask);
|
||||
diffuseAccum += Lgt.color * (Lgt.intensity * atten * diffuseNoL * shadow);
|
||||
}
|
||||
|
||||
float invSamples = 1.0 / (float)sampleCount;
|
||||
specularOut = specAccum * invSamples;
|
||||
subsurfaceOut = sssAccum * 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)
|
||||
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)
|
||||
{
|
||||
specularOut = 0.0;
|
||||
subsurfaceOut = 0.0;
|
||||
|
||||
float3 toLight = Lgt.position - worldPos;
|
||||
float dist = length(toLight);
|
||||
@@ -4333,19 +4495,22 @@ float3 PathTraceDirectSpotLight(float3 worldPos, float3 N, float3 V, float3 base
|
||||
float diffuseNoL = ComputeDiffuseLightingTerm(N, L);
|
||||
|
||||
float shadow = 1.0;
|
||||
if (Lgt.samples != 0u && diffuseNoL > 0.0001 && atten > 0.0)
|
||||
if (Lgt.samples != 0u && (diffuseNoL > 0.0001 || subsurfaceMask > 0.001) && atten > 0.0)
|
||||
shadow = TraceVisibilityBiased(worldPos, N, L, dist);
|
||||
|
||||
if (Lgt.pointRadiusPad <= 0.5)
|
||||
specularOut = ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, specularAlbedo);
|
||||
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);
|
||||
|
||||
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, Light Lgt, inout uint rng, out float3 specularOut)
|
||||
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)
|
||||
{
|
||||
specularOut = 0.0;
|
||||
subsurfaceOut = 0.0;
|
||||
|
||||
float3 toCenter = Lgt.position - worldPos;
|
||||
float centerDist = length(toCenter);
|
||||
@@ -4365,6 +4530,7 @@ float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V
|
||||
|
||||
float3 diffuseAccum = 0.0;
|
||||
float3 specAccum = 0.0;
|
||||
float3 sssAccum = 0.0;
|
||||
|
||||
[loop]
|
||||
for (uint s = 0u; s < sampleCount; ++s)
|
||||
@@ -4385,7 +4551,8 @@ float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V
|
||||
|
||||
float3 L = sampleVec / sampleDist;
|
||||
float NdotL = ComputeDiffuseLightingTerm(N, L);
|
||||
if (NdotL <= 0.0)
|
||||
float sssShape = (subsurfaceMask > 0.001) ? ComputeSubsurfaceShape(N, V, L) : 0.0;
|
||||
if (NdotL <= 0.0 && sssShape <= 0.001)
|
||||
continue;
|
||||
|
||||
float faceTerm = (Lgt.twoSided != 0)
|
||||
@@ -4399,9 +4566,18 @@ float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V
|
||||
if (Lgt.samples != 0u)
|
||||
shadow = TraceVisibilityBiased(worldPos, N, L, sampleDist);
|
||||
|
||||
if (Lgt.pointRadiusPad <= 0.5)
|
||||
{
|
||||
specAccum += ComputeSpecular(
|
||||
specAccum += (isSkeletal
|
||||
? ComputeSkeletalSpecular(
|
||||
N,
|
||||
V,
|
||||
L,
|
||||
baseAlbedo,
|
||||
Lgt.color,
|
||||
Lgt.intensity * faceTerm,
|
||||
1.0,
|
||||
shadow,
|
||||
specularAlbedo)
|
||||
: ComputeSpecular(
|
||||
N,
|
||||
V,
|
||||
L,
|
||||
@@ -4409,14 +4585,25 @@ float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V
|
||||
Lgt.intensity * faceTerm,
|
||||
1.0,
|
||||
shadow,
|
||||
specularAlbedo) * atten;
|
||||
}
|
||||
specularAlbedo)) * atten;
|
||||
|
||||
sssAccum += ComputeSkeletalSubsurfaceRadiance(
|
||||
N,
|
||||
V,
|
||||
L,
|
||||
baseAlbedo,
|
||||
Lgt.color,
|
||||
Lgt.intensity * faceTerm,
|
||||
atten,
|
||||
shadow,
|
||||
subsurfaceMask);
|
||||
|
||||
diffuseAccum += clamp(Lgt.color * (Lgt.intensity * NdotL * faceTerm * atten * shadow), 0.0, 4.0);
|
||||
}
|
||||
|
||||
float invSamples = 1.0 / (float)sampleCount;
|
||||
specularOut = specAccum * invSamples;
|
||||
subsurfaceOut = sssAccum * invSamples;
|
||||
return diffuseAccum * invSamples;
|
||||
}
|
||||
|
||||
@@ -4618,29 +4805,31 @@ float3 EstimateUnresolvedBounceRadiance(float3 hitPos, float3 hitN, float3 albed
|
||||
return clamp(albedo * max(lighting, 0.0), 0.0, 6.0);
|
||||
}
|
||||
|
||||
float3 EstimateShadowedBounceLight(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, Light Lgt, inout uint rng)
|
||||
float3 EstimateShadowedBounceLight(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, bool hitIsSkeletal, Light Lgt, inout uint rng)
|
||||
{
|
||||
float3 spec = 0.0;
|
||||
float3 sss = 0.0;
|
||||
float3 diffuse = 0.0;
|
||||
float3 hitSpecularAlbedo = LoadSceneSpecularAlbedo(hitPixel, hitAlbedo);
|
||||
float subsurfaceMask = hitIsSkeletal ? EstimateSkeletalSubsurfaceMask(hitAlbedo, hitSpecularAlbedo) * 0.45 : 0.0;
|
||||
|
||||
// Use the same visibility-capable direct-light samplers as the primary hit.
|
||||
// This supplies proper next-event estimation at secondary hits instead of the
|
||||
// old unoccluded light-list approximation.
|
||||
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
||||
{
|
||||
diffuse = PathTraceDirectPointLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, Lgt, rng, spec);
|
||||
diffuse = PathTraceDirectPointLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss);
|
||||
}
|
||||
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
||||
{
|
||||
diffuse = PathTraceDirectSpotLight(hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, Lgt, rng, spec);
|
||||
diffuse = PathTraceDirectSpotLight(hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss);
|
||||
}
|
||||
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
||||
{
|
||||
diffuse = PathTraceDirectRectLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, Lgt, rng, spec);
|
||||
diffuse = PathTraceDirectRectLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitSpecularAlbedo, hitIsSkeletal, subsurfaceMask, Lgt, rng, spec, sss);
|
||||
}
|
||||
|
||||
return clamp(diffuse, 0.0, 12.0);
|
||||
return clamp(diffuse + sss * 0.35, 0.0, 12.0);
|
||||
}
|
||||
|
||||
float3 EstimateBounceSkyLighting(float3 hitPos, float3 hitN, inout uint rng)
|
||||
@@ -4731,7 +4920,7 @@ float3 EstimateDirectLightingForBounceHit(uint2 hitPixel, float3 hitPos, float3
|
||||
uint lightIndex = (start + c * stride) % gLightCount;
|
||||
Light Lgt = gLights[lightIndex];
|
||||
float3 fast = EstimateFastBounceLight(hitPos, hitN, Lgt);
|
||||
float3 shadowed = EstimateShadowedBounceLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, Lgt, lightingRng);
|
||||
float3 shadowed = EstimateShadowedBounceLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitIsSkeletal, Lgt, lightingRng);
|
||||
lighting += shadowed - fast;
|
||||
}
|
||||
}
|
||||
@@ -5056,7 +5245,10 @@ float3 ApplyPrimarySpecularPost(float3 specularAccum, float ao, bool isSkeletal)
|
||||
// surfaces. Keep a mild occlusion tint, but let direct-light specular read.
|
||||
float specAo = lerp(0.58, 1.0, saturate(ao));
|
||||
if (isSkeletal)
|
||||
{
|
||||
specAo = lerp(specAo, 1.0, 0.50);
|
||||
specularAccum *= 0.42;
|
||||
}
|
||||
specularAccum *= specAo;
|
||||
|
||||
//if (isSkeletal)
|
||||
@@ -5077,9 +5269,11 @@ float3 PathTraceDeterministicLighting(
|
||||
float ao,
|
||||
float skyVis,
|
||||
float ambientSkyVis,
|
||||
out float3 specularAccum)
|
||||
out float3 specularAccum,
|
||||
out float3 subsurfaceAccum)
|
||||
{
|
||||
specularAccum = 0.0;
|
||||
subsurfaceAccum = 0.0;
|
||||
|
||||
float microShadow = lerp(0.75, 1.0, cavity);
|
||||
|
||||
@@ -5104,32 +5298,37 @@ float3 PathTraceDeterministicLighting(
|
||||
// argument only because the functions share the same signature as stochastic
|
||||
// helpers; it is not consumed by PathTraceDirect* in the current shader.
|
||||
uint directRng = InitRng(pixel, 0u, 0xD17EC7u);
|
||||
float subsurfaceMask = isSkeletal ? EstimateSkeletalSubsurfaceMask(baseAlbedo, specularAlbedo) : 0.0;
|
||||
|
||||
[loop]
|
||||
for (uint i = 0; i < gLightCount; ++i)
|
||||
{
|
||||
Light Lgt = gLights[i];
|
||||
float3 spec = 0.0;
|
||||
float3 sss = 0.0;
|
||||
float3 diffuse = 0.0;
|
||||
|
||||
if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT)
|
||||
{
|
||||
diffuse = PathTraceDirectPointLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, Lgt, directRng, spec);
|
||||
diffuse = PathTraceDirectPointLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss);
|
||||
}
|
||||
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT)
|
||||
{
|
||||
diffuse = PathTraceDirectSpotLight(worldPos, N, V, baseAlbedo, specularAlbedo, Lgt, directRng, spec);
|
||||
diffuse = PathTraceDirectSpotLight(worldPos, N, V, baseAlbedo, specularAlbedo, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss);
|
||||
}
|
||||
else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT)
|
||||
{
|
||||
diffuse = PathTraceDirectRectLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, Lgt, directRng, spec);
|
||||
diffuse = PathTraceDirectRectLight(pixel, worldPos, N, V, baseAlbedo, specularAlbedo, isSkeletal, subsurfaceMask, Lgt, directRng, spec, sss);
|
||||
}
|
||||
|
||||
lightingAccum += diffuse;
|
||||
specularAccum += spec;
|
||||
subsurfaceAccum += sss;
|
||||
}
|
||||
|
||||
specularAccum = ApplyPrimarySpecularPost(specularAccum, ao, isSkeletal);
|
||||
if (isSkeletal)
|
||||
subsurfaceAccum *= lerp(0.72, 1.0, saturate(ao));
|
||||
return ApplyPrimaryDiffusePost(lightingAccum, ao, microShadow, isSkeletal);
|
||||
}
|
||||
|
||||
@@ -5193,7 +5392,7 @@ float ComputeSpecularReflectionLocalEnergy(float3 worldPos, float3 N)
|
||||
continue;
|
||||
|
||||
float3 toLight = SafeNormalizeOr(Lgt.position - worldPos, N);
|
||||
float facing = saturate(dot(N, toLight));
|
||||
float facing = saturate((dot(N, toLight) + 0.18) / 1.18);
|
||||
float lightPeak = max(max(Lgt.color.r, Lgt.color.g), Lgt.color.b);
|
||||
|
||||
energy += atten * facing * Lgt.intensity * lightPeak;
|
||||
@@ -5201,7 +5400,7 @@ float ComputeSpecularReflectionLocalEnergy(float3 worldPos, float3 N)
|
||||
|
||||
// Keep reflections tied to nearby light contribution instead of becoming
|
||||
// a global mirror pass.
|
||||
return saturate(energy * 0.08);
|
||||
return saturate(energy * 0.12);
|
||||
}
|
||||
|
||||
float ComputeSpecularReflectionMaxDistance(float3 worldPos)
|
||||
@@ -5264,11 +5463,18 @@ float3 EstimateRayTracedSpecularReflection(
|
||||
float3 baseAlbedo,
|
||||
float3 specularAlbedo,
|
||||
float cavity,
|
||||
bool isSkeletal,
|
||||
inout uint rng)
|
||||
{
|
||||
if (gEnableSpecular == 0u || gMaxBounces <= 1u)
|
||||
return 0.0;
|
||||
|
||||
// Character materials should get soft direct specular, not mirror-like local
|
||||
// reflections. The G-buffer material model has no skeletal roughness map, so
|
||||
// reflected TLAS hits make skin/armor read much wetter than intended.
|
||||
if (isSkeletal)
|
||||
return 0.0;
|
||||
|
||||
N = SafeNormalizeOr(N, float3(0.0, 0.0, 1.0));
|
||||
V = SafeNormalizeOr(V, -N);
|
||||
|
||||
@@ -5280,7 +5486,7 @@ float3 EstimateRayTracedSpecularReflection(
|
||||
bool hasSpecularMap = LooksLikeAuthoredSpecularSample(rawSpecular, baseAlbedo);
|
||||
|
||||
float specPeak = SpecularPeak3(specularAlbedo);
|
||||
if (specPeak <= 0.015)
|
||||
if (specPeak <= 0.006)
|
||||
return 0.0;
|
||||
|
||||
float NoV = saturate(dot(N, V));
|
||||
@@ -5319,8 +5525,8 @@ float3 EstimateRayTracedSpecularReflection(
|
||||
{
|
||||
// Do not reflect sky forever. A missed finite reflection ray contributes
|
||||
// only a tiny local glossy sheen.
|
||||
reflectedRadiance = GetSkyRadiance(R) * 0.035;
|
||||
distanceFade = 0.20;
|
||||
reflectedRadiance = GetSkyRadiance(R) * 0.055;
|
||||
distanceFade = 0.28;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -5373,13 +5579,13 @@ float3 EstimateRayTracedSpecularReflection(
|
||||
}
|
||||
|
||||
reflectedRadiance *= reflectedSurfaceAverageColor;
|
||||
reflectedRadiance = CompressEmissiveRadiance(reflectedRadiance, hasSpecularMap ? 5.0 : 3.5);
|
||||
reflectedRadiance = CompressEmissiveRadiance(reflectedRadiance, hasSpecularMap ? 3.5 : 2.5);
|
||||
|
||||
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;
|
||||
? saturate(specPeak * 1.30)
|
||||
: saturate((specPeak - 0.08) * 0.70);
|
||||
float reflectionScale = hasSpecularMap ? 0.85 : 0.52;
|
||||
|
||||
if (reflectionStrength <= 0.001)
|
||||
return 0.0;
|
||||
@@ -5393,7 +5599,7 @@ float3 EstimateRayTracedSpecularReflection(
|
||||
localReflectionEnergy *
|
||||
distanceFade,
|
||||
0.0,
|
||||
3.5);
|
||||
2.6);
|
||||
}
|
||||
)"
|
||||
R"(
|
||||
@@ -5488,6 +5694,7 @@ void RayGen()
|
||||
}
|
||||
|
||||
float3 specularAccum = 0.0;
|
||||
float3 subsurfaceAccum = 0.0;
|
||||
float3 lightingAccum = PathTraceDeterministicLighting(
|
||||
pixel,
|
||||
worldPos,
|
||||
@@ -5500,7 +5707,8 @@ void RayGen()
|
||||
ao,
|
||||
skyVis,
|
||||
ambientSkyVis,
|
||||
specularAccum);
|
||||
specularAccum,
|
||||
subsurfaceAccum);
|
||||
lightingAccum += EstimateScreenSpaceEmissiveParticleLighting(pixel, worldPos, N);
|
||||
|
||||
// Only the indirect bounce path uses per-SPP randomness now. Direct lights,
|
||||
@@ -5540,6 +5748,7 @@ void RayGen()
|
||||
baseAlbedo,
|
||||
specularAlbedo,
|
||||
cavity,
|
||||
isSkeletal,
|
||||
reflectionRng);
|
||||
|
||||
float3 albedo = baseAlbedo * cavity;
|
||||
@@ -5554,6 +5763,7 @@ void RayGen()
|
||||
|
||||
float outputAo = isSkeletal ? lerp(ao, 1.0, 0.55) : ao;
|
||||
finalColor *= clamp(outputAo + 0.3, 0.0, 1.0);
|
||||
finalColor += subsurfaceAccum;
|
||||
|
||||
// Volumetric light scattering is radiance in the camera ray, not surface
|
||||
// reflectance, so add it after surface albedo/specular composition. Because
|
||||
|
||||
+250
-104
@@ -2971,7 +2971,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.020)
|
||||
#define gParallaxScale (clamp(gNormalMapStrength, 0.0, 4.0) * 0.028)
|
||||
#define gCameraWorldPos gCameraPomPad.xyz
|
||||
#define gCameraPomValid gCameraPomPad.w
|
||||
#define gUseNeuralPOM gNeuralPomPad.x
|
||||
@@ -3002,6 +3002,7 @@ cbuffer DrawCB : register(b0)
|
||||
#define QD3D12_TESS_TARGET_EDGE_PIXELS 28.0
|
||||
#define QD3D12_TESS_DISTANCE_NEAR 512.0
|
||||
#define QD3D12_TESS_DISTANCE_FAR 2200.0
|
||||
#define QD3D12_GEOMETRY_FLAG_SKELETAL 1u
|
||||
#define QD3D12_POM_DISTANCE_NEAR 384.0
|
||||
#define QD3D12_POM_DISTANCE_FAR 1800.0
|
||||
|
||||
@@ -3443,6 +3444,43 @@ float QD3D12_GetPomDepth(float2 uv)
|
||||
return saturate(1.0 - QD3D12_GetPomHeightFromSamples(uv));
|
||||
}
|
||||
|
||||
float QD3D12_GetPomDepthLOD(float2 uv, float lod)
|
||||
{
|
||||
float4 nm = gNormalMap.SampleLevel(gSamp2, uv, lod);
|
||||
float authored = QD3D12_AuthoredPomAlphaWeight(nm.a);
|
||||
if (authored > 0.5)
|
||||
return saturate(1.0 - nm.a);
|
||||
|
||||
float3 decoded = nm.xyz * 2.0 - 1.0;
|
||||
decoded.y *= gNormalMapYSign;
|
||||
|
||||
float slope = saturate(length(decoded.xy));
|
||||
float normalHeight = saturate(0.5 + (pow(slope, 0.80) - 0.35) * 0.42);
|
||||
|
||||
if (gUseTex0 > 0.5)
|
||||
{
|
||||
float normalDetailGate = saturate((slope - 0.040) * 8.0);
|
||||
if (normalDetailGate > 0.001)
|
||||
{
|
||||
float mipScale = exp2(lod);
|
||||
float2 texel = QD3D12_NormalMapTexelSize() * mipScale;
|
||||
float lumC = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv, lod).rgb);
|
||||
float lumL = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv - float2(texel.x, 0.0), lod).rgb);
|
||||
float lumR = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv + float2(texel.x, 0.0), lod).rgb);
|
||||
float lumU = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv - float2(0.0, texel.y), lod).rgb);
|
||||
float lumD = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv + float2(0.0, texel.y), lod).rgb);
|
||||
|
||||
float lumAvg = (lumL + lumR + lumU + lumD) * 0.25;
|
||||
float localContrast = abs(lumC - lumAvg);
|
||||
float diffuseWeight = saturate((localContrast - 0.018) * 16.0) * normalDetailGate;
|
||||
float diffuseHeight = saturate(0.5 + (lumC - lumAvg) * 1.85);
|
||||
normalHeight = lerp(normalHeight, diffuseHeight, diffuseWeight * 0.28);
|
||||
}
|
||||
}
|
||||
|
||||
return saturate(1.0 - normalHeight);
|
||||
}
|
||||
|
||||
float QD3D12_GetPomConfidence(float2 uv)
|
||||
{
|
||||
float minHeight = 0.5;
|
||||
@@ -3484,6 +3522,13 @@ float QD3D12_GetRegularPomFade(VSOut i, float2 baseUv)
|
||||
return saturate(confidence * distanceFade * grazingFade);
|
||||
}
|
||||
|
||||
struct QD3D12PomTraceResult
|
||||
{
|
||||
float2 uv;
|
||||
float confidence;
|
||||
float visibility;
|
||||
};
|
||||
|
||||
|
||||
// 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
|
||||
@@ -3750,106 +3795,145 @@ QD3D12NeuralPOMResult QD3D12_EvaluateNeuralPOM(VSOut i, float2 baseUv)
|
||||
r.active = 1.0;
|
||||
return r;
|
||||
}
|
||||
)HLSL"
|
||||
R"HLSL(
|
||||
QD3D12PomTraceResult QD3D12_TraceReliefPOM(VSOut i, float2 baseUv)
|
||||
{
|
||||
QD3D12PomTraceResult r;
|
||||
r.uv = baseUv;
|
||||
r.confidence = 0.0;
|
||||
r.visibility = 1.0;
|
||||
|
||||
if (gUseNormalMap < 0.5)
|
||||
return r;
|
||||
|
||||
if (gAlphaBlendPass > 0.5)
|
||||
return r;
|
||||
|
||||
if (gUseTex0 > 0.5)
|
||||
{
|
||||
float baseAlpha = gTex0.SampleLevel(gSamp0, baseUv, 0.0).a;
|
||||
if (baseAlpha < 0.985)
|
||||
return r;
|
||||
}
|
||||
|
||||
float minHeight = 0.5;
|
||||
float maxHeight = 0.5;
|
||||
float authoredWeight = 0.0;
|
||||
QD3D12_GetPomHeightStats(baseUv, minHeight, maxHeight, authoredWeight);
|
||||
|
||||
float heightRange = maxHeight - minHeight;
|
||||
float threshold = lerp(0.055, 0.014, authoredWeight);
|
||||
float confidence = saturate((heightRange - threshold) / max(0.18 - threshold, 0.001));
|
||||
confidence = confidence * confidence * (3.0 - 2.0 * confidence);
|
||||
if (confidence <= 0.035)
|
||||
return r;
|
||||
|
||||
float3 n, t, b;
|
||||
QD3D12_BuildPixelTBN(i, n, t, b);
|
||||
|
||||
float cameraConfidence = (gCameraPomValid >= 0.5) ? 1.0 : 0.35;
|
||||
float3 rawViewWS = (gCameraPomValid >= 0.5) ? (gCameraWorldPos - i.worldPos) : (-i.worldPos);
|
||||
float3 viewWS = QD3D12_SafeNormalize(rawViewWS, n);
|
||||
float NoV = dot(n, viewWS);
|
||||
if (NoV <= 0.025)
|
||||
return r;
|
||||
|
||||
float3 viewTS = QD3D12_SafeNormalize(float3(dot(viewWS, t), dot(viewWS, b), NoV), float3(0.0, 0.0, 1.0));
|
||||
float ndotv = saturate(viewTS.z);
|
||||
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.055, 0.18, ndotv);
|
||||
float fade = saturate(confidence * distanceFade * grazingFade * cameraConfidence);
|
||||
if (fade <= 0.001)
|
||||
return r;
|
||||
|
||||
float2 texel = QD3D12_NormalMapTexelSize();
|
||||
float2 uvGradX = ddx(baseUv);
|
||||
float2 uvGradY = ddy(baseUv);
|
||||
float footprint = max(length(uvGradX / max(texel, float2(1.0e-6, 1.0e-6))), length(uvGradY / max(texel, float2(1.0e-6, 1.0e-6))));
|
||||
float lod = clamp(log2(max(footprint, 1.0)), 0.0, 5.0);
|
||||
|
||||
float depthScale = gParallaxScale * lerp(0.68, 1.18, authoredWeight) * confidence * distanceFade * cameraConfidence;
|
||||
float vz = max(ndotv, lerp(0.18, 0.10, authoredWeight));
|
||||
float2 parallaxVector = (viewTS.xy / vz) * depthScale;
|
||||
|
||||
float parallaxLen = length(parallaxVector);
|
||||
float maxParallaxShift = lerp(0.014, 0.046, authoredWeight) * lerp(0.85, 1.18, confidence);
|
||||
if (parallaxLen > maxParallaxShift && parallaxLen > 1.0e-6)
|
||||
parallaxVector *= maxParallaxShift / parallaxLen;
|
||||
|
||||
float layerCountF = lerp(14.0, 46.0, saturate(1.0 - ndotv));
|
||||
layerCountF = lerp(10.0, layerCountF, distanceFade);
|
||||
layerCountF = lerp(layerCountF * 0.70, layerCountF, authoredWeight);
|
||||
uint layerCount = (uint)clamp(layerCountF + 0.5, 10.0, 48.0);
|
||||
float invLayerCount = rcp((float)layerCount);
|
||||
float2 deltaUv = parallaxVector * invLayerCount;
|
||||
|
||||
float2 prevUv = baseUv;
|
||||
float2 uv = baseUv;
|
||||
float prevRayDepth = 0.0;
|
||||
float rayDepth = 0.0;
|
||||
float prevSurfaceDepth = QD3D12_GetPomDepthLOD(baseUv, lod);
|
||||
float surfaceDepth = prevSurfaceDepth;
|
||||
|
||||
[loop]
|
||||
for (uint layer = 0u; layer < 48u; ++layer)
|
||||
{
|
||||
if (layer >= layerCount || rayDepth >= surfaceDepth)
|
||||
break;
|
||||
|
||||
prevUv = uv;
|
||||
prevRayDepth = rayDepth;
|
||||
prevSurfaceDepth = surfaceDepth;
|
||||
|
||||
uv -= deltaUv;
|
||||
rayDepth += invLayerCount;
|
||||
surfaceDepth = QD3D12_GetPomDepthLOD(uv, lod);
|
||||
}
|
||||
|
||||
float after = surfaceDepth - rayDepth;
|
||||
float before = prevSurfaceDepth - prevRayDepth;
|
||||
float denom = after - before;
|
||||
float w = (abs(denom) > 1.0e-5) ? saturate(after / denom) : 0.0;
|
||||
float2 refinedUv = lerp(uv, prevUv, w);
|
||||
|
||||
float2 loUv = uv;
|
||||
float2 hiUv = prevUv;
|
||||
[unroll]
|
||||
for (uint refine = 0u; refine < 5u; ++refine)
|
||||
{
|
||||
float2 midUv = (loUv + hiUv) * 0.5;
|
||||
float midT = dot(baseUv - midUv, parallaxVector) / max(dot(parallaxVector, parallaxVector), 1.0e-8);
|
||||
float midDepth = saturate(midT);
|
||||
float midSurface = QD3D12_GetPomDepthLOD(midUv, lod);
|
||||
if (midDepth < midSurface)
|
||||
hiUv = midUv;
|
||||
else
|
||||
loUv = midUv;
|
||||
}
|
||||
|
||||
refinedUv = lerp(refinedUv, (loUv + hiUv) * 0.5, 0.65);
|
||||
|
||||
float2 finalOffset = refinedUv - baseUv;
|
||||
float maxOffset = maxParallaxShift * 1.05;
|
||||
float finalLen = length(finalOffset);
|
||||
if (finalLen > maxOffset && finalLen > 1.0e-6)
|
||||
refinedUv = baseUv + finalOffset * (maxOffset / finalLen);
|
||||
|
||||
r.uv = lerp(baseUv, refinedUv, fade);
|
||||
r.confidence = fade;
|
||||
r.visibility = saturate(1.0 - length(r.uv - baseUv) / max(maxParallaxShift, 1.0e-5) * 0.18);
|
||||
return r;
|
||||
}
|
||||
|
||||
float2 QD3D12_ComputeParallaxUVWithNeural(VSOut i, float2 baseUv, QD3D12NeuralPOMResult nr)
|
||||
{
|
||||
if (nr.active > 0.5)
|
||||
return nr.uv;
|
||||
|
||||
if (gUseNormalMap < 0.5)
|
||||
return baseUv;
|
||||
|
||||
float confidence = QD3D12_GetPomConfidence(baseUv);
|
||||
if (confidence <= 0.05)
|
||||
return baseUv;
|
||||
|
||||
float parallaxScale = gParallaxScale * confidence;
|
||||
if (abs(parallaxScale) < 1e-6)
|
||||
return baseUv;
|
||||
|
||||
float3 n, t, b;
|
||||
QD3D12_BuildPixelTBN(i, n, t, b);
|
||||
|
||||
// Correct view vector. The old code used -worldPos, which only works when
|
||||
// the camera is exactly at world origin and causes the obvious offset bugs
|
||||
// as soon as the camera moves. Keep -worldPos only as a reduced compatibility
|
||||
// fallback for older paths that cannot derive a perspective camera.
|
||||
float cameraConfidence = (gCameraPomValid >= 0.5) ? 1.0 : 0.35;
|
||||
float3 rawViewWS = (gCameraPomValid >= 0.5) ? (gCameraWorldPos - i.worldPos) : (-i.worldPos);
|
||||
float3 viewWS = QD3D12_SafeNormalize(rawViewWS, n);
|
||||
float NoV = dot(n, viewWS);
|
||||
if (NoV <= 0.035)
|
||||
return baseUv;
|
||||
|
||||
float3 viewTS = QD3D12_SafeNormalize(float3(
|
||||
dot(viewWS, t),
|
||||
dot(viewWS, b),
|
||||
NoV
|
||||
), float3(0.0, 0.0, 1.0));
|
||||
|
||||
float ndotv = saturate(viewTS.z);
|
||||
float vz = max(ndotv, 0.22);
|
||||
|
||||
// Fade out before far surfaces shimmer or when the viewing angle is too
|
||||
// grazing for estimated height maps.
|
||||
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.08, 0.22, ndotv);
|
||||
|
||||
float scale = parallaxScale * distanceFade * grazingFade * cameraConfidence;
|
||||
if (scale <= 0.00045)
|
||||
return baseUv;
|
||||
|
||||
float layerCountF = lerp(10.0, 30.0, saturate(1.0 - ndotv));
|
||||
layerCountF = lerp(8.0, layerCountF, saturate(distanceFade));
|
||||
uint layerCount = (uint)clamp(layerCountF + 0.5, 8.0, 32.0);
|
||||
|
||||
// Offset-limited POM. Divide by a softened z term and clamp the max UV walk;
|
||||
// this removes the extreme stretched-offset artifacts on steep angles.
|
||||
// Standard POM walks opposite the view vector in tangent space. The shift
|
||||
// cap is intentionally tight for generated RGB-only height, looser for real
|
||||
// authored alpha height.
|
||||
float2 parallaxVector = (viewTS.xy / vz) * scale;
|
||||
float parallaxLen = length(parallaxVector);
|
||||
float maxParallaxShift = lerp(0.016, 0.040, confidence);
|
||||
if (parallaxLen > maxParallaxShift && parallaxLen > 1e-6)
|
||||
parallaxVector *= maxParallaxShift / parallaxLen;
|
||||
|
||||
float invLayerCount = rcp((float)layerCount);
|
||||
float2 deltaUv = parallaxVector * invLayerCount;
|
||||
|
||||
float2 uv = baseUv;
|
||||
float2 prevUv = uv;
|
||||
float currentLayerDepth = 0.0;
|
||||
float prevLayerDepth = 0.0;
|
||||
float currentDepth = QD3D12_GetPomDepth(uv);
|
||||
float prevDepth = currentDepth;
|
||||
|
||||
[loop]
|
||||
for (uint layer = 0u; layer < 32u; ++layer)
|
||||
{
|
||||
if (layer >= layerCount || currentLayerDepth >= currentDepth)
|
||||
break;
|
||||
|
||||
prevUv = uv;
|
||||
prevLayerDepth = currentLayerDepth;
|
||||
prevDepth = currentDepth;
|
||||
|
||||
uv -= deltaUv;
|
||||
currentLayerDepth += invLayerCount;
|
||||
currentDepth = QD3D12_GetPomDepth(uv);
|
||||
}
|
||||
|
||||
float afterDepth = currentDepth - currentLayerDepth;
|
||||
float beforeDepth = prevDepth - prevLayerDepth;
|
||||
float denom = afterDepth - beforeDepth;
|
||||
float weight = (abs(denom) > 1e-5) ? saturate(afterDepth / denom) : 0.0;
|
||||
float2 refinedUv = lerp(uv, prevUv, weight);
|
||||
|
||||
// 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 * confidence));
|
||||
QD3D12PomTraceResult trace = QD3D12_TraceReliefPOM(i, baseUv);
|
||||
return trace.uv;
|
||||
}
|
||||
)HLSL"
|
||||
R"HLSL(
|
||||
@@ -4006,10 +4090,7 @@ float4 BuildSpecularAlbedoCached(VSOut i, QD3D12MaterialEval m)
|
||||
{
|
||||
if (gUseSpecularMap > 0.0)
|
||||
{
|
||||
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);
|
||||
float2 specUv = m.uv0;
|
||||
float4 spec = gSpecularMap.Sample(gSamp4, specUv);
|
||||
float strength = max(gSpecularMapStrength, 0.0);
|
||||
|
||||
@@ -4021,8 +4102,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -4235,11 +4314,42 @@ float QD3D12_TessellationPatchEdgeFade(float3 bary)
|
||||
return smoothstep(0.0, 0.08, edgeDistance);
|
||||
}
|
||||
|
||||
uint QD3D12_DecodeGeometryFlag(float flag)
|
||||
{
|
||||
return (uint)floor(max(flag, 0.0) + 0.5);
|
||||
}
|
||||
|
||||
bool QD3D12_IsSkeletalGeometry(float flag)
|
||||
{
|
||||
return (QD3D12_DecodeGeometryFlag(flag) & QD3D12_GEOMETRY_FLAG_SKELETAL) != 0u;
|
||||
}
|
||||
|
||||
float QD3D12_ComputeCharacterTessEdgeFactor(VSOut a, VSOut b)
|
||||
{
|
||||
float distanceFade = QD3D12_TessellationEdgeDistanceFade(a, b);
|
||||
if (distanceFade <= 0.001)
|
||||
return 1.0;
|
||||
|
||||
float edgePixels = QD3D12_EdgeLengthPixels(a.currClip, b.currClip);
|
||||
float screenTerm = sqrt(max(edgePixels, 1.0) / 24.0);
|
||||
float strength = clamp(max(gNormalMapStrength, 0.0), 0.0, 4.0);
|
||||
|
||||
// Character tessellation is for silhouette/deformation smoothness, not
|
||||
// normal-map relief. Keep it bounded so animated MD5s do not turn rubbery.
|
||||
float nearFactor = 1.0 + screenTerm * 2.65 + strength * 0.22;
|
||||
nearFactor = clamp(nearFactor, 1.0, 8.0);
|
||||
|
||||
return clamp(lerp(1.0, nearFactor, distanceFade), QD3D12_TESS_MIN_FACTOR, 8.0);
|
||||
}
|
||||
|
||||
float QD3D12_ComputeNormalMapTessEdgeFactor(VSOut a, VSOut b)
|
||||
{
|
||||
if (gUseNormalMap <= 0.5)
|
||||
return 1.0;
|
||||
|
||||
if (QD3D12_IsSkeletalGeometry(a.attr.x) || QD3D12_IsSkeletalGeometry(b.attr.x))
|
||||
return QD3D12_ComputeCharacterTessEdgeFactor(a, b);
|
||||
|
||||
float distanceFade = QD3D12_TessellationEdgeDistanceFade(a, b);
|
||||
if (distanceFade <= 0.001)
|
||||
return 1.0;
|
||||
@@ -4331,6 +4441,38 @@ float4 QD3D12_Interp4(float4 a, float4 b, float4 c, float3 w)
|
||||
return a * w.x + b * w.y + c * w.z;
|
||||
}
|
||||
|
||||
float3 QD3D12_ProjectPointToTangentPlane(float3 p, float3 planePoint, float3 planeNormal)
|
||||
{
|
||||
planeNormal = QD3D12_SafeNormalize(planeNormal, float3(0.0, 0.0, 1.0));
|
||||
return p - planeNormal * dot(p - planePoint, planeNormal);
|
||||
}
|
||||
|
||||
float3 QD3D12_CharacterPhongTessellate(const OutputPatch<TessCP, 3> patch, float3 bary, float3 linearObjPos, float distanceFade)
|
||||
{
|
||||
float3 n0 = QD3D12_SafeNormalize(patch[0].objNormal, float3(0.0, 0.0, 1.0));
|
||||
float3 n1 = QD3D12_SafeNormalize(patch[1].objNormal, n0);
|
||||
float3 n2 = QD3D12_SafeNormalize(patch[2].objNormal, n0);
|
||||
|
||||
float3 q0 = QD3D12_ProjectPointToTangentPlane(linearObjPos, patch[0].objPos, n0);
|
||||
float3 q1 = QD3D12_ProjectPointToTangentPlane(linearObjPos, patch[1].objPos, n1);
|
||||
float3 q2 = QD3D12_ProjectPointToTangentPlane(linearObjPos, patch[2].objPos, n2);
|
||||
float3 phongObjPos = q0 * bary.x + q1 * bary.y + q2 * bary.z;
|
||||
|
||||
float normalAgreement = saturate((dot(n0, n1) + dot(n1, n2) + dot(n2, n0)) * 0.1667 + 0.5);
|
||||
float smoothAmount = 0.78 * distanceFade * smoothstep(0.10, 0.82, normalAgreement);
|
||||
|
||||
float3 delta = phongObjPos - linearObjPos;
|
||||
float maxEdgeLen = max(
|
||||
length(patch[0].objPos - patch[1].objPos),
|
||||
max(length(patch[1].objPos - patch[2].objPos), length(patch[2].objPos - patch[0].objPos)));
|
||||
float maxDelta = max(maxEdgeLen * 0.075, 0.01);
|
||||
float deltaLen = length(delta);
|
||||
if (deltaLen > maxDelta)
|
||||
delta *= maxDelta / max(deltaLen, 1.0e-5);
|
||||
|
||||
return linearObjPos + delta * smoothAmount;
|
||||
}
|
||||
|
||||
[domain("tri")]
|
||||
VSOut DSMain(HSConstOut tessFactors, float3 bary : SV_DomainLocation, const OutputPatch<TessCP, 3> patch)
|
||||
{
|
||||
@@ -4353,13 +4495,17 @@ VSOut DSMain(HSConstOut tessFactors, float3 bary : SV_DomainLocation, const Outp
|
||||
VSOut o;
|
||||
|
||||
float3 objNormal = QD3D12_SafeNormalize(i.objNormal, float3(0.0, 0.0, 1.0));
|
||||
bool isSkeletal = QD3D12_IsSkeletalGeometry(i.attr.x);
|
||||
float distanceFade = QD3D12_TessellationDisplacementFade(i.currClip);
|
||||
float3 baseObjPos = isSkeletal
|
||||
? QD3D12_CharacterPhongTessellate(patch, bary, i.objPos, distanceFade)
|
||||
: i.objPos;
|
||||
float height = QD3D12_GetFilteredTessHeight(i.uv0);
|
||||
float centeredHeight = QD3D12_CleanCenteredTessHeight(height);
|
||||
float displacementFade = QD3D12_TessellationDisplacementFade(i.currClip);
|
||||
displacementFade *= QD3D12_TessellationPatchEdgeFade(bary);
|
||||
float displacementFade = isSkeletal ? 0.0 : distanceFade * QD3D12_TessellationPatchEdgeFade(bary);
|
||||
float reliefConfidence = QD3D12_GetPomConfidence(i.uv0);
|
||||
float displacement = centeredHeight * gTessellationDisplacement * displacementFade * reliefConfidence;
|
||||
float3 displacedObjPos = i.objPos + objNormal * displacement;
|
||||
float3 displacedObjPos = baseObjPos + objNormal * displacement;
|
||||
|
||||
float4 worldPos = mul(gModelMatrix, float4(displacedObjPos, 1.0));
|
||||
float4 currClip = mul(gMVP, float4(displacedObjPos, 1.0));
|
||||
|
||||
Reference in New Issue
Block a user