- 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>
75 lines
2.4 KiB
C#
75 lines
2.4 KiB
C#
using UnityEngine;
|
|
|
|
public partial class MeshPainter
|
|
{
|
|
// --- Backend selection / switching ---
|
|
|
|
private void SetupBackend(bool gpu, Color32[][] canonical)
|
|
{
|
|
// SetupGpu returns false if the shaders are missing and it fell back to CPU; honor
|
|
// the real outcome so _gpuActive never claims GPU while _gpu is null (would NRE on
|
|
// the next SwitchBackend / paint).
|
|
if (gpu)
|
|
{
|
|
_gpuActive = SetupGpu(canonical);
|
|
}
|
|
else
|
|
{
|
|
SetupCpu(canonical);
|
|
_gpuActive = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>Switches the backend at runtime, carrying over all painted maps.</summary>
|
|
private void SwitchBackend(bool toGpu)
|
|
{
|
|
var canonical = new Color32[MAP_COUNT][];
|
|
for (int mi = 0; mi < MAP_COUNT; mi++) canonical[mi] = ReadCanonical(mi);
|
|
|
|
if (_gpuActive) TeardownGpu();
|
|
else TeardownCpu();
|
|
|
|
SetupBackend(toGpu, canonical);
|
|
_hasLastScreen = false;
|
|
|
|
Debug.Log($"[MeshPainter] Backend switched -> {(toGpu ? "GPU" : "CPU")} painting.", this);
|
|
}
|
|
|
|
/// <summary>Reads a map from the active backend into canonical (material) layout.</summary>
|
|
private Color32[] ReadCanonical(int mi)
|
|
{
|
|
if (_gpuActive)
|
|
{
|
|
var gm = _gpu[mi];
|
|
// MSO: read the packed final mask RT (R=metallic, G=occlusion, A=smoothness).
|
|
// Others: the display RT. The mask map is linear data; color maps read sRGB.
|
|
if (mi == MAP_MSO) return ReadRtPixels(gm.final, linear: true);
|
|
return ReadRtPixels(gm.display, linear: MapIsLinear(mi));
|
|
}
|
|
return (Color32[])_cpu[mi].pixels.Clone();
|
|
}
|
|
|
|
private static bool MapIsLinear(int mi) => mi == MAP_MSO;
|
|
|
|
private string[] MapProps(int mi)
|
|
{
|
|
switch (mi)
|
|
{
|
|
case MAP_ALBEDO: return new[] { "_BaseMap", "_MainTex" };
|
|
// One mask map drives both URP slots: _MetallicGlossMap (R/A) + _OcclusionMap (G).
|
|
case MAP_MSO: return new[] { "_MetallicGlossMap", "_OcclusionMap" };
|
|
default: return new[] { "_EmissionMap" };
|
|
}
|
|
}
|
|
|
|
private void AssignMapTexture(int mi, Texture tex)
|
|
{
|
|
if (!_materialInstance) return;
|
|
foreach (var p in MapProps(mi))
|
|
{
|
|
int id = Shader.PropertyToID(p);
|
|
if (_materialInstance.HasProperty(id)) _materialInstance.SetTexture(id, tex);
|
|
}
|
|
}
|
|
}
|