Geometry shaders part 3

Transform feedback, is one of the tools of the trade not often discussed on the net, in fact i couldn’t find a single tutorial, so i better write one. the essential function of this little feature is to enable you to do very complicated stuff in the geometry shader, stuff that would inevitably make things run slowly, or to assemble a vbo using tiny little parts and then save it to a buffer for later use, or for that matter constant use.

The most common use i would guess you would have for this is to take small patches of 64×64 quads and run it trough a series of algorithms to create a nice little dynamic terrain with plants and rocks without wasting to much shader resources doing it in real time, especially when it doesn’t change every frame. Which is preferable since it’s the shader resources are what keeps things fast and not the amount of geometry you have. It’s not limited to terrain though as it can be used to finalize any geometry at runtime.

Another use that’s been shown in numerous demos is marching cubes, its an algorithm that needs at least a couple of pages worth of lookup tables and complicated loops and branching statements, it definitely won’t run in any real time rendering any time soon, so here is a good place to use transform feedback, it allows you to run the algorithm on a small subset of the data each frame and thereby keeping the frame rate up.
I am not going to do that today, that’s at least a couple of tutorials itself, but if you like then read “GPU gems 3” for inspiration.

What i am going to do is a simple fractal and more specifically a Koch curve and a dragon curve since both of them modifies each segment of the previous iteration instead of copying itself into itself like the “Barnsley Fern” fractal. While such a thing would be theoretically possible, it’s definitely not the easiest thing to do and thus not for this tutorial.

The Koch curve is pretty easy to construct, basically you take a segment of it remove the middle third and replace it with an equilateral triangle and repeat for each segment generated, this is easy enough to do with the gpu since it excels at the simple vector and matrix operations needed.
The finished result of which should look a little bit like this.

koch curve

The tutorial is based on the first geometry tutorial so it’s good to look at that first, i modified it slightly to allow for multiple shader programs but it’s nothing major to worry about.

To acheve this fractal we first need to set up our buffers and vertex arrays, and i do say buffers as in many of them because we need to set up whats called a “ping pong” operation here and what that means is that for each iteration we set up one buffer to read from and one to write to and then swapping them once we are done. So we start with setting up the seed data, here it’s basically a line segment (the source code uses 3 to make a koch snowflake but it’s all the same)
[stextbox id=”grey”]

 p[0]=-1.0;
 p[1]=0.0;
 p[2]=0.0;
 p[3]=1.0;

 p[4]=1.0;
 p[5]=0.0;
 p[6]=0.0;
 p[7]=1.0;

kochsize=2;  // our starting vertex count

[/stextbox]
Then generate vertex arrays and buffers
[stextbox id=”grey”]

 glGenBuffersARB( 2, &bufferx[0] );    // Get A Valid Name for two buffers
 glGenVertexArrays(1, &p_vao1);   //generate the VAOs for this mesh
 glGenVertexArrays(1, &p_vao2);

[/stextbox]
Then set up our buffers/VAOs
[stextbox id=”grey”]

 glBindVertexArray(p_vao1); //bind the first vao
 glBindBufferARB( GL_ARRAY_BUFFER, bufferx[0] );            // Bind The firstBuffer
 glBufferData(GL_ARRAY_BUFFER, 60000*4*sizeof(GLfloat), 0, GL_DYNAMIC_DRAW_ARB);
 glBufferSubData(GL_ARRAY_BUFFER, 0, kochsize*4*sizeof(GLfloat), p); // add the seed vertics
 glVertexAttribPointer((GLuint)0, 4, GL_FLOAT, GL_FALSE, 0, 0);
 glEnableVertexAttribArray(0);

 glBindVertexArray(p_vao2); //bind the second vao
 glBindBufferARB( GL_ARRAY_BUFFER, bufferx[1] );            // Bind The second Buffer
 glBufferData(GL_ARRAY_BUFFER, 60000*4*sizeof(GLfloat), 0, GL_DYNAMIC_DRAW_ARB);
 glVertexAttribPointer((GLuint)0, 4, GL_FLOAT, GL_FALSE, 0, 0);
 glEnableVertexAttribArray(0);

 glBindVertexArray(0);

[/stextbox]
Note that it’s really important to fill the buffers beforehand as these don’t automatically grow when you render to them.

Next up we need to set up the shaders, you need two render programs one for the transform operation and one for the final rendering (i use in this example prgRender and prgTransform) their pretty much both just regular passtrough shaders with the difference that prgTransform doesn’t use the transform matrix in the vertex shader and that it has the geometry shader that makes the fractal, but more about that one later on.

But there is also another difference, which leads us into the topic for today transform feedback. In order for transform feedback to work you need to tell which output is getting written to the buffer, so before you link the program call these two lines
[stextbox id=”grey”]

 GLchar const * Strings[] = {"gl_Position"};
 glTransformFeedbackVaryings(ProgramObject, 1, Strings, GL_SEPARATE_ATTRIBS);

[/stextbox]
The string value here defines which output variables to write and in what order so if you want multiple buffers then write something like {“gl_Position”,”texturecoord”,”normal”}, and just to be clear “gl_Position” is the default output you always need to write to (do look up the openGL reference pages for more info on this).

No now we are ready to do the transform process, first set up the program
[stextbox id=”grey”]

 void transform(void)
{
 if (useshader) glUseProgramObjectARB(prgTransform);

[/stextbox]
Next bind the buffers and the VAO according to which turn it is
[stextbox id=”grey”]

//the boolean ping is set up as true at the beginning
 if (ping){glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, bufferx[1]);
 glBindVertexArray(p_vao1);
 ping=false;}
 else {glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, bufferx[0]);
 glBindVertexArray(p_vao2);
 ping=true;}

[/stextbox]
Again if your using multiple output variables then you need to bind multiple buffers to GL_TRANSFORM_FEEDBACK_BUFFER, just make sure to increase the second parameter for each according to the string we set up earlier with glTransformFeedbackVaryings.
Next you begin, draw, and end.
[stextbox id=”grey”]

glBeginTransformFeedback(GL_LINES);
glDrawArrays(GL_LINES, 0, kochsize);
glEndTransformFeedback();

[/stextbox]
Now it’s really important to bind everything before this point because this works a bit like immediate mode as you cant bind while using transform mode.
Finally finish up this function
[stextbox id=”grey”]

glBindVertexArray(0);
if (useshader) glUseProgramObjectARB(0);
kochsize*=4;  //fortunately the koch curve is very predictable
}

[/stextbox]
now each time you run this function a new set of more complex data will be written using the following shader, now it’s pretty self explanatory, but the basics is that you create 3 new vertics, the first two (posA and posB) are just the 1/3 and 2/3 points between the original vertics

posK is a bit more complex, but basically you take the vector from the first to the second original vertex, run it trough a crossproduct with a vector pointing inwards, that gives us a vector perpendicular to the line, essentially the 2D normal of the line, now multiply this vector with a sixth of the distance between the two original vertics multiplied by √3 (according to trigonometry an equilateral triangle will have a height of √3 of the sides have a size of 2, and in these case the sides have a size of  1/3 of the original vector so that means ( mag/6) *√3 in case you where wondering), so add the resulting vector to the half point between the original vertics and you got yourself the posK vertex, simple, then it’s just a matter of rendering 4 lines between these points in the right order.
[stextbox id=”grey”]

#version 150
in vec4 pos[];

void main( void )
{
vec3 vin=vec3(0.0,0.0,1.0); //vector pointing in
vec3 v1=(pos[1].xyz-pos[0].xyz)/3.0; //vector pointing 1/3 the way to pos[1]
vec3 v2=(pos[1].xyz-pos[0].xyz)/2.0; //vector pointing half the way to pos[1]
vec3 v3=cross(vin,normalize(v1)); // vector perpendicular to v1
float mag=length(pos[0].xyz-pos[1].xyz)/6;   // half the length of the line segment

vec4 posA=pos[0]+vec4(v1.xyz,0.0);  //should be the first segment
vec4 posB=pos[1]-vec4(v1.xyz,0.0);  //should be the second segment
vec4 posK=pos[0]+vec4(v2.xyz,0.0)+(vec4(v3.xyz,0.0)*(mag*sqrt(3)));//this is the K curve point

gl_Position = pos[0];
EmitVertex();
gl_Position = posA;
EmitVertex();
EndPrimitive();

gl_Position = posA;
EmitVertex();
gl_Position = posK;
EmitVertex();
EndPrimitive();

gl_Position = posK;
EmitVertex();
gl_Position = posB;
EmitVertex();
EndPrimitive();

gl_Position = posB;
EmitVertex();
gl_Position = pos[1];
EmitVertex();
EndPrimitive();
}

[/stextbox]
Now we just have to finish up by rendering, it’s exactly as befor except that because we use a ping pong method we need to take that in accomodation
[stextbox id=”grey”]

 if (ping) glBindVertexArray(p_vao1);
 else glBindVertexArray(p_vao2);
 glDrawArrays(GL_LINES, 0, kochsize);

[/stextbox]
The final result should be looking something like this if you use 3 lines as a seed instead of one
the koch snowflake

 

 

 

 

 

Download the source for MS Visual C++ 6 here
Download the source for MS Visual C++ 2008 here

Part 1 | Part 2


1 Comment

  • By Peter Wallström, June 11, 2011 @ 16:07

    Dragon curve version of the shader, because it only produces two lines for each incoming line you need to change the code from kochsize*=4; to kochsize*=2; in the transform function.
    it also works well if you render it with up to 6 million lines instead of 6000 as you get a nice highly detailed filled fractal.

    #version 150

    in vec4 pos[];

    void main( void )
    {

    vec3 vin=vec3(0.0,0.0,1.0); //vector pointing in

    vec3 v1=(pos[1].xyz-pos[0].xyz)/2.0; //vector pointing half the way to pos[1]
    vec3 v2=cross(vin,normalize(v1)); // vector perpendicular to v1
    float mag=length(pos[0].xyz-pos[1].xyz)/2; // half the length of the line segment

    vec4 posK=pos[0]+vec4(v1.xyz,0.0)+(vec4(v2.xyz,0.0)*(mag));//this is the K curve point

    //line 1

    gl_Position = posK;
    EmitVertex();
    gl_Position = pos[0];
    EmitVertex();
    EndPrimitive();

    gl_Position = posK;
    EmitVertex();
    gl_Position = pos[1];
    EmitVertex();

    EndPrimitive();
    }

Other Links to this Post

RSS feed for comments on this post. TrackBack URI

Leave a comment


WordPress Themes