计算机图形学编程(使用OpenGL和C++)(第2版)学习笔记 04.管理3D图形数据

1. 管理3D图形数据

我们有以下数据需要从C++程序传递给着色器:

  1. 3D图形相关数据 (以下称为顶点数据),这些数据在C++中放入一个缓冲区(数组中),并将这个缓冲区和着色器中声明的顶点属性绑定起来。

    • 顶点坐标
    • 顶点的颜色
    • 顶点的纹理坐标
    • 顶点的法向量
    • 顶点的切向量
    • 其它数据
  2. uniform变量数据

    • 纹理
    • 投影矩阵
    • 视图矩阵
    • 模型矩阵
    • 其它uniform变量(比如时间)

C++程序与着色器进行以上数据的传递,需要一些步骤:

  1. 只做一次的初始化工作,通常在init()函数中完成

    • 创建缓冲区
    • 将数据放入缓冲区
  2. 每一帧中都需要做的更新工作,通常在display() 或 update()函数中完成 。本书中,我们使用display()函数来更新数据。

    • 启用包含了顶点数据的顶点缓冲区
    • 将这个缓冲区与顶点属性绑定起来
    • 启用这个顶点属性
    • 将uniform变量数据传递给着色器
    • 渲染图形(调用glDrawArrays()函数)

1.1. 名词解释

1.1.1.VBO(顶点缓冲区对象)

VBO(Vertex Buffer Object,顶点缓冲区对象)是 OpenGL 中用于存储顶点数据的缓冲区对象。它将顶点数据(如顶点坐标、颜色、法向量、纹理坐标等)存储在 GPU 的显存中,从而提高渲染性能。

1.1.1.1. VBO 的特点:
  1. 高效性

    • 将数据存储在 GPU 显存中,避免每帧都从 CPU 向 GPU 传递数据。
    • 提高了数据传输和渲染的效率。
  2. 灵活性

    • 支持存储多种顶点数据(如位置、颜色、法向量等)。
    • 可以与多个着色器程序共享。
  3. 可控性

    • 开发者可以通过 OpenGL 提供的 API 对 VBO 进行管理(如创建、绑定、更新等)。
1.1.1.2. VBO 的使用步骤:
  1. 创建缓冲区
    使用 glGenBuffers() 函数生成一个或多个缓冲区对象。

    GLuint vbo;
    glGenBuffers(1, &vbo);
    
  2. 绑定缓冲区
    使用 glBindBuffer() 函数将缓冲区绑定到目标(如 GL_ARRAY_BUFFER)。

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    
  3. 传递数据
    使用 glBufferData() 函数将顶点数据传递到缓冲区。

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    
  4. 使用缓冲区
    在渲染时,通过顶点属性指针(glVertexAttribPointer())将缓冲区数据与着色器中的顶点属性绑定。

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);
    
  5. 解绑缓冲区
    渲染完成后,可以解绑缓冲区。

    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 的特点:
  1. 状态管理

    • VAO 记录了顶点属性的配置(如顶点属性指针、启用状态等)。
    • 当绑定一个 VAO 时,所有与顶点属性相关的操作都会记录到该 VAO 中。
  2. 简化操作

    • 使用 VAO 后,无需每次渲染时重新配置顶点属性,只需绑定相应的 VAO 即可。
    • 适合管理复杂的场景,尤其是需要频繁切换顶点数据的情况。
  3. 与 VBO 配合使用

    • VAO 本身不存储顶点数据,而是与 VBO 绑定,记录 VBO 的使用方式。
1.1.2.2. VAO 的使用步骤:
  1. 创建 VAO
    使用 glGenVertexArrays() 函数生成一个或多个 VAO。

    GLuint vao;
    glGenVertexArrays(1, &vao);
    
  2. 绑定 VAO
    使用 glBindVertexArray() 函数绑定 VAO,之后的顶点属性配置都会记录到该 VAO 中。

    glBindVertexArray(vao);
    
  3. 配置顶点属性
    配置顶点属性指针并启用顶点属性(通常与 VBO 配合使用)。

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);
    
  4. 解绑 VAO
    配置完成后,可以解绑 VAO。

    glBindVertexArray(0);
    
  5. 使用 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. 常见的顶点属性:
  1. 位置(Position)

    • 描述顶点在三维空间中的位置,通常是一个三维向量(x, y, z)。
    • 示例:vec3 position = vec3(1.0, 0.5, -1.0);
  2. 颜色(Color)

    • 描述顶点的颜色信息,通常是一个四维向量(r, g, b, a)。
    • 示例:vec4 color = vec4(1.0, 0.0, 0.0, 1.0);(红色)
  3. 纹理坐标(Texture Coordinate)

    • 用于纹理映射,描述顶点在纹理中的位置,通常是一个二维向量(u, v)。
    • 示例:vec2 texCoord = vec2(0.5, 0.5);
  4. 法向量(Normal)

    • 描述顶点的法线方向,用于光照计算,通常是一个三维向量。
    • 示例:vec3 normal = vec3(0.0, 1.0, 0.0);
  5. 其他自定义属性

    • 可以根据需要定义其他属性,例如切线、骨骼权重等。
1.1.3.2. 顶点属性的使用步骤:
  1. 在顶点着色器中声明属性

    • 使用 layout(location = x) 指定属性的位置。
    layout(location = 0) in vec3 position;
    layout(location = 1) in vec4 color;
    
  2. 在 OpenGL 中绑定顶点属性

    • 使用 glVertexAttribPointer() 函数将顶点数据与着色器中的属性绑定。
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
    glEnableVertexAttribArray(0);
    
  3. 传递数据到 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. 参数说明:
  1. index

    • 顶点属性的索引(location),对应顶点着色器中 layout(location = x) 的值。
    • 示例:如果顶点着色器中定义了 layout(location = 0) in vec3 position;,则 index 应为 0
  2. size

    • 每个顶点属性的分量数量,取值范围为 14
    • 示例:
      • 3 表示顶点属性是一个三维向量(如位置 vec3)。
      • 2 表示顶点属性是一个二维向量(如纹理坐标 vec2)。
  3. type

    • 数据类型,指定顶点属性的每个分量的数据类型。
    • 常见取值:
      • GL_FLOAT:浮点数。
      • GL_INT:整数。
      • GL_UNSIGNED_BYTE:无符号字节。
  4. normalized

    • 是否将整数类型的数据归一化到 [0, 1][-1, 1]
    • 取值:
      • GL_TRUE:归一化。
      • GL_FALSE:不归一化。
    • 示例:如果数据类型是 GL_UNSIGNED_BYTE,归一化后 255 会被映射为 1.0
  5. stride

    • 两个连续顶点属性之间的字节偏移量。
    • 如果顶点属性在缓冲区中是紧密排列的,可以设置为 0,表示 OpenGL 自动计算步长。
    • 示例:如果每个顶点包含位置(vec3)和颜色(vec3),则步长为 6 * sizeof(float)
  6. pointer

    • 数据在缓冲区中的起始位置(偏移量)。
    • 通常使用 0offsetof 宏来指定偏移量。
    • 示例:如果缓冲区中存储了位置和颜色数据,位置数据的偏移量为 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
1.2.3.3. 功能:
  • 启用指定索引的顶点属性数组,使其在渲染时生效。
  • 如果未启用某个顶点属性数组,OpenGL 将不会使用该属性的数据。
1.2.3.4. 使用步骤:
  1. 绑定 VAO 和 VBO
    在启用顶点属性之前,需要绑定顶点数组对象(VAO)和顶点缓冲区对象(VBO)。

  2. 调用 glVertexAttribPointer()
    配置顶点属性指针,指定顶点数据的布局。

  3. 调用 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); // 启用顶点属性
}

代码解释

  1. 三角形每个顶点有 3 个坐标和 3 个颜色分量 ,前面的立方体只有 3 个坐标分量
  2. 将绑定VBO,设置顶点属性指针,启用顶点属性等放在setupVertices函数中,而不是放在display函数中,这个目前只需做一次即可
  3. 由于顶点属性有变化,所以glVertexAttribPointerglEnableVertexAttribArray函数需要调用多次,其参数要依据顶点属性的个数变化

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); // 绘制立方体

图元

1.6. 参考

  1. 学习笔记完整代码下载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值