MeshPainter: Rückseiten-Abweisung über Flächen-Normale bei großem Brush

Zusätzlich zum Kugel-Test wird die Oberflächen-Normale jedes Texels gegen
die Brush-Normale geprüft (CPU/GPU/Preview). Neues Inspector-Feld
"Brush Max Surface Angle" (90-180°, Default 120°). Bei fehlenden Mesh-Normalen
Degradation zur reinen Kugel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-07-14 22:26:35 +02:00
1 parent 9d17baee85
commit cdd9fa9532
7 files changed
+68 -8

No files matched your search

+11 -2
View File
@@ -51,18 +51,21 @@ Shader "Hidden/MeshPaintBrush"
float3 _BrushV; // stamp up axis (unit, local space)
float3 _BrushNormal; // surface normal at the hit (unit, local space)
float _BrushRadius; // stamp half-extent in local mesh space
float _BrushNormalCutoff; // cos(maxAngle): paint only if dot(texelN, brushN) >= this (-1 = off)
sampler2D _MaskTex;
struct appdata
{
float4 vertex : POSITION; // local 3D position
float3 normal : NORMAL; // local surface normal
float2 uv : TEXCOORD0; // UV (target in the texture layout)
};
struct v2f
{
float4 pos : SV_POSITION;
float3 local : TEXCOORD0; // interpolated local 3D position
float4 pos : SV_POSITION;
float3 local : TEXCOORD0; // interpolated local 3D position
float3 normal : TEXCOORD1; // interpolated local surface normal
};
v2f vert (appdata v)
@@ -76,6 +79,7 @@ Shader "Hidden/MeshPaintBrush"
#endif
o.pos = float4(p, 0.0, 1.0);
o.local = v.vertex.xyz;
o.normal = v.normal;
return o;
}
@@ -91,6 +95,11 @@ Shader "Hidden/MeshPaintBrush"
// (both sides of a 3D edge) yet never bleeds onto far/adjacent surfaces past r.
clip(_BrushRadius * _BrushRadius - (du * du + dv * dv + dn * dn));
// Back-face rejection: the opposite side of thin geometry faces ~180deg away,
// so drop texels whose normal turns away from the clicked surface past the cutoff.
float nl = dot(normalize(i.normal), _BrushNormal);
clip(nl - _BrushNormalCutoff);
// Mask -> opacity. The sphere guarantees |du|,|dv| <= r -> uv in [0,1].
float2 muv = float2(du, dv) / _BrushRadius * 0.5 + 0.5;
fixed4 m = tex2D(_MaskTex, muv);
+9 -3
View File
@@ -43,6 +43,7 @@ Shader "Hidden/MeshPaintPreview"
float3 _BrushV; // stamp up axis (unit, local space)
float3 _BrushNormal; // surface normal at the hit (unit, local space)
float _BrushRadius; // stamp half-extent in local mesh space
float _BrushNormalCutoff; // cos(maxAngle): show only if dot(texelN, brushN) >= this (-1 = off)
float _PreviewOpacity; // overall preview alpha multiplier (>1 saturates to fully opaque)
float _PreviewMode; // 0 = on-surface (mesh), 1 = flat decal quad
sampler2D _MaskTex;
@@ -50,14 +51,16 @@ Shader "Hidden/MeshPaintPreview"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 local : TEXCOORD0; // object-space position (on-surface box test)
float2 uv : TEXCOORD1; // quad uv (decal sampling)
float4 pos : SV_POSITION;
float3 local : TEXCOORD0; // object-space position (on-surface box test)
float2 uv : TEXCOORD1; // quad uv (decal sampling)
float3 normal : TEXCOORD2; // object-space normal (on-surface back-face test)
};
v2f vert (appdata v)
@@ -66,6 +69,7 @@ Shader "Hidden/MeshPaintPreview"
o.pos = UnityObjectToClipPos(v.vertex);
o.local = v.vertex.xyz;
o.uv = v.uv;
o.normal = v.normal;
return o;
}
@@ -80,6 +84,8 @@ Shader "Hidden/MeshPaintPreview"
float dv = dot(d, _BrushV);
float dn = dot(d, _BrushNormal);
clip(_BrushRadius * _BrushRadius - (du * du + dv * dv + dn * dn));
// Back-face rejection: hide the preview on the opposite side of thin geometry.
clip(dot(normalize(i.normal), _BrushNormal) - _BrushNormalCutoff);
muv = float2(du, dv) / _BrushRadius * 0.5 + 0.5;
}
else