QT 您所在的位置:网站首页 openhl渲染gpu QT

QT

2023-08-15 13:31| 来源: 网络整理| 查看: 265

Qt 5的图形架构非常依赖OpenGL作为底层3D图形API,但近年来,随着Metal和Vulkan的推出,Qt 6完全改变了局面。Qt Quick中的所有3D图形现在都建立在新的3D图形抽象层之上,该抽象层称为 渲染硬件接口(RHI) 。这使Qt可以使用目标OS /平台上原生的3D图形API。所以Qt Quick现在默认会在Windows上使用Direct3D,在macOS上使用Metal。有关RHI的学习资料可参照QT官网。

本文主要使用QT5.14来学习QT封装的OpenGL的渲染。

一、QT中实现OpenGL渲染 1.1 QWindow实现渲染 1.1.1 框架介绍

首先必须继承QWindow类,之后重载两个虚函数event、exposeEvent。

event(): 重写此方法以处理发送到窗口的任何事件。如果事件已被识别并处理,则返回true.exposeEvent(): 每当窗口的某个区域无效时(例如,由于窗口系统中的曝光发生更改),窗口系统都会发送暴露事件。 该应用程序一旦获得isExposed()为true的值,便可以使用QBackingStore和QOpenGLContext开始渲染到窗口中。如果将窗口移出屏幕,使其完全被另一个窗口(图标化或类似窗口)遮盖,则可能会调用此函数,并且isExposed()的值可能更改为false。发生这种情况时,应用程序应停止其呈现,因为它不再对用户可见。isExposed(): 返回此窗口是否在窗口系统中公开。 class OpenGLWindow : public QWindow { Q_OBJECT public: explicit OpenGLWindow(QWindow *parent = 0); // ... protected: bool event(QEvent *event) override; void exposeEvent(QExposeEvent *event) override; private: // ... }; 1.1.2 设置窗口的surfaceType

您可以使用基于网格的QPainter或OpenGL进行绘制。最好在类构造函数中进行设置,例如:

OpenGLWindow::OpenGLWindow(QWindow *parent) : QWindow(parent) { setSurfaceType(QWindow::OpenGLSurface); }

通过调用函数setSurfaceType(QWindow :: OpenGLSurface),您可以指定要创建本机OpenGL窗口。

此时,QT框架会发送以下两个事件:

QEvent::UpdateRequest: 部件应该被重绘;QEvent::Expose: 当其屏幕上的内容无效,发送到窗口,并需要从后台存储刷新。

在实现重载的函数void ExposureEvent(QExposeEvent * event)中:

void OpenGLWindow::exposeEvent(QExposeEvent * /*event*/) { renderNow(); //渲染操作 }

此处我们仅需要进行渲染即可(下一小节介绍)。

在通用事件处理函数的实现中,event()我们仅UpdateRequest选择事件:

bool OpenGLWindow::event(QEvent *event) { switch (event->type()) { case QEvent::UpdateRequest: renderNow(); //渲染操作 return true; default: return QWindow::event(event); } }

然后我们的任务将很清楚 renderNow()实现使用OpenGL绘制的函数 。

1.1.3 OpenGL绘制实现

在Qt中QOpenGLFunctions封装了对本机OpenGL函数的访问。它既可以保留为数据成员,也可以作为实现继承。

1.1.3.1 框架调整 class OpenGLWindow : public QWindow, protected QOpenGLFunctions { Q_OBJECT public: explicit OpenGLWindow(QWindow *parent = 0); virtual void initialize() = 0; virtual void render() = 0; public slots: void renderLater(); void renderNow(); protected: bool event(QEvent *event) override; void exposeEvent(QExposeEvent *event) override; QOpenGLContext *m_context; // wraps the OpenGL context };

有两个纯粹的虚函数initialize()和render(),没有它们,OpenGL程序将无法执行。因此,该基类的用户提供这些功能(内容将在后面说明)。

除了renderNow()上面已经调用过的函数(其任务是即时OpenGL绘图)之外,还有另一个函数renderLater()。他们的任务最终是请求一个与垂直同步匹配的新字符调用,这最终对应UpdateRequest于在应用程序事件循环中发送事件。

void OpenGLWindow::renderLater() { requestUpdate(); }

严格来说,您还可以保存函数并直接requestUpdate()调用插槽,但是该名称最终指示在下一个VSync之前不会进行绘制。

在这一点上,可以预期与帧速率同步的两件事:

用双缓冲区绘制默认情况下,Qt配置为QEvent::UpdateRequest始终将其发送到VSync。对于60Hz的刷新率,当然可以假设直到切换字符缓冲区为止的时间不超过16 ms

发送UpdateRequest到事件循环中的变量的优点是:在同步周期(即16ms内)内多次调用此函数(例如通过信号插槽连接)最终被组合为一个事件,因此每个VSync仅绘制一次。否则会浪费计算时间。

最后,m_context应该指出新的私有成员变量。该上下文最终封装了本机OpenGL上下文,即OpenGL中使用的状态机。尽管这是动态生成的,但我们不需要析构函数,因为我们还会自动m_context清理QObject-parent关系。

在构造函数中,我们使用nullptr初始化指针变量。

OpenGLWindow::OpenGLWindow(QWindow *parent) : QWindow(parent), m_context(nullptr) { setSurfaceType(QWindow::OpenGLSurface); } 1.1.3.2 初始化OpenGL窗口

现在有几种初始化OpenGL绘图窗口的方法。您可以立即在构造函数中执行此操作,但是所有必需的资源(包括mesh/纹理等)都应该已经初始化。

现在,您可以实现自己的初始化函数,该函数最初由类的用户调用。或者,您可以在第一次显示窗口时执行此操作。这里有很多回旋余地,根据初始化对错误的复杂性和敏感性,具有显式初始化功能的变量当然是好的。

此处使用首次使用初始化的变体(这是在Qt中使用对话框时的常见模式)。这意味着该函数renderNow()需要初始化:

void OpenGLWindow::renderNow() { // only render if exposed if (!isExposed()) return; bool needsInitialize = false; // initialize on first call if (m_context == nullptr) { m_context = new QOpenGLContext(this); m_context->setFormat(requestedFormat()); m_context->create(); needsInitialize = true; } m_context->makeCurrent(this); if (needsInitialize) { initializeOpenGLFunctions(); initialize(); // call user code } render(); // call user code m_context->swapBuffers(this); }

该函数由exposeEvent()和调用一次event()。在这两种情况下,仅应在窗口实际可见的情况下进行绘制。因此,isExposed()首先检查该功能以查看窗口是否完全可见。如果不可见退出。

1.1.3.3 QOpenGLContext对象

现在是上面提到的首次使用初始化。

首先QOpenGLContext创建对象。接下来,设置各种特定于OpenGL的要求,从而将在QWindow中设置的格式传输到QOpenGLContext。

requestFormat()函数返回QWindow为表面(设置的格式QSurfaceFormat。其中包含颜色和深度缓冲区以及OpenGL渲染的抗锯齿设置。

requestFormat(): 返回此窗口的请求表面格式。如果平台实现不支持所请求的格式,则requestedFormat将与实际的窗口格式不同。

在初始化OpenGL上下文时,必须已经为QWindow定义了这种格式,即在第一次show()调用OpenGLWindow之前。

如果要避免这种错误源,则QSurfaceFormat实际上必须在请求所需函数时将初始化移至特殊函数。

通过调用m_context->create() OpenGL上下文(即状态)来创建,从而使用先前设置的格式参数。

如果要稍后更改格式参数(例如,抗锯齿),则必须首先在上下文对象中重置格式,然后create()再次调用。这将清除并替换之前的上下文。

上下文创建后,最重要的功能makeCurrent()和swapBuffers()服务。

调用m_context->makeCurrent(this)将上下文对象的内容传输到OpenGL状态。初始化的第二步在于调用函数 QOpenGLFunctions :: initializeOpenGLFunctions()。最后,平台特定的OpenGL库是动态集成的,并且将函数指针glXXX…提取到本地OpenGL函数()。最后,调用initialize()具有用户特定初始化的函数。然后,用户必须在功能中进行3D场景render()的实际渲染。最后,我们m_context->swapBuffers(this)将窗口缓冲区与渲染缓冲区交换。 QOpenGLFunctions :: initializeOpenGLFunctions(): 为当前上下文初始化OpenGL函数解析。调用此函数后,QOpenGLFunctions对象只能与当前上下文以及与其共享的其他上下文一起使用。再次调用initializeOpenGLFunctions()以更改对象的上下文关联。QOpenGLContext :: makeCurrent(): 在给定的surface上使当前线程中的上下文成为当前上下文。成功返回true,否则返回false。如果表面未暴露,或者由于例如应用程序被挂起而导致图形硬件不可用,则后者可能发生。QOpenGLContext :: swapBuffers(): 交换渲染表面的前后缓冲区。调用此命令以完成OpenGL渲染的框架,并确保在发出任何其他OpenGL命令(例如,作为新框架的一部分)之前再次调用makeCurrent()。

更新窗口缓冲区后,无需重新渲染即可将窗口移动到屏幕上的任何位置,甚至最小化。至少在我们开始处理场景中的动画之前,这是正确的。对于没有动画的应用程序,不要像Unity / Unreal / Irrlicht等游戏引擎那样自动重新渲染每一帧是有意义的。

如果我们仍然要设置动画(如果只是平滑的跟踪镜头),则应在函数结尾处调用renderNow()该函数renderLater(),以便在下一个VSync处接收新的调用。哦,是的:如果窗口是隐藏的(未暴露),则该函数当然会快速退出,并且renderLater()不会调用该函数。那将停止动画。为了使其再次开始运行,有一个已实现的事件函数exposeEvent()可以再次触发渲染。

这样,将完成OpenGL渲染窗口的中央基类。现在,我们便可以使用此基类实现渲染。

1.1.3.4 渲染窗口的实现

用户可自行创建类对象继承OpenGLWindow ,使用头文件调用特定的呈现窗口。比如:

class TriangleWindow : public OpenGLWindow { public: TriangleWindow(); ~TriangleWindow() Q_DECL_OVERRIDE; void initialize() Q_DECL_OVERRIDE; void render() Q_DECL_OVERRIDE; private: // VAO QOpenGLVertexArrayObject m_vao; // Vertex buffer QOpenGLBuffer m_vertexBufferObject; // shader programs QOpenGLShaderProgram *m_program; };

上述案例中的私有成员变量则是QT中对OpenGL的封装,具体使用下文介绍。

1.2 QOpenGLWindow实现渲染

QOpenGLWindow是增强的QWindow,它允许使用兼容QOpenGLWidget且类似于旧版QGLWidget的API轻松创建执行OpenGL渲染的窗口。与QOpenGLWidget不同,QOpenGLWindow不依赖于widgets模块,并提供更好的性能。

一个典型的应用程序将继承QOpenGLWindow并重新实现以下虚函数:

initializeGL(): 执行OpenGL资源初始化resizeGL(): 设置转换矩阵和其他与窗口大小有关的资源paintGL(): 发出OpenGL命令或使用QPainter绘制

要计划重绘,请调用update()函数。请注意,这不会立即导致对paintGL()的调用。连续多次调用update()不会以任何方式改变行为。

这是一个插槽,因此可以将其连接到QTimer::timeout()信号以执行动画。但是请注意,在现代OpenGL世界中,依靠同步到显示器的垂直刷新率是一个更好的选择。有关交换间隔的说明,请参见setSwapInterval()。交换间隔为1,这在大多数系统上都是默认情况下的情况,每次重新粉刷后QOpenGLWindow在内部执行的swapBuffers()调用将阻塞并等待vsync。这意味着只要交换完成,就可以通过调用update()再次调度更新,而无需依赖计时器。

要请求上下文的特定配置,请像其他任何QWindow一样使用setFormat()。除其他外,这允许请求给定的OpenGL版本和配置文件,或启用深度和模板缓冲区。

与QWindow不同,QOpenGLWindow允许自己打开一个画家并执行基于QPainter的绘制。

QOpenGLWindow支持多种更新行为。默认值NoPartialUpdate等效于基于OpenGL的常规QWindow或旧版QGLWidget。相比之下,PartialUpdateBlit以及PartialUpdateBlend更符合QOpenGLWidget工作的方式,其中总有一个额外的,专用的帧缓冲区对象存在。通过牺牲一些性能,这些模式可以在每个绘画上仅重画一个较小的区域,并保留前一帧的其余内容。这对于使用QPainter进行增量渲染的应用程序很有用,因为这样一来,它们不必在每个paintGL()调用上重新绘制整个窗口内容。

与QOpenGLWidget相似,QOpenGLWindow支持Qt :: AA_ShareOpenGLContexts属性。启用后,所有QOpenGLWindow实例的OpenGL上下文将彼此共享。这允许访问彼此的共享OpenGL资源。

1.2.1 QOpenGLWindow类的实现 1.2.1.1 QWindow与OpenGLWindow区别

要了解教程QWindow与OpenGLWindow类的异同,得具体看一下该类的实现,其中最重要的区别是继承层次结构。QOpenGLWindow从中得出QOpenGLPaintDevice基于光栅的硬件加速工程图QPainter。

但是有一个小问题。引用手册:

OpenGL绘制引擎中的抗锯齿是使用多重采样完成的。大多数硬件需要大量内存来进行多重采样,因此产生的质量与软件绘画引擎的质量不相称。OpenGL绘画引擎的优势在于其性能,而不是视觉渲染质量。(适用于QOpenGLPaintDevice的Qt文档5.9)

如果在OpenGL窗口中绘制了褪色的小部件或控件,则还会影响应用程序的整体外观,而且还会影响具有尖锐边缘的经典小部件。当Windows 10中的应用程序最终绘制一个像素缓冲区时,您可能会从Windows的模糊窗口中熟悉该问题,然后将该像素缓冲区作为纹理插入到3D窗口表面中。

如果要在OpenGL小部件中使用现有的绘图功能(基于QPainter),这仍然很有帮助。如果不需要该功能,PaintDevice及其所需的功能会带来一些不必要的开销(尤其是内存消耗)。

1.2.1.2 QWindow与OpenGLWindow相似之处 a.构造函数

构造函数看起来几乎与OpenGLWindow最初的类完全一样。除了将参数传递到私有Pimpl类中。

QOpenGLWindow::QOpenGLWindow(QOpenGLWindow::UpdateBehavior updateBehavior, QWindow *parent) : QPaintDeviceWindow(*(new QOpenGLWindowPrivate(nullptr, updateBehavior)), parent) { setSurfaceType(QSurface::OpenGLSurface); } b.事件处理函数 void QOpenGLWindow::paintEvent(QPaintEvent * /*event*/ ) { paintGL(); } void QOpenGLWindow::resizeEvent(QResizeEvent * /*event*/ ) { Q_D(QOpenGLWindow); d->initialize(); resizeGL(width(), height()); }

这paintEvent()简单地传递给用户要实现的功能paintGL()。在这方面,类似于QEvent::UpdateRequest等待的OpenGLWidget中的事件处理。但是,在调用paintEvent()函数直至创建QPaintEvent对象的过程中,将执行许多中间步骤,而这完全不需要。当您查看呼叫链时,思路将变得很清楚:

QPaintDeviceWindow::event(QEvent *event) // waits for QEvent::UpdateRequest QPaintDeviceWindowPrivate::handleUpdateEvent() QPaintDeviceWindowPrivate::doFlush() // calls QPaintDeviceWindowPrivate::paint() bool paint(const QRegion ®ion) { Q_Q(QPaintDeviceWindow); QRegion toPaint = region & dirtyRegion; if (toPaint.isEmpty()) return false; // Clear the region now. The overridden functions may call update(). dirtyRegion -= toPaint; beginPaint(toPaint); // here we call QOpenGLWindowPrivate::beginPaint() QPaintEvent paintEvent(toPaint); q->paintEvent(&paintEvent); // here we call QOpenGLWindowPrivate::paintEvent() endPaint(); // here we call QOpenGLWindowPrivate::endPaint() return true; }

或者,paintGL()可以从事件QPaintDeviceWindow::exposeEvent()处理例程进行调用,在该例程中直接进行调用QPaintDeviceWindowPrivate::doFlush()。这些功能beginPaint()并 endPaint()照顾临时帧缓冲区,在其中进行UpdateBehaviorQOpenGLWindow::PartialUpdateBlit和QOpenGLWindow::PartialUpdateBlend渲染。没有这些模式,该功能几乎不会发生。

c.初始化

resizeEvent()事件处理例程中的初始化调用也很有意思

void QOpenGLWindowPrivate::initialize() { Q_Q(QOpenGLWindow); if (context) return; if (!q->handle()) qWarning("Attempted to initialize QOpenGLWindow without a platform window"); context.reset(new QOpenGLContext); context->setShareContext(shareContext); context->setFormat(q->requestedFormat()); if (!context->create()) qWarning("QOpenGLWindow::beginPaint: Failed to create context"); if (!context->makeCurrent(q)) qWarning("QOpenGLWindow::beginPaint: Failed to make context current"); paintDevice.reset(new QOpenGLWindowPaintDevice(q)); if (updateBehavior == QOpenGLWindow::PartialUpdateBlit) hasFboBlit = QOpenGLFramebufferObject::hasOpenGLFramebufferBlit(); q->initializeGL(); }

实际上,该函数几乎OpenGLWindow::renderNow()与QWindow中的函数初始化部分完全一样。当然,除了QOpenGLWindowPaintDevice创建另一个实例。

1.3 QOpenGLWidget实现渲染 1.3.1 QOpenGLWidget介绍

在所有Qt OpenGL类中,这QOpenGLWidget是迄今为止记录最好的类。 具体列出以下几点:

所有渲染都发生在OpenGL帧缓冲区对象中。由于由帧缓冲区对象支持,因此QOpenGLWidget的行为与QOpenGLWindow非常相似,其更新行为设置为PartialUpdateBlit或PartialUpdateBlend。这意味着在两次paintGL()调用之间保留了内容,从而可以进行增量渲染。 注意: 大多数应用程序不需要增量渲染,因为它们将在每次绘画调用时渲染视图中的所有内容。将QOpenGLWidget添加到窗口中会为整个窗口打开基于OpenGL的合成。在某些特殊情况下,这可能并不理想,因此需要具有单独的本机子窗口的旧QGLWidget样式行为。了解此方法局限性的桌面应用程序(例如,涉及重叠,透明,滚动视图和MDI区域),可以将QOpenGLWindow与QWidget::createWindowContainer()一起使用。这是QGLWidget的现代替代方案,由于缺少附加的合成步骤,因此它比QOpenGLWidget更快。强烈建议将这种方法的使用限制在没有其他选择的情况下。请注意,此选项不适用于大多数嵌入式和移动平台,并且已知在某些台式机平台(例如macOS)上也存在问题。

基本上:OpenGL图像QOpenGLWidget 始终总是首先在缓冲区中渲染,然后根据构图规则(合成)在屏幕上绘制。当然,这比直接绘制要花费更长的时间。

缓冲绘图的主要优点是可以进行增量渲染。是否需要它在很大程度上取决于实际应用。实际上,仅当要渲染的窗口由几个单独的部分组成时,这才有意义。在这种情况下,应用程序也可以由几个OpenGL窗口组成,并且每个窗口都可以单独绘制。

关于可移植性和稳定性的最后一点也许并不完全无关紧要。因此,您可以从两个方面看整个事情:

使用QOpenGLWidget,但如果出现性能问题可以考虑进行切换其他;QWindow与OpenGLWindow是一个自写的轻量级类,如果在发生兼容性问题时可以切换到QOpenGLWidget 1.3.1.1 继承关系

如下类:

class Window : public QOpenGLWidget, protected QOpenGLFunctions { public: Window(QWidget * parent = nullptr); .... protected: void initializeGL() override; void paintGL() override; .... };

该类QOpenGLWidget本身并不继承自QOpenGLFunctions,这就是为什么必须将此类指定为附加基类的原因(还有另一种方法,但是不必在源代码中进行太多调整)。与其他小部件一样,构造函数也将父指针作为参数。

功能initializeGL()和 paintGL()是在QOpenGLWidget受保护的。

1.3.1.2 初始化

必须相应地扩展构造函数,以便将parent指针传递给基类:

Window::Window(QWidget * parent) : QOpenGLWidget(parent), m_vertexColors{ QColor("#f6a509"), QColor("#cb2dde"), QColor("#0eeed1"), QColor("#068918") }, m_program(nullptr), m_frameCount(5000) { setMinimumSize(600,400); }

如果该类是一个小部件,您也可以在此处设置最小大小。必须在第一次显示之前设置大小,否则窗口小部件将不可见(并且无法放大)。

使用继承的QOpenGLFunctions函数还需要初始化,但这必须通过调用ininitializeOpenGLFunctions()中的函数在initializeGL()完成。

void Window::initializeGL() { initializeOpenGLFunctions(); .... }

这UpdateBehavior被设置为QOpenGLWidget默认QOpenGLWidget::NoPartialUpdate,所以不必另行调整。

1.3.1.3 嵌入到QWidget

可以省略窗口小部件容器,并且像其他任何窗口小部件一样嵌入窗口小部件。 具体调用如下:

.... m_window= new Window(this); m_window->setFormat(format); // *** create the layout and insert widget container QVBoxLayout * vlay = new QVBoxLayout; vlay->setMargin(0); vlay->setSpacing(0); vlay->addWidget(m_window); .... 1.3.2 性能比较

QOpenGLWidget与直接通过QWindow使用或QOpenGLWindow使用您自己的OpenGLWindow类进行绘制相比,它要慢多少?

调整窗口大小行为是不同的。调整窗口小部件的大小(在Windows和其他平台上)以及在发布模式下编译程序时都存在明显的延迟。

有区别,但貌似对整体来说影响很小。在动画期间放大/缩小窗口时,优化的延迟效果可能是个问题。

二、QT封装的OpenGL与原生OpenGL API对比 2.1 着色器程序

该类QOpenGLShaderProgram封装了着色器程序,并提供了在本机OpenGL调用中实现的各种便利功能。

m_program = new QOpenGLShaderProgram();

这大致对应于以下OpenGL命令:

unsigned int shaderProgram; shaderProgram = glCreateProgram(); 2.2 着色器程序的编译和链接

QOpenGLShaderProgram::addShaderFromSourceFile()类中有几个重载函数,在这里使用带有文件名传输的变量。这些文件在资源文件中引用,因此是通过资源路径指定的。在此处和指定着色器程序的类型很重要。

通过返回代码指示成功或失败。错误处理的功能以后有时间再整理。

最后一步是着色器程序的链接,即,自定义变量的链接(着色器程序之间的通信)。

该类的函数QOpenGLShaderProgram最终封装了以下类型的OpenGL命令:

if (!m_program->addShaderFromSourceFile( QOpenGLShader::Vertex, ":/Vertex.vert")) { qDebug() glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); std::cout // this function is called for every frame to be rendered on screen const qreal retinaScale = devicePixelRatio(); // needed for Macs with retina display glViewport(0, 0, width() * retinaScale, height() * retinaScale); // set the background color = clear color glClearColor(0.1f, 0.1f, 0.2f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // use our shader program m_program->bind(); // bind the vertex array object, which in turn binds the vertex buffer object and // sets the attribute buffer in the OpenGL context m_vao.bind(); // now draw the triangles: // - GL_TRIANGLES - draw individual triangles // - 0 index of first triangle to draw // - 3 number of vertices to process glDrawArrays(GL_TRIANGLES, 0, 3); // finally release VAO again (not really necessary, just for completeness) m_vao.release(); }

前三个glXXX命令是本机OpenGL调用,实际上应该总是以这种方式出现。调整ViewPort(glViewport(…))对于调整大小操作以及删除颜色缓冲区()是必不可少的glClear(…)(其他缓冲区将在此调用中稍后删除)。devicePixelRatio()函数适用于缩放比例合适的屏幕(主要用于配备Retina显示屏的Mac)。

只要背景颜色(纯色)不变,此调用也可以移至初始化。

然后是有趣的部分。绑定着色器程序(m_programm->bind()),然后绑定顶点数组对象(VAO)(m_vao.bind())。后者确保在OpenGL上下文中也设置了顶点缓冲区对象和属性映射。然后可以将其用于简单绘制,为此,glDrawArrays(…)将再次使用本机OpenGL命令。

程序的这一部分在原生OpenGL代码中如下所示:

glUseProgram(shaderProgram); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); glBindVertexArray(0); 2.5 资源释放

剩下的就是清理析构函数中的保留资源。

TriangleWindow::~TriangleWindow() { // resource cleanup // since we release resources related to an OpenGL context, // we make this context current before cleaning up our resources m_context->makeCurrent(this); m_vao.destroy(); m_vertexBufferObject.destroy(); delete m_program; }

由于某些资源属于当前窗口的OpenGL上下文,因此您应该首先将OpenGL上下文设置为“当前”(m_context->makeCurrent(this);),以便可以安全地释放这些资源。

这样就可以TriangleWindow完成实施。

2.6 初始化纹理

如果使用原生OpenGL代码创建纹理,则其外观如下所示:

// erstelle Texturobjekt unsigned int texture; glGenTextures(1, &texture); // binde Textur glBindTexture(GL_TEXTURE_2D, texture); // setze Attribute: // Wrap style glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); // Border color in case of GL_CLAMP_TO_BORDER float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f }; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); // Texture Filtering glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Lade Texturdaten mittels 'stb_image.h' unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0); // Kopiere Daten in Texture und Erstelle Mipmap glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D);

如果使用QOpenGLTexture的话就会变成如下所示:

// erstelle Texturobjekt QOpenGLTexture * texture = new QOpenGLTexture(QOpenGLTexture::Target2D); texture->create(); // setze Attribute // Wrap style texture->setWrapMode(QOpenGLTexture::ClampToBorder); texture->setBorderColor(Qt::red); // Texture Filtering texture->setMinificationFilter(QOpenGLTexture::NearestMipMapLinear); texture->setMagnificationFilter(QOpenGLTexture::Linear); // Lade Bild QImage img(":/textures/brickwall.jpg"); // Kopiere Daten in Texture und Erstelle Mipmap texture->setData(img); // allocate() will be called internally

调用mipmap数据时,默认情况下将setData()生成这些数据,而无需其他参数。

调用时setData(),它将自动被调用,allocate()并且图像数据被复制到OpenGL纹理中。如果之后再次调用它allocate(),则会收到错误消息:GL_INVALID_OPERATION error generated. Texture is immutable.

至少在嵌入图像(和mipmap)的属性方面,纹理对象是不可变的。调用后setData(),实际上只能更改影响绑定数据解释的属性(即过滤器和换行样式)。如果要自己更改纹理,则必须销毁并重新创建对象。 2.6.1 着色器纹理链接

如果在一个着色器中使用多个纹理,则必须告诉着色器程序可以在哪个ID下找到纹理。

信息链如下所示:

在着色器程序(片段着色器)中指定纹理(sampler2D),例如brickTexture或roofTiles您需要提供其参数/统一索引,就好像它们是普通的统一变量→ brickTextureUniformID,roofTilesUniformID一样。使用这些变量ID,可以指定着色器参数。这些变量中的每一个都被赋予一个纹理ID,例如BrickTextureUniformID变量获得Texture#0,roofTilesUniformID获得Texture#1。为自己的纹理编号完全独立于统一的ID。渲染之前,您要集成纹理并指定纹理编号。

在初始化中,它看起来像这样:

SHADER(0)->setUniformValue(m_shaderPrograms[0].m_uniformIDs[1+i],i);

通常,您最多可以使用16个纹理。因此,在具有大量纹理的大型场景中,drawXXX不可避免地要拆分成多个调用。

2.7 帧缓冲区的初始化

使用OpenGL,您必须创建帧缓冲区,深度和模板附件,并为颜色值附加纹理:

// framebuffer configuration // ------------------------- glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); // create a color attachment texture glGenTextures(1, &textureColorbuffer); glBindTexture(GL_TEXTURE_2D, textureColorbuffer); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, scr_width, scr_height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0); // create a renderbuffer object for depth and stencil attachment (we won't be sampling these) glGenRenderbuffers(1, &rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, scr_width, scr_height); // use a single renderbuffer object for both a depth AND stencil buffer. glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // now actually attach it // now that we actually created the framebuffer and added all attachments we want to check if it is actually complete now if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) qDebug()


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有