前言
本篇是学习OpenGL时候做的实验,意在理解OpenGL渲染的相关操作,网上入门100多节课,看看了解下原理也可以,实际上开发起来就那些东西,我这篇看懂了可以去看看更深的课程了。本次项目是绘制一个彩色三角形并且让它动起来。
准备
首先是环境方面,我是用 c++QT框架开发,他里面已经集成了OpenGL模块很方便,qmake环境下在pro文件中加上Qt+=opengl就可以了,就glad要上网下载一下,这里就不提供了,下载好后放到项目中就行了。
原理
OpenGL其实就是一个状态机,通过他的api调用gpu函数在gpu上操作,比如在显存上开辟空间存放绘制数据等
vbo
简单理解就是存放在显存上的绘制数据,开辟空间放入数据等后面看我程序就行了。
vao
vao是vbo的描述属性,vbo里面的vec在gpu中是flaot数组,gpu哪里知道哪些是顶点数据,一个顶点有几个数据,哪些又是颜色数据,就是描述这些,和vbo一起绑定在OpenGL状态机的接口上
shader
这里就要提一下gpu的渲染了,他是先把顶点绘制出来,然后光栅化,比如画一个三角形,先通过vertxt部分glsl程序传入的顶点数据画出来顶点,然后进入到光栅化,将三个点围成的区域处理成许多小方格,每个小方格就是一个像素,也叫片元,vertxt程序中在开始的时候接收进来的颜色数据会传到fragment程序里面,fragment程序就会先处理已绘制的顶点颜色,然后中间那些片元的颜色咋搞,就用到了插值算法着色,插值算法就不提了,不懂去搜很简单。
程序实现
接下来我带着打架看看一个简单的三角形绘制,环境配置讲过了就不多说了,直接进入程序编写
UI
UI我搭建的很简单,用的mainwindow,里面放入openglwediget模块,选择这个模块是为了方便后期事件处理等比较方便,用纯c++写比较繁琐,qt有些api把这些过程封装起来了好用。然后这个窗口要提升一下,写了一个mywediget类,继承的是OpenGLwediget和OpenGL的函数
class myWediget : public QOpenGLWidget,protected QOpenGLExtraFunctions
提升的时候加入object,挂到对象树上管理。
mywediget
刚创建的时候做好初始化
构造函数:
myWediget(QWidget *parent = nullptr) ;
myWediget::myWediget(QWidget *parent) : QOpenGLWidget(parent) {}
重写了三个函数:
virtual void initializeGL() override;
virtual void resizeGL(int w, int h) override;
virtual void paintGL() override;
initializeGL()是初始化函数,resizeGL()是设置窗口函数,paintGL()是绘制函数。
初始化函数就是把vbo和vao放进去准备绘制的数据
void myWediget::initializeGL() {
initializeOpenGLFunctions();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// 1. 创建 VAO 和 VBO
glGenVertexArrays(1, &VAO);//posVBO,ColorVBO,VAO是创建好的GLuint变量=uint
glGenBuffers(1, &posVBO);//分别创建一个vao,posVBO,ColorVBO
glGenBuffers(1, &ColorVBO);
// 2. 绑定 VAO 和 VBO,vao是vbo的描述,把他们两个绑定起来
//QOpenGLExtraFunctions::glBindVertexArray(VAO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, posVBO);//GL_ARRAY_BUFFER是状态机上的vbo接口,把这个vbo绑上去
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//vertices是矩阵数据数组类型的,下面的color一样
glBindBuffer(GL_ARRAY_BUFFER, ColorVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
glBindBuffer(GL_ARRAY_BUFFER, posVBO);//当前状态机绑定的是colorvbo,我们要先绘制顶点,传入顶点数据,所以要绑定会posvbo
glEnableVertexAttribArray(0);//激活vao的0号位置,0号位置存放的就是posvbo的描述
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, ColorVBO);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
// 4. 解绑
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
prepareshader();
}
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);第一个参数是vao的0号位置,第二个参数是3,意思是数组中有三个顶点的数据,字符类型float,归一化这里不管填false,3 * sizeof(float)意思是步长,就是我们一个顶点有三个float数值x,y,z,三个为一个顶点,后面哪个是偏移量,这里用不上,填0,历史原因前面要加(void*)。
prepareshader
经过上面的操作以后数据准备好了,我们再要准备着色器程序,就是vertexshader和fragmentshader
c++写法是:
shaderprogram.addShaderFromSourceCode(QOpenGLShader::Vertex,
"#version 460 core\n"
"layout(location=0) in vec3 aPos;\n"
"layout(location=1) in vec3 aColor;\n"
"out vec3 Color;\n"
"void main() { gl_Position = vec4(aPos, 1.0); "
"Color=aColor;"
"}");
shaderprogram.addShaderFromSourceCode(QOpenGLShader::Fragment,
"#version 460 core\n"
"in vec3 Color;\n"
"out vec4 FragColor;\n"
"void main() { FragColor = vec4(Color, 1.0); }");
shaderprogram.link();
这样写glsl他代码没有高亮,你也不知道是对是错,也不方便管理,就在vscode中单独写了这两个glsl程序
vertexshader
#version 460 core
layout(location=0)in vec3 aPos;
layout(location=1)in vec3 aColor;
uniform float time;
out vec3 Color;
void main(){
float offsetx=sin(time);
gl_Position=vec4(aPos.x+offsetx,aPos.y,aPos.z,1.0);
Color=aColor;
}
fragmentshader
#version 460 core
in vec3 Color;
out vec4 FragColor;
void main(){FragColor=vec4(Color,1.0);}
把这两个程序加入到项目中,用qfile把他以文本形式读取出来
void shaderprogram::loadfile(const QString &vertexpath, const QString &fragmentpath)
{
auto readfile=[](const QString &path)->QString{
QFile file(path);
if(!file.open(QIODevice::ReadOnly|QIODevice::Text)){
qDebug()<<"文件打开失败";
return "";
}
else {
auto text= QString(file.readAll());
qDebug() << "读取文件内容:" << path << "\n" << text; // 输出实际读取的内容
return text;
}
};
QString vertextcode=readfile(vertexpath);
QString fragmentcode=readfile(fragmentpath);
if(vertextcode.isEmpty()||fragmentcode.isEmpty()){
qDebug()<<"代码读取为空!";
return;
}
//编译顶点着色器
if(!addShaderFromSourceCode(QOpenGLShader::Vertex,vertextcode)){
qDebug()<<"顶点着色器编译失败!"<<log();
return;
}
//编译片段着色器
if(!addShaderFromSourceCode(QOpenGLShader::Fragment,fragmentcode)){
qDebug()<<"片段着色器编译失败!"<<log();
return;
}
//链接shader
if(!link()){
qDebug()<<"链接shader失败"<<log();
return;
}
}
paintGL
这些弄好了以后就开始绘制了
void myWediget::paintGL() {
static QElapsedTimer timer;
if (!timer.isValid()) timer.start();
m_time = timer.elapsed() / 1000.0f;
glClear(GL_COLOR_BUFFER_BIT);//清理画布颜色
sdprogram.bind();绑定program
sdprogram.setUniformValue("time",m_time);
glBindVertexArray(VAO);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLES, 0, 3);
sdprogram.release();
update();//更新界面
}
把系统时间m_time传给vertexshader中的uniform time变量,让三角形随着时间变动移动。
到此就做完了,更多细节可以看我源码