OpenGL-Transform Feedback

 

Transform Feedback主要作用是获取顶点着色器或者几何着色器输出的数据,并将结果存储到一个或多个的缓冲对象里,主要用于粒子系统中。

以光栅化为界限划分frond end和back end两个阶段,该过程位于front end的最后一个阶段。

粒子系统:

粒子系统是为了模仿一些自然现象烟、灰尘、烟火、雨等所使用的技术,这些自然现象均由大量的小粒子组成,并以某种方式在一起移动。

为了模仿一个由粒子组成的自然现象,通常需要维护一个粒子的位置信息和一些其他的属性速率、颜色等等,并且在每一帧中执行下面的步骤:

(1) 更新每个粒子的属性,根据模拟现象的复杂度,计算可能简单也可能困难;

(2) 渲染粒子;

第一步更新粒子属性时发生在 CPU,应用程序访问顶点缓冲区就可以获得或者更新每个粒子的属性。第二步渲染发生在 GPU。

这样就会存在两个问题:

(1) 在CPU中更新粒子属性,要求GL驱动程序从GPU显存中拷贝顶点缓冲区的数据到CPU内存。如果粒子数量巨大,且帧率要求较高,会对程序的性能产生很大的影响;

(2) 更新粒子属性意味着需要在不同的数据上进行相同的数学计算,如果将这个过程在GPU上进行,会更好的发挥GPU的优势;

Transform Feedback:

OpenGL在3.0版本之后加入了新特性即Transform Feedback,主要的实现思路是在VS/GS处理之后,捕获即将装配为图元(点,线段,三角形)的顶点,将部分或者全部属性存放到一个特殊的缓存Transform Feedback Buffer中。

根据这个特性,它和顶点着色器可以组合成一个渲染管线,是否进行后续的光栅化处理由我们决定,这样一些通用的计算可以利用GPU强大的计算能力去做。同时,在下一帧渲染的时候,上一帧中输出的顶点信息可以在这一帧中作为顶点缓存使用,在这样的一个循环过程中,我们可以不借助于应用程序来实现对粒子信息的更新。

下面这幅图片介绍了Transform Feedback Buffer在管线中所处的位置:

 

如果没有使用GS,在调用绘制函数时传入的顶点数量的参数就是Transform Feedback缓存中图元的个数。

如果GS存在,因为GS能够创建和删除图元,那么图元的数量是未知的,所以Transform Feedback缓存中的图元数也就不确定。

不知道缓存里包含多少顶点,使用缓存中的数据进行绘图就会不明确,为了克服这个困难,Transform Feedback提供了一个新的绘图函数,这个函数不需要使用顶点的个数作为参数。系统会自动的为每一个缓存计算顶点数。当缓存被用来作为输入时,系统会自动使用之前计算出来的顶点数。如果多次将数据输入到Transform Feedback缓存中,那么顶点数也会相应的增加。

需要注意的是,在同一个绘制过程中,相同的缓存不能同时作为输入和输出。如果想要在一个顶点缓冲区中更新粒子,需要两个buffer 并进行切换。在第1帧的时候在buffer A中更新粒子并从buffer B中渲染粒子,在第2帧的时候在buffer B中更新粒子并从buffer A中渲染粒子。

示例:

相关API:

//创建Transform Feedback对象

GLuint glGenTransformFeedbacks(GLenum target,  GLuint *id)

//绑定

GLuint glBindTransformFeedback(GLenum target,  GLuint id)

//删除对象

GLuint glDeleteTransformFeedbacks(GLsizei n, const GLuint* ids)

//缓存的绑定

void glBindBufferBase(GLenum target, GLint index, GLuint buffer)

//部分缓存的绑定

void glBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size)

//设置缓存记录的变量

void glTransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar **varyings, GLenum bufferMode)

//启动

void glBeginTransformFeedback(GLenum primitiveMode)

//暂停

void glPauseTransformFeedback(void)

//重新启动

void glResumeTransformFeedback(void)

//停止

void glEndTransformFeedback(void)
View Code

示例代码:

// Link statically with GLEW
#define GLEW_STATIC

#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()
{
    // SFML window configuration
    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);

    // GL_INTERLEAVED_ATTRIBS: write all attributes to a buffer object
    // GL_SEPARATE_ATTRIBS: write attributes to multiple buffer objects
    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);

    // It returns the number of primitives, not the number of vertices. 
    printf("%u primitives written!

", primitives);

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

    glDeleteQueries(1, &query);

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

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

    glDeleteVertexArrays(1, &vao);

    window.close();

    return 0;
}
View Code
关山难越,谁悲失路之人; 萍水相逢,尽是他乡之客。
原文地址:https://www.cnblogs.com/xue0708/p/15185215.html