第二十五章 绘制简单物体总结

简单的说,编译顶点着色器。首先需要把顶点着色器的源代码存储在一个字符串中,其次用glCreateShader创建一个着色器(参数:GL_VERTEX_SHADER)
下一步把源码附加到着色器对象上,再编译它(glShaderSource和glCompileShader)
最后用glGetShaderiv来检查是否编译时有错误(参数:GL_COMPILE_STATUS,success)如果编译失败,则用glGetShaderInfoLog用来获取错误信息

片段着色器的输出变量是一个,输出的是最终的颜色。创建着色器还是用glCreateShader,但是参数不一样(GL_FRAGMENT_SHADER),其他没什么区别

需要将上述着色器链接成一个着色器程序对象,在渲染对象的时候激活这个着色器程序。首先需要创建一个程序对象,使用的glCreateProgram,返回值是
一个程序对象的ID引用。下面需要把之前的着色器附加到程序对象上(glAttachShader函数),再用glLinkProgram链接。当然,链接程序对象也可以进行检测是否
链接成功。使用glGetProgramInfoLog和glGetProgramiv。最后需要激活这个程序对象,参数使用刚才建立的程序对象,使用函数glUseProgram
最后,着色器对象链接到程序对象后,需要删除着色器对象,使用函数glDeleteShader

下面需要将顶点数据链接到顶点着色器的属性上。可以使用glVertexAttribPointer函数解析顶点数据,第一个参数是要配置哪个顶点属性;第二个参数是顶点属性的大小,
因为顶点属性是一个vec3,则是由3个值组成,大小就是3;第三个参数是指定数据的类型,一般是GL_FLOAT;第四个参数是确认是否要 数据标准化(GL_FALSE);
第五个参数是步长,明确 连续的顶点属性直接的间隔;第六个参数是void*,一般写成:(void*)0 标识 位置数据在缓冲中起始位置的偏移量

下面需要以顶点属性位置值作为参数,启用顶点属性(函数:glEnableVertexAttribArray),注意顶点属性默认是禁用的。


顶点缓冲对象是用VBO管理的,接着是两个着色器被程序对象链接,最后是链接顶点属性。
下面是绑定顶点数组对象。任何随后的顶点属性调用都会存储到VAO中。
优点:配置顶点属性指针时,只需要将调用执行一次,也就是把这个VAO执行一次,之后绘制物体时候只要绑定相应的VAO即可。

顶点数组对象会存储好几个东西:首先是启用顶点属性和禁用顶点属性(glEnableVertexAttribArray和glDisableVertexAttribArray);glVertexAttribPointer
设置顶点属性指针(两处:设置的顶点属性配置;调用与顶点属性关联的顶点缓冲对象)

glGenBuffers是用来生成VBO的函数;绑定使用的是glBindBuffer(参数:GL_ARRAY_BUFFER)
glGenVertexArrays是用来创建VAO的函数;绑定使用的是glBindVertexArray。
会得到一个存储了顶点属性配置和应用了VBO的VAO。绘制物体时拿出相应的VAO,绑定它,绘制完后再解绑VAO。

绘制物体,可以使用glDrawArrays函数,分为三步:使用当前激活的着色器;使用之前定义的顶点属性配置,VBO的顶点属性数据–这是通过VAO来间接绑定的;最后使用glDrawArrays
来绘制图元。

glDrawArrays有三个参数,第一个是图元类型,第二个是顶点数组的起始索引,第三个是绘制的顶点个数。

EBO–元素缓冲对象,也叫做索引缓冲对象(IBO)。它是一个缓冲区,存储OpenGL用来决定要绘制哪些顶点的索引,并设定绘制这些顶点的顺序。
使用元素缓冲对象有以下步骤:首先需要定义要存储的顶点,以及用一个数组来表示绘制处图元需要的索引;其次需要创建EBO–glGenBuffers绑定,再使用glBufferData把索引赋值到缓冲中,
与VBO不同的是,参数的宏定义有变换,为:GL_ELEMENT_ARRAY_BUFFER;最后用glDrawElements替换了glDrawArrays函数,
有四个参数:第一个是绘制的模式(三角形:GL_TRIANGLES),第二个是绘制顶点的总个数;第三个是索引的类型,这个填GL_UNSIGEND_INT(索引中值的类型。 必须是GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT或GL_UNSIGNED_INT之一。)
可以通过https://learn.microsoft.com/zh-cn/windows/win32/opengl/gldrawelements查看OpenGL内建函数的用法
最后一个参数,指定EBO中的偏移量。

使用EBO的glDrawElements函数的时候,需要从绑定的GL_ELEMENT_ARRAY_BUFFER目标中获取索引。则每次使用索引渲染对象时必须绑定相应的EBO,但是
顶点数组对象也跟踪元素缓冲区对象绑定,也就是说,绑定VAO的时候,最后一个元素缓冲区对象存储为VAO的元素缓冲区对象,则绑定到VAO的时候也就会自动绑定到EBO
补充:参数如果是GL_ELEMENT_ARRAY_BUFFER时,VAO会存储glBindBuffer的调用,同时也会存储解绑调用,所以需要先解绑VAO,再解绑索引数组缓冲。

总体步骤如下:首先绑定顶点数组对象,也就是VAO;其次,把顶点数组赋值到顶点缓冲里面,也就是绑定VBO,再使用glBufferData复制顶点数组;其次,
复制索引数组到索引缓冲中,也就是两步:说先是绑定EBO,其次是把索引数组赋值到索引缓冲中;最后设置顶点属性指针,使用glVertexAttribPointer函数,再激活
glEnableVertexAttribArray函数进行启用顶点属性。最后使用while循环,glUseProgram激活着色器,再绑定VAO,使用glDrawElements绘画,最后使用glBindVertexArray(0)解绑

补充:有一个模式叫做线框模式。需要通过函数glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)配置。其中两个参数:第一个表示将线框模型应用到所有的三角形的正面和背面,
第二个参数表示用线来绘制。再这个函数之后都是线框模式。glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)将其设置回默认模式。

<think>我们正在讨论在Qt中如何将QPainter与OpenGL结合使用。根据引用,有几种方法可以实现这一点。主要挑战在于QPainter会重置OpenGL状态,导致3D内容消失。以下是几种解决方案: 1. **在OpenGL绘制后使用QPainter绘制**(如引用[3]所示): - 在`paintGL()`函数中,先完成所有OpenGL渲染(绑定着色器、纹理、绘制物体等),然后解绑所有OpenGL资源(如解绑VAO,释放着色器程序)。 - 接着,创建`QPainter`对象并开始绘制2D内容(如文本、图形)。这样,QPainter不会干扰之前设置的OpenGL状态。 - 注意:必须确保在调用QPainter之前,OpenGL已经完成了所有绘制并且已经解绑了相关资源。 2. **使用帧缓冲对象(FBO)**(引用[3]提到的高性能方法): - 将文本或2D图形预先渲染到一个纹理(通过FBO),然后在OpenGL场景中将这个纹理贴到一个四边形上。 - 这种方法性能更好,尤其适合动态文本(因为避免了每帧都调用QPainter)。 - 步骤: - 创建一个离屏的FBO和关联的纹理。 - 将FBO绑定为当前渲染目标,使用QPainter将2D内容绘制到这个FBO中。 - 解绑FBO,此时纹理中包含了2D内容。 - 在OpenGL渲染过程中,像使用普通纹理一样使用这个纹理,将其渲染到屏幕上的一个四边形(如矩形)上。 3. **避免开启GL_CULL_FACE**(引用[1]提到): - 如果在使用QPainter时遇到显示问题(如部分内容不显示),检查是否开启了`glEnable(GL_CULL_FACE)`。在绘制2D内容之前,应该关闭背面剔除(因为QPainter绘制的2D多边形可能没有正确的朝向,导致被剔除)。 4. **使用QOpenGLWidget的overpaint功能**(引用[4]提到): - Qt提供了Overpainting示例,展示了如何在QGLWidget(或QOpenGLWidget)中使用QPainter在OpenGL场景上绘制。 - 基本思路与第一种方法类似:先绘制OpenGL场景,然后绘制2D覆盖内容。 **具体实现步骤(以第一种方法为例)**: 在`QOpenGLWidget`的子类中,重写`paintGL()`函数: ```cpp void MyGLWidget::paintGL() { // 清除缓冲区 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 1. 绘制OpenGL内容(3D场景) m_program->bind(); // ... 设置uniform,绑定纹理,绑定VAO等 glDrawElements(...); // 解绑 glBindVertexArray(0); m_program->release(); // 2. 绘制2D内容(使用QPainter) QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::yellow); painter.setFont(QFont("Arial", 16)); painter.drawText(10, 30, "Hello, OpenGL!"); painter.end(); // 结束绘制 } ``` **注意事项**: - 在调用QPainter绘制之前,确保OpenGL已经释放了所有绑定的资源(如着色器程序、VAO、VBO、纹理等),否则可能导致冲突。 - 如果同时使用深度测试,注意QPainter绘制的2D内容应该位于最上层,可能需要暂时禁用深度测试。 - 性能:如果2D内容复杂且每帧变化,使用QPainter直接绘制可能会影响性能。此时,考虑使用第二种方法(FBO)或者将静态2D内容缓存为纹理。 **引用说明**: - 引用[1]提醒我们注意OpenGL状态设置(如背面剔除)对QPainter的影响[^1]。 - 引用[2]描述了QPainter与OpenGL共用的场景和问题[^2]。 - 引用[3]提供了两种方法:直接绘制和生成纹理,并给出了代码片段[^3]。 - 引用[4]指向了Qt的Overpainting示例[^4]。 根据这些信息,你可以根据需求选择合适的方法。</think>### QPainter 结合 OpenGL 的绘制方法 在 Qt 中结合使用 QPainter 和 OpenGL 渲染时,主要有两种实现方式,核心挑战在于解决 OpenGL 状态被 QPainter 重置的问题: #### 方法一:顺序绘制法(推荐) **步骤**: 1. **先完成 OpenGL 渲染**: ```cpp void MyGLWidget::paintGL() { // 清除缓冲区 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 绑定OpenGL资源 m_program->bind(); m_texture->bind(0); glBindVertexArray(VAO); // 执行OpenGL绘制 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); // 解绑资源(关键步骤) glBindVertexArray(0); m_program->release(); ``` 2. **再使用 QPainter 绘制 2D 内容**: ```cpp // 开始QPainter绘制 QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); // 绘制带背景的文字 painter.fillRect(QRect(5, 5, 200, 30), QColor(0,0,0,128)); painter.setPen(Qt::yellow); painter.drawText(10, 25, "FPS: " + QString::number(fps)); painter.end(); // 结束绘制 } ``` **关键点**: - 必须严格解绑所有 OpenGL 资源(VAO/VBO/着色器)后再调用 QPainter[^3] - 避免开启 `glEnable(GL_CULL_FACE)`,否则可能导致 QPainter 内容不显示[^1] - 此方法适合简单叠加场景(如 HUD 信息显示) #### 方法二:纹理合成法(高性能方案) **适用场景**:动态文本或复杂 2D 图形 ```cpp // 1. 创建离屏FBO和纹理 QOpenGLFramebufferObject fbo(width, height); fbo.bind(); // 2. 使用QPainter绘制到纹理 QPainter painter(&fbo); painter.drawText(...); painter.end(); // 3. 将纹理绑定到四边形渲染 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, fbo.texture()); // ... 渲染四边形 ... ``` **优势**: - 避免每帧调用 QPainter 的性能开销 - 保持 OpenGL 状态稳定 - 支持动态内容更新[^3] #### 常见问题解决 1. **OpenGL 内容消失**: - 原因:QPainter 重置了 OpenGL 状态机 - 方案:确保在 QPainter 绘制前完成所有 OpenGL 渲染并解绑资源[^2] 2. **文字模糊**: ```cpp painter.setRenderHint(QPainter::TextAntialiasing); // 启用抗锯齿 painter.setFont(QFont("Arial", 16, QFont::Bold)); // 使用矢量字体 ``` 3. **深度测试冲突**: ```cpp glDisable(GL_DEPTH_TEST); // 绘制2D前禁用深度测试 // ...QPainter绘制... glEnable(GL_DEPTH_TEST); // 恢复设置 ``` > **最佳实践**:对于静态界面元素(如按钮),建议使用纹理法;对于实时变化的 HUD 信息,采用顺序绘制法更便捷。Qt 官方示例 *Overpainting* 演示了标准实现[^4]。 --- ### 相关问题 1. 如何解决 QPainter 绘制后 OpenGL 3D 模型消失的问题? 2. 在 QML 应用中如何实现 OpenGL 与 QPainter 的混合渲染? 3. 使用 FBO 进行离屏渲染时如何进行性能优化? 4. 如何实现跨平台(Windows/Linux/嵌入式)的 OpenGL + QPainter 方案? [^1]: 注意在 OpenGL 和 QPainter 结合使用时,如果开启 glEnable(GL_CULL_FACE) 可能会出现 QPainter 显示问题 [^2]: 调用了 QPainter 后 OpenGL 的状态被清空丢失,导致 3D 不显示 [^3]: 在 paintGL() 中先完成 OpenGL 渲染,解绑资源后再绘制文本 [^4]: The Overpainting example 展示如何在 QGLWidget 中使用 QPainter 覆盖 OpenGL 场景
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值