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

一、简介

本文介绍了 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_ARRAY_BUFFER, tbo);
    glBufferData(GL_ARRAY_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_ARRAY_BUFFER, tbo);
    // 注意!!! 由于我们使用 geometry shader 将一个输入变量(geoValue)变为三个输出(outputValue),
    // 因此 Transform feedback object 中用于接收数据的 buffer 大小应该为输入数据的 3 倍大
    glBufferData(GL_ARRAY_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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

探秘 AI Agent 之 Coze 智能体:从简介到搭建全攻略(4/30)

一、Coze 智能体概述 &#xff08;一&#xff09;Coze 智能体是什么 Coze 智能体是基于机器学习和自然语言处理技术的软件实体&#xff0c;它在人工智能领域扮演着重要的角色&#xff0c;能够像一个智能助手一样&#xff0c;通过与外界环境进行交互学习&#xff0c;进而执行各…

游戏引擎学习第47天

仓库: https://gitee.com/mrxiao_com/2d_game 昨天我们花了一点时间来修复一个问题&#xff0c;但基本上是在修复这个问题的过程中&#xff0c;我们决定添加一个功能&#xff0c;那就是在屏幕上控制多个实体。所以如果我有一个手柄&#xff0c;我可以添加另一个角色&#xff0…

CAPL如何设置或修改CANoe TCP/IP协议栈的底层配置

在CANoe中创建网络节点作为以太网主机时,可以给其配置独立的TCP/IP Stack。 配置的协议栈有一些底层配置参数可以在界面上设置或修改,比如: MTU上图中MTU显示500只是图形界面显示错误,正确值是1500。 TCP延迟确认这些参数也可以通过CAPL动态配置,甚至CAPL还可以配置很多界…

RabbitMQ实现消息发送接收——实战篇(路由模式)

本篇博文将带领大家一起学习rabbitMQ如何进行消息发送接收&#xff0c;我也是在写项目的时候边学边写&#xff0c;有不足的地方希望在评论区留下你的建议&#xff0c;我们一起讨论学习呀~ 需求背景 先说一下我的项目需求背景&#xff0c;社区之间可以进行物资借用&#xff0c…

计算机进制的介绍

一.进制介绍 对于整数&#xff0c;有四种表示方式: 1&#xff09;二进制:0,1&#xff0c;满2进1。 在golang中&#xff0c;不能直接使用二进制来表示一个整数&#xff0c;它沿用了c的特点。 参考:Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国 //赋值…

基于卷积神经网络的Caser算法

将一段交互序列嵌入到一个以时间为纵轴的平面空间中形成“一张图”后&#xff0c;基于卷积序列嵌入的推荐&#xff08;Caser&#xff09;算法利用多个不同大小的卷积滤波器&#xff0c;来捕捉序列中物品间的点级&#xff08;point-level&#xff09;、联合的&#xff08;union-…

基于STM32设计的粮食仓库(粮仓)环境监测系统

一、前言 当前项目使用的相关软件工具、传感器源代码工程已经上传到网盘&#xff08;实时更新项目内容&#xff09;&#xff1a;https://ccnr8sukk85n.feishu.cn/wiki/QjY8weDYHibqRYkFP2qcA9aGnvb?fromfrom_copylink 1.1 项目开发背景 随着现代农业的发展和粮食储存规模的…

计算机网络-传输层 TCP协议(上)

目录 报头结构 TCP的可靠传输机制 核心机制一&#xff1a;确认应答 TCP的序号和确认序号 核心机制二&#xff1a;丢包重传 核心机制三&#xff1a;连接管理 建立连接-三次握手 断开连接-四次挥手 核心机制四&#xff1a;滑动窗口 数据包已经抵达, ACK被丢了 数据包就…

【经验分享】容器云运维的知识点

最近忙于备考没关注&#xff0c;有次点进某小黄鱼发现首页出现了我的笔记还被人收费了 虽然我也卖了一些资源&#xff0c;但我以交流、交换为主&#xff0c;笔记都是免费给别人看的 由于当时刚刚接触写的并不成熟&#xff0c;为了避免更多人花没必要的钱&#xff0c;所以决定公…

纯血鸿蒙崛起,原生Android挑战?两大操作系统巅峰对决,智能设备未来谁主沉浮?

鸿蒙HarmonyOS和原生Android系统虽然在一些方面相似&#xff0c;但在架构、设计理念、API、开发工具等方面存在一些差异。鸿蒙系统的目标是跨设备、分布式的操作系统&#xff0c;强调多设备协同和资源共享&#xff0c;而Android则主要集中在智能手机和移动设备领域。 下面将从…

新手快速入门!低功耗4G模组Air780E——使用文件系统存储温湿度数据来啦~

小伙伴们&#xff0c;今天我们来学习低功耗4G模组Air780E快速入门之使用文件系统存储温湿度数据。一起接着看下去吧&#xff01; 一、编写脚本 1.1 准备资料 780E开发板 780E开发板设计资料 LuatOS-Air780E-文件系统的使用-程序源码demo TCP/UDP测试服务器 API使用介绍 …

vscode中插件ofExtensions的debug模式也无法查看U、p等openfoam中foam类型的变量

插件介绍&#xff1a; 主要内容如下&#xff1a; 以自编译的$HOME/OpenFOAM-7例&#xff0c;如果OFdebugopt设置为WM_COMPILE_OPTIONDebug&#xff0c;那最终的激活环境的命令为source $HOME/OpenFOAM/OpenFOAM-8/etc/bashrc WM_COMPILE_OPTIONDebug&#xff0c;这时候$FOAM_…

【收藏】Cesium 限制相机倾斜角(pitch)滑动范围

1.效果 2.思路 在项目开发的时候&#xff0c;有一个需求是限制相机倾斜角&#xff0c;也就是鼠标中键调整视图俯角时&#xff0c;不能过大&#xff0c;一般 pitch 角度范围在 0 至 -90之间&#xff0c;-90刚好为正俯视。 在网上查阅了很多资料&#xff0c;发现并没有一个合适的…

【经验分享】私有云运维的知识点

最近忙于备考没关注&#xff0c;有次点进某小黄鱼发现首页出现了我的笔记还被人收费了 虽然我也卖了一些资源&#xff0c;但我以交流、交换为主&#xff0c;笔记都是免费给别人看的 由于当时刚刚接触写的并不成熟&#xff0c;为了避免更多人花没必要的钱&#xff0c;所以决定公…

Please activate LaTeX Workshop sidebar item to render the thumbnail of a PDF

Latex代码中使用pdf图片&#xff0c;无法预览&#xff0c;提示&#xff1a; Please activate LaTeX Workshop sidebar item to render the thumbnail of a PDF 解决办法&#xff1a; 点击左边这个刷新下即可

QT:Widgets中的事件

事件的处理 (1)重新实现部件的paintEvent()、mousePressEvent()等事件处理函数。这是最常用的一种方法&#xff0c;不过它只能用来处理特定部件的特定事件。 (2)重新实现notify()函数。这个函数功能强大&#xff0c;提供了完全的控制&#xff0c;可以在事件过滤器得到事件之前…

Windows如何安装go环境,离线安装beego

一、安装go 1、下载go All releases - The Go Programming Language 通过网盘分享的文件&#xff1a;分享的文件 链接: https://pan.baidu.com/s/1MCbo3k3otSoVdmIR4mpPiQ 提取码: hxgf 下载amd64.zip文件&#xff0c;然后解压到指定的路径 2、配置环境变量 需要新建两个环境…

AI Agent:重塑业务流程自动化的未来力量(2/30)

《AI Agent&#xff1a;重塑业务流程自动化的未来力量》 摘要&#xff1a;整体思路是先介绍 AI Agent 的基本情况&#xff0c;再深入阐述其实现业务流程自动化的方法和在不同领域的应用&#xff0c;接着分析其价值和面临的挑战&#xff0c;最后得出结论&#xff0c;为读者全面…

R语言的数据结构-矩阵

【图书推荐】《R语言医学数据分析实践》-CSDN博客 《R语言医学数据分析实践 李丹 宋立桓 蔡伟祺 清华大学出版社9787302673484》【摘要 书评 试读】- 京东图书 (jd.com) R语言医学数据分析实践-R语言的数据结构-CSDN博客 矩阵是一个二维数组&#xff0c;矩阵中的元素都具有相…

springboot+javafx使用aop切面导致的fx:id不能被注入问题

记录一个我遇到得问题 问题描述 我本来使用AOP切面来进行全局异常管理&#xff0c;但是使用AOP之后fxml中通过fx:id绑定得参数无法被注入 Slf4j Component Aspect public class GlobalExceptionAspect {AfterThrowing(pointcut "execution(* com.shkj.videoclassifica…