diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index 3b18cfe0d..180a32144 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); @@ -5497,53 +5497,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) 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