Fur als 4. Paint-Map + Ziel-Opacity-Blend
Fur wird die vierte kanonische Paint-Map (RGB=Farbe, A=Länge). Integration über Seed (kahl), GPU/CPU-Paint, Undo, Netzwerk-Sync, Persistence (fur.png, v6) und Live-RT-Hook im FurShellRenderer. HUD-Binding (Toggle/Picker/Alpha) aktiviert. Fur-Alpha lerpt jetzt zu einer Ziel-Opacity (Slider_FurOpacity) statt additiv Richtung 1 zu akkumulieren (zwei Brush-Passes: Fade + Deposit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
43791abc79
commit
7536e8ec8b
11 files changed
+319
-20
No files matched your search
@@ -14,6 +14,11 @@ public partial class MeshPainter
|
||||
public bool sRGB; // sRGB color map (albedo/emission) vs linear data map (MSO)
|
||||
}
|
||||
|
||||
// MeshPaintBrush.shader pass indices: 0 = source-over brush, 1 = coverage seed,
|
||||
// 2 = fur fade (RGB + alpha*(1-op)), 3 = fur deposit (alpha += op*target).
|
||||
private const int FurFadePass = 2;
|
||||
private const int FurDepositPass = 3;
|
||||
|
||||
public Shader brushShader; // optional; else Shader.Find("Hidden/MeshPaintBrush")
|
||||
public Shader dilateShader; // optional; else Shader.Find("Hidden/MeshPaintDilate")
|
||||
public Shader packMsShader; // optional; else Shader.Find("Hidden/MeshPaintPackMS")
|
||||
@@ -84,6 +89,13 @@ public partial class MeshPainter
|
||||
StampCoverage(gm.display);
|
||||
PackMs(gm); // build the final mask RT once so the material shows the seeded values
|
||||
}
|
||||
else if (mi == MAP_FUR)
|
||||
{
|
||||
// Fur: alpha IS data (strand length), not coverage. Preserve the seeded/loaded alpha and skip
|
||||
// the coverage stamp, so unpainted texels stay bald (alpha 0) instead of being marked full
|
||||
// length. (Dilation is also skipped for fur in EndGpuStroke, so the alpha guard never matters.)
|
||||
UploadColorsToRT((Color32[])src.Clone(), gm.display, linear: gm.sRGB == false, preserveAlpha: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
UploadColorsToRT((Color32[])src.Clone(), gm.display, linear: gm.sRGB == false);
|
||||
@@ -159,11 +171,13 @@ public partial class MeshPainter
|
||||
|
||||
/// <summary>Writes Color32 RGB into a RenderTexture via a temporary texture. Alpha (the
|
||||
/// dilation coverage marker) is cleared to 0 here and stamped afterwards by StampCoverage,
|
||||
/// so the guard matches the GPU rasterizer exactly (see SeedGpuMap).</summary>
|
||||
private void UploadColorsToRT(Color32[] colors, RenderTexture rt, bool linear)
|
||||
/// so the guard matches the GPU rasterizer exactly (see SeedGpuMap). preserveAlpha keeps the
|
||||
/// source alpha instead - used by the fur map, whose alpha is painted length, not coverage.</summary>
|
||||
private void UploadColorsToRT(Color32[] colors, RenderTexture rt, bool linear, bool preserveAlpha = false)
|
||||
{
|
||||
var tmp = new Texture2D(textureSize, textureSize, TextureFormat.RGBA32, false, linear);
|
||||
for (int i = 0; i < colors.Length; i++) colors[i].a = 0;
|
||||
if (!preserveAlpha)
|
||||
for (int i = 0; i < colors.Length; i++) colors[i].a = 0;
|
||||
tmp.SetPixels32(colors);
|
||||
tmp.Apply(false);
|
||||
|
||||
@@ -251,10 +265,24 @@ public partial class MeshPainter
|
||||
if (paintOcclusion)
|
||||
StampMso(Mathf.Clamp01(occlusionOpacity), 3f); // B|A (occlusion only)
|
||||
}
|
||||
else if (mi == MAP_FUR)
|
||||
{
|
||||
// Fur alpha is DATA (strand length) that must ease toward furOpacity, not accumulate to 1.
|
||||
// Fixed-function can't lerp-to-target in one draw, so it is split into two stamps into the
|
||||
// SAME display RT (reference stays stable for FurMapTexture - no ping-pong):
|
||||
// pass FurFade: RGB source-over toward furColor + alpha *= (1-op)
|
||||
// pass FurDeposit: alpha += op * furOpacity
|
||||
// Net alpha = (1-op)*dst + op*target = lerp(dst, target, op); op = luma*mask.a*furColor.a.
|
||||
_brushMat.SetColor(BrushColorId, furColor);
|
||||
_brushMat.SetFloat(FurTargetId, Mathf.Clamp01(furOpacity));
|
||||
if (_brushMat.SetPass(FurFadePass)) DrawBakedMeshNow();
|
||||
if (_brushMat.SetPass(FurDepositPass)) DrawBakedMeshNow();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Albedo / emission: full RGBA, opacity carried by the colour's own alpha.
|
||||
_brushMat.SetColor(BrushColorId, mi == MAP_ALBEDO ? brushColor : emissionColor);
|
||||
// Albedo / emission: full RGBA, opacity carried by the colour's own alpha (source-over).
|
||||
Color paintCol = mi == MAP_ALBEDO ? brushColor : emissionColor;
|
||||
_brushMat.SetColor(BrushColorId, paintCol);
|
||||
_brushMat.SetFloat(ColorWriteMaskId, 15f);
|
||||
DrawStamp();
|
||||
}
|
||||
@@ -301,8 +329,10 @@ public partial class MeshPainter
|
||||
if (!IsChannelActive(mi)) continue;
|
||||
var gm = _gpu[mi];
|
||||
|
||||
// Edge padding against UV seams: dilate the display RT into temp, then swap.
|
||||
if (gm.drew && paddingTexels > 0)
|
||||
// Edge padding against UV seams: dilate the display RT into temp, then swap. Skipped for fur:
|
||||
// its alpha is length (not a coverage guard), so dilation has no valid source mask and the
|
||||
// display RT reference must stay stable for FurMapTexture (no ping-pong swap).
|
||||
if (gm.drew && paddingTexels > 0 && mi != MAP_FUR)
|
||||
{
|
||||
GL.sRGBWrite = false;
|
||||
_dilateMat.SetInt(PadId, paddingTexels);
|
||||
|
||||
Reference in new issue
Block a user