#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