[OpenGL] Transform feedback 介绍以及使用示例

news2024/12/15 18:31:50

一、简介

本文介绍了 OpenGL 中 Transform Feedback 方法的基本概念和代码示例。

二、Transform Feedback 介绍

1. Transform Feedback 简介

根据 OpenGL-wiki,Transform Feedback 是捕获由顶点处理步骤(vertex shader 和 geometry shader)生成的图元(Primitives)的过程,将这些图元的数据记录到缓冲区对象(Buffer Objects)中。这样可以保留物体的变换后渲染状态,(在GPU中)多次重新提交这些数据。

简单来讲,使用 Transform Feedback 可以将 vertex shader 和 geometry shader 处理后的数据存储到指定的 Buffer Objects 中,而不继续进行后续的 Clipper、Rasterizaer 和 Test & Blending 阶段。

Transform Feedback Buffer 在渲染管线中所处的位置如下图所示:
TB in render pipline

2. 使用 Transform feedback 可实现的功能

  1. 实现粒子系统:
    在粒子系统中,Transform Feedback 可以用来捕获粒子的状态信息(如位置、速度、颜色等),然后将这些数据存储到缓冲区中。在每一帧中,系统可以通过 Transform Feedback 来更新粒子的状态,从而避免在CPU中重新计算整个粒子系统。这种方式能有效地避免 CPU 参与数据更新,提高系统的性能。
  2. 进行GPU并行运算:
    使用 Transform Feedback,可以在 vertex shader 中执行并行计算任务,将计算结果直接存储在 Transform Feedback 对应的 buffer 中 (GPU 内存中)。
    例如,可以在 vertex shader 中执行物理计算(如模拟重力或其他力学运动),并将计算结果(例如新的顶点位置)存储在 buffer 中,作为新渲染 shader program 的输入,或者直接作为结果传输到 CPU 上。而不需要每次都在 CPU 计算再传输到 GPU 中。

三、使用示例

1. 使用 Transform Feedback 与 vertex shader

在本例中,使用 Transform feedback 方法在 GPU (vertex shader) 中进行并行运算,将输入的数据开平方后存储到 Transform feedback 对应的 buffer 中再传到 CPU 中进行打印输出。

1.1. 步骤

  1. 初始化 glfw, glad, 窗口
  2. 构建,编译 vertex shader
  3. 指定 Transform feedback object 的接收变量
  4. 链接 shader program
  5. 准备输入数据, VAO 和 VBO
  6. 设置绑定到 Transform feedback object 上的 buffer,用于接收输出数据
  7. 运行 shader 程序
    7.1 use shader program
    7.2 disable rasterizer
    7.3 begin transform feedback
    7.4 bind VAO
    7.5 draw
    7.6 end transform feedback
  8. 将 Transform feedback buffer 中的数据传输到 CPU
  9. 释放资源

1.2. 代码

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>
// 用于处理窗口大小改变的回调函数
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
// 用于处理用户输入的函数
void processInput(GLFWwindow *window);

// 指定窗口默认width和height像素大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

/****** vertex shader 代码 ******/
const char *vertexShaderSource = R"(
#version 330 core
layout (location = 0) in float inputValue;
out float outputValue;
void main()
{
    outputValue = sqrt(inputValue);
}
)";
/************************************/

int main()
{

    /****** 1.初始化glfw, glad, 窗口 *******/
    // glfw 初始化 + 配置 glfw 参数
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // glfw 生成窗口
    // (由于我们只使用 Transform feedback 进行并行运算,因此实际上不生成窗口也可以)
    GLFWwindow *window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        // 检查是否成功生成窗口,如果没有成功打印出错信息并且退出
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    // 设置窗口window的上下文
    glfwMakeContextCurrent(window);
    // 配置window变化时的回调函数
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // 使用 glad 加载 OpenGL 中的各种函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    /************************************/

    /****** 2.构建,编译 vertex shader ******/
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // 检查是否成功编译 vertex shader
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    // 生成 shader 程序
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);

    /****** 3. 指定 Transform feedback object 的接收变量  ******/

    // 该步骤必须在 glLinkProgram() 之前进行
    // 因为使用 Transform feedback 会改变 shader 程序的渲染流程,因此需要先指定 Transform feedback 的接收变量,告诉
    // OpenGL 应该如何链接、编译 shader 程序,再进行 glLinkProgram()
    const char *feedbackVaryings[] = {"outputValue"}; // 我们希望接收 vertex shader 的输出变量 out float outputValue
    // glTransformFeedbackVaryings() 函数的参数解释:
    // (1). 目标 shader program
    // (2). 目标接收变量的个数,即 feedbackVaryings 数组中 字符串的个数
    // (3). 目标接收的变量名字符串数组
    // (4). 接收变量的存储目标. GL_INTERLEAVED_ATTRIBS, 表示所有输出变量都输出到 一个 buffer 中,
    // GL_SEPARATE_ATTRIBS 表示 输出变量输出到不同的 buffer 中. 此处我们选择将输出存储到 一个 buffer 中
    glTransformFeedbackVaryings(shaderProgram, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
    /************************************/

    /****** 4. 链接 shader program  ******/
    glLinkProgram(shaderProgram); // 链接 shader program

    // 检查是否成功链接 vertex shader 和 fragment shader
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success)
    {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);

    /************************************/

    /****** 5.准备输入数据, VAO 和 VBO *******/
    float inputData[] = {0.0f, 0.5f, 1.0f};

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO); // 生成一个VAO对象
    glGenBuffers(1, &VBO);      // 生成一个VBO对象

    glBindVertexArray(VAO); // 绑定VAO

    glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(inputData), inputData,
                 GL_STATIC_DRAW); // 将vertices中的数据复制到刚刚绑定的VBO buffer中去,VBO buffer是GPU内存上的一块区域

    // glVertexAttribPointer() 用于指定顶点属性如何从绑定的 VBO 中读取数据,
    // 此时绑定的 buffer 是 VBO,因此location=0 处的顶点属性就从VBO中读数据 glVertexAttribPointer()
    // 需要6个参数,每个参数的含义如下:
    // (1). 指定要配置的顶点属性, 即 shader 中指定 数据 location 值,对于position部分,此处填入 0
    // (2). 指定顶点属性的大小,shader中我们将VBO中的数据传给了一个 float 类型的变量,
    // 每个顶点数据只有一个 float, 因此此处填 1
    // (3). 指定数据的类型,用于使用的是浮点型,因此此处填入 GL_FLOAT
    // (4). 指定是否自动对数据进行归一化,我们不需要自动诡异化,因此此处填入 GL_FALSE
    // (5). 指定VBO中顶点数据组之间的间隔,可以手动设置间隔,例如 sizeof(float),
    // 也可以填 0 让 Opengl 自动设置
    // (6). 指定读取VBO数据起始位置偏移,单位为字节,这里填入 (void *)(0*sizeof(float)) 即可
    glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0,
                          (void *)(0 * sizeof(float))); // 设置如何读取VBO中数据
    glEnableVertexAttribArray(0);                       // 启用 location = 0 处的顶点属性
    glBindBuffer(GL_ARRAY_BUFFER, 0);                   // 解绑VBO

    /************************************/

    /****** 6. 设置绑定到 Transform feedback object 上的 buffer,用于接收输出数据  ******/

    // 新建一个 buffer 用于作为 Transform feedback object 的 buffer,用来接收 计算后的输出结果
    GLuint tbo;
    glGenBuffers(1, &tbo);
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, tbo);
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(inputData), nullptr, GL_STATIC_READ);
    // 将 tbo 绑定到 TFO 的 0号卡槽上, 此处不对卡槽机制进行过多的讲解
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);

    /************************************/

    /****** 7. 运行 shader 程序  ******/
	// 7.1 use shader program
    glUseProgram(shaderProgram);
    // 关闭 光栅化阶段, 只使用 vertex shader 和 geometry shader (如果有的话) 阶段
    // 7.2 disable rasterizer
    glEnable(GL_RASTERIZER_DISCARD);
    // 开始绘制 Transform feedback
    // 此处只有 vertex shader,因此 glBeginTransformFeedback() 中指定的 图元 需要跟 glDrawArray() 中的相同
	// 7.3 begin transform feedback
    glBeginTransformFeedback(GL_POINTS);
    // 7.4 bind VAO
    glBindVertexArray(VAO); // 绑定VAO,指定当前使用的VAO对象
	// 7.5 draw
    glDrawArrays(GL_POINTS, 0,
                 3); // 输入数据数据只有三个 float,因此可以将每个数据分配给一个 point ,在 vertex shader 中进行计算处理
	// 7.6 end transform feedback
    glEndTransformFeedback(); // 结束 Transform feedback
    // 开启 光栅化阶段
    glDisable(GL_RASTERIZER_DISCARD);

    glBindVertexArray(0); // 解绑 VAO
    glFlush(); // 刷新 gl 命令 buffer 保证前面的命令都运行完成
    /************************************/
    
    /****** 8. 将 Transform feedback buffer 中的数据传输到 CPU   ******/
    // 接收 Transform feedback 对应的 buffer (tbo) 中的数据
    GLfloat feedback[3];
    glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);

    for (int i = 0; i < 3; i++)
    {
        printf("%f\n", feedback[i]);
    }

    /************************************/

    /****** 9.释放资源 ******/
    // 释放之前申请的 VBO, VAO 资源和 shader 程序
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    // glfw 释放 glfw使用的所有资源
    glfwTerminate();
    /************************************/
    return 0;
}

// 用于处理用户输入的函数
void processInput(GLFWwindow *window)
{
    // 当按下 Esc 按键时调用 glfwSetWindowShouldClose() 函数,关闭窗口
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// 在使用 OpenGL 和 GLFW 库时,处理窗口大小改变的回调函数
// 当窗口大小发生变化时,确保 OpenGL 渲染的内容能够适应新的窗口大小,避免图像被拉伸、压缩或出现其他比例失真的问题
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
    glViewport(0, 0, width, height);
}

1.3. 运行结果

0.000000
0.707107
1.000000

2. 使用 Transform Feedback 与 vertex + geometry shader

在本例中,使用 Transform feedback 方法在 vertex shader 和 geometry shader 中进行并行运算,在 vertex shader 中将输入的数据开平方,然后在 geometry shader 中将一份数据分别加0, 加1和加2,变为三份数据。最后存储到 Transform feedback 对应的 buffer 中再传到 CPU 中进行打印输出。

2.1. 步骤

  1. 初始化 glfw, glad, 窗口
  2. 构建,编译 vertex shader , geometry shader
  3. 指定 Transform feedback object 的接收变量
  4. 链接 shader program
  5. 准备输入数据, VAO 和 VBO
  6. 设置绑定到 Transform feedback object 上的 buffer,用于接收输出数据
  7. 运行 shader 程序
    7.1 use shader program
    7.2 disable rasterizer
    7.3 begin transform feedback
    7.4 bind VAO
    7.5 draw
    7.6 end transform feedback
  8. 将 Transform feedback buffer 中的数据传输到 CPU
  9. 释放资源

2.2 代码

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>
// 用于处理窗口大小改变的回调函数
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
// 用于处理用户输入的函数
void processInput(GLFWwindow *window);

// 指定窗口默认width和height像素大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

/****** vertex shader 代码 ******/
const char *vertexShaderSource = R"(
#version 330 core
layout (location = 0) in float inputValue;
out float geoValue;
void main()
{
    geoValue =  sqrt(inputValue);
}
)";

/****** geometry shader 代码 ******/
const char *geometryShaderSource = R"(
#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices=3) out;
in float[] geoValue;
out float outputValue;
void main()
{
    for(int i=0; i<3; i++){
        outputValue = geoValue[0] + i;
        EmitVertex();
    }
    EndPrimitive();
}
)";

/************************************/

int main()
{

    /****** 1.初始化glfw, glad, 窗口 *******/
    // glfw 初始化 + 配置 glfw 参数
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // glfw 生成窗口
    // (由于我们只使用 Transform feedback 进行并行运算,因此实际上不生成窗口也可以)
    GLFWwindow *window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        // 检查是否成功生成窗口,如果没有成功打印出错信息并且退出
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    // 设置窗口window的上下文
    glfwMakeContextCurrent(window);
    // 配置window变化时的回调函数
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // 使用 glad 加载 OpenGL 中的各种函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    /************************************/

    /****** 2.构建,编译 vertex shader, geometry shader ******/
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // 检查是否成功编译 vertex shader
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    // geometry shader
    unsigned int geometryShader = glCreateShader(GL_GEOMETRY_SHADER);
    glShaderSource(geometryShader, 1, &geometryShaderSource, NULL);
    glCompileShader(geometryShader);
    // 检查是否成功编译 geometry shader
    glGetShaderiv(geometryShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(geometryShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::GEOMETRY::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    // 生成 shader 程序
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, geometryShader);

    /****** 3. 指定 Transform feedback object 的接收变量  ******/

    // 该步骤必须在 glLinkProgram() 之前进行
    // 因为使用 Transform feedback 会改变 shader 程序的渲染流程,因此需要先指定 Transform feedback 的接收变量,告诉
    // OpenGL 应该如何链接、编译 shader 程序,再进行 glLinkProgram()
    const char *feedbackVaryings[] = {"outputValue"}; // 我们希望接收 vertex shader 的输出变量 out float outputValue
    // glTransformFeedbackVaryings() 函数的参数解释:
    // (1). 目标 shader program
    // (2). 目标接收变量的个数,即 feedbackVaryings 数组中 字符串的个数
    // (3). 目标接收的变量名字符串数组
    // (4). 接收变量的存储目标. GL_INTERLEAVED_ATTRIBS, 表示所有输出变量都输出到 一个 buffer 中,
    // GL_SEPARATE_ATTRIBS 表示 输出变量输出到不同的 buffer 中. 此处我们选择将输出存储到 一个 buffer 中
    glTransformFeedbackVaryings(shaderProgram, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
    /************************************/

    /****** 4. 链接 shader program  ******/
    glLinkProgram(shaderProgram); // 链接 shader program

    // 检查是否成功链接 vertex shader 和 fragment shader
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success)
    {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(geometryShader);

    /************************************/

    /****** 5.准备输入数据, VAO 和 VBO *******/
    float inputData[] = {0.0f, 0.5f, 1.0f};

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO); // 生成一个VAO对象
    glGenBuffers(1, &VBO);      // 生成一个VBO对象

    glBindVertexArray(VAO); // 绑定VAO

    glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(inputData), inputData,
                 GL_STATIC_DRAW); // 将vertices中的数据复制到刚刚绑定的VBO buffer中去,VBO buffer是GPU内存上的一块区域

    // glVertexAttribPointer() 用于指定顶点属性如何从绑定的 VBO 中读取数据,
    // 此时绑定的 buffer 是 VBO,因此location=0 处的顶点属性就从VBO中读数据 glVertexAttribPointer()
    // 需要6个参数,每个参数的含义如下:
    // (1). 指定要配置的顶点属性, 即 shader 中指定 数据 location 值,对于position部分,此处填入 0
    // (2). 指定顶点属性的大小,shader中我们将VBO中的数据传给了一个 float 类型的变量,
    // 每个顶点数据只有一个 float, 因此此处填 1
    // (3). 指定数据的类型,用于使用的是浮点型,因此此处填入 GL_FLOAT
    // (4). 指定是否自动对数据进行归一化,我们不需要自动诡异化,因此此处填入 GL_FALSE
    // (5). 指定VBO中顶点数据组之间的间隔,可以手动设置间隔,例如 sizeof(float),
    // 也可以填 0 让 Opengl 自动设置
    // (6). 指定读取VBO数据起始位置偏移,单位为字节,这里填入 (void *)(0*sizeof(float)) 即可
    glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0,
                          (void *)(0 * sizeof(float))); // 设置如何读取VBO中数据
    glEnableVertexAttribArray(0);                       // 启用 location = 0 处的顶点属性
    glBindBuffer(GL_ARRAY_BUFFER, 0);                   // 解绑VBO

    /************************************/

    /****** 6. 设置绑定到 Transform feedback object 上的 buffer,用于接收输出数据  ******/

    // 新建一个 buffer 用于作为 Transform feedback object 的 buffer,用来接收 计算后的输出结果
    GLuint tbo;
    glGenBuffers(1, &tbo);
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, tbo);
    // 注意!!! 由于我们使用 geometry shader 将一个输入变量(geoValue)变为三个输出(outputValue),
    // 因此 Transform feedback object 中用于接收数据的 buffer 大小应该为输入数据的 3 倍大
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(inputData) * 3, nullptr, GL_STATIC_READ);
    // 将 tbo 绑定到 TFO 的 0号卡槽上, 此处不对卡槽机制进行过多的讲解
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);

    /************************************/

    /****** 7. 运行 shader 程序  ******/
    // 7.1 use shader program
    glUseProgram(shaderProgram);
    // 7.2 disable rasterizer
    // 关闭 光栅化阶段, 只使用 vertex shader 和 geometry shader (如果有的话) 阶段
    glEnable(GL_RASTERIZER_DISCARD);
    // 7.3 begin transform feedback
    // 开始绘制 Transform feedback
    // 注意!!! 由于此时使用了 geometry shader, glBeginTransformFeedback() 中的图元应该与 geometry shader 的输出一致,
    // 尽管 geometry shader 中的 out 图元为 triangle_strip,
    // 但是 transform feedback 依旧独立地存储每个 triangle 的顶点数据,
    // 而不是使用 strip 对数据进行压缩
    glBeginTransformFeedback(GL_TRIANGLES);
    // 7.4 bind VAO
    glBindVertexArray(VAO); // 绑定VAO,指定当前使用的VAO对象
    // 7.5 draw
    glDrawArrays(GL_POINTS, 0,
                 3); // 输入数据数据只有三个 float,因此可以将每个数据分配给一个 point ,在 vertex shader 中进行计算处理
    // 7.6 end transform feedback
    glEndTransformFeedback(); // 结束 Transform feedback
    // 开启 光栅化阶段
    glDisable(GL_RASTERIZER_DISCARD);

    glBindVertexArray(0); // 解绑 VAO
    glFlush();            // 刷新 gl 命令 buffer 保证前面的命令都运行完成
    /************************************/

    /****** 8. 将 Transform feedback buffer 中的数据传输到 CPU   ******/
    // 接收 Transform feedback 对应的 buffer (tbo) 中的数据
    GLfloat feedback[9];
    glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);

    for (int i = 0; i < 9; i++)
    {
        printf("%f\n", feedback[i]);
    }

    /************************************/

    /****** 9.释放资源 ******/
    // 释放之前申请的 VBO, VAO 资源和 shader 程序
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    // glfw 释放 glfw使用的所有资源
    glfwTerminate();
    /************************************/
    return 0;
}

// 用于处理用户输入的函数
void processInput(GLFWwindow *window)
{
    // 当按下 Esc 按键时调用 glfwSetWindowShouldClose() 函数,关闭窗口
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// 在使用 OpenGL 和 GLFW 库时,处理窗口大小改变的回调函数
// 当窗口大小发生变化时,确保 OpenGL 渲染的内容能够适应新的窗口大小,避免图像被拉伸、压缩或出现其他比例失真的问题
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
    glViewport(0, 0, width, height);
}

2.3 结果

0.000000
1.000000
2.000000
0.707107
1.707107
2.707107
1.000000
2.000000
3.000000

四、参考

[1.] OpenGL-Transform Feedback
[2.] OpenGL–Transform feedback示例解析
[3.] OpenGL-wiki-Transform Feedback

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2260071.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

拆解大语言模型RLHF中的PPO

** 拆解大语言模型RLHF中的PPO ** 参考链接&#xff1a;https://zhuanlan.zhihu.com/p/645225982 为什么大多数介绍RLHF的文章&#xff0c;一讲到PPO算法的细节就戛然而止了呢&#xff1f;要么直接略过&#xff0c;要么就只扔出一个PPO的链接。然而LLM PPO跟传统的PPO还是有…

arcGIS使用笔记(无人机tif合并、导出、去除黑边、重采样)

无人机航拍建图之后&#xff0c;通过大疆智图软件可以对所飞行的区域的进行拼图&#xff0c;但是如果需要对拼好的图再次合并&#xff0c;则需要利用到arcGIS软件。下面介绍arcGIS软件在这个过程中常用的操作。 1.导入tif文件并显示的方法&#xff1a;点击“”图标进行导入操作…

利用代理IP爬取Zillow房产数据用于数据分析

引言 最近数据分析的热度在编程社区不断攀升&#xff0c;有很多小伙伴都开始学习或从事数据采集相关的工作。然而&#xff0c;网站数据已经成为网站的核心资产&#xff0c;许多网站都会设置一系列很复杂的防范措施&#xff0c;阻止外部人员随意采集其数据。为了解决这个问题&a…

免费送源码:Java+B/S+MySQL 多元化智能选课系统的设计与实现 计算机毕业设计原创定制

摘 要 多元化智能选课系统使用Java语言的Springboot框架&#xff0c;采用MVVM模式进行开发&#xff0c;数据方面主要采用的是微软的Mysql关系型数据库来作为数据存储媒介&#xff0c;配合前台技术完成系统的开发。 论文主要论述了如何使用JAVA语言开发一个多元化智能选课系统&…

(九)机器学习 - 多项式回归

多项式回归&#xff08;Polynomial Regression&#xff09;是一种回归分析方法&#xff0c;它将自变量 xx 和因变量 yy 之间的关系建模为 nn 次多项式。多项式回归的目的是找到一个 nn 次多项式函数&#xff0c;使得这个函数能够最好地拟合给定的数据点。 多项式回归的数学表达…

XX服务器上的npm不知道咋突然坏了

收到同事的V&#xff0c;说是&#xff1a;182上的npm不知道咋突然坏了&#xff0c;查到这里了&#xff0c;不敢动了。 咱一定要抓重点&#xff1a;突然坏了。这里的突然肯定不是瞬间&#xff08;大概率是上次可用&#xff0c;这次不可用&#xff0c;中间间隔了多长时间&#x…

Vizcom:AI驱动的草图到3D设计革命

Vizcom是一家领先的AI技术公司,专注于为工业设计师提供工具,将手绘草图快速转化为可制造的3D模型,从而加速产品迭代和创新。 公司背景与愿景 成立于2021年的Vizcom由前Nvidia工业设计师Jordan Taylor创立。Taylor凭借其深厚的创意设计背景和技术敏锐度,看到了生成对抗网络…

html自带的input年月日(date) /时间(datetime-local)/星期(week)/月份(month)/时间(time)控件

年月日期控件 type"date" <input type"date" id"StartDate" valueDateTime.Now.ToString("yyyy-MM-dd") /> //设置值 $("#StartDate").val("2024-12-12"); //获取值 var StartDate$("#StartDate&quo…

【51单片机】独立按键快速上手

51单片机独立按键是单片机控制系统中常用的一种输入方式&#xff0c;它相当于一种电子开关&#xff0c;按下时开关接通&#xff0c;松开时开关断开。 开关功能‌&#xff1a;独立按键内部通常包含一个有弹性的金属片&#xff0c;当按键被按下时&#xff0c;金属片与触点接触&a…

SpringCloud和Nacos的基础知识和使用

1.什么是SpringCloud ​ 什么是微服务&#xff1f; ​ 假如我们需要搭建一个网上购物系统&#xff0c;那么我们需要哪些功能呢&#xff1f;商品中心、订单中心和客户中心等。 ​ 当业务功能较少时&#xff0c;我们可以把这些功能塞到一个SpringBoot项目中来进行管理。但是随…

手机实时提取SIM卡打电话的信令声音--社会价值(一、方案解决了什么问题)

手机实时提取SIM卡打电话的信令声音 --社会价值(一、方案解决了什么问题) 一、前言 这段时间&#xff0c;我们在技术范围之外陷入了一个自证或者说下定义的怪圈&#xff0c;即要怎么样去介绍或者描述&#xff1a;我们是一个什么样的产品。它在当前这个世界上&#xff0c;处于…

使用navicat新旧版本,连接PostgreSQL高版本报错问题图文解决办法

使用navicat新旧版本&#xff0c;连接PostgreSQL高版本报错问题图文解决办法 一、问题现象&#xff1a;二、出现原因三、解决方法&#xff1a;1、升级Navicat版本&#xff1a;2、使用低版本的postgreSQL&#xff1a;3、修改Navicat的dll二进制文件&#xff1a;navicat版本15nav…

12.1【JAVA EXP4】next项目

next项目构建问题 详解一下这个页面 什么是Node选项&#xff1f; Node选项是指在运行Node.js应用程序时可以传递给Node.js进程的一系列命令行参数。这些选项可以让开发者控制Node.js的行为&#xff0c;例如设置内存限制、启用或禁用某些功能、指定调试端口等 --inspect 和 --i…

【操作系统】实验九:设备驱动程序

实验9 设备驱动程序 在钻研Linux内核的人当中&#xff0c;大多数人都是在写设备驱动程序。尽管由于设备的特殊性&#xff0c;使得每个驱动程序都不一样。但是编写设备驱动程序的许多原则和基本技巧都是一样的&#xff0c;甚至Windows下的设备驱动程序从原理上讲也与之相通。在…

腾讯云COS跨域访问CORS配置

腾讯云COS跨域访问CORS配置方法如下&#xff0c;参考以下截图&#xff1a; 参考文章&#xff1a; 跨域及CORS-Nginx配置CORS

从EXCEL表格到WEB TABLE的实践

前言 EXCEL管理数据 Bootstrap Bootstrap 是一个流行的开源前端框架&#xff0c;它由 Twitter 的员工开发&#xff0c;用于快速开发响应式和移动设备优先的网页和应用程序。 jQuery jQuery 是一个快速、小巧且功能丰富的 JavaScript 库。它简化了 HTML 文档的遍历、事件处理…

python中向量指的是什么意思

一、向量是什么 在数学中&#xff0c;向量&#xff08;也称为欧几里得向量、几何向量、矢量&#xff09;&#xff0c;指具有大小&#xff08;magnitude&#xff09;和方向的量。它可以形象化地表示为带箭头的线段。箭头所指&#xff1a;代表向量的方向&#xff1b;线段长度&am…

高数 导数

文章目录 一&#xff0c;导数的知识点 二&#xff0c;单侧导数 三&#xff0c;可导和连续的关系 四&#xff0c;复合函数求导 五&#xff0c;参数方程求导 六&#xff0c;高阶导数求导 七&#xff0c;隐函数求导 八&#xff0c;微分基础 一&#xff0c;导数常用的知识点 …

基于Spring Boot的同城宠物照看系统的设计与实现

一、摘要 在快节奏的现代生活中&#xff0c;宠物已成为许多家庭不可或缺的一部分。然而&#xff0c;宠物照看服务的需求也随之增长。为了满足这一需求&#xff0c;我们设计并实现了一款同城宠物照看系统&#xff0c;该系统利用Java技术和MySQL数据库&#xff0c;为用户提供一个…

光伏逆变器负载的维护和保养方法有哪些?

光伏逆变器是光伏发电系统中的关键设备&#xff0c;它将太阳能电池板产生的直流电转换为交流电&#xff0c;为家庭和工业用电提供稳定的电力。为了保证光伏逆变器的正常运行和延长其使用寿命&#xff0c;我们需要对其进行定期的维护和保养。以下是一些建议&#xff1a; 清洁&a…