7

我遇到了这个问题,并且知道它可以做得更好。

问题:

当用 Qt 小部件覆盖 QGLWidget(Qt OpenGL 上下文视图)时,Qt 在每个 Qt 帧之后重绘这些小部件。

Qt 并不是为了不断地以 >60fps 的速度重绘整个窗口,所以这非常慢。

我的点子:

让 Qt 使用其他东西来绘制:透明纹理。让 OpenGL 在重绘时使用此纹理并将其绘制在其他所有内容之上。使 Qt 将与 OpenGL 上下文视图的所有交互重定向到绘制到纹理上的小部件。

优点是 Qt 只需要在需要时重绘(例如,悬停或单击小部件,或文本字段中的文本光标闪烁),并且可以进行更快的部分重绘。

我的问题:

如何解决这个问题?我怎样才能告诉 Qt 绘制到纹理?我如何将与小部件的交互重定向到另一个小部件(例如,如果我将鼠标移动到上下文视图中复选框位于绘制到纹理小部件中的区域上方,Qt 应该将此事件注册到复选框并重新绘制以反映它的状态)

4

1 回答 1

5

出于同样的原因,我将我的 2D 和 3D 渲染分开用于类似 CAD 的应用程序,尽管在我的情况下,我的 2D 东西不是小部件 - 但它不应该有所作为。这是解决问题的方法:

  1. 当您的小部件更改将其渲染到 aQGLFramebufferObject时,请通过将 FBO 用作您和调用中的QPaintDevicefor a来执行此操作。对您拥有的许多小部件重复此操作,但仅在同一个 FBO 上 - 不要为每个小部件创建一个 FBO……请记住先清除它,就像“正常”帧缓冲区一样。QPainterQGLWidget::paintEvent(..)myWidget->render( myQPainter, ...)
  2. QGLFramebufferObject当您当前的 OpenGL 背景发生变化时,使用标准 OpenGL 调用以相同的方式将其渲染到另一个上。
  3. 创建一个通过顶点着色器(“相机”将只是一个单位立方体)和一个非常简单的片段着色器,可以将两个纹理叠加在一起。
  4. 在结束时QGLWidget::paintEvent(..),激活您的着色器程序,将帧缓冲区绑定为它的纹理(myFBO->texture()获取句柄),并渲染一个单元四边形。因为您的相机是一个单位正方形,并且视口大小定义了 FBO 大小,所以它将完美地填充视口像素。

然而,这是容易的部分......困难的部分是小部件交互。因为您本质上是在渲染一个“代理”,所以您必须中继“真实”和“代理”小部件之间的交互,同时保持“真实”小部件不可见。这是我将如何开始:

  • 一些操作系统在渲染小部件而不显示它们时有点奇怪,因此您可能必须在实例化后显示然后隐藏小部件 - 由于 Qt 中聪明的绘制队列,它不太可能真正出现在屏幕上。
  • 捕获视口中的所有鼠标事件,计算出光标在哪个“代理”小部件上(如果有),然后将其偏移以获得“真实”隐藏小部件的相对位置 - 该值将取决于哪个父对象'真正的'小部件有,如果有的话。然后在重绘小部件帧缓冲区之前将事件传递给“真实”小部件。

我应该声明我还必须创建一个“标记”系统来很好地处理重绘。您不希望每个小部件事件都触发小部件 FBO 重绘,因为可能有许多同时发生的事件(不要只考虑鼠标) - 但您只想要一个重绘。所以我创建了一个系统,如果应用程序中的任何东西都可以在视觉上改变视口中的任何东西,那么它会将视口标记为“脏”。然后设置一个QTimer无论您瞄准多少fps(在我的情况下,场景可能会变得非常沉重,所以我还计时了一帧花费了多长时间,然后将该值+10%用作下一次检查的计时器延迟,这样系统就渲染滞后时不会被轰炸)。然后检查脏状态:如果脏,重绘;否则不要。我发现使用两个脏标志让生活变得更轻松,一个用于 3D 东西,一个用于 2D - 但是如果您需要为 OpenGL 绘图保持恒定的绘制速率,则可能不需要两个。

我想我所做的并不是最简单的方法,但它为调优和分析提供了充足的空间——从长远来看,这让生活更轻松。所有的答案肯定不在这篇文章中,但希望它能让你走上策略的道路。

于 2012-08-07T16:52:48.757 回答