HTrace WSGI composites indirect lighting after post-processing using the GBuffer albedo/occlusion of the player's stock URP GBuffer pass. That pass knew neither the painted-height AO semantics nor the brightness/saturation grade, so desaturated paint reappeared fully saturated in shadow/indirect light and painted height modulated GI with the raw 0.5-centered value. - PlayerCamoShared.hlsl (new): single source for camo knobs, ApplyPaintedHeightSurface and ApplyCamoBodyGrade shared by both passes - PlayerCamoGBuffer.hlsl (new): CamoGBufferFragment = URP 17.3 Lit GBuffer fragment + height-AO remap on occlusion + grade applied to albedo - PlayerCamoLit.shader: GBuffer pass now uses CamoGBufferFragment - PlayerCamo.hlsl: consumes the shared helpers (forward path unchanged) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
81 lines
5.1 KiB
HLSL
81 lines
5.1 KiB
HLSL
#ifndef PLAYER_CAMO_SHARED_INCLUDED
|
|
#define PLAYER_CAMO_SHARED_INCLUDED
|
|
|
|
// Camo knobs + painted-height surface remap SHARED by the ForwardLit pass (PlayerCamo.hlsl) and the
|
|
// UniversalGBuffer pass (PlayerCamoGBuffer.hlsl).
|
|
//
|
|
// Why the GBuffer pass needs this at all: the project renders FORWARD, so the only consumer of the
|
|
// UniversalGBuffer pass is HTrace WSGI. Its screen-space GI composite (ColorComposeURP) multiplies
|
|
// the traced irradiance by THIS pass's diffuse albedo (GBuffer0.rgb) and occlusion (GBuffer1.a) and
|
|
// adds the result AFTER post-processing - i.e. after the forward pass's camo grade already ran.
|
|
// Anything that grades the body's diffuse look (brightness/saturation, painted-height AO) must
|
|
// therefore be mirrored into the GBuffer, or HTrace re-injects the UNGRADED look wherever indirect
|
|
// light dominates (paint keeps its full saturation in shadows, painted height swings the GI).
|
|
//
|
|
// Declared OUTSIDE UnityPerMaterial (LitInput.hlsl owns that CBUFFER). The player is a handful of
|
|
// instances, so the resulting SRP-batcher break is irrelevant.
|
|
half _HalfLambertBrightness; // legacy name: a GENERAL brightness/exposure (see PlayerCamo.hlsl docs)
|
|
half _Saturation; // 0 = neutral, -1 = greyscale, >0 = punchier (see PlayerCamo.hlsl docs)
|
|
|
|
// --- Painted height (from the occlusion channel) ---
|
|
// The painted occlusion value (G of the MSO mask map, bound to _OcclusionMap AND _ParallaxMap) is
|
|
// reinterpreted as a 0.5-centred HEIGHT field:
|
|
// 0.5 -> neutral: no displacement, flat relief normal, AO = 1 (identical to the unpainted look)
|
|
// <0.5 -> recessed (hole) >0.5 -> raised (hill)
|
|
// The UV displacement is URP's stock single-step parallax (ParallaxOffset1Step already does
|
|
// height*amp - amp/2, i.e. the 0.5 centring) under the _PARALLAXMAP keyword. Here we add the
|
|
// matching per-pixel relief NORMAL from the height gradient (the dominant cue) and remap the ambient
|
|
// occlusion so only the LOWER half darkens, with the darkening DEPTH driven by _OcclusionThreshold
|
|
// (a shift, see below).
|
|
half _HeightNormalStrength;
|
|
// Runtime intensities tuned from the paint HUD. _HeightNormalIntensity scales the relief normal
|
|
// (default 1). _OcclusionThreshold is a SHIFT (0..1, default 0.5): the AO darkening depth at the
|
|
// deepest painted hole - 0 = no darkening at all, 1 = AO reaches full black at occ=0.
|
|
half _HeightNormalIntensity;
|
|
half _OcclusionThreshold;
|
|
float4 _OcclusionMap_TexelSize;
|
|
|
|
// Remaps the painted occlusion/height value into surface AO + a relief normal. Runs after URP's
|
|
// InitializeStandardLitSurfaceData (uv is already parallax-displaced) and before InitializeInputData,
|
|
// so the relief normal flows into inputData.normalWS through the tangent frame.
|
|
void ApplyPaintedHeightSurface(float2 uv, inout SurfaceData surfaceData)
|
|
{
|
|
#if defined(_OCCLUSIONMAP)
|
|
// Only the lower (recessed) half of the painted height darkens. _OcclusionThreshold is a SHIFT =
|
|
// how DEEP the darkening reaches at the deepest hole: AO fades from 1 (occ>=0.5, neutral/hill)
|
|
// down to (1 - shift) at occ=0. shift=0 -> AO stays 1 everywhere (no darkening, no black spots),
|
|
// shift=0.5 -> AO in [0.5,1], shift=1 -> AO in [0,1] (full darkening). Linear over occ in [0,0.5].
|
|
half occ = SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, uv).g;
|
|
half depth = saturate(1.0h - occ * 2.0h); // 0 at occ>=0.5 (neutral/hill), 1 at occ=0 (deepest)
|
|
half ao = 1.0h - depth * _OcclusionThreshold; // darken toward (1 - shift) as the hole deepens
|
|
surfaceData.occlusion = LerpWhiteTo(ao, _OcclusionStrength);
|
|
#endif
|
|
|
|
#if defined(_PARALLAXMAP)
|
|
// Per-pixel relief normal from the height (occlusion) gradient via central differences over one
|
|
// texel. A flat field (occ=0.5 everywhere) yields zero deltas -> (0,0,1), so neutral stays unbent.
|
|
float2 texel = _OcclusionMap_TexelSize.xy;
|
|
half hL = SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, uv - float2(texel.x, 0.0)).g;
|
|
half hR = SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, uv + float2(texel.x, 0.0)).g;
|
|
half hD = SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, uv - float2(0.0, texel.y)).g;
|
|
half hU = SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, uv + float2(0.0, texel.y)).g;
|
|
half strength = _HeightNormalStrength * _HeightNormalIntensity;
|
|
half3 reliefTS = normalize(half3((hL - hR) * strength,
|
|
(hD - hU) * strength, 1.0h));
|
|
surfaceData.normalTS = BlendNormalRNM(surfaceData.normalTS, reliefTS);
|
|
#endif
|
|
}
|
|
|
|
// Camo body-tone grade: a GENERAL brightness/exposure and a saturation control - camo tools to
|
|
// sink/lift the body tone and to mute/punch its colour to match the scene. Neutral at brightness 0 /
|
|
// saturation 0 (x1 multiply, no-op lerp). Linear in the colour, so applying it to the ALBEDO (GBuffer
|
|
// path) matches applying it to the lit result (forward path) for the diffuse contribution.
|
|
void ApplyCamoBodyGrade(inout half3 color)
|
|
{
|
|
color *= 1.0h + _HalfLambertBrightness;
|
|
half camoLuma = dot(color, half3(0.2126h, 0.7152h, 0.0722h)); // Rec.709 luma
|
|
color = lerp(camoLuma.xxx, color, 1.0h + _Saturation);
|
|
}
|
|
|
|
#endif
|