From 3a4b878304e5442868a8cca49b444df681ae69fd Mon Sep 17 00:00:00 2001 From: miloszunio Date: Sun, 1 Nov 2026 13:12:01 +0100 Subject: [PATCH 1/2] fix(gl46): read back the editor cursor depth and correct the depth pass bias GL46 read the depth under the editor cursor in the depth pass of the scene, drawn with glPolygonOffset(-1, -1), and took it as it was: the ground came out about a pixel row farther than it lies (metres at a grazing angle). Same correction as 7078edf for vulkan: a 3x3 block read back, the slope and the unit of the bias taken back. The depth is also worked out in the view of the frame it was asked in, and the point kept in world space, so a moved cursor or camera does not shift it. See #238. --- rendering/opengl46/opengl46renderer.cpp | 97 ++++++++++++++++++------- 1 file changed, 72 insertions(+), 25 deletions(-) diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index 55959a4b9..649722015 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -407,7 +407,7 @@ bool opengl46_renderer::Init(GLFWwindow *Window) if (Global.iMultisampling) { m_depth_pointer_rb = std::make_unique(); - m_depth_pointer_rb->alloc(depth_pointer_format, 1, 1); + m_depth_pointer_rb->alloc(depth_pointer_format, 3, 3); // the pixel under the cursor and the ones around it m_depth_pointer_fb = std::make_unique(); m_depth_pointer_fb->attach(*m_depth_pointer_rb, GL_DEPTH_ATTACHMENT); @@ -5496,53 +5496,100 @@ void opengl46_renderer::Pick_Node_Callback(std::functionis_busy()) { - // determine point to examine - glm::vec2 mousepos = Global.cursor_pos; - mousepos.y = Global.window_size.y - mousepos.y; // cursor coordinates are flipped compared to opengl + // the depths asked for before, around where the cursor was then + auto const count{m_cursorblocksize.x * m_cursorblocksize.y}; + std::array depths{}; + if ((count > 0) && (count <= static_cast(depths.size())) && m_depth_pointer_pbo->read_data(m_cursorblocksize.x, m_cursorblocksize.y, depths.data(), 4)) + { + auto const centre{m_cursorposition - m_cursorblockcorner}; + auto const depth_at = [&](glm::ivec2 const Pixel) { + if ((Pixel.x < 0) || (Pixel.y < 0) || (Pixel.x >= m_cursorblocksize.x) || (Pixel.y >= m_cursorblocksize.y)) + { + return 0.f; + } + return depths[static_cast(Pixel.y * m_cursorblocksize.x + Pixel.x)]; + }; + float pointdepth{depth_at(centre)}; + if (m_cursorbiased && (pointdepth > 0.f)) + { + // read in the depth pass of the scene (Render_Scene_Depth()), which draws everything a little farther than it lies + // (glPolygonOffset(-1, -1)), so the colour pass passes on the same surface. the slope of the depth grows towards the + // horizon, and the ground seen at a grazing angle came out a pixel's worth of ground farther than it lies, metres off + // some way from the camera. the slope is taken back as in the vulkan renderer: the pixels next to it on the same triangle + // are moved by the same bias, so their difference is the slope; of the two sides the smaller, which is less likely + // to lie on another surface, and the larger of the two axes, the one implementations use for m. then the unit, r + auto const slope = [&](glm::ivec2 const Step) { + auto result{-1.f}; + for (auto const side : {-1, 1}) + { + auto const other{depth_at(centre + Step * side)}; + if (other > 0.f) + { + auto const difference{std::abs(other - pointdepth)}; + result = (result < 0.f ? difference : std::min(result, difference)); + } + } + return std::max(result, 0.f); + }; + pointdepth += std::max(slope({1, 0}), slope({0, 1})) + std::ldexp(1.f, std::ilogb(pointdepth) - 23); + } + m_mousehit = pointdepth > 0.0f; + if (m_mousehit) + { + auto const offset{glm::unProjectZO(glm::vec3(m_cursorposition, pointdepth), m_cursorview, m_cursorprojection, m_cursorviewport)}; + m_worldmouseposition = m_cursorcamera + glm::dvec3{offset}; + m_mouseknown = true; + } + } - glm::ivec2 bufferpos; - bufferpos = glm::ivec2(mousepos * glm::vec2(Global.gfx_framebuffer_width, Global.gfx_framebuffer_height) / glm::vec2(Global.window_size)); - bufferpos = glm::clamp(bufferpos, glm::ivec2(0, 0), glm::ivec2(Global.gfx_framebuffer_width - 1, Global.gfx_framebuffer_height - 1)); - - float pointdepth = std::numeric_limits::max(); - - m_depth_pointer_pbo->read_data(1, 1, &pointdepth, 4); + // ask for the depths around where the cursor is now + glm::vec2 mousepos = Global.cursor_pos; + mousepos.y = Global.window_size.y - mousepos.y; // cursor coordinates are flipped compared to opengl + glm::ivec2 const extent{Global.gfx_framebuffer_width, Global.gfx_framebuffer_height}; + auto bufferpos{glm::ivec2(mousepos * glm::vec2(extent) / glm::max(glm::vec2(Global.window_size), glm::vec2(1.f)))}; + bufferpos = glm::clamp(bufferpos, glm::ivec2(0, 0), extent - 1); + // the pixels around the one under the cursor too, for the slope of the depth there + auto const blocksize{glm::clamp(extent, glm::ivec2(1), glm::ivec2(3))}; + auto const blockcorner{glm::clamp(bufferpos - 1, glm::ivec2(0), extent - blocksize)}; if (!Global.iMultisampling) { - m_depth_pointer_pbo->request_read(bufferpos.x, bufferpos.y, 1, 1, 4, GL_DEPTH_COMPONENT, GL_FLOAT); + m_depth_pointer_pbo->request_read(blockcorner.x, blockcorner.y, blocksize.x, blocksize.y, 4, GL_DEPTH_COMPONENT, GL_FLOAT); } else if (Global.gfx_skippipeline) { - gl::framebuffer::blit(nullptr, m_depth_pointer_fb.get(), bufferpos.x, bufferpos.y, 1, 1, GL_DEPTH_BUFFER_BIT, 0); + gl::framebuffer::blit(nullptr, m_depth_pointer_fb.get(), blockcorner.x, blockcorner.y, blocksize.x, blocksize.y, GL_DEPTH_BUFFER_BIT, 0); m_depth_pointer_fb->bind(); - m_depth_pointer_pbo->request_read(0, 0, 1, 1, 4, GL_DEPTH_COMPONENT, GL_FLOAT); + m_depth_pointer_pbo->request_read(0, 0, blocksize.x, blocksize.y, 4, GL_DEPTH_COMPONENT, GL_FLOAT); m_depth_pointer_fb->unbind(); } else { - gl::framebuffer::blit(m_viewports.front()->msaa_fb.get(), m_depth_pointer_fb.get(), bufferpos.x, bufferpos.y, 1, 1, GL_DEPTH_BUFFER_BIT, 0); + gl::framebuffer::blit(m_viewports.front()->msaa_fb.get(), m_depth_pointer_fb.get(), blockcorner.x, blockcorner.y, blocksize.x, blocksize.y, GL_DEPTH_BUFFER_BIT, 0); m_depth_pointer_fb->bind(); - m_depth_pointer_pbo->request_read(0, 0, 1, 1, 4, GL_DEPTH_COMPONENT, GL_FLOAT); + m_depth_pointer_pbo->request_read(0, 0, blocksize.x, blocksize.y, 4, GL_DEPTH_COMPONENT, GL_FLOAT); m_viewports.front()->msaa_fb->bind(); } - - if (pointdepth != std::numeric_limits::max()) - { - m_mousehit = pointdepth > 0.0f; - if (pointdepth > 0.0f) - m_worldmousecoordinates = glm::unProjectZO(glm::vec3(bufferpos, pointdepth), glm::mat4(glm::mat3(m_colorpass.pass_camera.modelview())), m_colorpass.pass_camera.projection(), - glm::vec4(0, 0, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height)); - } + // and the view it's drawn in, for when the depths come back + m_cursorposition = bufferpos; + m_cursorblockcorner = blockcorner; + m_cursorblocksize = blocksize; + m_cursorbiased = m_scenedepthpass; + m_cursorview = glm::mat4(glm::mat3(m_colorpass.pass_camera.modelview())); + m_cursorprojection = m_colorpass.pass_camera.projection(); + m_cursorcamera = m_colorpass.pass_camera.position(); + m_cursorviewport = glm::vec4(0, 0, extent.x, extent.y); } - return m_colorpass.pass_camera.position() + glm::dvec3{m_worldmousecoordinates}; + return m_colorpass.pass_camera.position() + Mouse_Position(); } void opengl46_renderer::Update(double const Deltatime) From df66315f55eab733cbff79a10e8a1d4f0b212573 Mon Sep 17 00:00:00 2001 From: miloszunio Date: Sun, 1 Nov 2026 13:12:41 +0100 Subject: [PATCH 2/2] fix(gl46): keep the editor cursor point in world space, with the view of its depth read --- rendering/opengl46/opengl46renderer.h | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index 788e6b80d..a49c8dd5a 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -150,7 +150,7 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk { void Forget_Node( scene::basic_node const *Node ) override; glm::dvec3 - Mouse_Position() const override { return m_worldmousecoordinates; } + Mouse_Position() const override { return m_mouseknown ? m_worldmouseposition - m_colorpass.pass_camera.position() : glm::dvec3 { 0.0 }; } bool Mouse_Hit() const override { return m_mousehit; } glm::mat4 @@ -748,8 +748,20 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk { std::array m_shadowpass; // parametrs of most recent shadowmap pass for each of csm stages TSubModel const *m_pickcontrolitem{nullptr}; scene::basic_node *m_picksceneryitem{nullptr}; - glm::vec3 m_worldmousecoordinates { 0.f }; + // the point of the scene under the cursor of the editor, in world space (Update_Mouse_Position()) + glm::dvec3 m_worldmouseposition { 0.0 }; + bool m_mouseknown { false }; // whether the cursor hit anything yet bool m_mousehit { false }; + // the depths around the cursor asked of the gpu: where they lie in the image, whether they come from the depth pass of the + // scene (drawn a little farther than it lies), and the view they were drawn in + glm::ivec2 m_cursorposition { 0 }; + glm::ivec2 m_cursorblockcorner { 0 }; + glm::ivec2 m_cursorblocksize { 0 }; + bool m_cursorbiased { false }; + glm::mat4 m_cursorview { 1.f }; + glm::mat4 m_cursorprojection { 1.f }; + glm::dvec3 m_cursorcamera { 0.0 }; + glm::vec4 m_cursorviewport { 0.f }; #ifdef EU07_USE_DEBUG_CAMERA renderpass_config m_worldcamera; // debug item #endif