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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//创建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)
示例代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// 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!\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; }