Add painted-height HUD sliders for AO shift and relief intensity
Wire three material-backed look sliders (occlusion AO shift, relief normal intensity, parallax intensity) through the paint HUD, its layout/state persistence, the .paint slot format (bumped to v4), owner-authoritative network sync and the runtime reset, mirroring the existing camo-knob path so they save, restore, replicate and reset alongside the brush values. Change the occlusion AO mapping from a division threshold to a linear shift: AO fades from 1 down to (1 - shift) at the deepest painted hole, so a 0 shift no longer leaves black spots. Enable _NORMALMAP alongside _PARALLAXMAP so URP transforms the painted relief normal into world space (otherwise it discards normalTS and the relief is invisible), and pin the added keyword in the build keep-alive material. Stop the pipette from picking the occlusion/height value and turn its toggle off after a pick. Default the knobs to 0.2 shift / 1.4 normal / 1.0 parallax across the shader, painter, network and HUD fallbacks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
fdcb4f8d8a
commit
59939b3496
11 files changed
+216
-49
No files matched your search
@@ -6,11 +6,12 @@ public partial class MeshPainter
|
||||
// --- Pipette (eyedropper) ---
|
||||
//
|
||||
// A sampling MODE rather than a paint channel: while active (Shift or Ctrl held, or the HUD toggle), a
|
||||
// left-click raycasts onto ANY surface (not just the paint mesh) and reads every channel value at
|
||||
// the hit point (albedo, metallic, smoothness, occlusion, emission) into the brush properties -
|
||||
// with every opacity/alpha forced to 1 so re-painting reproduces the picked look at full strength.
|
||||
// left-click raycasts onto ANY surface (not just the paint mesh) and reads the channel values at
|
||||
// the hit point (albedo, metallic, smoothness, emission) into the brush properties - with every
|
||||
// opacity/alpha forced to 1 so re-painting reproduces the picked look at full strength. Occlusion is
|
||||
// excluded: it drives painted height now, so the pipette neither samples it nor enables its toggle.
|
||||
//
|
||||
// On a FOREIGN object the data channels (metallic/smoothness/occlusion/emission) come texel-exact
|
||||
// On a FOREIGN object the data channels (metallic/smoothness/emission) come texel-exact
|
||||
// from the material (raw, UNLIT). Albedo follows the 'pipetteAlbedoFromScreen' toggle: OFF (default)
|
||||
// reads it texel-exact from the material too; ON takes it from the rendered, fully-lit on-screen
|
||||
// pixel ("what you see" - honours baked lightmaps/ambient/SSAO), combining material PBR fidelity
|
||||
@@ -19,16 +20,16 @@ public partial class MeshPainter
|
||||
// terrain are read texel-exact.
|
||||
|
||||
[Header("Brush - pipette (eyedropper)")]
|
||||
[Tooltip("Pipette mode: a click samples ALL channel values at the hit point on ANY surface " +
|
||||
"(albedo, metallic, smoothness, occlusion, emission) and adopts them into the brush " +
|
||||
"(all alphas set to 1) instead of painting. On a foreign object the data channels are read " +
|
||||
"texel-exact from the material; albedo follows 'Pipette Albedo From Screen'. Held button picks once.")]
|
||||
[Tooltip("Pipette mode: a click samples the channel values at the hit point on ANY surface " +
|
||||
"(albedo, metallic, smoothness, emission - NOT occlusion/height) and adopts them into the " +
|
||||
"brush (all alphas set to 1) instead of painting. On a foreign object the data channels are " +
|
||||
"read texel-exact from the material; albedo follows 'Pipette Albedo From Screen'. Held button picks once.")]
|
||||
public bool pipette = false;
|
||||
|
||||
[Tooltip("On a foreign-object pick, where albedo comes from. OFF (default): albedo is read " +
|
||||
"texel-exact from the material like the other channels (raw, unlit). ON: albedo is taken " +
|
||||
"from the lit on-screen pixel ('what you see' - honours baked lightmaps/ambient/SSAO). " +
|
||||
"The data channels (metallic/smoothness/occlusion/emission) always come from the material.")]
|
||||
"The data channels (metallic/smoothness/emission) always come from the material.")]
|
||||
[SerializeField] private bool pipetteAlbedoFromScreen = false;
|
||||
|
||||
/// <summary>
|
||||
@@ -44,9 +45,10 @@ public partial class MeshPainter
|
||||
private const float TerrainFoliageHeightEpsilon = 0.3f;
|
||||
|
||||
/// <summary>
|
||||
/// Raycasts at a screen position against ANY collider, samples every channel value at the hit
|
||||
/// (texel-exact where possible) and copies it into the brush, forcing all opacities/alphas to
|
||||
/// 1 and enabling every paint channel. Reads regardless of which channels are currently toggled.
|
||||
/// Raycasts at a screen position against ANY collider, samples the channel values at the hit
|
||||
/// (texel-exact where possible) and copies them into the brush, forcing the sampled opacities/alphas
|
||||
/// to 1 and enabling those paint channels. Occlusion/height is excluded (see FinalizePick). Reads
|
||||
/// regardless of which channels are currently toggled.
|
||||
/// Pick paths, by what was hit:
|
||||
/// - our paint mesh -> texel-exact from the painted maps (full PBR);
|
||||
/// - terrain ground -> blend of the terrain layers by their splat weights (full PBR);
|
||||
@@ -83,18 +85,19 @@ public partial class MeshPainter
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Common tail of every successful pick: forces all opacities/alphas to 1, enables every paint
|
||||
/// channel (a whole-material pick implies repainting all of it), leaves pipette mode (one-shot)
|
||||
/// and notifies listeners (the in-game HUD) to re-sync their widgets.
|
||||
/// Common tail of every successful pick: forces the sampled opacities/alphas to 1, enables the
|
||||
/// sampled paint channels, leaves pipette mode (one-shot) and notifies listeners (the in-game HUD) to
|
||||
/// re-sync their widgets. Occlusion is deliberately EXCLUDED: that channel now drives painted height,
|
||||
/// not a surface property to sample, so the pipette neither reads it (see the pick paths) nor enables
|
||||
/// its toggle - the deliberate height brush value is left untouched.
|
||||
/// </summary>
|
||||
private void FinalizePick()
|
||||
{
|
||||
metallicSmoothnessOpacity = 1f;
|
||||
occlusionOpacity = 1f;
|
||||
|
||||
paintAlbedo = true;
|
||||
paintMetallicSmoothness = true;
|
||||
paintOcclusion = true;
|
||||
paintOcclusion = false;
|
||||
paintEmission = true;
|
||||
|
||||
pipette = false;
|
||||
@@ -136,16 +139,16 @@ public partial class MeshPainter
|
||||
Color32 e = em[idx];
|
||||
|
||||
// sRGB bytes map straight back into the gamma-space brush colours (round-trips exactly).
|
||||
// Occlusion (m.g) is NOT picked: it now drives painted height, kept as the deliberate brush value.
|
||||
brushColor = new Color(a.r / 255f, a.g / 255f, a.b / 255f, 1f);
|
||||
metallic = m.r / 255f;
|
||||
occlusion = m.g / 255f;
|
||||
smoothness = m.a / 255f;
|
||||
emissionColor = new Color(e.r / 255f, e.g / 255f, e.b / 255f, 1f);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Foreign-object pick: the data channels (metallic/smoothness/occlusion/emission) always come
|
||||
/// Foreign-object pick: the data channels (metallic/smoothness/emission) always come
|
||||
/// texel-exact from the material via <see cref="PickFromForeignMaterial"/> (raw, unlit). Albedo
|
||||
/// follows <see cref="pipetteAlbedoFromScreen"/>: when set, it is overwritten with the rendered,
|
||||
/// fully-lit on-screen pixel ("what you see" - honours baked lightmaps/ambient/SSAO); otherwise
|
||||
@@ -237,13 +240,7 @@ public partial class MeshPainter
|
||||
metallic = Mathf.Clamp01(metallicVal);
|
||||
smoothness = Mathf.Clamp01(smoothVal);
|
||||
|
||||
// --- Occlusion: OcclusionMap.g (linear), lerp(1, g, strength) ---
|
||||
float occStrength = GetFloatProp(mat, "_OcclusionStrength", null, 1f);
|
||||
float occ = 1f;
|
||||
Texture occTex = GetTexProp(mat, "_OcclusionMap", null);
|
||||
if (hasUv && occTex)
|
||||
occ = Mathf.Lerp(1f, SampleTexel(occTex, ApplyST(mat, "_OcclusionMap", null, uv), false).g, occStrength);
|
||||
occlusion = Mathf.Clamp01(occ);
|
||||
// Occlusion is deliberately NOT sampled (that channel drives painted height now, see FinalizePick).
|
||||
|
||||
// --- Emission: EmissionMap (sRGB) * EmissionColor ---
|
||||
Color emColor = GetColorProp(mat, "_EmissionColor", null, Color.black);
|
||||
@@ -292,7 +289,7 @@ public partial class MeshPainter
|
||||
/// <summary>
|
||||
/// Blends the terrain's layers at a ground point by their splat (alphamap) weights: the weighted
|
||||
/// diffuse colour becomes albedo and the weighted per-layer metallic/smoothness scalars feed
|
||||
/// those channels; occlusion is 1 and emission black. Terrain has no Renderer/material, so this
|
||||
/// those channels; emission is black (occlusion is left as the brush's height value). Terrain has no Renderer/material, so this
|
||||
/// is the only way to read its surface.
|
||||
/// </summary>
|
||||
private bool PickFromTerrainLayers(TerrainData td, Vector3 local, float nx, float nz)
|
||||
@@ -333,8 +330,7 @@ public partial class MeshPainter
|
||||
brushColor = new Color(Mathf.Clamp01(albedo.r * inv), Mathf.Clamp01(albedo.g * inv), Mathf.Clamp01(albedo.b * inv), 1f);
|
||||
metallic = Mathf.Clamp01(metallicVal * inv);
|
||||
smoothness = Mathf.Clamp01(smoothVal * inv);
|
||||
occlusion = 1f;
|
||||
emissionColor = new Color(0f, 0f, 0f, 1f);
|
||||
emissionColor = new Color(0f, 0f, 0f, 1f); // occlusion left untouched (now painted height)
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -371,8 +367,7 @@ public partial class MeshPainter
|
||||
brushColor = new Color(Mathf.Clamp01(c.r), Mathf.Clamp01(c.g), Mathf.Clamp01(c.b), 1f);
|
||||
metallic = 0f;
|
||||
smoothness = 0f;
|
||||
occlusion = 1f;
|
||||
emissionColor = new Color(0f, 0f, 0f, 1f);
|
||||
emissionColor = new Color(0f, 0f, 0f, 1f); // occlusion left untouched (now painted height)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user