OPenGL--Transform feedback示例解析

简述

英文地址https://open.gl/feedback

到目前为止,我们总是将顶点数据发送到图形处理器,并且只在帧缓存中生成绘制的像素。如果我们想要在经过顶点着色器或几何着色器之后捕获这些顶点呢?在这一章中,我们将探讨一种方法,即transform feedback。

到目前为止,我们已经使用了VBO(顶点缓冲区对象)来存储用于绘制操作的顶点。

Transform feedback 允许着色器把顶点写回这些。

例如,您可以构建一个顶点着色器,它模拟重力并将更新后的顶点位置写回缓冲区。这样,您就不必将这些数据从图形内存传输到主内存。最重要的是,你可以从如今GPU的巨大的并行处理能力中获益。

 

基本Transform feedback

我们将从头开始,以便最终程序能够清楚地演示Transform feedback是多么简单。

不幸的是,这次没有预览,因为我们不会在这一章里画任何东西!尽管这个特性可以用来简化像粒子模拟这样的效果,但是解释这些内容超出了这些文章的范围。在您了解了转换反馈的基础之后,您将能够发现并理解关于这些主题的大量文章。

让我们从一个简单的顶点着色器开始。

const GLchar* vertexShaderSrc = R"glsl(
    in float inValue;
    out float outValue;

    void main()
    {
        outValue = sqrt(inValue);
    }
)glsl";

这个顶点着色器似乎没有多大意义。它没有设置一个gl_position,它只接受一个任意的浮点数作为输入。幸运的是,我们可以使用Transform feedback来捕获结果,我们马上就会看到。

GLuint shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(shader, 1, &vertexShaderSrc, nullptr);
glCompileShader(shader);

GLuint program = glCreateProgram();
glAttachShader(program, shader);

编译着色器,创建一个程序并附加着色器,但是不要调用glLinkProgram()!在链接程序之前,我们必须告诉OpenGL,我们想要捕获到一个缓冲区中的输出属性。

const GLchar* feedbackVaryings[] = { "outValue" };
glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);

第一个参数是不言而喻的,是着色器程序。第二个参数和第三个参数指定了输出名称数组和数组本身的长度,最后一个参数指定了应该如何写入数据。

以下两种格式可供选择:

--GL_INTERLEAVED_ATTRIBS:将所有属性写入一个缓冲区对象。。

--GL_SEPARATE_ATTRIBS: 将属性写入多个缓冲区对象,或将不同的偏移量写入缓冲区。

有时对于每个属性都有单独的缓冲区是很有用的,但是为了让我们对这个演示保持简单。现在您已经指定了输出变量,您可以链接并激活该程序。这是因为链接过程依赖于关于输出的设置。

glLinkProgram(program);
glUseProgram(program);

之后,创建和绑定VAO:

GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

现在,创建的顶点着色器的输入数据缓冲区:

GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);

数据中的数字是我们想要的数字,用来计算平方根,Transform feedback将帮助我们得到结果。

GLint inputAttrib = glGetAttribLocation(program, "inValue");
glEnableVertexAttribArray(inputAttrib);
glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);

Transform feedback将返回outValue的值,但是首先我们需要创建一个VBO来保存这些值,就像输入的顶点一样:

 

GLuint tbo;
glGenBuffers(1, &tbo);
glBindBuffer(GL_ARRAY_BUFFER, tbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(data), nullptr, GL_STATIC_READ);

注意,我们现在传递了一个nullptr,以创建一个足够大的缓冲区,以便容纳所有生成的浮点数,但是没有指定任何初始数据。设置一个适当的使用类型GL_STATIC_READ,它表示我们打算将OpenGL写入到这个缓冲区中,我们的应用程序可以从中读取。(请参阅使用类型的参考资料)

现在我们已经为渲染计算过程做了所有的准备。由于我们不打算画任何东西,光栅化器应该被禁用:

glEnable(GL_RASTERIZER_DISCARD);

为了实际绑定我们在上面创建的缓冲区作为转换反馈缓冲区,我们必须使用一个名为glBindBufferBase的新函数。

glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);

第一个参数是目前需gl_transform_feedback_buffer让未来的扩展。第二个参数是输出 变量的指数,这是简单的零因为我们只有一个。最后一个参数 指定绑定的缓冲区对象。

做画的电话之前,你必须输入变换反馈模式:

目前,第一个参数是GL_TRANSFORM_FEEDBACK_BUFFER,以支持将来的扩展。第二个参数是输出变量的索引,它是0,因为我们只有一个。最后一个参数指定要绑定的缓冲区对象。

在进行绘制调用之前,您必须设置transform_feedback模式:

glBeginTransformFeedback(GL_POINTS);

它肯定会让人想起昔日的“美好时光”!就像上一章的几何着色一样,原始模式的可能值也有一些限制。

--GL_POINTS — GL_POINTS

--GL_LINES — GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_LINES_ADJACENCY, GL_LINE_STRIP_ADJACENCY

--GL_TRIANGLES — GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES_ADJACENCY,

--GL_TRIANGLE_STRIP_ADJACENCY

如果你只有一个顶点着色器,就像我们现在所做的那样,普通模式必须与被绘制的那个元素匹配:

glDrawArrays(GL_POINTS, 0, 5);

即使我们现在使用的是数据,单个数字仍然可以看作是单独的“点”,所以我们使用普通模式。

结束转换反馈模式:

glEndTransformFeedback();

通常,在绘图操作结束时,我们将交换缓冲区以在屏幕上显示结果。我们仍然希望在尝试访问结果之前确保渲染操作已经完成,所以我们刷新OpenGL的命令缓冲区:

glFlush();

将结果返回现在就像将缓冲区数据复制回数组一样简单:

GLfloat feedback[5];
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);

如果您现在在数组中打印值,那么您应该在终端中看到输入的平方根:

printf("%f %f %f %f %f\n", feedback[0], feedback[1], feedback[2], feedback[3], feedback[4]);

打印结果

恭喜你,你现在知道如何让你的GPU执行带有顶点着色器的通用任务!当然,像OpenCL这样的真正的GPGPU框架通常更好,但是Feedback transform的优势在于,您可以直接在绘图操作中重新使用数据,

例如将Feedback transform缓冲区作为数组缓冲区进行绑定,并执行正常的绘图调用。

如果你有显卡和驱动程序支持它,你也可以在OpenGL4.3中使用计算着色器,它实际上是为与绘图相关的任务设计的。

以下是完整的代码

// Link statically with GLEW
#define GLEW_STATIC

// Headers
#include <GL/glew.h>
#include <SFML/Window.hpp>

// Vertex shader
const GLchar* vertexShaderSrc = R"glsl(
    #version 150 core

    in float inValue;
    out float outValue;

    void main()
    {
        outValue = sqrt(inValue);
    }
)glsl";

int main()
{
    sf::ContextSettings settings;
    settings.depthBits = 24;
    settings.stencilBits = 8;

    sf::Window window(sf::VideoMode(800, 600, 32), "Transform Feedback", sf::Style::Titlebar | sf::Style::Close, settings);

    // Initialize GLEW
    glewExperimental = GL_TRUE;
    glewInit();

    // Compile shader
    GLuint shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(shader, 1, &vertexShaderSrc, nullptr);
    glCompileShader(shader);

    // Create program and specify transform feedback variables
    GLuint program = glCreateProgram();
    glAttachShader(program, shader);

    const GLchar* feedbackVaryings[] = { "outValue" };
    glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);

    glLinkProgram(program);
    glUseProgram(program);

    // Create VAO
    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // Create input VBO and vertex format
    GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };

    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);

    GLint inputAttrib = glGetAttribLocation(program, "inValue");
    glEnableVertexAttribArray(inputAttrib);
    glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);

    // Create transform feedback buffer
    GLuint tbo;
    glGenBuffers(1, &tbo);
    glBindBuffer(GL_ARRAY_BUFFER, tbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data), nullptr, GL_STATIC_READ);

    // Perform feedback transform
    glEnable(GL_RASTERIZER_DISCARD);

    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);

    glBeginTransformFeedback(GL_POINTS);
        glDrawArrays(GL_POINTS, 0, 5);
    glEndTransformFeedback();

    glDisable(GL_RASTERIZER_DISCARD);

    glFlush();

    // Fetch and print results
    GLfloat feedback[5];
    glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);

    printf("%f %f %f %f %f\n", feedback[0], feedback[1], feedback[2], feedback[3], feedback[4]);

    glDeleteProgram(program);
    glDeleteShader(shader);

    glDeleteBuffers(1, &tbo);
    glDeleteBuffers(1, &vbo);

    glDeleteVertexArrays(1, &vao);

    window.close();

    return 0;
}


Feedback transform和几何着色器

当你有几何着色器,变换反馈操作将 捕捉几何着色器的输出而不是顶点着色器。对于 例子:

// 顶点着色器
const GLchar* vertexShaderSrc = R"glsl(
    in float inValue;
    out float geoValue;

    void main()
    {
        geoValue = sqrt(inValue);
    }
)glsl";

//几何着色器
const GLchar* geoShaderSrc = R"glsl(
    layout(points) in;
    layout(triangle_strip, max_vertices = 3) out;

    in float[] geoValue;
    out float outValue;

    void main()
    {
        for (int i = 0; i < 3; i++) {
            outValue = geoValue[0] + i;
            EmitVertex();
        }

        EndPrimitive();
    }
)glsl";

几何着色器接受顶点着色器处理的一个点,生成一个三角形,每个点都有一个更高的值。

GLuint geoShader = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geoShader, 1, &geoShaderSrc, nullptr);
glCompileShader(geoShader);

...

glAttachShader(program, geoShader);

编译并将几何着色器附加到程序中以开始使用它。

const GLchar* feedbackVaryings[] = { "outValue" };
glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);

虽然现在的输出现在来自于几何着色器,但我们没有改变名称,所以这段代码保持不变。

因为每个输入顶点将生成3个顶点作为输出,所以Transform feedback 缓冲区现在需要3倍于输入缓冲区的大小:

glBufferData(GL_ARRAY_BUFFER, sizeof(data) * 3, nullptr, GL_STATIC_READ);

当使用几何着色器时,指定为glBeginTransformFeedback的类型必须与几何着色器的输出类型匹配:

glBeginTransformFeedback(GL_TRIANGLES);

检索输出仍然是相同的:

//获取并打印结果
GLfloat feedback[15];
glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);
 
for (int i = 0; i < 15; i++) {
    printf("%f\n", feedback[i]);
}

打印结果

虽然你要注意反馈的数据类型和你的缓冲区的大小 ,但添加几何着色器的方程并没有太大变化, 除了负责输出着色器。

以下是完整的代码

// Link statically with GLEW
#define GLEW_STATIC

// Headers
#include <GL/glew.h>
#include <SFML/Window.hpp>

// Vertex shader
const GLchar* vertexShaderSrc = R"glsl(
    #version 150 core

    in float inValue;
    out float geoValue;

    void main()
    {
        geoValue = sqrt(inValue);
    }
)glsl";

// Geometry shader
const GLchar* geoShaderSrc = R"glsl(
    #version 150 core

    layout(points) in;
    layout(triangle_strip, max_vertices = 3) out;

    in float[] geoValue;
    out float outValue;

    void main()
    {
        for (int i = 0; i < 3; i++) {
            outValue = geoValue[0] + i;
            EmitVertex();
        }

        EndPrimitive();
    }
)glsl";

int main()
{
    sf::ContextSettings settings;
    settings.depthBits = 24;
    settings.stencilBits = 8;

    sf::Window window(sf::VideoMode(800, 600, 32), "Transform Feedback", sf::Style::Titlebar | sf::Style::Close, settings);

    // Initialize GLEW
    glewExperimental = GL_TRUE;
    glewInit();

    // Compile shaders
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSrc, nullptr);
    glCompileShader(vertexShader);

    GLuint geoShader = glCreateShader(GL_GEOMETRY_SHADER);
    glShaderSource(geoShader, 1, &geoShaderSrc, nullptr);
    glCompileShader(geoShader);

    // Create program and specify transform feedback variables
    GLuint program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, geoShader);

    const GLchar* feedbackVaryings[] = { "outValue" };
    glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);

    glLinkProgram(program);
    glUseProgram(program);

    // Create VAO
    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // Create input VBO and vertex format
    GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };

    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);

    GLint inputAttrib = glGetAttribLocation(program, "inValue");
    glEnableVertexAttribArray(inputAttrib);
    glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);

    // Create transform feedback buffer
    GLuint tbo;
    glGenBuffers(1, &tbo);
    glBindBuffer(GL_ARRAY_BUFFER, tbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data) * 3, nullptr, GL_STATIC_READ);

    // Perform feedback transform
    glEnable(GL_RASTERIZER_DISCARD);

    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);

    glBeginTransformFeedback(GL_TRIANGLES);
        glDrawArrays(GL_POINTS, 0, 5);
    glEndTransformFeedback();

    glDisable(GL_RASTERIZER_DISCARD);

    glFlush();

    // Fetch and print results
    GLfloat feedback[15];
    glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);

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

    glDeleteProgram(program);
    glDeleteShader(geoShader);
    glDeleteShader(vertexShader);

    glDeleteBuffers(1, &tbo);
    glDeleteBuffers(1, &vbo);

    glDeleteVertexArrays(1, &vao);

    window.close();

    return 0;
}

Transform feedback变量反馈

正如我们在前一章中看到的,几何着色器有惟一的属性来生成一个可变数量的数据。幸运的是,有一些方法可以通过"查询对象"获得输出图元的个数。

就像OpenGL中的所有其他对象一样,你必须先创建一个:

GLuint query;
glGenQueries(1, &query);

然后在调用glBeginTransformFeedback之前,你必须告诉OpenGL跟踪图元的数量:

glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);

调用glendtransformfeedback后,你可以停止“记录”:

glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);

检索结果如下:

GLuint primitives;
glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitives);

然后,您可以将该值与其他数据一起打印出来:

printf("%u primitives written!\n\n", primitives);

打印结果

以下是完整的代码

// Link statically with GLEW
#define GLEW_STATIC

// Headers
#include <GL/glew.h>
#include <SFML/Window.hpp>

// Vertex shader
const GLchar* vertexShaderSrc = R"glsl(
    #version 150 core

    in float inValue;
    out float geoValue;

    void main()
    {
        geoValue = sqrt(inValue);
    }
)glsl";

// Geometry shader
const GLchar* geoShaderSrc = R"glsl(
    #version 150 core

    layout(points) in;
    layout(triangle_strip, max_vertices = 3) out;

    in float[] geoValue;
    out float outValue;

    void main()
    {
        for (int i = 0; i < 3; i++) {
            outValue = geoValue[0] + i;
            EmitVertex();
        }

        EndPrimitive();
    }
)glsl";

int main()
{
    sf::ContextSettings settings;
    settings.depthBits = 24;
    settings.stencilBits = 8;

    sf::Window window(sf::VideoMode(800, 600, 32), "Transform Feedback", sf::Style::Titlebar | sf::Style::Close, settings);

    // Initialize GLEW
    glewExperimental = GL_TRUE;
    glewInit();

    // Compile shaders
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSrc, nullptr);
    glCompileShader(vertexShader);

    GLuint geoShader = glCreateShader(GL_GEOMETRY_SHADER);
    glShaderSource(geoShader, 1, &geoShaderSrc, nullptr);
    glCompileShader(geoShader);

    // Create program and specify transform feedback variables
    GLuint program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, geoShader);

    const GLchar* feedbackVaryings[] = { "outValue" };
    glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);

    glLinkProgram(program);
    glUseProgram(program);

    // Create VAO
    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // Create input VBO and vertex format
    GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };

    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);

    GLint inputAttrib = glGetAttribLocation(program, "inValue");
    glEnableVertexAttribArray(inputAttrib);
    glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);

    // Create transform feedback buffer
    GLuint tbo;
    glGenBuffers(1, &tbo);
    glBindBuffer(GL_ARRAY_BUFFER, tbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data) * 3, nullptr, GL_STATIC_READ);

    // Create query object to collect info
    GLuint query;
    glGenQueries(1, &query);

    // Perform feedback transform
    glEnable(GL_RASTERIZER_DISCARD);

    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);

    glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);
        glBeginTransformFeedback(GL_TRIANGLES);
            glDrawArrays(GL_POINTS, 0, 5);
        glEndTransformFeedback();
    glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);

    glDisable(GL_RASTERIZER_DISCARD);

    glFlush();

    // Fetch and print results
    GLuint primitives;
    glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitives);

    GLfloat feedback[15];
    glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);

    printf("%u primitives written!\n\n", primitives);

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

    glDeleteQueries(1, &query);

    glDeleteProgram(program);
    glDeleteShader(geoShader);
    glDeleteShader(vertexShader);

    glDeleteBuffers(1, &tbo);
    glDeleteBuffers(1, &vbo);

    glDeleteVertexArrays(1, &vao);

    window.close();

    return 0;
}

注意,它返回的是图元的数量,而不是顶点的数量。因为我们有15个顶点,每个三角形都有3个,我们有5个图元。

查询对象也可以用来记录一些东西,比如在处理几何着色器和gltime用时,用来测量在服务器上花费的时间(图形卡)工作的时间。如果你被困在某个地方,

结论

你现在已经对几何学的阴影和Transform feedback有了足够的了解,让你的图形卡做一些非常有趣的工作,而不仅仅是绘画!您甚至可以组合Variable feedback和光栅化来更新顶点并在同一时间绘制它们!

猜你喜欢

转载自blog.csdn.net/niu2212035673/article/details/79041437