OpenGL - 绘制三角形
前言
- 本节目标
- 掌握VAO,VBO,VEO的概念和用法
- 在窗口内绘制三角形
- 重难点都在本节
渲染管线
渲染管线这个名字听起来高端,其实很简单。管线就意味着串行执行,一步步来的代码。说白了就是一个流程图,没有什么并行,架构等复杂的逻辑。借用一张流程图来说:
- 渲染基础理论:所有的2d,3d物体可以拆分成若干个三角形。
- 最重要的输入:顶点数据
- 着重看蓝色部分,因为蓝色部分是开发者可以控制的
- 顶点着色器,这里着色二字不恰当。其实作用就是点定位代码。
- 片元着色器,在光栅化后 会转换成像素点,这里的功能就是计算像素最终颜色。这里的计算就非常的精细了
通过上述流程图可知如果我们要实现绘制一个三角形,写顶点着色器就可以了,如果带颜色再加一个片段着色器。
所以首先我们来画一个不带颜色的三角形
绘制三角形
下面是示例是绘制两个三角形组成的正方形,整体思路如下图
- 上图展示了一个2d/3d对象的生成步骤
- 数据源生成是一个难点,不可能人工输入,这个可以交给某些数据源生成器,比如Unity直观地拖拉拽。在本次示例比较简单,直接给出顶点数据
- 数据源生成VAO比较复杂,需要经过VBO,VAO的辅助
- 着色器代码编写也是难点,这个后续章节再聊。本节给个简单顶点着色器代码如下
kotlin
#version 330 core
layout (location = 0) in vec3 position;
out vec3 ourColor;
out vec2 TexCoord;
void main()
{
gl_Position = vec4(position, 1.0f);
}
给出顶点数据
kotlin
GLfloat vertices[] = {
-0.5f, -0.5f, -0.5f, // 1
0.5f, -0.5f, -0.5f, // 2
0.5f, 0.5f, -0.5f, // 3
0.5f, 0.5f, -0.5f, // 4
-0.5f, 0.5f, -0.5f, // 5
-0.5f, -0.5f, -0.5f, // 6
};
创建VAO(顶点数组对象)
- 这个步骤肯定是需要VBO辅助的,可以理解为VBO创建顶点的内存,VAO用于指向。
- 可能需要VEO辅助,VEO的作用是描述顶点数据的意义
下面介绍下这个三个对象即VAO,VBO,VEO
创建VAO对象
kotlin
// 创建VAO对象
GLuint VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
- 绘制对象的时候就使用它,VBO, EBO主要是用来辅助内存分配的。内存分配完成后,VAO会指向内存地址。
- 一个对象对应一个VAO
创建VBO
顶点缓冲对象,用于OpenGL在GPU管理顶点数据的对象,从而提升GPU访问顶点数据的性能。
kotlin
GLuint VBO;
// 1. 分配起始地址
glGenBuffers(1, &VBO);
// 2. 绑定其实是指定到GPU
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 3. 分配内存大小
glBufferData(GL_ARRAY_BUFFER, sizeof(DRAW_LIGHT::vertices), DRAW_LIGHT::vertices, GL_STATIC_DRAW);
// 4. 配置内存定义,这里即一次取3个float, 从这个三个中取3个,作为postion = 0 的属性供shader使用
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid *) 0);
// 5. 这一步例行公事,意思是启用顶点属性
glEnableVertexAttribArray(0);
- 创建VBO的过程就是对内存分配的描述。即用C++/OPENgl 去描述顶点数组每个内存块的意义。
- 步骤是分配地址 -> 绑定地址 -> 分配数据内存 -> 描述内存如何分配
- 作用是辅助内存分配。可以思考下为啥不能直接用VAO进行内存分配?
- VBO是可以复用的,即可以重复利用VBO指针
- VAO和VBO简单的关系可以如下图
这个比较重要,我们在举一个复杂的例子, 如下
一个复杂的vbo例子
kotlin
GLfloat vertices[] = {
// Positions // Colors // Texture Coords
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // Top Right
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // Bottom Right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // Bottom Left
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // Top Left
};
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// Color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
// TexCoord attribute
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
- 我们纵向看glVertexAttribPointer的前两个参数组成了两个数组[0,1,2],[3,3,2]。这两个数组定义了VBO的内存模型。
- 首先一个顶点的内存容量为8个GLfloat。且内存分配为3,3,2, 如下图
- 在渲染管线的顶点着色器这一步中,读取GLSL。如下。
kotlin
#version 330 core
# 读取 location 0, 即上图蓝色部分,交给position变量
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;
out vec3 ourColor;
out vec2 TexCoord;
void main()
{
gl_Position = vec4(position, 1.0f);
ourColor = color;
// We swap the y-axis by substracing our coordinates from 1. This is done because most images have the top y-axis inversed with OpenGL's top y-axis.
// TexCoord = texCoord;
TexCoord = vec2(texCoord.x, 1.0 - texCoord.y);
}
💡 注意上述代码,不是本节使用的着色器。只是为了说明原理用。本章所使用的着色器已在本节开头列出
创建EBO
用于描述顶点数据的意义
kotlin
GLfloat vertices[] = {
-0.5f, -0.5f, -0.5f, // 1
0.5f, -0.5f, -0.5f, // 2
0.5f, 0.5f, -0.5f, // 3
0.5f, 0.5f, -0.5f, // 4
-0.5f, 0.5f, -0.5f, // 5
-0.5f, -0.5f, -0.5f, // 6
};
GLuint indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
- 上述代码描述了第一个三角形用顶点的 0,1,3作为数据源绘制,第二个三角形用1,2,3作为数据源绘制
- 如何不用EBO描述,默认就是0,1,2为第一个,3,4,5为第二个
- 从上面两条容易想到,EBO可以优化顶点的数量,比如一个矩形用默认的方式要写6个点(2 (三角形个数)* 3(三角形顶点数)), 但其实矩形只有四个顶点。这种情况就可以用EBO实现
核心绘制代码
kotlin
shader.Use();
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
- shader是顶点着色器和片元着色器的封装。先忽略
glBindVertexArray
这里是使用VAOglDrawArrays
这里出发渲染管线,先经过顶点着色器绘制顶点,再通过片元着色器给片元上色