Leave brush opacities untouched when picking with the pipette
The pipette now samples only what is painted (albedo, metallic, smoothness, emission), not how strongly - the albedo alpha, metallic/smoothness opacity and emission alpha are preserved instead of being forced to 1 in every pick path and in FinalizePick. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
bb74f748bf
commit
addc606bb3
1 file changed
+25
-24
@@ -7,9 +7,10 @@ public partial class MeshPainter
|
|||||||
//
|
//
|
||||||
// A sampling MODE rather than a paint channel: while active (Shift or Ctrl held, or the HUD toggle), a
|
// 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 the channel values at
|
// 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
|
// the hit point (albedo, metallic, smoothness, emission) into the brush properties. The brush
|
||||||
// opacity/alpha forced to 1 so re-painting reproduces the picked look at full strength. Occlusion is
|
// opacities/alphas (albedo alpha, metallic/smoothness opacity, emission alpha) are left UNTOUCHED,
|
||||||
// excluded: it drives painted height now, so the pipette neither samples it nor enables its toggle.
|
// so a pick changes only WHAT is painted, not how strongly. 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/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)
|
// from the material (raw, UNLIT). Albedo follows the 'pipetteAlbedoFromScreen' toggle: OFF (default)
|
||||||
@@ -22,8 +23,8 @@ public partial class MeshPainter
|
|||||||
[Header("Brush - pipette (eyedropper)")]
|
[Header("Brush - pipette (eyedropper)")]
|
||||||
[Tooltip("Pipette mode: a click samples the channel values at the hit point on ANY surface " +
|
[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 " +
|
"(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 " +
|
"brush (opacities/alphas left untouched) instead of painting. On a foreign object the data " +
|
||||||
"read texel-exact from the material; albedo follows 'Pipette Albedo From Screen'. Held button picks once.")]
|
"channels are read texel-exact from the material; albedo follows 'Pipette Albedo From Screen'. Held button picks once.")]
|
||||||
public bool pipette = false;
|
public bool pipette = false;
|
||||||
|
|
||||||
[Tooltip("On a foreign-object pick, where albedo comes from. OFF (default): albedo is read " +
|
[Tooltip("On a foreign-object pick, where albedo comes from. OFF (default): albedo is read " +
|
||||||
@@ -46,8 +47,8 @@ public partial class MeshPainter
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raycasts at a screen position against ANY collider, samples the channel values at the hit
|
/// 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
|
/// (texel-exact where possible) and copies them into the brush, leaving the brush opacities/alphas
|
||||||
/// to 1 and enabling those paint channels. Occlusion/height is excluded (see FinalizePick). Reads
|
/// untouched and enabling those paint channels. Occlusion/height is excluded (see FinalizePick). Reads
|
||||||
/// regardless of which channels are currently toggled.
|
/// regardless of which channels are currently toggled.
|
||||||
/// Pick paths, by what was hit:
|
/// Pick paths, by what was hit:
|
||||||
/// - our paint mesh -> texel-exact from the painted maps (full PBR);
|
/// - our paint mesh -> texel-exact from the painted maps (full PBR);
|
||||||
@@ -85,16 +86,15 @@ public partial class MeshPainter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Common tail of every successful pick: forces the sampled opacities/alphas to 1, enables the
|
/// Common tail of every successful pick: enables the sampled paint channels, leaves pipette mode
|
||||||
/// sampled paint channels, leaves pipette mode (one-shot) and notifies listeners (the in-game HUD) to
|
/// (one-shot) and notifies listeners (the in-game HUD) to re-sync their widgets. The brush
|
||||||
/// re-sync their widgets. Occlusion is deliberately EXCLUDED: that channel now drives painted height,
|
/// opacities/alphas (albedo alpha, metallic/smoothness opacity, emission alpha) are left UNTOUCHED,
|
||||||
/// not a surface property to sample, so the pipette neither reads it (see the pick paths) nor enables
|
/// so a pick changes only WHAT is painted, not how strongly. Occlusion is deliberately EXCLUDED:
|
||||||
/// its toggle - the deliberate height brush value is left untouched.
|
/// 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>
|
/// </summary>
|
||||||
private void FinalizePick()
|
private void FinalizePick()
|
||||||
{
|
{
|
||||||
metallicSmoothnessOpacity = 1f;
|
|
||||||
|
|
||||||
paintAlbedo = true;
|
paintAlbedo = true;
|
||||||
paintMetallicSmoothness = true;
|
paintMetallicSmoothness = true;
|
||||||
paintOcclusion = false;
|
paintOcclusion = false;
|
||||||
@@ -138,12 +138,13 @@ public partial class MeshPainter
|
|||||||
Color32 m = mso[idx];
|
Color32 m = mso[idx];
|
||||||
Color32 e = em[idx];
|
Color32 e = em[idx];
|
||||||
|
|
||||||
// sRGB bytes map straight back into the gamma-space brush colours (round-trips exactly).
|
// sRGB bytes map straight back into the gamma-space brush colours (round-trips exactly). The
|
||||||
|
// alpha (paint opacity) is deliberately preserved, not overwritten - a pick changes only the colour.
|
||||||
// Occlusion (m.g) is NOT picked: it now drives painted height, kept as the deliberate brush value.
|
// 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);
|
brushColor = new Color(a.r / 255f, a.g / 255f, a.b / 255f, brushColor.a);
|
||||||
metallic = m.r / 255f;
|
metallic = m.r / 255f;
|
||||||
smoothness = m.a / 255f;
|
smoothness = m.a / 255f;
|
||||||
emissionColor = new Color(e.r / 255f, e.g / 255f, e.b / 255f, 1f);
|
emissionColor = new Color(e.r / 255f, e.g / 255f, e.b / 255f, emissionColor.a);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +164,7 @@ public partial class MeshPainter
|
|||||||
// Keep the material's texel-exact PBR; optionally replace albedo with the lit on-screen
|
// Keep the material's texel-exact PBR; optionally replace albedo with the lit on-screen
|
||||||
// colour. No camera (headless) -> keep the material's raw albedo rather than failing.
|
// colour. No camera (headless) -> keep the material's raw albedo rather than failing.
|
||||||
if (pipetteAlbedoFromScreen && TrySampleScreenColor(screenPos, out Color lit))
|
if (pipetteAlbedoFromScreen && TrySampleScreenColor(screenPos, out Color lit))
|
||||||
brushColor = new Color(Mathf.Clamp01(lit.r), Mathf.Clamp01(lit.g), Mathf.Clamp01(lit.b), 1f);
|
brushColor = new Color(Mathf.Clamp01(lit.r), Mathf.Clamp01(lit.g), Mathf.Clamp01(lit.b), brushColor.a);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +225,7 @@ public partial class MeshPainter
|
|||||||
Color albedo = baseCol;
|
Color albedo = baseCol;
|
||||||
if (hasUv && baseTex)
|
if (hasUv && baseTex)
|
||||||
albedo = SampleTexel(baseTex, ApplyST(mat, "_BaseMap", "_MainTex", uv), true) * baseCol;
|
albedo = SampleTexel(baseTex, ApplyST(mat, "_BaseMap", "_MainTex", uv), true) * baseCol;
|
||||||
brushColor = new Color(Mathf.Clamp01(albedo.r), Mathf.Clamp01(albedo.g), Mathf.Clamp01(albedo.b), 1f);
|
brushColor = new Color(Mathf.Clamp01(albedo.r), Mathf.Clamp01(albedo.g), Mathf.Clamp01(albedo.b), brushColor.a);
|
||||||
|
|
||||||
// --- Metallic / Smoothness: MetallicGloss map (linear) ---
|
// --- Metallic / Smoothness: MetallicGloss map (linear) ---
|
||||||
// URP/Lit: metallic = map.r ; smoothness = map.a * _Smoothness (or _Metallic/_Glossiness scalars).
|
// URP/Lit: metallic = map.r ; smoothness = map.a * _Smoothness (or _Metallic/_Glossiness scalars).
|
||||||
@@ -248,7 +249,7 @@ public partial class MeshPainter
|
|||||||
Color emCol = emColor;
|
Color emCol = emColor;
|
||||||
if (hasUv && emTex)
|
if (hasUv && emTex)
|
||||||
emCol = SampleTexel(emTex, ApplyST(mat, "_EmissionMap", null, uv), true) * emColor;
|
emCol = SampleTexel(emTex, ApplyST(mat, "_EmissionMap", null, uv), true) * emColor;
|
||||||
emissionColor = new Color(Mathf.Clamp01(emCol.r), Mathf.Clamp01(emCol.g), Mathf.Clamp01(emCol.b), 1f);
|
emissionColor = new Color(Mathf.Clamp01(emCol.r), Mathf.Clamp01(emCol.g), Mathf.Clamp01(emCol.b), emissionColor.a);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -327,10 +328,10 @@ public partial class MeshPainter
|
|||||||
if (weightSum <= 1e-4f) return false;
|
if (weightSum <= 1e-4f) return false;
|
||||||
float inv = 1f / weightSum; // alphamap weights normally sum to 1, but guard anyway
|
float inv = 1f / weightSum; // alphamap weights normally sum to 1, but guard anyway
|
||||||
|
|
||||||
brushColor = new Color(Mathf.Clamp01(albedo.r * inv), Mathf.Clamp01(albedo.g * inv), Mathf.Clamp01(albedo.b * inv), 1f);
|
brushColor = new Color(Mathf.Clamp01(albedo.r * inv), Mathf.Clamp01(albedo.g * inv), Mathf.Clamp01(albedo.b * inv), brushColor.a);
|
||||||
metallic = Mathf.Clamp01(metallicVal * inv);
|
metallic = Mathf.Clamp01(metallicVal * inv);
|
||||||
smoothness = Mathf.Clamp01(smoothVal * inv);
|
smoothness = Mathf.Clamp01(smoothVal * inv);
|
||||||
emissionColor = new Color(0f, 0f, 0f, 1f); // occlusion left untouched (now painted height)
|
emissionColor = new Color(0f, 0f, 0f, emissionColor.a); // occlusion left untouched (now painted height)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,10 +365,10 @@ public partial class MeshPainter
|
|||||||
{
|
{
|
||||||
if (!TrySampleScreenColor(screenPos, out Color c)) return false;
|
if (!TrySampleScreenColor(screenPos, out Color c)) return false;
|
||||||
|
|
||||||
brushColor = new Color(Mathf.Clamp01(c.r), Mathf.Clamp01(c.g), Mathf.Clamp01(c.b), 1f);
|
brushColor = new Color(Mathf.Clamp01(c.r), Mathf.Clamp01(c.g), Mathf.Clamp01(c.b), brushColor.a);
|
||||||
metallic = 0f;
|
metallic = 0f;
|
||||||
smoothness = 0f;
|
smoothness = 0f;
|
||||||
emissionColor = new Color(0f, 0f, 0f, 1f); // occlusion left untouched (now painted height)
|
emissionColor = new Color(0f, 0f, 0f, emissionColor.a); // occlusion left untouched (now painted height)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user