当前位置: 首页 > news >正文

在QT中使用OpenGL

参考资料:

主页 - LearnOpenGL CN

https://blog.csdn.net/qq_40120946/category_12566573.html

由于OpenGL的大多数实现都是由显卡厂商编写的,当产生一个bug时通常可以通过升级显卡驱动来解决。

OpenGL中的名词解释

OpenGL 上下文(Context)

  • 作用

    • OpenGL上下文就像是一个画家的工具箱,它包含了所有当前可用的工具(如画笔、颜料、画布等),它存储了OpenGL的所有状态(如当前颜色、线条粗细、是否启用混合等)。

    • 每个窗口/线程通常有一个独立的上下文,每个上下文是独立的,就像一个“沙盒”,不同上下文的资源默认不共享(除非显式设置共享)。

    • 上下文必须绑定到某个“窗口”或“绘图表面”(如HWNDQOpenGLWidget等),才能进行渲染。

为什么需要多个上下文?

多线程渲染(每个线程通常需要一个独立的上下文)。

不同窗口可能需要不同的OpenGL版本(如一个用OpenGL 2.1,另一个用OpenGL 4.6)。

  • 例子

    • 如果你有两个画家(两个OpenGL上下文),他们各自有自己的画笔和颜料,互不影响。

    • 当你切换上下文(比如从窗口A切换到窗口B),就像换了一个画家,他用的工具和之前的不一样。

OpenGL 状态机(State Machine)

OpenGL状态机决定了当前如何绘制图形,它由一系列可开关的设置组成,就像画家的当前工作模式

  • 作用

    • 控制OpenGL的绘制行为,比如:

      • 当前颜色(glColor3f(1.0, 0.0, 0.0) → 红色)

      • 是否启用深度测试(glEnable(GL_DEPTH_TEST)

      • 当前绑定的纹理(glBindTexture(GL_TEXTURE_2D, textureId)

    • 这些状态会一直保持,直到你手动改变它们。

  • 例子

    • 画家当前选择的是红色颜料glColor3f(1.0, 0.0, 0.0)),那么接下来他画的所有东西都是红色,直到他换颜色。

    • 画家决定是否使用尺子glEnable(GL_LINE_SMOOTH)),如果启用,画线会更平滑。

OpenGL (ObjectID)

对象ID(如VAOVBOTexture的ID)就像是工具箱里的每件工具的编号

  • 作用

    • OpenGL管理的资源(如缓冲区、纹理、着色器)都有一个唯一的GLuint类型的ID。

    • 你需要绑定(Bind)这些ID到OpenGL的某个目标(Target),才能使用它们。

  • 例子

    • 画家有3支画笔(3个VBO),编号分别是1、2、3。

    • 他必须拿起某支笔glBindBuffer(GL_ARRAY_BUFFER, vboId))才能用它画画。

    • 如果他不绑定任何笔(glBindBuffer(GL_ARRAY_BUFFER, 0)),就没法画。

着色器 —— 着色器 - LearnOpenGL CN

顶点着色器

核心作用:处理每个顶点的坐标变换和属性计算。

  • 坐标变换:将输入的顶点坐标(如模型空间坐标)转换为裁剪空间坐标(Clip Space),这是透视投影和视口变换的基础。
  • 属性传递:处理顶点属性(如位置、法线、纹理坐标、颜色),并将处理后的数据传递给后续阶段(如几何着色器或片段着色器)。
  • 应用场景
    • 实现模型的位移、旋转、缩放(通过矩阵变换)。
    • 计算顶点光照效果的初始值(如法线变换)。
    • 实现顶点动画(如骨骼动画、波浪效果)。

特点

  • 对每个顶点执行一次,并行计算效率高。
  • 必须输出裁剪空间坐标(gl_Position)。

// 顶点着色器示例(简化)
#version 330 core
layout (location = 0) in vec3 aPosition;  // 顶点位置
layout (location = 1) in vec3 aColor;     // 顶点颜色
out vec3 vColor;                          // 传递给片段着色器的颜色

uniform mat4 model;       // 模型矩阵
uniform mat4 view;        // 视图矩阵
uniform mat4 projection;  // 投影矩阵

void main() {
    // 坐标变换:模型空间 → 世界空间 → 视图空间 → 裁剪空间
    gl_Position = projection * view * model * vec4(aPosition, 1.0);
    vColor = aColor;      // 传递颜色属性
}

片段着色器

核心作用:计算每个片段(Fragment,可理解为潜在像素)的最终颜色。

  • 颜色计算:基于插值后的顶点属性(如颜色、纹理坐标)和外部数据(如光照、材质、纹理),计算像素的 RGBA 值。
  • 高级效果:实现光照模型(如 Phong、PBR)、阴影、纹理采样、透明度、后处理(如模糊、色调调整)。
  • 应用场景
    • 渲染物体材质和纹理。
    • 实现光照、反射、折射效果。
    • 创建特效(如雾效、发光、粒子)。

特点

  • 对每个光栅化后的片段执行一次,计算量通常比顶点着色器大。
  • 必须输出一个颜色值(out vec4 FragColor)。

#version 330 core
in vec3 vColor;           // 从顶点着色器接收的颜色
out vec4 FragColor;       // 输出的最终颜色

void main() {
    // 直接使用插值后的颜色
    FragColor = vec4(vColor, 1.0);
}

VBO(Vertex Buffer Object)—— 顶点缓冲区对象

VBO 是OpenGL中用于存储顶点数据(如位置、颜色、纹理坐标等)的缓冲区对象。它直接在GPU内存中分配空间,避免CPU到GPU的重复数据传输,提高渲染效率。

作用

  • 存储顶点属性数据(例如:float vertices[] = {x1,y1,z1, x2,y2,z2, ...})。

  • 通过glBindBuffer绑定后,OpenGL才知道从哪里读取数据。

类比

  • VBO = 颜料管

    • 每个颜料管(VBO)存储一种原始数据(如红色颜料管、蓝色颜料管)。

    • 颜料管本身不知道如何被使用,只是数据的容器。

VAO(Vertex Array Object)—— 顶点数组对象

VAO 是一个“配置容器”,用于记录:

  • 当前绑定的VBO。

  • 顶点属性的解析方式(如位置是3个float,颜色是4个float等)。

  • 顶点属性的启用状态(通过glEnableVertexAttribArray)。

作用

  • 避免重复配置:每次绘制时无需重新绑定VBO和设置属性格式。

  • 提高性能:OpenGL可以直接读取预定义的顶点数据布局。

类比

  • VAO = 调色板(或画笔套装)

    • 调色板(VAO)记录了:

      • 哪些颜料管(VBO)被挤到调色板上。

      • 每种颜料的用途(如红色用于轮廓,蓝色用于填充)。

    • 画家(OpenGL)拿起调色板后,直接知道如何作画。

VBO 和 VAO 的联系与区别

联系

  1. VAO 依赖 VBO

    • VAO本身不存储顶点数据,它只是记录“如何从VBO中读取数据”。

    • 必须先绑定VBO,再配置VAO(就像先挤颜料到调色板,再定义用途)。

区别

特性VBOVAO
存储内容原始顶点数据(如位置、颜色)顶点属性的配置(如何读数据)
是否直接参与绘制是(数据源)否(只是数据格式的封装)
绑定目标GL_ARRAY_BUFFER无目标(直接绑定)
性能优化减少CPU-GPU数据传输减少绘制时的状态切换开销

一个VAO绑定多个VBO吗?——可以

VAO可以关联多个VBO(例如:一个VBO存位置,另一个存颜色)。
只需在绑定VAO后,依次绑定不同VBO并配置属性:

glBindVertexArray(vaoId);
// 绑定位置VBO
glBindBuffer(GL_ARRAY_BUFFER, vboPos);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
// 绑定颜色VBO
glBindBuffer(GL_ARRAY_BUFFER, vboColor);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, NULL);

基础光照

环境光照(Ambient Lighting):

即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。

漫反射光照(Diffuse Lighting):

模拟光源对物体的方向性影响(Directional Impact)。它是风氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。

镜面光照(Specular Lighting):

模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。

法向量

法向量是一个垂直于顶点表面的(单位)向量。由于顶点本身并没有表面(它只是空间中一个独立的点),我们利用它周围的顶点来计算出这个顶点的表面。我们能够使用一个小技巧,使用叉乘对立方体所有的顶点计算法向量,但是由于3D立方体不是一个复杂的形状,所以我们可以简单地把法线数据手工添加到顶点数据中。更新后的顶点数据数组可以在这里找到。试着去想象一下,这些法向量真的是垂直于立方体各个平面的表面的(一个立方体由6个平面组成)。

计算漫反射光照需要什么?

  • 法向量:一个垂直于顶点表面的向量。
  • 定向的光线:作为光源的位置与片段的位置之间向量差的方向向量。为了计算这个光线,我们需要光的位置向量和片段的位置向量。

OpenGL 图形渲染管线全流程解析

OpenGL 状态机与对象管理基础
  • 状态机特性:OpenGL 通过上下文(Context)维护运行状态,通过设置选项、操作缓冲修改状态,以当前上下文执行渲染。
  • 对象引用机制:对象通过整数 ID(objectId)绑定至上下文目标位置,存储选项配置,避免重复设置(如模型数据绑定)。
阶段像素位置相关像素颜色相关
顶点着色器转换 3D 坐标至裁剪空间,为投影做准备处理顶点颜色等属性(可传递给片段着色器)
几何着色器(可选)修改图元形状(如生成更多顶点)可输出顶点颜色属性
图元装配定义几何形状的轮廓无直接颜色处理
光栅化确定像素的屏幕坐标(x, y)传递片段位置给片段着色器
片段着色器无直接位置处理计算像素的 RGBA 颜色值
Alpha 测试与混合基于深度判断像素可见性混合颜色值,确定最终显示的颜色

数据流

一、像素位置的确定:3D 到 2D 坐标转换与光栅化阶段

1. 顶点着色器(Vertex Shader):初步坐标变换
  • 作用:将输入的 3D 顶点坐标(如模型本地坐标)转换为裁剪空间坐标(Clip Space),并完成顶点属性(如位置、法线)的基础处理。
  • 与像素位置的关系:此阶段将顶点从模型空间映射到裁剪空间,为后续投影到 2D 屏幕做准备,但尚未直接确定像素位置。
2. 图元装配(Primitive Assembly):几何形状定义
  • 作用:将顶点按指定图元类型(如三角形、线段)装配成几何形状,确定渲染的基本单元(如一个三角形由 3 个顶点组成)。
  • 与像素位置的关系:明确了 3D 空间中几何图形的轮廓,但仍处于抽象的几何阶段。
3. 光栅化(Rasterization):像素位置生成
  • 作用:将图元(如三角形)映射到屏幕的 2D 像素网格,计算每个图元覆盖的像素坐标,并生成对应的片段(Fragment)
  • 关键细节
    • 此阶段通过插值计算图元在屏幕上的覆盖范围,确定每个像素的位置(x, y 坐标)。
    • 裁切(Clipping)会丢弃视图外的像素,减少无效计算。
  • 结论光栅化阶段直接确定了像素的屏幕位置

二、像素颜色的计算:片段处理与最终输出阶段

1. 片段着色器(Fragment Shader):颜色值计算
  • 作用:接收光栅化生成的片段(包含位置、纹理坐标等信息),结合 3D 场景数据(如光照、阴影、材质属性、纹理采样),计算每个像素的最终颜色值(RGBA)。
  • 关键细节
    • 可通过光照模型(如兰伯特、Phong)计算光照对颜色的影响。
    • 支持纹理采样,将纹理贴图的颜色映射到像素。
  • 结论片段着色器是像素颜色计算的核心阶段
2. Alpha 测试与混合(Alpha Test & Blending):颜色最终确定
  • 作用
    • 深度测试:比较当前片段与已渲染像素的深度值,决定是否保留当前像素(近物覆盖远物)。
    • Alpha 混合:根据透明度(Alpha 值)混合当前像素与背景像素的颜色,实现半透明效果(如玻璃、烟雾)。
  • 与颜色的关系:此阶段不直接计算颜色,而是基于深度和 Alpha 值对片段着色器输出的颜色进行筛选或混合,最终确定屏幕上显示的像素颜色。
    图形渲染管线核心流程拆解
    1. 3D 到 2D 坐标转换阶段

      • 顶点着色器(Vertex Shader):输入单个顶点,将 3D 坐标转换为特定空间坐标(如裁剪空间),并处理顶点属性(如位置、颜色)。
      • 几何着色器(Geometry Shader)(可选):输入顶点组(图元),通过生成新顶点扩展或修改图元形状(如从单个三角形生成两个三角形)。
    2. 图元装配与光栅化阶段

      • 图元装配(Primitive Assembly):将顶点按指定图元类型(如三角形、点)装配为几何形状,确定渲染基本单元。
      • 光栅化(Rasterization):将图元映射为屏幕像素,生成片段(Fragment);裁切(Clipping)丢弃视图外像素,提升效率。
    3. 片段处理与最终输出阶段

      • 片段着色器(Fragment Shader):计算像素最终颜色,结合 3D 场景数据(光照、阴影、材质等)生成颜色值。
      • Alpha 测试与混合(Alpha Test & Blending):通过深度 / 模板测试判断像素可见性,基于 Alpha 值实现透明度混合,确定最终渲染结果。
    管线核心逻辑总结

    “状态机管理对象状态 → 管线各阶段逐级处理顶点与图元 → 从 3D 坐标映射至 2D 像素并渲染”,这一流程构成了 OpenGL 将几何数据转化为屏幕图像的完整技术链路。

    完整OpenGL绘图流程

    OpenGL步骤Qt (QOpenGLWidget)画家类比
    1. 初始化OpenGL上下文

    QOpenGLWidget 构造函数自动创建OpenGL上下文

    initializeOpenGLFunctions()加载OpenGL函数,初始化Qt的OpenGL函数绑定

    画家准备好画布、工具箱(上下文=工具箱+画布)。
    2. 定义顶点数据initializeGL()中定义数组(如float vertices[]画家决定画什么(如“画一个三角形,左下红、右下绿、顶部蓝”)。
    3. 创建VBOglGenBuffers() + glBufferData() 在initializeGL()中完成把颜料挤到调色盘上(VBO=颜料管,存储原始颜色和位置数据)。
    4. 创建VAOglGenVertexArrays() + 配置属性(glVertexAttribPointer)在initializeGL()中完成

    调色盘(VAO)记录:

    - 红色颜料用于轮廓

    - 蓝色颜料用于填充。

    5. 清空屏幕paintGL()中调用 glClear()画家擦干净画布
    6. 绑定VAOpaintGL()中调用 glBindVertexArray(VAO)画家拿起调色盘(绑定VAO后,OpenGL知道如何读取VBO数据)。
    7. 绘制调用paintGL()中调用 glDrawArrays()画家用调色盘上的颜料开始作画
    8. 解绑VAO可调用 glBindVertexArray(0),但Qt通常省略画家放下调色盘
    9.调整画布大小重写 resizeGL(int w, int h),自动调用画家根据画布大小调整画笔范围。

    10.清理资源

    QOpenGLWidget 析构函数自动释放VAO/VBO画家收工,清理调色盘和颜料。

     原生OpenGL对比Qt绘图流程关键区别总结

    特性Qt (QOpenGLWidget)原生OpenGL (GLFW)
    上下文管理自动创建和管理OpenGL上下文需手动初始化(如GLFW/GLEW)
    窗口集成直接嵌入Qt窗口体系需手动处理窗口事件(如GLFW)
    函数调用通过 QOpenGLFunctions 调用OpenGL API直接调用OpenGL API(如 glDrawArrays
    生命周期自动调用 initializeGL()paintGL()需手动编写渲染循环
    适用场景Qt应用程序中嵌入3D绘图跨平台游戏/独立图形应用

    Qt 的 QOpenGLWidget 绘图完整代码——制一个三色三角形

    1.1 创建自定义 QOpenGLWidget 子类
    // MyGLWidget.h
    #include <QOpenGLWidget>
    #include <QOpenGLFunctions>class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions {
    public:MyGLWidget(QWidget* parent = nullptr) : QOpenGLWidget(parent) {}protected:void initializeGL() override;  // 初始化OpenGLvoid paintGL() override;       // 绘制void resizeGL(int w, int h) override; // 窗口大小变化时调整private:GLuint VAO, VBO;  // OpenGL对象
    };
    1.2 实现 initializeGL(初始化阶段)
    // MyGLWidget.cpp
    void MyGLWidget::initializeGL() {initializeOpenGLFunctions();  // 初始化Qt的OpenGL函数绑定// 定义顶点数据(位置 + 颜色)float vertices[] = {// 位置          // 颜色-0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f, // 左下(红)0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f, // 右下(绿)0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f  // 顶部(蓝)};// 创建并绑定VBOglGenBuffers(1, &VBO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 创建并绑定VAOglGenVertexArrays(1, &VAO);glBindVertexArray(VAO);// 配置位置属性(前3个float)glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// 配置颜色属性(后3个float,偏移量=3*sizeof(float))glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);// 解绑(非必须,但安全)glBindVertexArray(0);glBindBuffer(GL_ARRAY_BUFFER, 0);
    }
    1.3 实现 paintGL(渲染阶段)
    void MyGLWidget::paintGL() {glClearColor(0.2f, 0.3f, 0.3f, 1.0f);  // 设置清屏颜色(深绿色)glClear(GL_COLOR_BUFFER_BIT);          // 清空颜色缓冲glBindVertexArray(VAO);                // 绑定VAO(自动关联VBO和属性)glDrawArrays(GL_TRIANGLES, 0, 3);      // 绘制三角形
    }
    1.4 实现 resizeGL(窗口调整)
    void MyGLWidget::resizeGL(int w, int h) {glViewport(0, 0, w, h);  // 调整视口大小
    }
    1.5 在Qt窗口中使用 MyGLWidget
    // main.cpp
    #include <QApplication>
    #include "MyGLWidget.h"int main(int argc, char** argv) {QApplication app(argc, argv);MyGLWidget widget;widget.show();return app.exec();
    }
    http://www.xdnf.cn/news/990775.html

    相关文章:

  • Python 元组
  • 使用spring-ai-alibaba接入大模型
  • mysql基本操作语句 增删改查基础语法速查表
  • MTK-USB模式动态设置
  • VScode安装配置PYQT6
  • MS7200+MS1824 HD转AV/S-Video/VGA/YPbPr/RGB888/BT601、656/BT1120转换器
  • Pandas时间数据处理:从基础到进阶的实战指南
  • 利用高分辨率卫星遥感数据以更智能、更快速的方式勘测评估能源开采现场
  • 第四章 文件管理
  • 软件测试用例设计总结
  • Position Embedding 有哪些方式?
  • @Indexed原理与实战
  • Java大模型开发入门 (3/15): 拥抱官方标准 - 使用OpenAI官方Java SDK调用DeepSeek
  • 航电系统之轨迹克隆技术篇
  • pyvis报错AttributeError: ‘NoneType‘ object has no attribute ‘render‘
  • python打卡day51@浙大疏锦行
  • 期权末日轮实值期权盈利未平仓怎么办?
  • 【多模态/T5】[特殊字符] 为什么视频生成模型还在用T5?聊聊模型选择的学问
  • Windows版PostgreSQL 安装 postgis扩展
  • 大数据下的分页通用架构设计:从随机IO到顺序IO
  • Gartner<Reference Architecture Brief: Data Integration>学习心得
  • 嵌入式程序存储结构
  • HW中常态化反钓鱼训练的具体战略部署
  • 【网络】每天掌握一个Linux命令 - netperf
  • 6. TypeScript 函数
  • 提升集装箱及金属包装容器制造交付效率:数字化项目管理系统的核心优势
  • 异常谋杀案--Java异常处理篇
  • 工程论文: TORL: Scaling Tool-Integrated RL
  • StackOverflowError
  • (javaSE)继承和多态:成员变量,super,子类构造方法,super和this,初始化, protected 继承方式 final关键字 继承与组合