OpenGL基本架构知识
环境:Qt Creator + C++
参考书籍:《计算机图形学编程(使用OpenGL和C++)》作者:V.斯科特.戈登 约翰.克莱维吉 (人民邮电出版社)
参考博客:https://blog.csdn.net/weixin_59876363/article/details/122570371
一、绪论
简介
-
OpenGL是什么
- 用于渲染2D和3D矢量图形的跨语言、跨平台的应用程序编程接口(API)
- 提供了多级图形管线和GLSL(高级着色语言)进行模型的像素化
- 核心库使用c编写,同时支持多种系统和语言
-
核心模式/可编程管线模式:
将顶点数据经过管线处理,最终生成显示的像素点
。以下为渲染顺序:- 顶点着色器:必须自己实现,依靠顶点数据和着色器指令控制顶点的渲染位置和方式
- 曲面细分着色器:可以在简单图形上生成大量网格图元(常用的是三角)
- 几何着色器:赋予程序员一次处理一个图元(多个顶点)的能力(常用的是三角)
- 光栅化:将输入的图元转化成二维图像片段,即像素绘制的片段
- 片段着色器:为光栅化的像素点指定颜色
- 测试与混合
-
GLSL运行在GPU上,OS不是总能捕捉运行的错误,通常需要进行GLSL运行日志的打印进行debug
-
GLSL代码加载进入着色器的过程
- C++获取GLSL着色器代码,可以在文件中读取,也可以硬编码在字符串中
- 创建OpenGL空着色器对象并将GLSL着色器代码加载进着色器对象(一般至少要提供顶点着色器和片段着色器的代码,其他着色器代码是可选的)
- 最后使用OpenGL命令编译链接着色器对象,并将该对象加载进硬件
-
QOpenGLWidget提供了三个便捷的虚函数,可以进行重载实现典型的OpenGL任务
- initializeGL:初始化设置OpenGL资源和状态
- paintGL:渲染OpenGL场景,widget需要进行实时更新的调用
- resizeGL:设置OpenGL视口和投影等。widget调整大小时调用
-
顶点着色器会在GPU上创建内存,通过GL_ARRAY_BUFFER缓冲类型的顶点缓冲对象进行管理,每一个VBO记录了一种状态。
- 数据:从内存加载顶点缓冲对象(Vertex Buffer Objects ,VBO)到的显存中
- 规则:通过顶点数组对象(Vertex Array Object,VAO)对顶点数据进行解释
-
OpenGL是一个巨大的状态机,数据输入后,绘制状态参数(材质,光照,连接方式···)决定了输出的图像。OpenGL通过改变上下文变量来改变OpenGL状态,从而告知OpenGL如何绘制图像
-
在paintGL之外的地方调用绘制函数,没有意义,绘制图像最终将被paintGL()覆盖。如果不想被覆盖则应该使用widget的update()函数进行安排和更新
缓冲区对象
-
缓冲区绑定流程
// 1.声明VBO和VAO GLuint vao[1];// GLuint实际就是unsigned int GLuint vbo[2]; // 2.创建缓冲区,并将返回的ID存入VAO和VBO glGenVertexArrays(1, &VAO); glGenBuffers(2, &VBO);// (创建ID的数目, 用来保存返回ID的数组) // 3.将VAO和VBO绑定成缓冲区对象 glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); // 4. 初始化缓冲区数据 glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW); // 5. 告诉GPU如何解释VBO中的属性信息 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), nullptr); // 6. 启用0号顶点属性 glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glEnableVertexAttribArray(2); glBindBuffer(GL_ARRAY_BUFFER, 0); // 7. 使用glDrawArrays()绘制对象
-
着色器的绑定:每个缓冲区需要有在顶点着色器中声明的相应的顶点属性变量
layout(location = 0) in vec3 position // 位置值为0的顶点属性指针指向的数据,每次抓取3个到position中
- layout修饰符是将顶点属性和特定缓冲区关联的方法,这里的0就是绑定的0号ID的VBO
- in关键字表示顶点属性从缓冲区接收数值
- vec3的意思是着色器每次调用会抓取三个浮点类型的值,即一个坐标(x, y, z)
- 变量名称是position
-
通常把顶点数据放在一个缓冲区中,并把这个缓冲区和着色器中声明的顶点属性相关联
-
在OpenGL中,缓冲区被包含在顶点缓冲对象(Vertex Buffer Object, VBO)中,VBO在C++/OpenGL应用程序中被声明和实例化。一个场景可能需要很多个VBO,通常我们在初始化的阶段生成并填充若干个VBO,方便后续直接使用。
-
使用
uniform
关键字在着色器中声明统一变量
GLSL
-
GLSL(OpenGL Shading Language)使用C语言作为基础高阶着色语言,避免了使用汇编语言或硬件规格语言的复杂性。
// 典型程序 #version version_number in type in_variable_name; in type in_variable_name; out type out_variable_name; uniform type uniform_name; void main(){ out_variable_name = weird_stuff_we_processed; }
-
OpenGL确保至少有16个包含4分量的顶点属性可以使用,但是可以声明的顶点属性数量存在上限
-
GLSL的容器
- 向量(Vetor)
- vecn:n个float类型
- bvecn:n个boolean类型
- ivecn:n个intgers类型
- uvecn:n个无符号整形
- dvecn:n个double类型
- 矩阵(Matrix)
- 向量(Vetor)
-
向量重组
vec2 vect = vex2(0.5, 0.7); vec4 result = vex4(vect, 0.0, 0.0);
-
如果类型和名字都一致,OpenGL可以把不同程序文件变量都链接在一起
#version 330 core // 顶点着色器定义和声明变量 layout (location = 0) in vec3 aPos; out vec4 vertexColor; void main() { gl_Position = vec4(aPos, 1.0); vertexColor = vec4(0.5, 0.0, 0.0, 1.0); } #version 330 core // 片段着色器接受变量 out vec4 FragColor; in vec4 vertexColor;// 接收的变量 void main() { FragColor = vertexColor; }
如果将它们封装在单独的资源文件中,使用shaderProgram.addShaderFromSourceFile()函数进行连接,则首行必须是版本号
-
在
paintGL()
以外的位置绘制openGLmakeCurrent(); // openGL绘制函数 doneCurrent(); update();
如果不使用上述函数进行包含,绘制的图像将会被paintGL( )覆盖
-
顶点着色器的输入来源于openGL形式化的数据,使用
layout(location = 0)
链接到顶点数据,可以在cpu上配置顶点属性 -
GPU和CPU之间的数据传输
- 使用glGetAttribLocation查村属性位置值
- 绑定GPU的属性值,告诉它向cpu解析的属性值
// 1.属性值查询的方式 shaderProgram.bind(); // 查询属性位置值,如果查询不到返回-1 Glint posLocation = shaderProgram.attributeLocation("aPos"); // 告诉显卡如何解析缓冲里的属性值 glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float),(void*)0); // 2.绑定的方式 shaderProgram.bind(); GLint posLocation = 2; shaderProgram.bindAttributeLocation("aPos", poslocation); glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0); // 开始VAO管理的第三个属性值 glENableVertexAttribArray(posLocation);
-
uniform是一种CPU向GPU中着色器发送数据的方式,是全局的Global,可以被任意着色器程序在任意阶段访问
- 如果声明一个uniform却没有用过,编译器会默认移除这个变量,导致最后编译出的版本中并不会包含它,这可能导致几个非常麻烦的错误
- OpenGL在其核心是一个c库,所以不支持类型的重载 ,在函数参数类型不同时,需要为其定义新的函数。
-
在Qt中可以使用QTimer的timeout信号槽,传递一个随着时间改变的值
-
将顶点着色器中的数据传到片段着色器中
// 顶点着色器 #version 330 core layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aColor; out vec3 ourColor;# 传递的值 void main(){ gl_Position = vex4(aPos, 1.0); ourColor = aColor; } // 片段着色器 #version 330 core out vec4 FragColor; in vec3 ourColor; void main(){ FragColor = vec4(ourColor, 1.0); }
纹理处理
- 当出现复杂图形绘制时候通常采用纹理贴图采样的方式,即对一个图片进行裁剪后的部分显示
https://blog.csdn.net/weixin_59876363/article/details/122807398