Camo: generalize brightness + add persistent saturation

Move brightness out of the half-lambert diffuse term to a final exposure
multiply on the whole shaded body color (diffuse + GI + specular), and add
a saturation grade (0 = neutral, -1 = grayscale, >0 = boosted) on the same
final stage. Saturation rides as a 4th component on CamoKnobs (Vector3 ->
Vector4) so it inherits all three persistence paths: HUD JSON, network
replication (_camo NetworkVariable), and painting-slot metadata. Wires the
renamed Slider_Brightness and new Slider_Saturation. Bumps HudFileVersion
and PaintMetaVersion with load-time remaps so existing paintings keep their
look (old 1=neutral saturation remaps to 0=neutral).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-07-08 07:23:40 +02:00
1 parent 45b7de05e4
commit 50781e32d2
7 files changed
+87 -36

No files matched your search

+25 -9
View File
@@ -3,7 +3,7 @@
// 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:
// Knobs - _HalfLambert 0, _HalfLambertBrightness 0 and _Saturation 0 => byte-identical URP/Lit:
//
// _HalfLambert (0..1): form-flattening diffuse over two segments -
// 0 -> saturate(N.L) plain Lambert (no-op)
@@ -14,9 +14,16 @@
// (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.
// _HalfLambertBrightness (-1..1): GENERAL brightness (exposure) on the WHOLE shaded body - diffuse,
// ambient/GI AND specular - as a final multiply (1 + value), default
// 0 = neutral. NO LONGER tied to the half-Lambert diffuse term (the
// "HalfLambert" in the name is legacy): dial down to sink the body
// into a dark scene, up to lift it into a bright one. Applied in
// CamoForwardFragment, before fog and the X-ray glow.
// _Saturation (-1..1): colour saturation of the shaded body, default 0 = neutral. -1 =
// greyscale, <0 = muted (blends into muted scenes), >0 = punchier.
// Luma-preserving lerp(grey, colour, 1 + _Saturation) toward Rec.709
// grey, same final stage as brightness.
// _SpecularDampen (0..1): scales down the DIRECT specular highlight (a strong shape cue) on
// top of the _HalfLambert fade.
//
@@ -29,7 +36,8 @@
// Declared OUTSIDE UnityPerMaterial (LitInput.hlsl owns that CBUFFER). The player is a handful of
// instances, so the resulting SRP-batcher break is irrelevant.
half _HalfLambert;
half _HalfLambertBrightness;
half _HalfLambertBrightness; // legacy name: now a GENERAL brightness/exposure (CamoForwardFragment), not a half-Lambert-only shift
half _Saturation;
half _SpecularDampen;
// --- Painted height (from the occlusion channel) ---
@@ -103,13 +111,11 @@ half3 LightingPhysicallyBasedCamo(BRDFData brdfData, BRDFData brdfDataClearCoat,
// 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.
// (Brightness is NOT applied here anymore - it is a general final-color exposure in CamoForwardFragment.)
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);
half diffuseTerm = lerp(lerp(ndlSat, classicHL, t1), 1.0h, t2); // in [0,1], no negative light
half3 atten = lightColor * lightAttenuation;
half3 diffuseRadiance = atten * diffuseTerm; // flattened + brightness-shifted
@@ -291,6 +297,16 @@ void CamoForwardFragment(
InitializeBakedGIData(input, inputData);
half4 color = UniversalFragmentCamo(inputData, surfaceData);
// Camo body-tone grade on the WHOLE shaded body (direct + ambient/GI + specular): 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. Applied before fog (part of the body look) and before
// the X-ray reveal glow below, so the reveal keeps its authored intensity. Neutral at brightness 0
// / saturation 0 (x1 multiply, no-op lerp) => byte-identical to stock when the camo knobs are off.
color.rgb *= 1.0h + _HalfLambertBrightness;
half camoLuma = dot(color.rgb, half3(0.2126h, 0.7152h, 0.0722h)); // Rec.709 luma
color.rgb = lerp(camoLuma.xxx, color.rgb, 1.0h + _Saturation);
color.rgb = MixFog(color.rgb, inputData.fogCoord);
// X-Ray reveal glow on the VISIBLE parts. URP draws the SRPDefaultUnlit XRayOverlay pass BEFORE
+4 -2
View File
@@ -12,10 +12,12 @@ Shader "Custom/PlayerCamoLit"
{
Properties
{
// --- Camouflage knobs (both 0 => identical to URP/Lit) ---
// --- Camouflage knobs (HalfLambert 0, Brightness 0, Saturation 0 => identical to URP/Lit) ---
[Header(Camouflage)]
_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
// Legacy property name; now a GENERAL brightness/exposure on the whole shaded body (see PlayerCamo.hlsl).
_HalfLambertBrightness("Brightness (general exposure, 0 = neutral)", Range(-1.0, 1.0)) = 0.0
_Saturation("Saturation (0 = neutral, -1 = greyscale, >0 = punchier)", Range(-1.0, 1.0)) = 0.0
_SpecularDampen("Specular Dampen", Range(0.0, 1.0)) = 0.5
// Specular vs Metallic workflow