Fur: Albedo-gekoppelte Länge, Toggle-Gate, Soft-Edge, World-Gravity, Pipette & FP-Hide
- Fur-Map trägt nur noch R = Länge (linearer Datenkanal); Farbe kommt per-texel aus der Live-Albedo-RT (an _BaseMap des Shell-Shaders gebunden) - Toggle_Fur als echtes Gate (IsChannelActive = fur), Default aus, furLength 0.5 - Weicher Alpha-Übergang (radial/tip/length coverage) statt Hard-Clip; Transparent- Pass mit Depth-Bias (Offset -1,-1) gegen Body-Durchflimmern - Root dunkler / Tip heller aus gemalter Farbe abgeleitet (kein Fix-Tint) - Gravity/Curl in World-Space nach Transform (unabhängig von Mesh-Rotation) - Pipette aktiviert Fur-Toggle immer; liest R-Kanal als Länge - First-Person blendet FurShellRenderer mit aus - Netzwerk (FlagFur), Undo, Persistence (fur.png R=Länge) mitgezogen Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
7536e8ec8b
commit
1b783a39be
15 files changed
+254
-306
No files matched your search
@@ -14,10 +14,7 @@ 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;
|
||||
// MeshPaintBrush.shader pass indices: 0 = source-over brush, 1 = coverage seed.
|
||||
|
||||
public Shader brushShader; // optional; else Shader.Find("Hidden/MeshPaintBrush")
|
||||
public Shader dilateShader; // optional; else Shader.Find("Hidden/MeshPaintDilate")
|
||||
@@ -91,10 +88,10 @@ public partial class MeshPainter
|
||||
}
|
||||
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);
|
||||
// Fur R IS data (strand length), not coverage. Upload the seeded/loaded R verbatim (linear
|
||||
// data map) and skip the coverage stamp, so unpainted texels stay bald (R 0) instead of being
|
||||
// marked full length. (Dilation is also skipped for fur in EndGpuStroke.)
|
||||
UploadColorsToRT((Color32[])src.Clone(), gm.display, linear: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -169,15 +166,14 @@ public partial class MeshPainter
|
||||
return rt;
|
||||
}
|
||||
|
||||
/// <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). 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)
|
||||
/// <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). Fur carries no coverage (its length lives in R), so a
|
||||
/// zero alpha is correct there too.</summary>
|
||||
private void UploadColorsToRT(Color32[] colors, RenderTexture rt, bool linear)
|
||||
{
|
||||
var tmp = new Texture2D(textureSize, textureSize, TextureFormat.RGBA32, false, linear);
|
||||
if (!preserveAlpha)
|
||||
for (int i = 0; i < colors.Length; i++) colors[i].a = 0;
|
||||
for (int i = 0; i < colors.Length; i++) colors[i].a = 0;
|
||||
tmp.SetPixels32(colors);
|
||||
tmp.Apply(false);
|
||||
|
||||
@@ -267,16 +263,15 @@ public partial class MeshPainter
|
||||
}
|
||||
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();
|
||||
// Fur stores ONLY strand length in R (the colour comes from the albedo map at render). Gated by
|
||||
// its own toggle: the length eases toward furLength with the brush's alpha as the deposit
|
||||
// weight. Reuse the source-over pass (0) on R only, so R = lerp(dst.R, furLength, op) with
|
||||
// op = luma*mask.a*brushColor.a. SetVector + EncodeData (not SetColor) keeps furLength as raw
|
||||
// linear data - the fur RT is a linear data map the shell samples directly for length.
|
||||
float fl = EncodeData(furLength);
|
||||
_brushMat.SetVector(BrushColorId, new Vector4(fl, fl, fl, Mathf.Clamp01(brushColor.a)));
|
||||
_brushMat.SetFloat(ColorWriteMaskId, 8f); // R only (bits: R=8, G=4, B=2, A=1)
|
||||
DrawStamp();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user