使用QOpenGLWidget无法读取GL_DEPTH_COMPONENT

3

我正在实现在QOpenGLWidget中选择3D对象的简单点击功能。为此,我需要将2D鼠标坐标转换为3D世界空间坐标。之前我已经使用QGLWidget实现了整个过程。但是,在使用QOpenGLWidget时,我无法读取像素的GL_DEPTH_COMPONENT:

float z;
glReadPixels(pixel.x, height - pixel.y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z);

'z' 始终为 0。 为了确保我的像素坐标正确,我尝试接收 GL_RGBA 值:

float rgba[4];
glReadPixels((int)p_temp.x(), (int) (viewport[3] - p_temp.y()), 1, 1, GL_RGBA, GL_FLOAT, rgba);

该函数返回正确的像素颜色。

为了使其正常工作,我必须将像素坐标的域从本地更改为父级坐标。这可能是因为GL_VIEWPORT相应地设置为父部件的大小造成的:

int viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);

对于QGLWidget,这将返回:{0, 0, this->width(), this->height()}

对于QOpenGLWidget,这将返回:{0, 0, this->parent()->width(), this->parent()->height()}

顺便提一下,我的OpenGL版本是4.5,并且使用GL_DEPTH_COMPONENT时没有出现任何OpenGL错误。

现在,我有点不知道自己可能错过了什么。有什么想法吗?


尝试使用GL_DEPTH_STENCIL而不是GL_DEPTH_COMPONENT? - Andrew Williamson
你怎么知道深度没有被读取?你得到了什么值,你期望得到什么值?另外,你确定你有深度缓冲区吗? - Nicol Bolas
@Nicol Bolas 如果没有深度缓冲区,使用GL_DEPTH_COMPONENT读取时,OpenGL会报错吗? - Andrew Williamson
我已经调用了glEnable(GL_DEPTH_TEST)来启用默认的深度缓冲区。在尝试读取深度数据后,我检查了glGetError()并返回了GL_NO_ERROR。有趣的是,即使我注释掉了glEnable(GL_DEPTH_TEST),它仍然会返回相同的结果,因此我不确定该函数是否考虑了实际上是否存在深度缓冲区以从中读取像素数据。如上所述,无论我点击哪个对象,所有值都为0。 - Basti Vagabond
@AndrewWilliamson 是的,它应该是这样的:"如果格式为GL_DEPTH_COMPONENT且没有深度缓冲区,则生成GL_INVALID_OPERATION。" - Reto Koradi
如果是这样的话,我猜我的深度缓冲区并不是问题所在。据我了解,深度值始终会在近裁剪面和远裁剪面之间进行归一化... 这篇文章 暗示背景具有默认深度值1。这是有道理的,因为深度测试在远裁剪面结束。正如我所提到的,每个像素的GL_DEPTH_COMPONENT值都为0。从技术上讲,这意味着片段位于近裁剪面上...考虑到我的几何图形明显在其后面,这是没有意义的。 - Basti Vagabond
1个回答

5

QOpenGLWidget工作在一个底层的帧缓冲对象(FBO)中,如果启用了多重采样,则不能简单地从该FBO读取深度分量。最简单的解决方案是将样本设置为零,因此您的代码将如下所示:

QSurfaceFormat format;
format.setVersion(2, 1);
format.setProfile(QSurfaceFormat::CoreProfile);

format.setSamples(0);

QSurfaceFormat::setDefaultFormat(format);

或者您可以使用多重采样,例如format.setSamples(4),但还需要一个不带多重采样的额外FBO,深度缓冲区可以在其中复制。

class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
//
// OTHER WIDGET RELATED STUFF
//
QOpenGLFramebufferObject *mFBO=nullptr;

MyGLWidget::paintGL()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  //
  // DRAW YOUR SCENE HERE!
  //

  QOpenGLContext *ctx = QOpenGLContext::currentContext();

  // FBO must be re-created! is there a way to reset it?
  if(mFBO) delete mFBO;

  QOpenGLFramebufferObjectFormat format;
  format.setSamples(0);
  format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
  mFBO = new QOpenGLFramebufferObject(size(), format);

  glBindFramebuffer(GL_READ_FRAMEBUFFER, defaultFramebufferObject());
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFBO->handle());
  ctx->extraFunctions()->glBlitFramebuffer(0, 0, width(), height(), 0, 0, mFBO->width(), mFBO->height(), GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT, GL_NEAREST);

  mFBO->bind(); // must rebind, otherwise it won't work!

  float mouseDepth = 1.f;
  glReadPixels(mouseX, mouseY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &mouseDepth);

  mFBO->release();
}
};

1
我和楼主有完全相同的问题,这并没有解决我的问题。 - Paul Childs
首先,在调用小部件的show()之前,尝试在main.cpp中将QSurfaceFormat::samples设置为零。必须这样工作,因为FBO不会进行多重采样。https://www.khronos.org/opengl/wiki/GL_EXT_framebuffer_multisample - Ponzifex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接