Fix brush size varying with UV density; decouple preview opacity
Brush radius now derives from the mesh-wide average UV density instead of the hit triangle's own density. Per-triangle density varied up to ~7.6x across the mesh (spiking at UV seams and stretched islands), so the same brushSize painted a visibly larger patch on seam triangles than on evenly-mapped ones. A single global scale keeps the brush the same physical size everywhere; the stamp remains a 3D box, so seams are still bridged. Also decouple the hover preview opacity from the brush albedo alpha: the preview now stays fully opaque and is scaled only by the Preview Opacity property, since the ring is a size/shape indicator, not a paint-strength preview. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
410f9808b5
commit
523bfd3b0e
1 file changed
+15
-21
@@ -669,7 +669,10 @@ public partial class MeshPainter : MonoBehaviour
|
||||
// Spray paints scattered dots, but the preview shows the round footprint (the area
|
||||
// the dots land in), so the hover ring stays a stable, readable size indicator.
|
||||
_previewMat.SetTexture(MaskTexId, spray ? _proceduralMask : _resolvedMask);
|
||||
_previewMat.SetFloat(PreviewOpacityId, Mathf.Max(0f, previewOpacity) * Mathf.Clamp01(brushColor.a));
|
||||
// Preview stays fully opaque regardless of the brush's albedo alpha; only the
|
||||
// Preview Opacity property scales it (the hover ring is a size/shape indicator,
|
||||
// not a paint-strength preview).
|
||||
_previewMat.SetFloat(PreviewOpacityId, Mathf.Max(0f, previewOpacity));
|
||||
|
||||
int layer = gameObject.layer;
|
||||
|
||||
@@ -999,8 +1002,9 @@ public partial class MeshPainter : MonoBehaviour
|
||||
|
||||
/// <summary>
|
||||
/// Raycasts at a screen position against the paint mesh and computes the brush
|
||||
/// placement: hit point (local space), local 3D radius (constant visual size via UV
|
||||
/// density) and the orthonormal tangent frame (u/v/n). Shared by painting and preview.
|
||||
/// placement: hit point (local space), local 3D radius (constant physical size from the
|
||||
/// mesh-wide average UV density) and the orthonormal tangent frame (u/v/n). Shared by
|
||||
/// painting and preview.
|
||||
/// </summary>
|
||||
private bool TryComputeBrushPlacement(Vector2 screenPos, out Vector3 pLocal, out float radiusLocal,
|
||||
out Vector3 u, out Vector3 v, out Vector3 nLocal, out int hitTri)
|
||||
@@ -1011,25 +1015,15 @@ public partial class MeshPainter : MonoBehaviour
|
||||
if (_verts == null || hit.triangleIndex < 0) return false;
|
||||
|
||||
pLocal = transform.InverseTransformPoint(hit.point);
|
||||
hitTri = hit.triangleIndex;
|
||||
|
||||
// Convert the brush radius (texels) into a local 3D radius using the hit
|
||||
// triangle's UV density so the brush keeps a constant visual size.
|
||||
float scale = _avgUvToLocal;
|
||||
int t = hit.triangleIndex;
|
||||
hitTri = t;
|
||||
if (t * 3 + 2 < _tris.Length)
|
||||
{
|
||||
int i0 = _tris[t * 3], i1 = _tris[t * 3 + 1], i2 = _tris[t * 3 + 2];
|
||||
float area3D = 0.5f * Vector3.Cross(_verts[i1] - _verts[i0], _verts[i2] - _verts[i0]).magnitude;
|
||||
float areaUv = 0.5f * Mathf.Abs(Cross2D(_uvs[i1] - _uvs[i0], _uvs[i2] - _uvs[i0]));
|
||||
if (areaUv > 1e-9f)
|
||||
{
|
||||
float s = Mathf.Sqrt(area3D / areaUv);
|
||||
scale = Mathf.Clamp(s, _avgUvToLocal * 0.25f, _avgUvToLocal * 4f); // guard degenerate UVs
|
||||
}
|
||||
}
|
||||
|
||||
radiusLocal = (brushSize / (float)textureSize) * scale;
|
||||
// Brush radius (texels) -> constant local 3D radius via the mesh-wide average UV
|
||||
// density. Deliberately NOT the hit triangle's own density: that varied up to ~7.6x
|
||||
// across the mesh (spiking at UV seams / stretched islands), so the same brushSize
|
||||
// painted a visibly larger patch on seam triangles than on evenly-mapped ones. A single
|
||||
// global scale keeps the brush the same physical size everywhere; the stamp is still a
|
||||
// 3D box, so seams are bridged as before.
|
||||
radiusLocal = (brushSize / (float)textureSize) * _avgUvToLocal;
|
||||
if (radiusLocal <= 0f) return false;
|
||||
|
||||
// Tangent-plane frame for the square stamp / mask projection.
|
||||
|
||||
Reference in new issue
Block a user