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

@@ -30,7 +30,8 @@ public partial class MeshPainter
// is), so saves without a fur.png load fine - the fur map stays seeded (bald). NOTE: the fur.png layout
// changed to R = length (was RGB colour + A length); pre-change fur.png files load stale length data and
// should be repainted.
private const int PaintMetaVersion = 6;
// v8: camoSaturation semantics changed to an offset (0 = neutral, -1 = greyscale; was 1 = neutral in v7).
private const int PaintMetaVersion = 8;
private const string MetaFileName = "painting.json";
private const string AlbedoFileName = "albedo.png";
@@ -57,6 +58,7 @@ public partial class MeshPainter
public float camoHalfLambert;
public float camoHalfLambertBrightness;
public float camoSpecularDampen;
public float camoSaturation; // v7+; remapped on load per meta.version (semantics changed in v8)
public float occlusionThreshold;
public float heightNormalIntensity;
public float parallaxIntensity;
@@ -149,6 +151,7 @@ public partial class MeshPainter
camoHalfLambert = CamoHalfLambert,
camoHalfLambertBrightness = CamoHalfLambertBrightness,
camoSpecularDampen = CamoSpecularDampen,
camoSaturation = CamoSaturation,
occlusionThreshold = OcclusionThreshold,
heightNormalIntensity = HeightNormalIntensity,
parallaxIntensity = ParallaxIntensity,
@@ -200,6 +203,12 @@ public partial class MeshPainter
CamoHalfLambert = meta.camoHalfLambert;
CamoHalfLambertBrightness = meta.camoHalfLambertBrightness;
CamoSpecularDampen = meta.camoSpecularDampen;
// Saturation semantics changed in v8 (0 = neutral, -1 = greyscale; was 1 = neutral in v7).
// v7 stored the old scheme -> shift by -1 to preserve its look. Pre-v7 has no field (deserializes
// to 0), which is already neutral under the new scheme.
CamoSaturation = meta.version >= 8 ? meta.camoSaturation
: meta.version == 7 ? meta.camoSaturation - 1f
: 0f;
OcclusionThreshold = meta.occlusionThreshold;
HeightNormalIntensity = meta.heightNormalIntensity;
ParallaxIntensity = meta.parallaxIntensity;
@@ -78,6 +78,16 @@ public partial class MeshPainter : MonoBehaviour
set => SetMatFloat(SpecularDampenId, value);
}
/// <summary>General colour saturation of the shaded body (0 = neutral, -1 = greyscale, &gt;0 = punchier).
/// A camo body-tone knob alongside <see cref="CamoHalfLambertBrightness"/>; drives the material's
/// <c>_Saturation</c> (offset: shader lerps to grey by 1 + value). Persisted + replicated with the
/// other camo knobs (see <see cref="CamoKnobs"/>).</summary>
public float CamoSaturation
{
get => GetMatFloat(SaturationId, 0f);
set => SetMatFloat(SaturationId, value);
}
// Painted-height look knobs on the same material instance, surfaced/guarded/persisted/replicated
// like the camo knobs above. Defaults match the shader so the unpainted look is unchanged.
@@ -104,11 +114,12 @@ public partial class MeshPainter : MonoBehaviour
set => SetMatFloat(ParallaxId, Mathf.Max(0f, value) * ParallaxBase);
}
/// <summary>The three camo knobs as one vector (x = half-Lambert, y = its brightness, z = specular dampen).</summary>
public Vector3 CamoKnobs
/// <summary>The four camo knobs as one vector (x = half-Lambert, y = brightness, z = specular dampen,
/// w = saturation) - one payload for persistence + network replication.</summary>
public Vector4 CamoKnobs
{
get => new(CamoHalfLambert, CamoHalfLambertBrightness, CamoSpecularDampen);
set { CamoHalfLambert = value.x; CamoHalfLambertBrightness = value.y; CamoSpecularDampen = value.z; }
get => new(CamoHalfLambert, CamoHalfLambertBrightness, CamoSpecularDampen, CamoSaturation);
set { CamoHalfLambert = value.x; CamoHalfLambertBrightness = value.y; CamoSpecularDampen = value.z; CamoSaturation = value.w; }
}
/// <summary>The three painted-height knobs as one vector (x = occlusion threshold, y = relief normal intensity, z = parallax intensity).</summary>
@@ -454,6 +465,7 @@ public partial class MeshPainter : MonoBehaviour
private const float ParallaxBase = 0.02f;
private static readonly int HalfLambertId = Shader.PropertyToID("_HalfLambert");
private static readonly int HalfLambertBrightnessId = Shader.PropertyToID("_HalfLambertBrightness");
private static readonly int SaturationId = Shader.PropertyToID("_Saturation");
private static readonly int SpecularDampenId = Shader.PropertyToID("_SpecularDampen");
private static readonly int XRayColorId = Shader.PropertyToID("_XRayColor");
private static readonly int XRayPulseSpeedId = Shader.PropertyToID("_XRayPulseSpeed");