Add spray brush, paint interval, and per-channel paint opacity
Spray brush mode: bakes a procedural scattered-dot mask per stamp (random position, size, alpha within the brush disc) and paints it through the existing single-stamp pipeline; dot centers clamped to (0.5 - dotR) so the spray footprint matches the solid brush radius. Paint interval: float on MeshPainter; 0 keeps the continuous interpolated stroke, >0 emits a single timed dab every x.xx seconds. Color picker alpha: horizontal alpha bar on both HSV pickers, runtime-built in Rebind (no prefab re-bake), passed through to brushColor.a / emissionColor.a. Metallic/smoothness + occlusion paint opacity: new opacity fields applied in both CPU and GPU backends (GPU MSO draw split into MS and occlusion sub-stamps for independent opacity), with cloned alpha sliders wired into the HUD. Also includes the prefab layout tweaks and scene adjustments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
af3aa34d66
commit
b07d9a22d2
9 files changed
+1227
-86
No files matched your search
@@ -182,27 +182,59 @@ public partial class MeshPainter
|
||||
Graphics.SetRenderTarget(gm.display);
|
||||
GL.sRGBWrite = gm.sRGB; // sRGB color map vs linear data map (MS)
|
||||
|
||||
SetBrushColorForMap(mi);
|
||||
// Per-channel write mask: lets the shared mask map (R=metallic, G=smoothness work,
|
||||
// B=occlusion) be painted by metallic/smoothness and occlusion INDEPENDENTLY -
|
||||
// untouched channels keep their value while coverage (A) still accumulates.
|
||||
_brushMat.SetFloat(ColorWriteMaskId, BrushColorMaskForMap(mi));
|
||||
_brushMat.SetTexture(MaskTexId, _resolvedMask);
|
||||
// Stamp placement is shared by every draw on this map.
|
||||
_brushMat.SetTexture(MaskTexId, _stampMask);
|
||||
_brushMat.SetVector(BrushCenterId, pLocal);
|
||||
_brushMat.SetFloat(BrushRadiusId, radiusLocal);
|
||||
_brushMat.SetVector(BrushUId, u);
|
||||
_brushMat.SetVector(BrushVId, v);
|
||||
_brushMat.SetVector(BrushNormalId, n);
|
||||
if (!_brushMat.SetPass(0)) continue;
|
||||
|
||||
int sub = _bakedMesh.subMeshCount;
|
||||
for (int i = 0; i < sub; i++)
|
||||
Graphics.DrawMeshNow(_bakedMesh, Matrix4x4.identity, i);
|
||||
if (mi == MAP_MSO)
|
||||
{
|
||||
// Metallic/smoothness and occlusion share one texture but paint with their OWN
|
||||
// opacity, so each is a separate stamp (own _BrushColor.a + per-channel write
|
||||
// mask). Untouched channels keep their value while coverage (A) accumulates.
|
||||
if (paintMetallicSmoothness)
|
||||
StampMso(Mathf.Clamp01(metallicSmoothnessOpacity), 13f); // R|G|A (skip B = occlusion)
|
||||
if (paintOcclusion)
|
||||
StampMso(Mathf.Clamp01(occlusionOpacity), 3f); // B|A (occlusion only)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Albedo / emission: full RGBA, opacity carried by the colour's own alpha.
|
||||
_brushMat.SetColor(BrushColorId, mi == MAP_ALBEDO ? brushColor : emissionColor);
|
||||
_brushMat.SetFloat(ColorWriteMaskId, 15f);
|
||||
DrawStamp();
|
||||
}
|
||||
|
||||
gm.drew = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issues one mask-map stamp: raw (metallic, smoothness, occlusion) data with the given paint
|
||||
/// opacity in alpha and a per-channel colour write mask, so metallic/smoothness and occlusion
|
||||
/// stay independently paintable with their own opacity. EncodeData pre-compensates the Color
|
||||
/// property's sRGB->linear upload so the values round-trip exactly (CPU parity); alpha is not converted.
|
||||
/// </summary>
|
||||
private void StampMso(float opacity, float colorMask)
|
||||
{
|
||||
_brushMat.SetVector(BrushColorId, new Vector4(
|
||||
EncodeData(metallic), EncodeData(smoothness), EncodeData(occlusion), opacity));
|
||||
_brushMat.SetFloat(ColorWriteMaskId, colorMask);
|
||||
DrawStamp();
|
||||
}
|
||||
|
||||
/// <summary>Draws the baked mesh once per sub-mesh with the current brush pass (UV-space rasterization).</summary>
|
||||
private void DrawStamp()
|
||||
{
|
||||
if (!_brushMat.SetPass(0)) return;
|
||||
int sub = _bakedMesh.subMeshCount;
|
||||
for (int i = 0; i < sub; i++)
|
||||
Graphics.DrawMeshNow(_bakedMesh, Matrix4x4.identity, i);
|
||||
}
|
||||
|
||||
private void EndGpuStroke()
|
||||
{
|
||||
Graphics.SetRenderTarget(null);
|
||||
@@ -230,33 +262,6 @@ public partial class MeshPainter
|
||||
GL.sRGBWrite = _prevSRGB;
|
||||
}
|
||||
|
||||
/// <summary>Sets _BrushColor for the given map. Albedo/emission are colors; the mask map packs (metallic, smoothness, occlusion) as raw data.</summary>
|
||||
private void SetBrushColorForMap(int mi)
|
||||
{
|
||||
switch (mi)
|
||||
{
|
||||
case MAP_ALBEDO:
|
||||
_brushMat.SetColor(BrushColorId, brushColor);
|
||||
break;
|
||||
case MAP_MSO:
|
||||
// Raw data via SetVector. Work layout: R=metallic, G=smoothness, B=occlusion
|
||||
// (ColorMask picks the channels). _BrushColor is a Color property, so Unity
|
||||
// applies sRGB->linear on upload in a linear project - we pre-apply the inverse
|
||||
// so the data round-trips EXACTLY and matches the CPU's raw byte writes.
|
||||
_brushMat.SetVector(BrushColorId, new Vector4(
|
||||
EncodeData(metallic), EncodeData(smoothness), EncodeData(occlusion), 1f));
|
||||
break;
|
||||
default: // emission
|
||||
_brushMat.SetColor(BrushColorId, emissionColor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ColorMask bitmask for the brush draw (ColorWriteMask: R=8, G=4, B=2, A=1). Color maps
|
||||
/// write everything; the mask map writes only the active sub-channels so metallic/smoothness
|
||||
/// and occlusion can be painted independently into one texture. Coverage (A) is always written.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Pre-compensates a raw data value (0..1) for the brush's Color property, which Unity
|
||||
/// converts sRGB->linear on upload in a linear project. Applying the inverse here makes
|
||||
@@ -268,13 +273,4 @@ public partial class MeshPainter
|
||||
v = Mathf.Clamp01(v);
|
||||
return QualitySettings.activeColorSpace == ColorSpace.Linear ? Mathf.LinearToGammaSpace(v) : v;
|
||||
}
|
||||
|
||||
private float BrushColorMaskForMap(int mi)
|
||||
{
|
||||
if (mi != MAP_MSO) return 15f; // RGBA
|
||||
bool ms = paintMetallicSmoothness, occ = paintOcclusion;
|
||||
if (ms && occ) return 15f; // R|G|B|A
|
||||
if (ms) return 13f; // R|G|A (skip B = occlusion)
|
||||
return 3f; // B|A (skip R,G = metallic/smoothness) -> occlusion only
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user