Rework the camo shader to flatten toward darkness, drop FormSoftness

Rebuild the camo controls around hiding a painted player in a dark scene. _HalfLambert is now a two-segment curve (0 = no effect / URP Lit, 0.5 = classic half-lambert soft terminator, 1 = fully flat) and a companion _HalfLambertBrightness (-1..1) additively shifts the flattened diffuse so the body tone matches the scene. _SpecularDampen still scales down the direct highlight. The view-coupled _FormSoftness normal-bend is removed entirely (it flipped light/dark with the camera); all knobs are now term-based and view-independent, and stay byte-identical to URP/Lit at their no-op values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-29 20:12:31 +02:00
1 parent 67e3696339
commit b5bd1fdb8b
2 files changed
+46 -33

No files matched your search

+42 -29
View File
@@ -1,37 +1,37 @@
#ifndef PLAYER_CAMO_INCLUDED
#define PLAYER_CAMO_INCLUDED
// Camouflage lighting for the player avatar. Goal: keep the player lit by the scene (so its
// brightness matches the surroundings) but de-emphasise the 3D FORM, so a correctly painted
// avatar is harder to spot. Three independent, tunable knobs - all 0 => byte-identical URP/Lit:
// Camouflage lighting for the player avatar. Goal: keep the player painted to match its
// surroundings while de-emphasising the 3D FORM, so a correctly painted avatar is harder to spot.
// Knobs - _HalfLambert at 0 and _HalfLambertBrightness at 0 => byte-identical URP/Lit:
//
// _FormSoftness (0..1) : bends the per-pixel normal toward the view direction before shading.
// Flattens diffuse + specular + reflections together -> the silhouette
// shading that reveals curvature softens from every light angle.
// _HalfLambert (0..1) : wraps the DIRECT diffuse term, saturate(N.L) -> N.L*0.5+0.5, lifting
// the shadow side (classic half-Lambert). Specular stays clamped.
// _SpecularDampen (0..1) : scales down the DIRECT specular highlight (a strong shape cue).
// _HalfLambert (0..1): form-flattening diffuse over two segments -
// 0 -> saturate(N.L) plain Lambert (no-op)
// 0.5 -> N.L * 0.5 + 0.5 classic half-Lambert (soft terminator,
// no hard, form-revealing shadow line)
// 1 -> 1 fully FLAT: the direct diffuse no longer
// reveals the 3D form. View-INDEPENDENT
// (operates on the shading term, not a view-/light-bent normal, so
// there is no bright/dark flip when orbiting). The DIRECT specular
// highlight (a strong shape cue) is faded out with it, gone at 1.
// _HalfLambertBrightness (-1..1): additive shift of the (flattened) diffuse term, default 0. Tunes
// the body brighter/darker to match the scene tone (e.g. flatten to
// 1, then dial this down to sink into a dark scene). Clamped at 0.
// _SpecularDampen (0..1): scales down the DIRECT specular highlight (a strong shape cue) on
// top of the _HalfLambert fade.
//
// Implementation note: URP's LightingPhysicallyBased() hard-codes saturate(N.L) inline with no
// override hook, so a faithful half-Lambert needs the forward light loop reimplemented. This file
// is a 1:1 copy of URP 17.3 UniversalFragmentPBR that delegates ALL the fragile parts (GI / APV /
// shadow mask / AO / clustered light loop / final color) to URP's own helpers and only swaps the
// per-light shading call for the camo variant below.
// override hook, so the dark-converging half-Lambert needs the forward light loop reimplemented.
// This file is a 1:1 copy of URP 17.3 UniversalFragmentPBR that delegates ALL the fragile parts
// (GI / APV / shadow mask / AO / clustered light loop / final color) to URP's own helpers and only
// swaps the per-light shading call for the camo variant below.
//
// Declared OUTSIDE UnityPerMaterial (LitInput.hlsl owns that CBUFFER). The player is a handful of
// instances, so the resulting SRP-batcher break is irrelevant.
half _FormSoftness;
half _HalfLambert;
half _HalfLambertBrightness;
half _SpecularDampen;
// Bend the shading normal toward the camera. n and viewDirWS are unit; for a visible (front-facing)
// fragment N.V > 0, so the lerp never collapses to zero - SafeNormalize guards the rare grazing case.
half3 SoftenNormalCamo(half3 normalWS, half3 viewDirWS)
{
half3 n = lerp(normalWS, viewDirWS, _FormSoftness);
return half3(SafeNormalize(n));
}
// Per-light shading: copy of URP's LightingPhysicallyBased with the diffuse term wrapped
// (half-Lambert) and the specular term dampened. Diffuse and specular use SEPARATE radiance so the
// wrap lifts only the diffuse shadow side and never smears specular onto back faces.
@@ -42,10 +42,20 @@ half3 LightingPhysicallyBasedCamo(BRDFData brdfData, BRDFData brdfDataClearCoat,
{
half NdotL = dot(normalWS, lightDirectionWS);
half ndlSat = saturate(NdotL);
half diffuseTerm = lerp(ndlSat, NdotL * 0.5h + 0.5h, _HalfLambert);
// Form-flattening diffuse driven by _HalfLambert over two segments:
// 0 -> saturate(N.L) plain Lambert (no-op, byte-identical URP/Lit)
// 0.5 -> N.L * 0.5 + 0.5 classic half-Lambert (soft terminator, no hard shadow line)
// 1 -> 1 fully flat: the direct diffuse no longer reveals the 3D form.
// _HalfLambertBrightness then shifts the whole (flattened) term up/down to tune the body tone;
// clamped at 0 so it never produces negative light.
half classicHL = NdotL * 0.5h + 0.5h; // already in [0,1] for N.L in [-1,1]
half t1 = saturate(_HalfLambert * 2.0h); // 0..1 across _HalfLambert in [0, 0.5]
half t2 = saturate(_HalfLambert * 2.0h - 1.0h); // 0..1 across _HalfLambert in [0.5, 1]
half diffuseTerm = lerp(lerp(ndlSat, classicHL, t1), 1.0h, t2);
diffuseTerm = max(0.0h, diffuseTerm + _HalfLambertBrightness);
half3 atten = lightColor * lightAttenuation;
half3 diffuseRadiance = atten * diffuseTerm; // wrapped (half-Lambert)
half3 diffuseRadiance = atten * diffuseTerm; // flattened + brightness-shifted
half3 specularRadiance = atten * ndlSat; // clamped (no back-face specular)
half3 color = brdfData.diffuse * diffuseRadiance;
@@ -63,7 +73,9 @@ half3 LightingPhysicallyBasedCamo(BRDFData brdfData, BRDFData brdfDataClearCoat,
spec = spec * (1.0 - clearCoatMask * coatFresnel) + (brdfCoat * clearCoatMask).xxx;
#endif
spec *= (1.0h - _SpecularDampen);
// Fade the bright, form-revealing highlight out with _HalfLambert (gone at 1, so no highlight
// survives at full camo), on top of the explicit _SpecularDampen.
spec *= (1.0h - _SpecularDampen) * (1.0h - _HalfLambert);
color += spec * specularRadiance;
}
#endif
@@ -170,8 +182,8 @@ half4 UniversalFragmentCamo(InputData inputData, SurfaceData surfaceData)
#endif
}
// Replicates URP's LitPassFragment body and inserts the normal-soften step before GI + lighting,
// so GI (probe) direction, direct lighting and reflections all use the flattened normal.
// Replicates URP's LitPassFragment body. The camo flattening lives entirely in the light loop
// (UniversalFragmentCamo), so the true normal is kept and GI/reflections stay physically correct.
void CamoForwardFragment(
Varyings input
, out half4 outColor : SV_Target0
@@ -203,8 +215,9 @@ void CamoForwardFragment(
InputData inputData;
InitializeInputData(input, surfaceData.normalTS, inputData);
// Camo: flatten the shading normal toward the camera (no-op when _FormSoftness == 0).
inputData.normalWS = SoftenNormalCamo(inputData.normalWS, inputData.viewDirectionWS);
// Camo: the form is de-emphasised purely inside the light loop (direct diffuse driven toward the
// dark/shadow value + specular faded), so the true normal is kept here and GI/reflections stay
// physically correct. No normal bending -> view-independent, no bright/dark flip when orbiting.
SETUP_DEBUG_TEXTURE_DATA(inputData, UNDO_TRANSFORM_TEX(input.uv, _BaseMap));
+4 -4
View File
@@ -6,16 +6,16 @@
// transfer 1:1, the Lit inspector keeps working, and the runtime MeshPainter (which only sets
// _BaseColor / PBR keywords on the material instance) is unaffected. The ONLY change is the
// ForwardLit pass: its fragment is CamoForwardFragment, which reimplements URP's forward shading
// while delegating GI / shadows / clustered lights to URP and applying the three camo knobs. All
// while delegating GI / shadows / clustered lights to URP and applying the camo knobs. All
// other passes (ShadowCaster, GBuffer, DepthOnly, DepthNormals, Meta, ...) are stock URP/Lit.
Shader "Custom/PlayerCamoLit"
{
Properties
{
// --- Camouflage knobs (all 0 => identical to URP/Lit) ---
// --- Camouflage knobs (both 0 => identical to URP/Lit) ---
[Header(Camouflage)]
_FormSoftness("Form Softness (normal -> view)", Range(0.0, 1.0)) = 0.4
_HalfLambert("Half Lambert (lift shadow side)", Range(0.0, 1.0)) = 0.5
_HalfLambert("Half Lambert (0 none, 0.5 classic, 1 flat)", Range(0.0, 1.0)) = 0.5
_HalfLambertBrightness("Half Lambert Brightness Shift", Range(-1.0, 1.0)) = 0.0
_SpecularDampen("Specular Dampen", Range(0.0, 1.0)) = 0.5
// Specular vs Metallic workflow