1. 管理3D图形数据
我们有以下数据需要从C++程序传递给着色器:
-
3D图形相关数据 (以下称为顶点数据),这些数据在C++中放入一个缓冲区(数组中),并将这个缓冲区和着色器中声明的顶点属性绑定起来。
- 顶点坐标
- 顶点的颜色
- 顶点的纹理坐标
- 顶点的法向量
- 顶点的切向量
- 其它数据
-
uniform变量数据
- 纹理
- 投影矩阵
- 视图矩阵
- 模型矩阵
- 其它uniform变量(比如时间)
C++程序与着色器进行以上数据的传递,需要一些步骤:
-
只做一次的初始化工作,通常在init()函数中完成
- 创建缓冲区
- 将数据放入缓冲区
-
每一帧中都需要做的更新工作,通常在display() 或 update()函数中完成 。本书中,我们使用display()函数来更新数据。
- 启用包含了顶点数据的顶点缓冲区
- 将这个缓冲区与顶点属性绑定起来
- 启用这个顶点属性
- 将uniform变量数据传递给着色器
- 渲染图形(调用glDrawArrays()函数)
1.1. 名词解释
1.1.1.VBO(顶点缓冲区对象)
VBO(Vertex Buffer Object,顶点缓冲区对象)是 OpenGL 中用于存储顶点数据的缓冲区对象。它将顶点数据(如顶点坐标、颜色、法向量、纹理坐标等)存储在 GPU 的显存中,从而提高渲染性能。
1.1.1.1. VBO 的特点:
-
高效性:
- 将数据存储在 GPU 显存中,避免每帧都从 CPU 向 GPU 传递数据。
- 提高了数据传输和渲染的效率。
-
灵活性:
- 支持存储多种顶点数据(如位置、颜色、法向量等)。
- 可以与多个着色器程序共享。
-
可控性:
- 开发者可以通过 OpenGL 提供的 API 对 VBO 进行管理(如创建、绑定、更新等)。
1.1.1.2. VBO 的使用步骤:
-
创建缓冲区:
使用glGenBuffers()
函数生成一个或多个缓冲区对象。GLuint vbo; glGenBuffers(1, &vbo);
-
绑定缓冲区:
使用glBindBuffer()
函数将缓冲区绑定到目标(如GL_ARRAY_BUFFER
)。glBindBuffer(GL_ARRAY_BUFFER, vbo);
-
传递数据:
使用glBufferData()
函数将顶点数据传递到缓冲区。glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
-
使用缓冲区:
在渲染时,通过顶点属性指针(glVertexAttribPointer()
)将缓冲区数据与着色器中的顶点属性绑定。glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(0);
-
解绑缓冲区:
渲染完成后,可以解绑缓冲区。glBindBuffer(GL_ARRAY_BUFFER, 0);
1.1.1.3. VBO 的作用:
- 减少 CPU 和 GPU 之间的数据传输:将顶点数据存储在显存中,避免频繁的数据传输。
- 提高渲染性能:通过批量处理顶点数据,减少绘制调用的开销。
- 支持复杂的顶点数据:可以存储多种顶点属性(如位置、颜色、法向量等),并与着色器灵活绑定。
1.1.2. VAO(顶点数组对象)
VAO(Vertex Array Object,顶点数组对象)是 OpenGL 中用于管理顶点属性状态的对象。它存储了与顶点数据相关的状态信息,例如顶点属性指针、启用状态以及与 VBO 的绑定关系。通过 VAO,可以简化顶点数据的管理和切换。
1.1.2.1. VAO 的特点:
-
状态管理:
- VAO 记录了顶点属性的配置(如顶点属性指针、启用状态等)。
- 当绑定一个 VAO 时,所有与顶点属性相关的操作都会记录到该 VAO 中。
-
简化操作:
- 使用 VAO 后,无需每次渲染时重新配置顶点属性,只需绑定相应的 VAO 即可。
- 适合管理复杂的场景,尤其是需要频繁切换顶点数据的情况。
-
与 VBO 配合使用:
- VAO 本身不存储顶点数据,而是与 VBO 绑定,记录 VBO 的使用方式。
1.1.2.2. VAO 的使用步骤:
-
创建 VAO:
使用glGenVertexArrays()
函数生成一个或多个 VAO。GLuint vao; glGenVertexArrays(1, &vao);
-
绑定 VAO:
使用glBindVertexArray()
函数绑定 VAO,之后的顶点属性配置都会记录到该 VAO 中。glBindVertexArray(vao);
-
配置顶点属性:
配置顶点属性指针并启用顶点属性(通常与 VBO 配合使用)。glBindBuffer(GL_ARRAY_BUFFER, vbo); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(0);
-
解绑 VAO:
配置完成后,可以解绑 VAO。glBindVertexArray(0);
-
使用 VAO:
在渲染时,只需绑定相应的 VAO 即可。glBindVertexArray(vao); glDrawArrays(GL_TRIANGLES, 0, 3);
1.1.2.3. VAO 的作用:
- 简化顶点属性管理:通过 VAO,可以避免每次渲染时重复配置顶点属性。
- 提高渲染效率:VAO 将顶点属性的配置存储在 GPU 中,减少了 CPU 的开销。
- 支持多对象渲染:可以为每组顶点数据创建一个 VAO,方便在不同对象之间切换。
1.1.3. 顶点属性(vertex attribute)
顶点属性是 OpenGL 中用于描述顶点数据的属性信息。每个顶点可以包含多个属性,例如位置、颜色、法向量、纹理坐标等,这些属性会被传递到顶点着色器中进行处理。
1.1.3.1. 常见的顶点属性:
-
位置(Position):
- 描述顶点在三维空间中的位置,通常是一个三维向量(x, y, z)。
- 示例:
vec3 position = vec3(1.0, 0.5, -1.0);
-
颜色(Color):
- 描述顶点的颜色信息,通常是一个四维向量(r, g, b, a)。
- 示例:
vec4 color = vec4(1.0, 0.0, 0.0, 1.0);
(红色)
-
纹理坐标(Texture Coordinate):
- 用于纹理映射,描述顶点在纹理中的位置,通常是一个二维向量(u, v)。
- 示例:
vec2 texCoord = vec2(0.5, 0.5);
-
法向量(Normal):
- 描述顶点的法线方向,用于光照计算,通常是一个三维向量。
- 示例:
vec3 normal = vec3(0.0, 1.0, 0.0);
-
其他自定义属性:
- 可以根据需要定义其他属性,例如切线、骨骼权重等。
1.1.3.2. 顶点属性的使用步骤:
-
在顶点着色器中声明属性:
- 使用
layout(location = x)
指定属性的位置。
layout(location = 0) in vec3 position; layout(location = 1) in vec4 color;
- 使用
-
在 OpenGL 中绑定顶点属性:
- 使用
glVertexAttribPointer()
函数将顶点数据与着色器中的属性绑定。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position)); glEnableVertexAttribArray(0);
- 使用
-
传递数据到 GPU:
- 使用 VBO(顶点缓冲区对象)存储顶点数据,并通过 VAO(顶点数组对象)管理顶点属性。
1.1.3.3. 顶点属性的作用:
- 描述顶点数据:顶点属性是顶点数据的核心组成部分,用于描述顶点的各种特性。
- 与着色器交互:顶点属性会被传递到顶点着色器中,用于图形的变换、光照、纹理映射等操作。
- 支持灵活的渲染:通过自定义顶点属性,可以实现复杂的渲染效果,例如法线贴图、骨骼动画等。
顶点属性是 OpenGL 渲染管线中顶点数据的重要组成部分,结合 VBO 和 VAO,可以高效地管理和使用这些属性。
1.2. 常见函数
1.2.1. glVertexAttribPointer
glVertexAttribPointer
函数用于设置顶点属性指针,将顶点数据与顶点着色器中的属性绑定。它需要指定属性的位置、数据类型、是否归一化、步长以及偏移量。
1.2.2. glVertexAttribPointer()
的参数介绍
glVertexAttribPointer()
是 OpenGL 中用于设置顶点属性指针的函数,它将顶点缓冲区中的数据与顶点着色器中的顶点属性绑定。
1.2.2.1. 函数原型:
void glVertexAttribPointer(
GLuint index,
GLint size,
GLenum type,
GLboolean normalized,
GLsizei stride,
const void* pointer
);
1.2.2.2. 参数说明:
-
index
- 顶点属性的索引(location),对应顶点着色器中
layout(location = x)
的值。 - 示例:如果顶点着色器中定义了
layout(location = 0) in vec3 position;
,则index
应为0
。
- 顶点属性的索引(location),对应顶点着色器中
-
size
- 每个顶点属性的分量数量,取值范围为
1
到4
。 - 示例:
3
表示顶点属性是一个三维向量(如位置vec3
)。2
表示顶点属性是一个二维向量(如纹理坐标vec2
)。
- 每个顶点属性的分量数量,取值范围为
-
type
- 数据类型,指定顶点属性的每个分量的数据类型。
- 常见取值:
GL_FLOAT
:浮点数。GL_INT
:整数。GL_UNSIGNED_BYTE
:无符号字节。
-
normalized
- 是否将整数类型的数据归一化到
[0, 1]
或[-1, 1]
。 - 取值:
GL_TRUE
:归一化。GL_FALSE
:不归一化。
- 示例:如果数据类型是
GL_UNSIGNED_BYTE
,归一化后255
会被映射为1.0
。
- 是否将整数类型的数据归一化到
-
stride
- 两个连续顶点属性之间的字节偏移量。
- 如果顶点属性在缓冲区中是紧密排列的,可以设置为
0
,表示 OpenGL 自动计算步长。 - 示例:如果每个顶点包含位置(
vec3
)和颜色(vec3
),则步长为6 * sizeof(float)
。
-
pointer
- 数据在缓冲区中的起始位置(偏移量)。
- 通常使用
0
或offsetof
宏来指定偏移量。 - 示例:如果缓冲区中存储了位置和颜色数据,位置数据的偏移量为
0
,颜色数据的偏移量为3 * sizeof(float)
。
1.2.2.3. 示例代码:
// 假设顶点数据包含位置 (vec3) 和颜色 (vec3)
glBindBuffer(GL_ARRAY_BUFFER, vbo);
// 设置位置属性 (location = 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 设置颜色属性 (location = 1)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
1.2.3. glEnableVertexAttribArray()
函数介绍
glEnableVertexAttribArray()
是 OpenGL 中用于启用顶点属性数组的函数。它指定了某个顶点属性(如位置、颜色、纹理坐标等)是否被激活,从而允许 OpenGL 在渲染时使用该顶点属性。
1.2.3.1. 函数原型:
void glEnableVertexAttribArray(GLuint index);
1.2.3.2. 参数说明:
index
- 顶点属性的索引(location),对应顶点着色器中
layout(location = x)
的值。 - 示例:如果顶点着色器中定义了
layout(location = 0) in vec3 position;
,则index
应为0
。
- 顶点属性的索引(location),对应顶点着色器中
1.2.3.3. 功能:
- 启用指定索引的顶点属性数组,使其在渲染时生效。
- 如果未启用某个顶点属性数组,OpenGL 将不会使用该属性的数据。
1.2.3.4. 使用步骤:
-
绑定 VAO 和 VBO:
在启用顶点属性之前,需要绑定顶点数组对象(VAO)和顶点缓冲区对象(VBO)。 -
调用
glVertexAttribPointer()
:
配置顶点属性指针,指定顶点数据的布局。 -
调用
glEnableVertexAttribArray()
:
启用顶点属性数组。
1.2.3.5. 示例代码:
// 假设顶点数据包含位置 (vec3) 和颜色 (vec3)
glBindBuffer(GL_ARRAY_BUFFER, vbo);
// 设置位置属性 (location = 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0); // 启用位置属性
// 设置颜色属性 (location = 1)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1); // 启用颜色属性
1.2.3.6. 注意事项:
- 每个顶点属性都需要单独调用
glEnableVertexAttribArray()
。 - 如果不启用某个顶点属性,OpenGL 将忽略该属性的数据。
- 在渲染完成后,可以通过
glDisableVertexAttribArray()
禁用顶点属性数组。
1.3. 红色立方体
从(0,0,8)看位于(0,-2,0)的立方体,立方体颜色为红色。
1.3.1. 顶点着色器
#version 430
// 指定 GLSL 的版本为 4.30
layout (location=0) in vec3 position;
// 输入变量,表示顶点的三维位置,绑定到 location = 0
uniform mat4 mv_matrix;
// uniform 变量,表示模型-视图矩阵,用于将顶点从模型空间变换到视图空间
uniform mat4 proj_matrix;
// uniform 变量,表示投影矩阵,用于将顶点从视图空间变换到裁剪空间
void main(void)
// 主函数,计算顶点的最终位置
{
gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
// 将顶点位置从模型空间依次变换到视图空间和裁剪空间
// 最终结果存储在内置变量 gl_Position 中,用于后续的光栅化阶段
}
1.3.2. 片段着色器
#version 430
// 指定 GLSL 的版本为 4.30
out vec4 color;
// 输出变量,表示片段的最终颜色
uniform mat4 mv_matrix;
// uniform 变量,模型-视图矩阵(未使用)
uniform mat4 proj_matrix;
// uniform 变量,投影矩阵(未使用)
void main(void)
// 主函数,计算片段的最终颜色
{
color = vec4(1.0, 0.0, 0.0, 1.0);
// 将片段颜色设置为红色,RGBA 格式,其中 R=1.0,G=0.0,B=0.0,A=1.0(完全不透明)
}
1.3.3. main.cpp (部分代码,完整代码见 GitHub)
setupVertices(void) {
float vertexPositions[108] = {
// 立方体的顶点位置数据,立方体的面数是 6,每个面由2个三角形组成,每个三角形有3 个顶点,每个顶点有 3 个坐标值,共计6*2*3*3=108个坐标值
-1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f,
1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f,
1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f
};
glGenVertexArrays(1, vao); // 生成 VAO
glBindVertexArray(vao[0]); // 绑定 VAO
glGenBuffers(numVBOs, vbo); // 生成 VBO
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // 绑定 VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW); // 将顶点数据传递到 VBO
}
void display(GLFWwindow* window, double currentTime) {
glClear(GL_DEPTH_BUFFER_BIT); // 清除深度缓冲区
glUseProgram(renderingProgram); // 使用着色器程序
// 获取 uniform 变量的位置
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
// 获取窗口尺寸并计算宽高比
glfwGetFramebufferSize(window, &width, &height);
aspect = (float)width / (float)height;
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f); // 设置投影矩阵
vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ)); // 设置视图矩阵
mMat = glm::translate(glm::mat4(1.0f), glm::vec3(cubeLocX, cubeLocY, cubeLocZ)); // 设置模型矩阵
mvMat = vMat * mMat; // 计算模型-视图矩阵
// 将矩阵传递到着色器
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // 绑定 VBO
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); // 设置顶点属性指针
glEnableVertexAttribArray(0); // 启用顶点属性
glEnable(GL_DEPTH_TEST); // 启用深度测试
glDepthFunc(GL_LEQUAL); // 设置深度测试函数
glDrawArrays(GL_TRIANGLES, 0, 36); // 绘制立方体
}
1.4. 彩色三角形
1.4.1. 顶点着色器
#version 430
// 指定 GLSL 的版本为 4.30
layout (location=0) in vec3 position;
layout (location=1) in vec3 color; // 输入变量,表示顶点的颜色,绑定到 location = 1
// 输入变量,表示顶点的三维位置,绑定到 location = 0
uniform mat4 mv_matrix;
// uniform 变量,表示模型-视图矩阵,用于将顶点从模型空间变换到视图空间
uniform mat4 proj_matrix;
// uniform 变量,表示投影矩阵,用于将顶点从视图空间变换到裁剪空间
out vec3 v_color;
// 输出变量,表示顶点的颜色,绑定到 location = 0
void main(void)
// 主函数,计算顶点的最终位置
{
gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
// 将顶点位置从模型空间依次变换到视图空间和裁剪空间
// 最终结果存储在内置变量 gl_Position 中,用于后续的光栅化阶段
v_color = color;
// 将顶点颜色传递给片段着色器
}
1.4.2. 片段着色器
#version 430
// 指定 GLSL 的版本为 4.30
in vec3 v_color;
// 输入变量,表示顶点的颜色(未使用)
out vec4 color;
// 输出变量,表示片段的最终颜色
uniform mat4 mv_matrix;
// uniform 变量,模型-视图矩阵(未使用)
uniform mat4 proj_matrix;
// uniform 变量,投影矩阵(未使用)
void main(void)
// 主函数,计算片段的最终颜色
{
color = vec4(v_color, 1.0);
// 将片段颜色设置为红色,RGBA 格式,其中 R=1.0,G=0.0,B=0.0,A=1.0(完全不透明)
}
1.4.3. main.cpp (部分代码,完整代码见 GitHub)
void setupVertices(void) {
float vertexPositions[3*6] = {
// 三角形的顶点坐标,颜色
-1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
};
glGenVertexArrays(1, vao); // 生成 VAO
glBindVertexArray(vao[0]); // 绑定 VAO
glGenBuffers(numVBOs, vbo); // 生成 VBO
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // 绑定 VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW); // 将顶点数据传递到 VBO
//glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // 绑定 VBO
glVertexAttribPointer(0, 3, GL_FLOAT, false, 6 * sizeof(float), (void*)0); // 设置顶点属性指针:坐标
glVertexAttribPointer(1, 3, GL_FLOAT, false, 6 * sizeof(float), (void*)(3 * sizeof(float))); // 设置顶点属性指针:颜色
glEnableVertexAttribArray(0); // 启用顶点属性
glEnableVertexAttribArray(1); // 启用顶点属性
}
代码解释
- 三角形每个顶点有 3 个坐标和 3 个颜色分量 ,前面的立方体只有 3 个坐标分量
- 将绑定VBO,设置顶点属性指针,启用顶点属性等放在
setupVertices
函数中,而不是放在display
函数中,这个目前只需做一次即可 - 由于顶点属性有变化,所以
glVertexAttribPointer
及glEnableVertexAttribArray
函数需要调用多次,其参数要依据顶点属性的个数变化
1.5. 渲染一个对象的多个副本
1.5.1. C++中渲染
这种方式本质是把一个对象复制多次,然后分别绘制,display()代码如下
for(int i = 0; i < 5; i++){
// 随机生成立方体位置
//glm::mat4 rMat= glm::rotate(glm::mat4(1.0f), (float)(rand()%360), glm::vec3(1.0f, 1.0f, 1.0f)); // 随机旋转矩阵
glm::mat4 rMat = glm::rotate(glm::mat4(1.0f), (float)(1.75*currentTime), glm::vec3(1.0f, 1.0f, 1.0f)); // 随机旋转矩阵
//glm::mat4 tMat = glm::translate(glm::mat4(1.0f), glm::vec3(cubeLocX + (float)(rand() % 360), cubeLocY + (float)(rand() % 180), cubeLocZ + (float)(rand() % 90)));
glm::mat4 tMat = glm::translate(glm::mat4(1.0f), glm::vec3(cubeLocX + (float)(i*5.0f), cubeLocY + (float)(i*2.5), cubeLocZ - (float)(i*2.5)));
mMat = tMat*rMat; // 设置模型矩阵
mvMat = vMat * mMat; // 计算模型-视图矩阵
// 将矩阵传递到着色器
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // 绑定 VBO
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); // 设置顶点属性指针
glEnableVertexAttribArray(0); // 启用顶点属性
glEnable(GL_DEPTH_TEST); // 启用深度测试
glDepthFunc(GL_LEQUAL); // 设置深度测试函数
glDrawArrays(GL_TRIANGLES, 0, 36); // 绘制立方体
}
要想每个顶点显示不同的颜色,需要修改顶点着色器
顶点着色器
#version 430
// 指定 GLSL 的版本为 4.30
layout (location=0) in vec3 position;
// 输入变量,表示顶点的三维位置,绑定到 location = 0
uniform mat4 mv_matrix;
// uniform 变量,表示模型-视图矩阵,用于将顶点从模型空间变换到视图空间
uniform mat4 proj_matrix;
// uniform 变量,表示投影矩阵,用于将顶点从视图空间变换到裁剪空间
out vec4 v_color;
void main(void)
// 主函数,计算顶点的最终位置
{
gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
// 将顶点位置从模型空间依次变换到视图空间和裁剪空间
// 最终结果存储在内置变量 gl_Position 中,用于后续的光栅化阶段
v_color = vec4(position,1.0)*0.5+vec4(0.5,0.5,0.5,0.5);
}
片段着色器
#version 430
// 指定 GLSL 的版本为 4.30
in vec4 v_color;
out vec4 color;
// 输出变量,表示片段的最终颜色
uniform mat4 mv_matrix;
// uniform 变量,模型-视图矩阵(未使用)
uniform mat4 proj_matrix;
// uniform 变量,投影矩阵(未使用)
void main(void)
// 主函数,计算片段的最终颜色
{
color=v_color;
}
这种方式,每个立方体都是独立的,没有相互影响,但是每个立方体都是单独绘制,效率较低
1.5.2. 着色器中渲染一个对象多个副本
在着色器中我们通过gl_InstanceID
来区分不同的立方体,gl_InstanceID
表示当前正在绘制的立方体的编号,从 0 开始,每个立方体编号不同,gl_InstanceID
在顶点着色器中可用,gl_InstanceID
在片段着色器中不可用,因为片段着色器没有gl_InstanceID
变量
顶点着色器
#version 430
// 指定 GLSL 的版本为 4.30
layout (location=0) in vec3 position;
// 输入变量,表示顶点的三维位置,绑定到 location = 0
// uniform 变量,表示模型-视图矩阵,用于将顶点从模型空间变换到视图空间
//uniform mat4 mv_matrix;
uniform mat4 m_matrix;
uniform mat4 v_matrix;
uniform mat4 proj_matrix;
// uniform 变量,表示投影矩阵,用于将顶点从视图空间变换到裁剪空间
uniform float tf; // uniform 变量,表示时间,用于动画效果
out vec4 v_color;
mat4 buildTranslate(float x, float y, float z)
{
// 构建平移矩阵
mat4 translate = mat4(1.0);
translate[3][0] = x;
translate[3][1] = y;
translate[3][2] = z;
return translate;
}
mat4 buildRotateX(float angle)
{
// 构建旋转矩阵
mat4 rotate =mat4(1.0,0.0,0.0,0.0,
0.0,cos(angle),-sin(angle),0.0,
0.0,sin(angle),cos(angle),0.0,
0.0,0.0,0.0,1.0);
// 旋转矩阵的构建公式,使用了旋转角度和旋转轴的坐标
return rotate;
}
mat4 buildRotateY(float angle)
{
// 构建旋转矩阵
mat4 rotate =mat4(cos(angle),0.0,sin(angle),0.0,
0.0,1.0,0.0,0.0,
-sin(angle),0.0,cos(angle),0.0,
0.0,0.0,0.0,1.0);
return rotate;
}
mat4 buildRotateZ(float angle)
{
// 构建旋转矩阵
mat4 rotate =mat4(cos(angle),-sin(angle),0.0,0.0,
sin(angle),cos(angle),0.0,0.0,
0.0,0.0,1.0,0.0,
0.0,0.0,0.0,1.0);
return rotate;
}
void main(void)
// 主函数,计算顶点的最终位置
{
float i=gl_InstanceID;
// 获取当前实例的 ID,用于实例化渲染
mat4 rotateX = buildRotateX( 1* 1.75f*tf);
mat4 rotateY = buildRotateY( 1 * 1.75f*tf);
mat4 rotateZ = buildRotateZ( 1 * 1.75f*tf);
mat4 rotate = rotateX * rotateY * rotateZ;
// 通过平移、旋转矩阵构建最终的旋转矩阵
float x = float(i * 5.0f);
float y = float(i * 2.5f);
float z = float(i * 2.5f);
mat4 matrix =buildTranslate(-8,-2,0)* buildTranslate(x, y, -z)*rotate;
//mat4 mv_matrix = v_matrix * m_matrix * translate;
mat4 mv_matrix = v_matrix * matrix;
// 计算模型-视图矩阵,用于将顶点从模型空间变换到视图空间
gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
// 将顶点位置从模型空间依次变换到视图空间和裁剪空间
// 最终结果存储在内置变量 gl_Position 中,用于后续的光栅化阶段
v_color = vec4(position,1.0)*0.5+vec4(0.5,0.5,0.5,0.5);
}
display()
由glDrawArraysInstanced
代替glDrawArrays
,参数glDrawArraysInstanced
的最后一个参数表示实例化渲染的副本数量
glDrawArraysInstanced(GL_TRIANGLES, 0, 36,5); // 绘制立方体