资讯动态

【qt openGL入门笔记】QT OpenGL基础知识合集

发布时间:2026/10/29 20:05:22 来源:尧图企业网站定制
前言本篇专门汇集零散在其他文章中的基础知识仅梳理裸GL开发的关键要点。书籍基础知识还是得看书下面这本是opengl3.0的不是3.3的但基础知识是通的。OpenGL超级宝典(第5版中文版)简述OpenGL 是规范。它定义函数行为实现由显卡驱动提供。原生开发需要 glad/glew 加载函数指针而 Qt 已经进行了封装可以直接调用不用在.pro文件中加上qt opengl;OpenGL 是状态机。OpenGL的状态通常被称为OpenGL上下文(Context)不是某个物体的具体对象我们通过改变一些上下文变量来改变OpenGL状态最后我们使用当前OpenGL上下文来渲染绘图。它的工作流程绑定bind对象 → 配置状态 → 触发绘制命令。关键点不是绘制函数的参数而是上下文此刻的配置状态。Qt 与原生 OpenGL 的对应关系learnopengl 技术栈类glfw 窗口QOpenGLWidgetglad函数加载QOpenGLFunctions_3_3_CoreGLM数学库QMatrix4x4 / QVector3D / QQuaternionstb_image读图QImage手写着色器编译QOpenGLShaderProgram交换缓冲/轮询事件框架自动paintGLQt 本身封装有opengl相关的类可以直接继承使用但这种封装是比较原始的属于裸GL开发。完整 OpenGL 渲染流程一张简易图这张图不完整但有关键流程供参考。简易流程VBO/VAO 顶点数据 → 顶点规范 → Draw Call 启动管线 → 顶点着色器 → 细分着色器可选 → 几何着色器可选 → 顶点后处理裁剪、透视除法、视口变换 → 图元装配组装点/线/三角形可做面剔除 → 光栅化生成片段并插值属性 → 片段着色器计算片段颜色 → 逐片元测试与混合模板、深度、混合等 → 帧缓冲 → 屏幕详细流程说明简易流程的全过程已经包含了CPU-GPU处理的全过程其中CPU应用端包含前两个过程CPU应用端1. 顶点规范 Vertex Specification准备 VBO/EBO、VAO、纹理、Uniform、着色器程序等VBO顶点缓冲对象把数据从内存搬进显存GPU 不能随机访问CPU 内存CPU 侧数组对 GPU 来说只是一串“不知道存放在哪的字节”。VBO 显存里分配的一块缓冲glBufferData负责把 CPU 数据拷进去。以下是QT OpenGL开发中常见的把数据从内存搬进显存的代码片段//顶点数据 GLfloat 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, // 正上蓝 }; //VBO把数据从内存搬进显存 QOpenGLBuffer m_vbo; m_vbo.create(); // 申请一个 VBO 句柄 m_vbo.bind(); // 绑定它之后的操作都针对它 m_vbo.allocate(vertices, sizeof(vertices));// 把数据上传到显存以上代码片段通常是在裸GL开发的初始化函数initializeGL完成一次需要注意的是initializeGL函数不是必要的前置函数。另外与VBO数据配套的glDrawArrays绘制函数只允许取连续的顶点坐标数据如果一个VBO需要装多个形状的顶点坐标并且存在重复的顶点坐标时VBO方式会存在冗余最好用EBO方式。EBO索引缓冲对象把索引数据从内存搬进显存属于VBO的升级版。比如以下需要渲染一个三角形和一个四边形其中0、1顶点共用。GLfloat vertices[] { // ---- 位置 x,y,z ------ -- 颜色 r,g,b -- -- 纹理 u,v -- /*0*/ -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // 左下 /*1*/ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下 /*2*/ 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f, // 正上U取中间 /*3*/ -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // 左上 /*4*/ 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f // 右上 }; // EBO 索引告诉 GPU 按哪个顺序、跳到哪些下标去取顶点 // 前 3 个 {0,1,2} - 三角形 (GL_TRIANGLES) // 后 4 个 {0,1,3,4} - 四边形 (GL_TRIANGLE_STRIP之字排列自动拼 2 个三角形) // 顶点 0、1 被两段索引同时引用这就是复用。 GLushort indices[] { 0, 1, 2, // 三角形索引段EBO 偏移 0 字节起共 3 个索引 0, 1, 3, 4 // 四边形索引段EBO 偏移 3*26 字节起共 4 个索引 }; // EBO把索引数据搬进显存 m_ebo.create(); m_ebo.bind(); m_ebo.allocate(indices, sizeof(indices));VAO顶点数组对象VAO 不存顶点数据只存“顶点数据怎么读”的规则。如下代码在initializeGL初始化阶段setAttributeBuffer、enableAttributeArray会被VAO记录在绘制函数paintGL中只需要进行m_vao.bind()就不用再重新写 setAttributeBuffer、enableAttributeArray。void MyGLWidget::initializeGL(){ //准备顶点数据位置 颜色 // 每个顶点x, y, z, r, g, b 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 // 上蓝色 }; QOpenGLVertexArrayObject m_vao; //创建并配置 VAO m_vao.create(); m_vao.bind(); // 之后所有属性配置都会记录到这个 VAO 里 //创建 VBO 并上传数据 m_vbo.create(); m_vbo.bind(); m_vbo.allocate(vertices, sizeof(vertices)); // 颜色属性----以下配置被VAO记录 m_program.setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 3, 6 * sizeof(float)); m_program.enableAttributeArray(1); } void MyGLWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT); m_program.bind(); m_vao.bind(); // 一条绑定恢复之前记录的所有顶点属性配置 glDrawArrays(GL_TRIANGLES, 0, 3); m_vao.release();//释放VAO m_program.release(); }纹理 (Texture)GPU 端的“高清贴图仓库”初始化阶段在 VBO 中为每个顶点存储了 UV 坐标例如(0,0)或(1,1)。这些坐标是归一化的范围在0.0到1.0之间与图片的实际像素尺寸如 1024x1024无关。加载一张纹理图片纹理图片让形状物体看起来更像真实的物体它是存储在显存中的一块特殊数据区域用于为模型表面提供丰富的细节如颜色、法线、粗糙度等而无需增加大量的顶点数据。光栅化阶段线性插值‌计算出三角形内部每一个像素对应的 UV 坐标。片段着色器阶段QT OpenGL调用内置函数texture()它接收两个参数‌Sampler‌指向绑定的QOpenGLTexture即那张图片。‌UV Coord‌光栅化后插值得到的当前像素的 UV 坐标。GPU 拿着这个 UV 坐标去QOpenGLTexture里“查表”取出对应的颜色值作为该像素的最终颜色。void GLWidget::initializeGL(){ // 格式X, Y, Z, U, V float vertices[] { // 位置 // 纹理坐标 -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // 左下 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // 右下 0.0f, 0.5f, 0.0f, 0.5f, 1.0f // 顶部 }; QOpenGLTexture *m_texture nullptr; // 纹理对象 // 加载纹理 QImage img(:/images/wall.jpg); if (img.isNull()) { qDebug() Texture load failed!; } else { // 加载图片并创建纹理对象数据上传至 GPU 显存 m_texture new QOpenGLTexture(img.mirrored()); m_texture-setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); // 缩小时用 mipmap m_texture-setMagnificationFilter(QOpenGLTexture::Linear); // 放大时线性插值 m_texture-setWrapMode(QOpenGLTexture::ClampToEdge); // UV 超出 [0,1] 时截断 } } void GLWidget::paintGL(){ m_texture-bind(0);// 绑定到纹理单元 0 --在着色器中通过 uniform sampler2D 接收这个单元的数据 }Uniform变量Uniform 是 OpenGL 中 CPU 向 GPU 着色器传递‌全局共享常量数据‌的特殊变量它的值在单次绘制调用中对所有顶点、所有片段完全一致不会随顶点变化而改变这也是它名字“统一的”的由来。核心特性‌数据存储在 GPU 的常量区着色器内只能读取、不能修改由 CPU 通过glUniform*系列函数直接赋值不需要走 VBO 顶点缓冲传递。‌典型用途‌传递模型/视图/投影变换矩阵、光照参数、材质颜色、时间变量等全局渲染状态是实现3D模型旋转、场景光照动态调整的核心数据通道。‌与顶点属性的区别‌顶点属性是逐顶点独有的数据如每个点的位置、颜色而 Uniform 是所有顶点共享的全局数据用它传递变换矩阵能避免逐顶点重复上传数据大幅提升性能。QOpenGLShader vShader(QOpenGLShader::Vertex); vShader.compileSourceCode( #version 150\n in vec3 aPos;\n // 输入来自顶点的坐标VBO 的前3个float in vec3 aColor;\n // 输入来自顶点的颜色VBO 的后3个float out vec3 vColor;\n // 输出转发给片段着色器 uniform mat4 uMatrix;\n // 声明一个全局 Uniform 变量 void main() {\n gl_Position uMatrix * vec4(aPos, 1.0);\n// 使用这个矩阵变换顶点 vColor aColor;\n // 把颜色往下传 } ); //获取 Uniform 在 GPU 中的位置索引 int matrixLocation m_program.uniformLocation(uMatrix); if (matrixLocation ! -1) { //在 CPU 内存中计算矩阵 QMatrix4x4 matrix; matrix.rotate(m_angle, 0.0f, 0.0f, 1.0f); // 绕Z轴旋转 //将数据从 CPU 复制到 GPU 的 Uniform 存储区 m_program.setUniformValue(matrixLocation, matrix); // 传入 int location }2. Draw Call 启动管线glDrawArrays / glDrawElements 把顶点数据送入 GPU。在前面的VBO介绍中已经介绍过使用glDrawArrays读取连续的顶点。而glDrawElements则是读取EBO的索引数据。typedef enum enElementsType{ ELEMNNT_TRIANGLE0, ELEMNNT_QUAD }TEnElementsType; GLushort indices[] { 0, 1, 2, // 三角形索引段EBO 偏移 0 字节起共 3 个索引 0, 1, 3, 4 // 四边形索引段EBO 偏移 3*26 字节起共 4 个索引 glDrawArrays(GL_TRIANGLES, 0, 3); // 用下标0~2这3个顶点三角形 // 三角形索引 0,1,2 glDrawElements(GL_TRIANGLE_STRIP, 3, GL_UNSIGNED_SHORT, (const void *)(m_shapelist[ELEMNNT_TRIANGLE] * sizeof(GLushort))); // 四边形索引 0,1,3,4 —— 偏移 3 个索引 3 * 2 字节 glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, (const void *)(m_shapelist[ELEMNNT_QUAD] * sizeof(GLushort)));注意这里的参数GL_TRIANGLES、GL_TRIANGLE_STRIP在图元装配中生效这里只是传参。GPU 渲染管线3. 顶点处理 Vertex Processing顶点着色器 (Vertex Shader)这是顶点处理阶段的入口也是唯一‌必须存在‌的可编程阶段。它为每个输入的顶点执行一次主要职责包括‌属性传递与计算‌接收 VBO 中的顶点属性如位置、颜色、法线、纹理坐标进行初步计算后通过out变量传递给后续阶段。例如计算每个顶点的法线在世界空间下的方向用于后续的光照计算。‌坐标变换核心任务‌将顶点从局部坐标系Model Space逐步变换到裁剪坐标系Clip Space。通常通过乘以 MVP 矩阵模型-视图-投影矩阵实现gl_PositionProjection×View×Model×vec4(aPos,1.0)‌模型变换‌将物体从局部空间移到世界空间。‌视图变换‌将世界空间移到相机观察空间。‌投影变换‌将观察空间映射到标准化设备坐标NDC为后续的透视除法做准备。‌并行性‌GPU 会对所有顶点独立并行处理顶点之间在此阶段‌不能互相通信‌。如下顶点着色器的GLSL中包含顶点属性如位置aPos、颜色aColor和坐标变换(uMatrix)。// 3. 顶点着色器 (使用 GLSL 150 标准--类C语言) QOpenGLShader vShader(QOpenGLShader::Vertex); vShader.compileSourceCode( #version 150\n in vec3 aPos;\n // 输入来自顶点的坐标VBO 的前3个float in vec3 aColor;\n // 输入来自顶点的颜色VBO 的后3个float out vec3 vColor;\n // 输出转发给片段着色器 uniform mat4 uMatrix;\n // 外部传入的变换矩阵 void main() {\n gl_Position uMatrix * vec4(aPos, 1.0);\n // 顶点最终位置系统内置输出 vColor aColor;\n // 把颜色往下传 } );4. 顶点后处理 Vertex Post-Processing顶点后处理完成后输出的窗口坐标会直接送入‌图元装配‌和‌光栅化‌阶段GPU 开始将三角形图元拆解为一个个像素片段。裁剪这一步的核心目标是‌只保留视锥体View Frustum内部的图元‌完全剔除或分割超出可视范围的部分避免无效的光栅化计算。内置视锥体裁剪‌GPU 自动基于顶点着色器输出的gl_Position裁剪坐标的x,y,z分量与w分量的关系进行判断顶点满足-w ≤ x ≤ w、-w ≤ y ≤ w、-w ≤ z ≤ w才会被保留超出这个范围的顶点所在图元会被直接丢弃跨边界的图元会被硬件自动切割成新的、完全在视锥体内的图元。‌用户自定义裁剪平面‌你可以在顶点着色器中通过内置变量gl_ClipDistance[]自定义额外的裁剪半空间非负值代表顶点在该裁剪平面内侧GPU 会自动按你定义的平面进行二次裁剪常用于实现水面反射、模型剖面效果。‌深度裁剪开启GL_DEPTH_CLAMP后超出近/远裁剪面的 Z 值会被强制钳制到近远平面范围内不会被直接剔除常用于实现阴影贴图中避免远处物体被错误裁剪的效果。透视除法这是从“裁剪空间”到“标准化设备坐标NDC”的关键数学转换步骤完全由硬件自动完成。视口变换 Viewport Transform最后一步将标准化的 NDC 坐标映射到你指定的实际窗口像素区域完成从“虚拟3D空间”到“屏幕像素”的落地。在resizeGL(int w, int h)中调用的glViewport(0, 0, w, h)就是在配置这个阶段的参数。如果窗口大小变化后忘记更新视口会出现画面拉伸、坐标偏移的问题。5. 图元装配 Primitive Assembly图元是顶点处理阶段的输入单位本质是顶点的组合规则基础类型只有点、线、三角形三种。如果最终绘制的是一共五角星GPU并不知道最终的相关而是按照装配规则遍历顶点坐标进行点/线/三角形绘制绘制的效果由开发人员负责所以开发人员要在几何上熟悉坐标点以及绘制规则才有可能得到预期的绘制图形。装配规则见下面的表格。枚举值装配规则产出GL_POINTS每个顶点一个点n 个点GL_LINES每 2 个顶点一条线n/2 条线段GL_LINE_STRIP首尾相连的折线n−1 条线段GL_LINE_LOOP折线 末点连回起点n 条线段GL_TRIANGLES每 3 个一组互不相干n/3 个三角形GL_TRIANGLE_STRIP滑动窗口共享前两个顶点n−2 个三角形GL_TRIANGLE_FAN第一个顶点当枢纽辐射n−2 个三角形GL_PATCHES交给细分着色器处理由 TES 决定注意在Draw Call 启动管线的glDrawArrays / glDrawElements函数的参数中设置有装配规则这就是GPU处理图元的装配规则只是glDrawArrays / glDrawElements函数不是直通GPU而是先把参数传进来等到顶点后处理完成后才到图元装配环节。6. 光栅化 Rasterization这是图形渲染管线中连接“几何世界”与“像素世界”的桥梁。它的核心任务是将顶点处理后生成的抽象图元如三角形、线、点转换为屏幕上一个个具体的‌片段Fragment。片段对应屏幕上的一个潜在像素点包含了该像素的所有渲染属性颜色、深度、UV等GPU 会为每个片元独立执行片段着色器最终输出到帧缓冲。光栅化阶段完全由 GPU 硬件固定管线执行效率极高主要包含以下三个核心过程1. 图元到片段的转换 (Primitive to Fragment)光栅化的第一步是确定哪些像素被图元覆盖。GPU 会遍历屏幕上的像素网格判断每个像素的中心点是否位于图元内部。点的光栅化‌根据glPointSize设置的大小覆盖对应数量的像素。线的光栅化‌根据线宽和端点样式计算覆盖的像素集合。现代 OpenGL 通常推荐使用三角形模拟粗线以获得更好的抗锯齿效果。‌三角形光栅化‌GPU 使用‌重心坐标Barycentric Coordinates‌算法。对于三角形内的任意一点 PP都可以表示为三个顶点 A,B,CA,B,C 的加权平均PαAβBγCPαAβBγC其中 αβγ1αβγ1且 α,β,γ≥0α,β,γ≥0。如果某个像素中心满足这个条件它就被判定为在三角形内部从而生成一个片段。2. 属性插值 (Attribute Interpolation)一旦确定了片段的位置GPU 需要知道该片段的具体属性如颜色、纹理坐标、法线。这些属性并非凭空产生而是基于三角形三个顶点的属性进行‌插值计算‌。‌颜色‌实现平滑着色Gouraud Shading颜色在三角形表面自然过渡无突兀色块。纹理坐标 (UV)‌确保纹理贴图在透视视角下保持正确的比例不会出现“远处纹理拉伸”的现象。法线‌为后续片段着色器中的 Phong 光照计算提供准确的表面朝向。注意在 Qt OpenGL 中只要在顶点着色器中通过out变量传递属性并在片段着色器中通过in变量接收GPU 会自动处理这种复杂的透视校正插值无需手动编写代码。但需确保顶点数据布局Stride/Offset正确否则插值结果会错乱。7. 片段处理 Fragment Processing片段着色器 Fragment Shader片段着色器是完全可编程的 GPU 程序它的输入是光栅化阶段经过透视校正插值后的所有属性输出是该片段最终的颜色值它运行在光栅化生成的每一个片段上是实现纹理采样、光照计算、材质效果的核心。片段着色器输出的颜色值并不会直接写入屏幕它还需要经过后续的深度测试、模板测试、颜色混合等固定管线阶段只有所有测试都通过后才会最终更新帧缓冲中的像素值。执行粒度‌GPU 为每一个片段独立执行一次片段着色器即使是同一个三角形上的两个相邻像素也会各自运行完整的片段着色器逻辑。‌核心约束‌片段着色器必须输出一个vec4类型的 RGBA 颜色值如果你没有指定输出变量OpenGL 的颜色缓冲结果将是未定义的通常会直接渲染出全黑或全白的画面。‌并行特性‌现代 GPU 通常会以 32 或 64 个片段为一组Warp/Wavefront并行执行片段着色器这也是 GPU 渲染性能远超 CPU 的核心原因。片段着色器的所有输入数据都来自光栅化阶段的插值结果主要分为三类‌顶点着色器透传的插值属性‌顶点着色器中声明的out变量在片段着色器中用同名同类型的in变量接收GPU 会自动完成透视校正插值。比如你从顶点着色器传出来的纹理坐标、顶点颜色、世界空间法线都会在三角形表面平滑过渡不会出现断层或透视拉伸的问题。‌Uniform 全局变量‌和顶点着色器完全共享同一套 Uniform 数据CPU 端通过QOpenGLShaderProgram::setUniformValue传入的变换矩阵、光照参数、时间变量等在片段着色器中可以直接读取使用。‌纹理采样器‌通过uniform sampler2D等类型的 Uniform 变量访问绑定到对应纹理单元的QOpenGLTexture对象使用texture()内置函数从纹理中采样出对应像素的颜色数据。注意在 Qt OpenGL 中顶点着色器的out变量和片段着色器的in变量必须类型完全匹配变量名可以不同但在程序链接时会自动通过位置绑定推荐保持同名避免出错。8. 逐片元 / 逐样本操作 Per-Sample Operations当一个片段从片段着色器输出后它依次经历以下“生死判决”‌像素所有权‌不属于当前窗口→ ‌丢弃‌。‌裁剪测试‌在 Scissor 矩形外→ ‌丢弃‌。‌模板测试‌模板值不匹配→ ‌丢弃‌或仅更新模板缓冲取决于配置。‌深度测试‌被前方物体遮挡→ ‌丢弃‌或仅更新深度缓冲取决于配置。‌混合/逻辑操作‌若启用混合与帧缓冲颜色计算出新颜色。若启用逻辑操作执行位运算。若均未启用直接覆盖。‌写掩码过滤‌根据 Color/Depth/Stencil Mask 决定最终写入哪些通道。‌写入帧缓冲‌最终数据存入显存呈现到屏幕。9. 写入帧缓冲 FramebufferOpenGL 的‌帧缓冲Framebuffer‌是渲染管线的终点它不仅仅是一块显存区域而是一个包含多个‌附件Attachments‌的容器。这些附件决定了渲染结果最终存储在哪里、以什么格式存储。默认情况下OpenGL 会将渲染结果写入由窗口系统提供的‌默认帧缓冲Default Framebuffer‌直接显示在屏幕上。但在高级渲染技术如后期处理、阴影映射、延迟渲染中我们需要创建‌帧缓冲对象FBO, Framebuffer Object‌将渲染结果“离线”保存到纹理或渲染缓冲中这就是所谓的‌离屏渲染Off-screen Rendering‌。帧缓冲的核心由以下三类附件组成它们共同构成了完整的像素信息1. 颜色附件 (Color Attachment)颜色附件用于存储片段着色器输出的最终颜色值RGBA。它是帧缓冲中最核心的部分没有颜色附件就看不到任何图像。2. 深度附件 (Depth Attachment)深度附件用于存储每个片段的深度值Z 值是实现‌深度测试Depth Test‌和‌遮挡剔除‌的基础。3. 模板附件 (Stencil Attachment)模板附件用于存储每个片段的模板值通常为 8 位整数配合‌模板测试Stencil Test‌实现复杂的遮罩、轮廓和体积阴影效果。附件类型主要用途是否必需常见格式‌颜色附件‌存储像素颜色 (RGBA)‌是‌GL_RGBA8,GL_RGBA16F,GL_RGBA32F‌深度附件‌存储深度值 (Z)用于遮挡剔除否GL_DEPTH_COMPONENT24,GL_DEPTH_COMPONENT32F‌模板附件‌存储模板值用于遮罩/轮廓否GL_STENCIL_INDEX8(常与深度合并)‌深度-模板‌同时存储深度和模板否GL_DEPTH24_STENCIL8// 1.创建与配置 // 创建一个带深度附件的 FBO QOpenGLFramebuffer *fbo new QOpenGLFramebuffer(QSize(800, 600), QOpenGLFramebuffer::CombinedDepthStencil); // 如果需要颜色纹理用于后期处理 GLuint colorTexture fbo-texture(); //2. 绑定与渲染 fbo-bind(); // 绑定 FBO后续所有绘制命令都写入此 FBO而非屏幕 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // 执行绘制逻辑... myShaderProgram-bind(); drawScene(); fbo-release(); // 解绑 FBO恢复默认帧缓冲屏幕

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑