Movatterモバイル変換


[0]ホーム

URL:


Fork me!

Transform feedback

Up until now we've always sent vertex data to the graphics processor and onlyproduced drawn pixels in framebuffers in return. What if we want to retrieve thevertices after they've passed through the vertex or geometry shaders? In thischapter we'll look at a way to do this, known astransform feedback.

So far, we've used VBOs (Vertex Buffer Objects) to store vertices to be used fordrawing operations. The transform feedback extension allows shaders to writevertices back to these as well. You could for example build a vertex shader thatsimulates gravity and writes updated vertex positions back to the buffer. Thisway you don't have to transfer this data back and forth from graphics memory tomain memory. On top of that, you get to benefit from the vast parallelprocessing power of today's GPUs.

Basic feedback

We'll start from scratch so that the final program will clearly demonstratehow simple transform feedback is. Unfortunately there's no preview this time,because we're not going to draw anything in this chapter! Although this featurecan be used to simplify effects like particle simulation, explaining these is abit beyond the scope of these articles. After you've understood the basics oftransform feedback, you'll be able to find and understand plenty of articlesaround the web on these topics.

Let's start with a simple vertex shader.

const GLchar* vertexShaderSrc = R"glsl(    in float inValue;    out float outValue;    void main()    {        outValue = sqrt(inValue);    })glsl";

This vertex shader does not appear to make much sense. It doesn't set agl_Position and it only takes a single arbitrary float as input. Luckily, wecan use transform feedback to capture the result, as we'll see momentarily.

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

Compile the shader, create a program and attach the shader, but don't callglLinkProgram yet! Before linking the program, we have to tell OpenGL whichoutput attributes we want to capture into a buffer.

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

The first parameter is self-explanatory, the second and third parameter specifythe length of the output names array and the array itself, and the finalparameter specifies how the data should be written.

The following two formats are available:

Sometimes it is useful to have separate buffers for each attribute, but letskeep it simple for this demo. Now that you've specified the output variables,you can link and activate the program. That is because the linking processdepends on knowledge about the outputs.

glLinkProgram(program);glUseProgram(program);

After that, create and bind the VAO:

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

Now, create a buffer with some input data for the vertex shader:

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);

The numbers indata are the numbers we want the shader to calculate the squareroot of and transform feedback will help us get the results back.

With regards to vertex pointers, you know the drill by now:

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

Transform feedback will return the values ofoutValue, but first we'll need tocreate a VBO to hold these, just like the input vertices:

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

Notice that we now pass anullptr to create a buffer big enough to hold all ofthe resulting floats, but without specifying any initial data. The appropriateusage type is nowGL_STATIC_READ, which indicates that we intend OpenGL towrite to this buffer and our application to read from it. (Seereference for usage types)

We've now made all preparations for therendering computationprocess. As we don't intend to draw anything, the rasterizer should be disabled:

glEnable(GL_RASTERIZER_DISCARD);

To actually bind the buffer we've created above as transform feedback buffer,we have to use a new function calledglBindBufferBase.

glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);

The first parameter is currently required to beGL_TRANSFORM_FEEDBACK_BUFFERto allow for future extensions. The second parameter is the index of the outputvariable, which is simply0 because we only have one. The final parameterspecifies the buffer object to bind.

Before doing the draw call, you have to enter transform feedback mode:

glBeginTransformFeedback(GL_POINTS);

It certainly brings back memories of the oldglBegin days! Just like thegeometry shader in the last chapter, the possible values for the primitive modeare a bit more limited.

If you only have a vertex shader, as we do now, the primitivemust match theone being drawn:

glDrawArrays(GL_POINTS, 0, 5);

Even though we're now working with data, the single numbers can still be seen asseparate "points", so we use that primitive mode.

End the transform feedback mode:

glEndTransformFeedback();

Normally, at the end of a drawing operation, we'd swap the buffers to presentthe result on the screen. We still want to make sure the rendering operation hasfinished before trying to access the results, so we flush OpenGL's commandbuffer:

glFlush();

Getting the results back is now as easy as copying the buffer data back to anarray:

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

If you now print the values in the array, you should see the square roots of theinput in your terminal:

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

Congratulations, you now know how to make your GPU perform general purposetasks with vertex shaders! Of course a real GPGPU framework likeOpenCLis generally better at this, but the advantage of transform feedback is that youcan directly repurpose the data in drawing operations, by for example bindingthe transform feedback buffer as array buffer and performing normal drawingcalls.

If you have a graphics card and driver that supports it, you could also usecompute shaders in OpenGL 4.3instead, which were actually designed for tasks that are less related to drawing.

You can find the full codehere.

Feedback transform and geometry shaders

When you include a geometry shader, the transform feedback operation willcapture the outputs of the geometry shader instead of the vertex shader. Forexample:

// Vertex shaderconst GLchar* vertexShaderSrc = R"glsl(    in float inValue;    out float geoValue;    void main()    {        geoValue = sqrt(inValue);    })glsl";// Geometry shaderconst 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";

The geometry shader takes a point processed by the vertex shader and generates2 more to form a triangle with each point having a 1 higher value.

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

Compile and attach the geometry shader to the program to start using it.

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

Although the output is now coming from the geometry shader, we've not changedthe name, so this code remains unchanged.

Because each input vertex will generate 3 vertices as output, the transformfeedback buffer now needs to be 3 times as big as the input buffer:

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

When using a geometry shader, the primitive specified toglBeginTransformFeedback must match the output type of the geometry shader:

glBeginTransformFeedback(GL_TRIANGLES);

Retrieving the output still works the same:

// Fetch and print resultsGLfloat feedback[15];glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback);for (int i = 0; i < 15; i++) {    printf("%f\n", feedback[i]);}

Although you have to pay attention to the feedback primitive type and the sizeof your buffers, adding a geometry shader to the equation doesn't change muchother than the shader responsible for output.

The full code can be foundhere.

Variable feedback

As we've seen in the previous chapter, geometry shaders have the unique propertyto generate a variable amount of data. Luckily, there are ways to keep track ofhow many primitives were written by usingquery objects.

Just like all the other objects in OpenGL, you'll have to create one first:

GLuint query;glGenQueries(1, &query);

Then, right before callingglBeginTransformFeedback, you have to tell OpenGLto keep track of the number of primitives written:

glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);

AfterglEndTransformFeedback, you can stop "recording":

glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);

Retrieving the result is done as follows:

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

You can then print that value along with the other data:

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

Notice that it returns the number of primitives, not the number of vertices.Since we have 15 vertices, with each triangle having 3, we have 5 primitives.

Query objects can also be used to record things such asGL_PRIMITIVES_GENERATEDwhen dealing with just geometry shaders andGL_TIME_ELAPSED to measure timespent on the server (graphics card) doing work.

Seethe full code if you got stuck somewhere on the way.

Conclusion

You now know enough about geometry shaders and transform feedback to make yourgraphics card do some very interesting work besides just drawing! You can evencombine transform feedback and rasterization to update vertices and draw themat the same time!

Exercises




[8]
ページ先頭

©2009-2025 Movatter.jp