OpenGL Transform Feedback
1,实际上这个原理类似opencl,将数据通过draw api做运算,一般通过的绘制方法:
glBeginTransformFeedback(GL_POINTS); glDrawArrays(GL_POINTS, 0, 5); glEndTransformFeedback();
如上绘制5个点,每个点携带一个 float数值,然后再vert shading里面做运算。
下面一个案例将准备好的数值传入gpu,然后做sqrt运算:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#define GLEW_STATIC // GLEW #include <GL/glew.h> #include <cstdlib> #undef GLFW_DLL // GLFW #include <GLFW/glfw3.h> #include <iostream> #include <sstream> #include <fstream> #include <string> using namespace std; string readFile(const char *path){ ifstream stream; stringstream ss; stream.exceptions(ifstream::badbit); try { stream.open(path); // open file ss << stream.rdbuf(); // get strings from file } catch (ifstream::failure e) { cout << "ERROR::OPEN FILE:" << path << endl; } // close file handle stream.close(); // get str() from stringstream string shaderCode = ss.str(); return shaderCode; } void init(){ string code = readFile("shaders/feedback/CP_01.vert"); const char * vertexShaderSrc = code.c_str(); // Create shader and compile 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); // binding the out value const GLchar* feedbackVaryings[] = { "outValue" }; glTransformFeedbackVaryings(program, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS); // finally link program and use 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; glCreateBuffers(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); // deafault glBeginTransformFeedback(GL_POINTS); glDrawArrays(GL_POINTS, 0, 5); glEndTransformFeedback(); glDisable(GL_RASTERIZER_DISCARD); glFlush(); // Fetch and print results GLfloat feedback[5]; // parm1: target // parm2: offset // parm3: size // parm4: void *data // glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback); //OGL 4.5 use-> glGetNamedBufferSubData() glGetNamedBufferSubData(tbo,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); } int main(){ glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_VISIBLE,GL_FALSE); GLFWwindow * window = glfwCreateWindow(800,600,"Hello",NULL,NULL); glfwMakeContextCurrent(window); // glew init glewInit(); /* while( !glfwWindowShouldClose( window ) ) { glfwPollEvents(); glfwSwapBuffers(window); } */ init(); glfwTerminate(); glfwDestroyWindow(window); return 0; }
2,有趣的是切换双重缓冲的buffer方法:
这个来自红皮书的:https://github.com/openglredbook/examples/blob/master/src/03-xfb/03-xfb.cpp
if ((frame_count & 1) != 0) { glBindVertexArray(vao[1]); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, vbo[0]); } else { glBindVertexArray(vao[0]); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, vbo[1]); }
双缓冲更新 如图用的上述方法。帧0 用的buffer2, 帧1 buffer1, 来回切换,传递到glsl中。
在https://open.gl/feedback结尾有个小练习:https://open.gl/content/code/c8_exercise_1.txt,使用的是初始数据->OPENGL->Feedback to client( getSubData() ) -> OPENGL update粒子。
3,在https://open.gl/feedback最后一个,输入5个float数值,然后再输入到geometry shader:
// Vertex shader const GLchar* vertexShaderSrc = R"glsl( in float inValue; out float geoValue; void main() { geoValue = sqrt(inValue); } )glsl"; // Geometry shader 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";
从 vertex shader里输出的geoValue传递到Geometry shader,geoValue.然后每个点生成3个顶点(geometry shader里是输出三角形),也就是15个数据。
注意geoValue传入的获取方法,我们传入的是点,所以index 只有[0].
如果输入的是三角形则:
geoValue[0] 代表三角形第一个顶点的数据
geoValue[1] 代表三角形第二个顶点的数据
geoValue[2] 代表三角形第三个顶点的数据
最后feedback开始接受数据的时候比如是三角形接入:
glBeginTransformFeedback(GL_TRIANGLES);
最后拿数据也是15个:
// 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]); }
4,有个很重要的问题,如果你在vertex shading里面的:
#version 450 core layout (location = 0) in vec2 P; layout (location = 1) in vec2 V; layout (location = 2) in vec2 OrigP; out vec2 outP; out vec2 outV; uniform vec2 mousePos; uniform float dt = 0.01; void main() { gl_Position = vec4(P,0, 1.0); outP = vec2(0,0); outV = vec2(0,0); }
C++里这么feedback,必须要把outP,outV处的变量写上一个值。不填写的话,fragmentshader就会错误的显示黑色。
const GLchar* feedbackVaryings[] = { "outP", "outV" }; glTransformFeedbackVaryings(program, 2, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
鼠标与粒子交互:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#version 450 core layout (location = 0) in vec2 P; // prev P layout (location = 1) in vec2 V; // prev V layout (location = 2) in vec2 OrigP; // original P out vec2 outP; // next frame P out vec2 outV; // next Frame V uniform vec2 mousePos; uniform float dt = 0.01; void main() { vec2 next_v = OrigP - P; if(length(mousePos - OrigP) < 0.75f){ vec2 a = 1.5f * normalize(mousePos - P); next_v = V + a * dt; } if(length(next_v) > 1.0f){ next_v *= 0.25; } vec2 next_p = P + next_v * dt; outP = next_p; outV = next_v; gl_Position = vec4(next_p, 0.0, 1.0); }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#define GLEW_STATIC // GLEW #include <GL/glew.h> #include <chrono> #include <cstdlib> #undef GLFW_DLL // GLFW #include <GLFW/glfw3.h> #include "utils.h" using namespace std; using namespace AlgebraMaster; const int width = 800; const int height = 800; static GLuint shaderProgram; static GLuint VAO,VBO; static GLuint TFBO; // transform feedback object // Fragment shader const GLchar* fragmentShaderSrc = R"glsl( #version 450 core out vec4 outColor; void main() { outColor = vec4(1.0, 0.0, 0.0, 1.0); } )glsl"; void init_particles_data(GLfloat *data, int numdata){ glCreateVertexArrays(1, &VAO); glBindVertexArray(VAO); glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * numdata, data, GL_STREAM_DRAW); //glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STREAM_DRAW); //glNamedBufferData(VBO,sizeof(data),data,GL_STREAM_DRAW); //glNamedBufferStorage(VBO,sizeof(data),data, 0); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), 0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*)(2 * sizeof(GLfloat))); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*)(4 * sizeof(GLfloat))); glGenBuffers(1, &TFBO); glBindBuffer(GL_ARRAY_BUFFER, TFBO); glBufferData(GL_ARRAY_BUFFER, 400 * sizeof(GLfloat), nullptr, GL_STATIC_READ); //glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, TFBO); glPointSize(10.0f); } void initShader(){ string vert_code = readFile("shaders/feedback/mouse_track_particles.vert"); string frag_code = readFile("shaders/feedback/mouse_track_particles.frag"); const char * vertexShaderSrc = vert_code.c_str(); const char * fragShaderSrc = frag_code.c_str(); // Compile shaders GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSrc, nullptr); glCompileShader(vertexShader); GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSrc, nullptr); glCompileShader(fragmentShader); // Create shaderProgram and specify transform feedback variables shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); const GLchar* feedbackVaryings[] = { "outP", "outV" }; glTransformFeedbackVaryings(shaderProgram, 2, feedbackVaryings, GL_INTERLEAVED_ATTRIBS); glLinkProgram(shaderProgram); glUseProgram(shaderProgram); } void display(){ //glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, TFBO); // Perform feedback transform and draw vertices glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, TFBO); glBeginTransformFeedback(GL_POINTS); glDrawArrays(GL_POINTS, 0, 100); glEndTransformFeedback(); } void cursor_pos_callback(GLFWwindow *w, double x, double y); void framebuffer_size_callback(GLFWwindow* window, int width, int height); int main(){ glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //glfwWindowHint(GLFW_VISIBLE,GL_FALSE); GLFWwindow * window = glfwCreateWindow(width,height,"Hello",NULL,NULL); glfwSetCursorPosCallback(window, cursor_pos_callback); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwMakeContextCurrent(window); // glew init glewInit(); // vbo and initialize data // Create input VBO and vertex format GLfloat data[600] = {}; // Vertex format: 6 floats per vertex: // pos.x pox.y vel.x vel.y origPos.x origPos.y // Set original and initial positions for (int y = 0; y < 10; y++) { for (int x = 0; x < 10; x++) { data[60 * y + 6 * x] = 0.2f * x - 0.9f; data[60 * y + 6 * x + 1] = 0.2f * y - 0.9f; data[60 * y + 6 * x + 4] = 0.2f * x - 0.9f; data[60 * y + 6 * x + 5] = 0.2f * y - 0.9f; } } initShader(); init_particles_data(data, 100 * 6); GLfloat feedback[400]; auto t_prev = std::chrono::high_resolution_clock::now(); while( !glfwWindowShouldClose( window ) ) { // Clear the screen to black glClearColor(0.2f, 0.2f, 0.2f, 0.2f); glClear(GL_COLOR_BUFFER_BIT); // Calculate delta time auto t_now = std::chrono::high_resolution_clock::now(); float time = std::chrono::duration_cast<std::chrono::duration<float>>(t_now - t_prev).count(); t_prev = t_now; //cout << newx <<" " <<newy << endl; GLint uniMousePos = glGetUniformLocation(shaderProgram, "mousePos"); cout << uniMousePos << endl; double x; double y; glfwGetCursorPos(window,&x,&y); auto newx = (x / 400.0) - 1.0; auto newy = (-y / 400.0 ) + 1.0; glUniform2f(uniMousePos, newx, newy); // Render content display(); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, TFBO); glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback); //cout << feedback[0] << " " << feedback[1] <<endl; for (int i = 0; i < 100; i++) { data[6 * i] = feedback[4 * i]; data[6 * i + 1] = feedback[4 * i + 1]; data[6 * i + 2] = feedback[4 * i + 2]; data[6 * i + 3] = feedback[4 * i + 3]; } // glBufferData() would reallocate the whole vertex data buffer, which is unnecessary here. // glBufferSubData() is used instead - it updates an existing buffer. glBindBuffer(GL_ARRAY_BUFFER,VBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(data), data); glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); glfwDestroyWindow(window); return 0; } void cursor_pos_callback(GLFWwindow *w, double x, double y){ // NDC SPACE double newx = x / (double(width)/2.0) -1; double newy = -y / (double(height)/2.0) +1; //cout << newx <<" " <<newy << endl; GLint uniMousePos = glGetUniformLocation(shaderProgram, "mousePos"); glUniform2f(uniMousePos, newx, newy); } void framebuffer_size_callback(GLFWwindow* window, int width, int height) { // make sure the viewport matches the new window dimensions; note that width and // height will be significantly larger than specified on retina displays. glViewport(0, 0, width, height); }
源码中从GPU内从copy下来,只copy到feedback一部分,只拷贝vec2 P ,vec2 V 共100个点,所以有400个float数据。
最后填充到VBO中,关键的地方来了:防止内存重新创建。
// glBufferData() would reallocate the whole vertex data buffer, which is unnecessary here. // glBufferSubData() is used instead - it updates an existing buffer. glBindBuffer(GL_ARRAY_BUFFER,VBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(data), data);
REF:
OpenGL Programming Guide Ninth Edition https://github.com/openglredbook/examples/blob/master/src/03-xfb/03-xfb.cpp