From 0321778d38d4325c129275c25c28ea0dfc8e2678 Mon Sep 17 00:00:00 2001 From: CypherNaugh_0x <9931495+CypherNaught-0x@users.noreply.github.com> Date: Sat, 10 Oct 2026 19:23:49 +0200 Subject: [PATCH] Perspective views no longer show hidden edges through solids: lines, decals and ghosts were pulled toward the eye by a fixed step in depth, and perspective depth is not linear in distance (near plane at about 0.1 % of the distance), so at the model that step spanned tens of millimetres and hole bottoms, the far side of bores and the back edges showed through the part (and through section caps). In perspective the point now moves toward the eye by a fraction of its distance (about 0.2 mm at 140 mm, like the orthographic step); orthographic is unchanged. Not covered by an automated test (no GPU in the test suite); checked in the app with before/after screenshots --- crates/ui-egui/src/gpu.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/src/gpu.rs b/crates/ui-egui/src/gpu.rs index 13acae14..32d10492 100644 --- a/crates/ui-egui/src/gpu.rs +++ b/crates/ui-egui/src/gpu.rs @@ -146,7 +146,8 @@ pub struct ViewportCallback { pub access: Option, /// Draw silhouette outlines around the model (visual styles with edges). pub outline: bool, - /// Eye position and 1 for a perspective camera (0: orthographic), for the section cap's depth. + /// Eye position and 1 for a perspective camera (0: orthographic), for the section cap's depth + /// and the depth bias. pub eye: [f32; 4], } @@ -279,7 +280,7 @@ const SHADER: &str = r#" struct U { vp: mat4x4, back: vec4, // xyz = toward the eye, w = 1 for linear output - screen: vec4, // viewport width, height (px), depth bias, silhouettes on (1) + screen: vec4, // viewport width, height (px), depth bias (see `biased_z`), silhouettes on (1) clip: vec4, // section plane: normal, d (zero normal: no section) cap: vec4, // colour of the inside of cut bodies ana: vec4, // surface analysis: mode (1 zebra, 2 draft, 3 curvature), parameter @@ -483,11 +484,23 @@ fn shade(i: TOut) -> vec4 { return out_color(vec4(min(i.c.rgb * k + vec3(spec), vec3(1.0)), i.c.a)); } +/// Clip-space z of `p` (clipped to `c`) pulled `s` depth biases toward the eye (pushed away +/// when negative). Orthographic depth is linear in distance, so the bias is a fixed step of it. +/// Perspective depth is not: near the far plane a fixed step spans tens of millimetres (edges +/// showed through walls), so the point moves toward the eye by a fraction of its distance. +fn biased_z(p: vec3, c: vec4, s: f32) -> f32 { + if (u.eye.w > 0.5) { + let q = u.vp * vec4(mix(p, u.eye.xyz, u.screen.z * 8.0 * s), 1.0); + return q.z / max(q.w, 1e-6) * c.w; + } + return c.z - u.screen.z * s * c.w; +} + @vertex fn vs_ghost(@location(0) p: vec3, @location(1) n: vec3, @location(2) c: vec4) -> TOut { var o: TOut; o.pos = u.vp * vec4(p, 1.0); - o.pos.z = o.pos.z + u.screen.z * 4.0 * o.pos.w; + o.pos.z = biased_z(p, o.pos, -4.0); o.n = n; o.c = c; o.wp = p; @@ -519,7 +532,7 @@ fn vs_line(@builtin(vertex_index) vi: u32, @location(0) a: vec3, @location( let base = select(ca, cb, t > 0.5); let off = nrm * s * w * 0.5 + dir * (t * 2.0 - 1.0) * w * 0.5; var o: LOut; - o.pos = vec4(base.xy + off / half * base.w, base.z - u.screen.z * base.w, base.w); + o.pos = vec4(base.xy + off / half * base.w, biased_z(select(a, b, t > 0.5), base, 1.0), base.w); o.c = c; o.wp = select(a, b, t > 0.5); return o; @@ -546,7 +559,7 @@ fn vs_img(@location(0) p: vec3, @location(1) uv: vec2, @location(2) a: var o: IOut; o.pos = u.vp * vec4(p, 1.0); // Pulled toward the eye like lines, so a decal on a face is not lost in it. - o.pos.z = o.pos.z - u.screen.z * o.pos.w; + o.pos.z = biased_z(p, o.pos, 1.0); o.uv = uv; o.a = a; o.wp = p;