From 3a4b878304e5442868a8cca49b444df681ae69fd Mon Sep 17 00:00:00 2001 From: miloszunio Date: Sun, 1 Nov 2026 13:12:01 +0100 Subject: [PATCH] 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)