Split mesh painter into partials and add occlusion via combined mask map

- Split MeshPainter into partial classes (Setup/Backend/Gpu/Cpu) for maintainability
- Add ambient occlusion painting, packed with metallic/smoothness into one
  MSO mask map (R=metallic, G=occlusion, A=smoothness) bound to both
  _MetallicGlossMap and _OcclusionMap, kept independently paintable via
  per-channel ColorMask
- Gate painting from a single IsChannelActive source so AO-only strokes work
- Blend albedo/emission in linear space on CPU to match the GPU framebuffer
- Pre-compensate data-channel writes for sRGB->linear so GPU matches CPU
- Harden GPU shader fallback to reflect real backend state

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-23 19:31:52 +02:00
1 parent 86c0a33687
commit c7ec12e6d9
11 files changed
+927 -776

No files matched your search

+6
View File
@@ -20,6 +20,7 @@ Shader "Hidden/MeshPaintBrush"
{
_BrushColor ("Brush Color", Color) = (1,0,0,1)
_MaskTex ("Brush Mask", 2D) = "white" {}
_ColorWriteMask ("Color Write Mask", Float) = 15
}
SubShader
{
@@ -29,6 +30,11 @@ Shader "Hidden/MeshPaintBrush"
ZTest Always
ZWrite Off
Cull Off
// Per-channel write mask: lets the shared mask map (R=metallic, G=smoothness,
// B=occlusion) be painted by metallic/smoothness and occlusion independently.
// ColorWriteMask bits: R=8, G=4, B=2, A=1 (15 = all). Alpha (coverage) is always
// written so the dilation pass still sees the freshly painted texels.
ColorMask [_ColorWriteMask]
// Source-over for color; alpha accumulates coverage toward 1 (painted marker
// for the dilation pass). Soft edges keep partial alpha.
Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha
+12 -9
View File
@@ -1,14 +1,16 @@
// Packs the metallic-smoothness WORK layout into the URP _MetallicGlossMap layout.
// Packs the mask-map WORK layout into the URP mask-map layout.
//
// While painting, the metallic-smoothness map is kept in a "work" RenderTexture as
// R = metallic, G = smoothness, A = coverage (painted marker for the dilation pass).
// While painting, the mask map is kept in a "work" RenderTexture as
// R = metallic, G = smoothness, B = occlusion, A = coverage (painted marker for dilation).
// This lets the same brush + dilate shaders (which use alpha as the coverage marker)
// drive it exactly like the albedo map. But URP's _MetallicGlossMap expects
// R = metallic, A = smoothness.
// This pass converts work -> final after each stroke: out = (work.r, 0, 0, work.g).
// drive it exactly like the albedo map. The packed result is one texture bound to BOTH
// _MetallicGlossMap (URP reads R = metallic, A = smoothness) and
// _OcclusionMap (URP reads G = occlusion).
// This pass converts work -> final after each stroke: out = (work.r, work.b, 0, work.g)
// = (metallic, occlusion, 0, smoothness).
//
// Both work and final are linear RenderTextures (metallic/smoothness are data, not
// color), so no gamma conversion happens.
// Both work and final are linear RenderTextures (these are data, not color), so no gamma
// conversion happens.
Shader "Hidden/MeshPaintPackMS"
{
Properties
@@ -35,7 +37,8 @@ Shader "Hidden/MeshPaintPackMS"
fixed4 frag (v2f_img i) : SV_Target
{
fixed4 c = tex2D(_MainTex, i.uv);
return fixed4(c.r, 0.0, 0.0, c.g); // R = metallic, A = smoothness
// work (R=metallic, G=smoothness, B=occlusion) -> (metallic, occlusion, 0, smoothness)
return fixed4(c.r, c.b, 0.0, c.g);
}
ENDCG
}