From e5c8eddedb235dbc09a1530b929203bde87e4a0e Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 24 Jun 2026 10:22:44 +0300 Subject: [PATCH] fix(camera): centre ortho depth range so deep zoom doesn't clip the drawing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zooming in close made the drawing vanish. The orthographic near/far were near = distance*0.001, far = distance*1000, which parks the target plane at ndc-z ~= 0.001 — flush against the near plane. The shared draw-order depth bias shifts clip-z by up to DRAW_ORDER_BIAS (0.001), so a front-biased entity landed exactly at z = 0 and dropped out as soon as f32 rounding (which worsens at high zoom) tipped it past the plane. ortho_depth_range() now returns a symmetric near/far (distance ± r) that centres the target plane at ndc-z ~= 0.5, giving the bias half the depth buffer of headroom on each side. Perspective is unchanged. Applied to both view_proj and view_proj_rte. Co-Authored-By: Claude Opus 4.8 --- src/scene/view/camera.rs | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/scene/view/camera.rs b/src/scene/view/camera.rs index b5303c74a..9d5d9c048 100644 --- a/src/scene/view/camera.rs +++ b/src/scene/view/camera.rs @@ -93,8 +93,6 @@ impl Camera { // ── Projection matrices ──────────────────────────────────────────────── pub fn view_proj(&self, bounds: Rectangle) -> Mat4 { - let near = self.distance * 0.001; - let far = self.distance * 1000.0; let aspect = bounds.width / bounds.height; // Up vector: use the rotation to find which world direction is "up" @@ -103,24 +101,39 @@ impl Camera { let view = Mat4::look_at_rh(self.eye(), self.target.as_vec3(), up_dir); let proj = match self.projection { - Projection::Perspective => Mat4::perspective_rh(self.fov_y, aspect, near, far), + Projection::Perspective => { + Mat4::perspective_rh(self.fov_y, aspect, self.distance * 0.001, self.distance * 1000.0) + } Projection::Orthographic => { let h = self.ortho_size(); let w = h * aspect; + let (near, far) = self.ortho_depth_range(); Mat4::orthographic_rh(-w, w, -h, h, near, far) } }; OPENGL_TO_WGPU * proj * view } + /// Orthographic near/far that CENTRE the target plane at ndc-z ≈ 0.5. + /// + /// The draw-order depth bias shifts clip-z by ±`DRAW_ORDER_BIAS` (0.001). + /// The old range (`near = distance*0.001`, `far = distance*1000`) parked the + /// geometry at ndc-z ≈ 0.001 — right on the near plane — so a front-biased + /// entity landed exactly at z = 0 and got clipped the moment f32 rounding + /// (worse at high zoom) tipped it past the plane, making the drawing vanish. + /// A symmetric range gives the bias half the depth buffer of headroom on + /// each side; ortho permits a negative near. + fn ortho_depth_range(&self) -> (f32, f32) { + let r = (self.distance * 1000.0).max(1.0); + (self.distance - r, self.distance + r) + } + /// Relative-to-eye view-projection: identical projection, but the view /// matrix carries rotation only (translation zeroed). Positions fed to it /// must already be expressed relative to the eye (done per-vertex with /// double-single precision in the shader), so the large eye translation /// never enters the f32 matrix and large-coordinate jitter disappears. pub fn view_proj_rte(&self, bounds: Rectangle) -> Mat4 { - let near = self.distance * 0.001; - let far = self.distance * 1000.0; let aspect = bounds.width / bounds.height; let up_dir = self.rotation * Vec3::Y; @@ -129,10 +142,13 @@ impl Camera { view.w_axis = glam::vec4(0.0, 0.0, 0.0, 1.0); let proj = match self.projection { - Projection::Perspective => Mat4::perspective_rh(self.fov_y, aspect, near, far), + Projection::Perspective => { + Mat4::perspective_rh(self.fov_y, aspect, self.distance * 0.001, self.distance * 1000.0) + } Projection::Orthographic => { let h = self.ortho_size(); let w = h * aspect; + let (near, far) = self.ortho_depth_range(); Mat4::orthographic_rh(-w, w, -h, h, near, far) } };