☰
QOpenGL入门基础教学
2026/10/14 3:35:10 网站建设 项目流程

目录

  • 1,要包含的头文件
  • 2,OPenGL 渲染原理简介
  • 3,OPenGL ,VAO,VBO 简介
    • 3.1,VBO(Vertex Buffer Object,顶点缓冲区对象)
    • 3.2,VAO(Vertex Array Object,顶点数组对象)
  • 4,OPenGL 基础 API
  • 4,OPenGL 重点 API
  • 5,三角形示例

1,要包含的头文件

<GLTools.h> : 总入口头文件,包含大量辅助类型、工具函数。 <GLShaderManager.h> : 着色器管理器头文件

2,OPenGL 渲染原理简介

OPenGL 是实现图形渲染的 API ,并不是图形引擎,也不是 IG(Image Generator 图像生成器) 。


如上图所示展示了渲染的过程。
1,顶点着色器 Vertex Shader(第一个可编程阶段)
… 对顶点做坐标变换:模型矩阵、视图矩阵、投影矩阵(MVP 变换),把模型本地坐标转换成裁剪空间坐标。
… 可以传递顶点附属数据:顶点颜色、纹理坐标、法线,传给后面阶段。
.
2,几何着色器 Geometry Shader(可选,可编程)
…输入:完整图元(比如三角形、线段、点,是顶点着色器输出之后组装好的一整个图元)
…作用:
可以修改、增加、删除图元。
比如:输入一个点,把它扩展成一个三角形;把一条线加宽生成四边形。
.
3,形状 (图元) 装配 Shape Assembly(固定管线阶段,不能写代码)
…根据你绘图函数指定的图元类型(GL_TRIANGLES三角形 /GL_LINES线 /GL_POINTS点),把零散顶点拼装成完整图元(三角形、线段)。
同时会做裁剪:把完全在屏幕外的图元直接扔掉,只保留部分在视口内的图元。
.
4,光栅化 Rasterization(固定阶段)
…把三维三角形(矢量几何)转换成一堆二维像素小格子(片元 fragment)
简单说:把三角形这个几何形状,“铺满” 屏幕上对应的像素方格,生成大量片元。
.
5,片元着色器 Fragment Shader(第二个核心可编程阶段)
…计算这个片元最终的颜色,是画面效果核心!
你在这里写 GLSL 可以做:纹理采样、光照计算、阴影、材质颜色等。
每个片元单独并行运行片元着色器。
.
6,测试与混合 Tests and Blending(固定阶段)
输入:片元着色器输出的片元颜色
按顺序执行一系列测试,不通过的片元直接丢弃,不会画到屏幕:
裁剪测试、模板测试(Stencil Test)
深度测试(Depth Test,最常用):对比这个片元深度和已经存在帧缓存里的深度,如果这个片元在物体后面,直接丢弃,解决物体遮挡。
如果测试通过:
执行混合 Blending,用来做透明效果:把当前片元颜色 和 屏幕上已有的颜色按公式混合(比如半透明物体)。

最后,最终颜色写入帧缓冲(Framebuffer),送到显示器显示。

3,OPenGL ,VAO,VBO 简介

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

VBO 本质是在 GPU 显存中开辟的一块内存区域。

  • 在 CPU 内存里,顶点只是普通数组(float vertices[]),放在 RAM;
  • 调用glBufferData,CPU 会把这份数组复制到 GPU 显存,这块显存就是 VBO。
    一旦拷贝完成,后续绘制的时候,GPU 直接读显存,不需要每次绘图都从 CPU 传数据,极大提速。

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

VAO不存储顶点数据!
VAO 是一个状态容器(状态对象),保存一套「怎么解析 VBO 里面数据」的全部配置。
它记录的配置信息:

  1. 绑定了哪一个 VBO;
  2. 每个顶点属性(attribute)的设置:
    • 属性编号(location,顶点着色器layout(location=0))
    • 数据类型:float /int
    • 几个分量:3 个 (x,y,z) 还是 2 个 (u,v)
    • 是否归一化
    • 步长 stride:相邻两个顶点之间隔多少字节
    • 偏移 offset:从这块 VBO 内存的哪个位置开始读这个属性

4,OPenGL 基础 API

  • void glGenVertexArrays(GLsizei n, GLuint *arrays)
    生成若干个 VAO 的 ID(顶点数组对象名称)。
参数 1. GLsizei n:输入参数,想要生成多少个 VAO 的 ID(数量,整数) - 示例写`1`:只生成 1 个 VAO 2. GLuint *arrays:**输出参数,指针**。 - 传入一个`GLuint`变量 / 数组的地址。OpenGL 会把生成好的 VAO ID 写到这个内存地址。 - 如果你只要 1 个 VAO:传`&VAO`(变量地址) - 如果一次性生成多个:传数组,如`GLuint vaos[3]; glGenVertexArrays(3, vaos);`
  • void glGenBuffers(GLsizei n, GLuint *buffers);
    生成若干个缓冲区对象 ID(VBO/EBO 都用这个函数)。
### 参数 1. `GLsizei n`:输入,要生成多少个缓冲区 ID 2. `GLuint *buffers`:输出指针参数,缓冲区 ID 写入这个地址。
  • void glBindVertexArray(GLuint array);
    绑定指定 ID 的 VAO 为当前上下文激活的 VAO。
### 参数 `GLuint array`:输入参数,**VAO 的 ID 编号**。 - 填你用`glGenVertexArrays`得到的 VAO 变量 - `0`代表解绑
  • void glBindBuffer(GLenum target, GLuint buffer);
    把缓冲区 (VBO/EBO) 绑定到某个目标(target)上。
### 参数 1. `GLenum target`:输入,**缓冲区目标类型**(告诉 GPU 这个缓冲区用来干嘛) - `GL_ARRAY_BUFFER`:顶点缓冲区(VBO,存顶点坐标、颜色),就是我们写 VAO/VBO 代码最常用的 - `GL_ELEMENT_ARRAY_BUFFER`:索引缓冲区(EBO/IBO,存放顶点索引) - 还有其他:`GL_UNIFORM_BUFFER`等(暂时不用管) 2. `GLuint buffer`:输入参数,缓冲区 ID(glGenBuffers 生成的 VBO/EBO 编号) - 传 0 代表解绑这个 target 上的缓冲区

4,OPenGL 重点 API

  • void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage);
    为当前绑定的缓冲区(VBO)在 GPU 显存开辟内存,并且把 CPU 的数据拷贝到这块显存里
参数 1. GLenum target : 和`glBindBuffer`的 target 保持一致。 GL_ARRAY_BUFFER :VBO(顶点数据缓冲区,我们现在用的) GL_ELEMENT_ARRAY_BUFFER :EBO 索引缓冲区 2. GLsizeiptr size 要分配多少**字节**的显存。 一般写法:`sizeof(vertices)`,`vertices`是 CPU 端存放顶点的数组。 单位是**字节 Byte**,不是顶点数量,不是 float 数量! 3. const void *data : CPU 内存中数据源的指针。 如果填数组名`vertices`:把 CPU 数组里的数据上传到 GPU 显存 如果填`NULL`:只在 GPU 开辟一块空显存,**不上传任何初始数据**,后面再用`glBufferSubData`更新 4. GLenum usage 提示 GPU:这块显存**将如何读写**,GPU 根据这个选择显存存放位置(性能提示,不是强制限制) 常用两个: GL_STATIC_DRAW:数据**几乎不会修改**,CPU 上传后很少改动,GPU 优先放高速显存(静态模型,最常用) GL_DYNAMIC_DRAW`:数据会频繁修改(动态物体、粒子) 还有`GL_STREAM_DRAW`(每一帧都更新)

示例:

float vertices[]={-0.5f,-0.5f,0,0.5f,-0.5f,0,0,0.5f,0};glBindBuffer(GL_ARRAY_BUFFER,VBO);glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);

。。。
。。。
。。。

  • void glVertexAttribPointer(
    GLuint index,
    GLint size,
    GLenum type,
    GLboolean normalized,
    GLsizei stride,
    const void *pointer
    );

告诉 GPU:如何从 VBO 的显存里面读取某一组顶点属性(attribute,例如顶点坐标、颜色)

### 参数逐个详解 1. GLuint index :属性编号 (location) 对应顶点着色器 `layout(location = index)`。 例:`layout(location = 0) in vec3 aPos;` → index 填 0,代表这个配置是给 0 号属性 aPos。 2. GLint size :**每个顶点这个属性有几个分量** vec3(x,y,z) → size=3;vec2(uv) → size=2;vec4 → size=4 不是顶点总数!是单个属性的分量数量。 3. GLenum type :数据的基础类型 我们常用:`GL_FLOAT` 浮点数。其他还有 GL_INT、GL_SHORT 等。 4. GLboolean normalized :是否归一化 GL_FALSE :浮点数直接原样读取(坐标一般选 false) GL_TRUE :整数类型映射到 [-1,1] 或 [0,1] 区间(颜色用 uint8 的时候常用) 5. GLsizei stride :**步长,相邻两个顶点之间,间隔多少字节** 从这个顶点的属性起点,到下一个顶点同一个属性起点的字节距离。 交错存储例子:`[x,y,z,r,g,b][x,y,z,r,g,b]` 每个顶点:3float (位置)+3float (颜色) → stride = 6 * sizeof (float) 如果**这个 VBO 只存这一种属性(单独存储)**,stride 填 0,OpenGL 自动计算。 6. const void *pointer :**偏移量 offset** 当前属性,从 VBO 内存的**哪个字节位置开始读**。 是**字节偏移**,不是数组下标!类型是 void*,用`(void*)字节数`写法。 如果属性从缓冲区最开头读:`(void*)0` 交错例子:位置占 3 个 float,颜色从后面开始读:`(void*)(3*sizeof(float))`

示例:

// 顶点:x y z// -0.5,-0.5,0// 0.5,-0.5,0// 0, 0.5, 0glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);glEnableVertexAttribArray(0);

5,三角形示例

#include"MyOpenGLWidget.h"#include<QSurfaceFormat>#include<QDebug>unsigned intVBO;unsigned intVAO;//顶点坐标float vertices[]={-0.5f,-0.5f,0.0f,// 左下角0.5f,-0.5f,0.0f,// 右下角0.0f,0.5f,0.0f// 顶部};constchar*vertexShaderSource="#version 330 core\n""layout (location = 0) in vec3 aPos;\n""void main()\n""{\n""gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n""}\0";constchar*fragmentShaderSource="#version 330 core\n""out vec4 FragColor;\n""void main()\n""{\n""FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n""}\0";unsigned int shaderProgram;MyOpenGLWidget::MyOpenGLWidget(QWidget*parent):QOpenGLWidget(parent){// 关键:请求 OpenGL 3.3 Core Profile 上下文// 否则默认上下文版本可能低于3.3,导致 #version 330 core 着色器编译失败QSurfaceFormat format;format.setVersion(3,3);format.setProfile(QSurfaceFormat::CoreProfile);setFormat(format);}MyOpenGLWidget::~MyOpenGLWidget(){}voidMyOpenGLWidget::initializeGL(){// 初始化当前 OpenGL 上下文的函数指针// QOpenGLFunctions_3_3_Core::initializeOpenGLFunctions()// 如果上下文不是 3.3 Core,会返回 falsebool ok=initializeOpenGLFunctions();qDebug()<<"initializeOpenGLFunctions:"<<ok;glClearColor(0.0f,0.0f,0.0f,1.0f);// VAO:保存顶点属性配置信息(怎么解析VBO里的数据)// VBO:GPU显存缓冲区,用来存放顶点原始坐标数据glGenVertexArrays(1,&VAO);// 生成1个顶点数组对象VAO,把返回的ID存入变量VAOglGenBuffers(1,&VBO);// 生成1个顶点缓冲对象VBO,把返回的ID存入变量VBOglBindVertexArray(VAO);// 绑定VAO到当前上下文,之后所有顶点属性配置都会记录进这个VAOglBindBuffer(GL_ARRAY_BUFFER,VBO);// 绑定VBO到当前上下文,之后所有顶点属性配置都会记录进这个VBO//将数据传入显存glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);// 把顶点数据复制到当前绑定到的 VBO//告知显卡如何解析glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);//开启 VAO 属性glEnableVertexAttribArray(0);glBindVertexArray(0);glBindBuffer(GL_ARRAY_BUFFER,0);//编译顶点着色器unsigned int vertexShader=glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader,1,&vertexShaderSource,NULL);glCompileShader(vertexShader);//编译片段着色器unsigned int fragmentShader=glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader,1,&fragmentShaderSource,NULL);glCompileShader(fragmentShader);//link shadersshaderProgram=glCreateProgram();glAttachShader(shaderProgram,vertexShader);glAttachShader(shaderProgram,fragmentShader);glLinkProgram(shaderProgram);// 链接成功后可以删除着色器对象glDeleteShader(vertexShader);glDeleteShader(fragmentShader);}voidMyOpenGLWidget::resizeGL(int w,int h){// 用像素尺寸设置视口,兼容高 DPI 缩放constqreal dpr=devicePixelRatioF();constint pw=static_cast<int>(w*dpr);constint ph=static_cast<int>(h*dpr);glViewport(0,0,pw,ph);}voidMyOpenGLWidget::paintGL(){// 清屏glClear(GL_COLOR_BUFFER_BIT);// 关键:每帧都要重新绑定着色器程序,因为 Qt 可能在 paintGL 之间重置 OpenGL 状态glUseProgram(shaderProgram);glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES,0,3);}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询