learnopengl-1-你好三角形
learnopengl-1-你好三角形
环境的搭建和窗口的生成有时间之后补上。
本篇笔记先按照网站的顺序梳理一边基本概念,之后结合傅老师的讲解,串联所有概念和步骤。
OpenGL的图形渲染管线(Graphics Pipline)
图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。
- 顶点着色器(Vertex Shader):顶点着色器主要的目的是把3D坐标转为另一种3D坐标;
- 图元装配(Primitive Assembly):将顶点着色器输出的所有顶点装配成指定图元的形状;
- 几何着色器(Geometry Shader):几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状;
- 光栅化(Rasterization Stage):把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment);
- 片段着色器(Fragment Shader):计算一个像素的最终颜色;
- 测试和混合(Test and Blending):检测深度决定哪些片段被舍弃,以及检查alpha值对物体进行混合;
顶点输入
定义这样的顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。
VBO(顶点缓冲对象)
VBO用来储存输入的一大堆顶点数据,可以一次性将一大批数据发送到显卡上。
以下代码就是VBO的基本定义和使用方法。
1 |
|
EBO(元素缓冲对象)
这个本来是在教程最后讲解的,但是EBO也是对输入的顶点数据进行处理的对象。
EBO简单来说就是用来解决顶点重复定义的问题。
假如现在要通过绘制两个三角形的方法绘制一个矩形,如果没有EBO,需要定义如下顶点集合:
1 |
|
可以看到有一些相同的顶点被重复定义了,而EBO是一个缓冲区,就像一个顶点缓冲区对象一样,它存储 OpenGL 用来决定要绘制哪些顶点的索引。
1 |
|
之后EBO的使用也和VBO类似,之后在渲染循环中,将glDrawArrays()替换成glDrawElements()
1 |
|
着色器
顶点着色器
shader语法和编写在下一节具体展开,这里只简单介绍shader如何配置。
和之前的VBO和EBO类似,也需要先定义一个shader编号,之后生成shader,告诉生成的shader源码是哪些,最后编译shader
1 |
|
片段着色器
和顶点着色器完全一样的用法
1 |
|
着色器程序对象
之前编译好的两个着色器并不能直接使用,需要将其合并(代码上体现出来就是将之前两个shader附加到着色器程序上),之后链接成一个着色器程序对象,在渲染时激活这个着色器程序。
1 |
|
在把着色器对象链接到程序对象之后,就可以删除着色器对象了
链接顶点属性
这部分在第一次读的时候较难理解,之后结合傅老師/OpenGL教學 第一章才理解。
我们之前定义了VBO来将一堆顶点数据输入GPU,但是GPU其实并不知道这些顶点数据的含义,glVertexAttribPointer函数就是为了告知GPU如何正确理解这些“杂乱”的顶点信息的。
1 |
|
顶点数组对象(VAO)
VAO使得当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。
由以上图可以看出VAO包含很多attribute pointer和一个EBO,且可以看出真正储存顶点信息的是一个个的VBO,VAO中存储着如何去解释VBO中数据的顶点属性指针和EBO。
深入理解VAO和VBO
可以使用一个VAO绑定多个VBO但是要注意:
多个VBO中的顶点信息不能重复,如VBO1中存储所有的顶点位置信息,VBO2中存储所有的颜色信息,不能VBO1和VBO2都存储位置信息。
如果混合存储,如下图,绑定VBO2的时候会覆盖掉之前绑定的VBO1;
1 |
|
将顶点信息和颜色信息分别存入到VBO1和VBO2中,再将VBO1和VBO2绑定在VAO上就可以正常显示两个三角形;
1 |
|
要先绑定VAO,再绑定EBO,否则会出错。
1 |
|
总结
首先要理解OpenGL采用状态机架构,所以绑定就意味着使用。
练习
添加更多顶点到数据中,使用glDrawArrays,尝试绘制两个彼此相连的三角形
关键代码:
1
2
3
4
5
6
7
8
9
10
11float triangle[] = {
-1.0f, -0.5f, 0.0f,
0.0f, -0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
0.0f, -0.5f, 0.0f,
1.0f, -0.5f, 0.0f,
0.5f, 0.5f, 0.0f
};
// 渲染循环
glDrawArrays(GL_TRIANGLES, 0, 6);创建相同的两个三角形,但对它们的数据使用不同的VAO和VBO
关键代码:
这里要注意要分别为两个VAO设置顶点属性,两个VAO该怎样处理VBO中的数据都要分别告知。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25unsigned int VAO[2], VBO[2];
glGenBuffers(2, VBO);
glGenVertexArrays(2, VAO);
glBindVertexArray(VAO[0]);
glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(triangle1), triangle1, GL_STATIC_DRAW);
// 为VAO[0]设置顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(VAO[1]);
glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(triangle2), triangle2, GL_STATIC_DRAW);
// 为VAO[1]设置顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 渲染循环
glBindVertexArray(VAO[0]);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(VAO[1]);
glDrawArrays(GL_TRIANGLES, 0, 3);创建两个着色器程序,第二个程序使用一个不同的片段着色器,输出黄色;再次绘制这两个三角形,让其中一个输出为黄色
关键代码:1
2
3
4
5
6
7
8// 渲染循环
glUseProgram(shaderProgram1);
glBindVertexArray(VAO[0]);
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(shaderProgram2);
glBindVertexArray(VAO[1]);
glDrawArrays(GL_TRIANGLES, 0, 3);