环境:Qt Creator + C++
参考书籍:《计算机图形学编程(使用OpenGL和C++)》作者:V.斯科特.戈登 约翰.克莱维吉 (人民邮电出版社)
参考博客:https://blog.csdn.net/weixin_59876363/article/details/122570371
OpenGL是什么
核心模式/可编程管线模式:
将顶点数据经过管线处理,最终生成显示的像素点
。以下为渲染顺序:
GLSL运行在GPU上,OS不是总能捕捉运行的错误,通常需要进行GLSL运行日志的打印进行debug
GLSL代码加载进入着色器的过程
QOpenGLWidget提供了三个便捷的虚函数,可以进行重载实现典型的OpenGL任务
顶点着色器会在GPU上创建内存,通过GL_ARRAY_BUFFER缓冲类型的顶点缓冲对象进行管理,每一个VBO记录了一种状态。
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中
通常把顶点数据放在一个缓冲区中,并把这个缓冲区和着色器中声明的顶点属性相关联
在OpenGL中,缓冲区被包含在顶点缓冲对象(Vertex Buffer Object, VBO)中,VBO在C++/OpenGL应用程序中被声明和实例化。一个场景可能需要很多个VBO,通常我们在初始化的阶段生成并填充若干个VBO,方便后续直接使用。
使用uniform
关键字在着色器中声明统一变量
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的容器
向量重组
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()
以外的位置绘制openGL
makeCurrent();
// openGL绘制函数
doneCurrent();
update();
如果不使用上述函数进行包含,绘制的图像将会被paintGL( )覆盖
顶点着色器的输入来源于openGL形式化的数据,使用layout(location = 0)
链接到顶点数据,可以在cpu上配置顶点属性
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,可以被任意着色器程序在任意阶段访问
在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