PDA

View Full Version : Getting vertex data from a tesselation



Boris008
12-07-2005, 04:16 PM
Hi all,

Does anyone know how to get polygon data out of a tessellation? I want the ordered vertices for a series of triangles created by the tessellator.

I have a complex object and a class that will tessellate it and draw it using gluTessEndPolygon(...), gluTessEndContour( ... ). However, I'd prefer to stuff the tessellated vertices from several complex shapes into a large array so that I can use vertex arrays later in the code. Basically I just want OpenGL to perform the tessellation calculations so that I can cache the vertices for each triangle in each tessellated shape. Is this possible?

Thanks,

Jeff

12-12-2005, 03:27 AM
IIRC, you can use the callbacks to manipulate the generated data however you see fit. I believe there is a demo of this in the official programming guide.

Look at the bottom of this page:
http://www.rush3d.com/reference/opengl-redbook-1.1/chapter12.html

Relic
12-12-2005, 03:51 AM
That was about OpenGL feedback mode. There is a direct way to get the GLU tesselator data without any OpenGL call.
You need to specify callback functions with the gluTessCallback anyway.
You must write one for the callback functions GLU_TESS_BEGIN, GLU_TESS_VERTEX and GLU_TESS_END.
Instead of using OpenGL calls there, implement functions which store the data where you want.
If your geometry is built from overlapping contours you also need to write a callback function for GLU_TESS_COMBINE.
In the latter case check the manual of the gluTessProperty call if you have the correct winding order set for what you want.

12-12-2005, 04:05 AM
Dear Relic, Thanks. Thanks for making me look like an idiot. I really appreciate it.

:D

Wow, I need to dust the cranium once an a while!

And now with my memory so brutally jarred, it occurred to me that you might also want to include an error callback, to catch that pesky configuration that manages to slip through the cracks for time to time...

Relic
12-12-2005, 05:04 AM
LOL. I thought that was what you mentioned with
"IIRC, you can use the callbacks to manipulate the generated data however you see fit." and I just decorated it with precise hints to the function names which need to be understood. ;)

12-12-2005, 05:45 AM
You're right. My bad all the way. I zigged when I clearly should have zagged. I must be loosing my mind! ;)

The hell of it is, I used to have some code for this very thing somewhere! How I managed to confuse feedback with tessellation and botch that link is, well, beyond the scope of this thread (read: too embarrassing to mention). I could have sworn that there was a demo of this in the RedBook. I guess I'm just getting old and absent minded, or just plain stupid, though I prefer the former.

Anyway, I can promise you this: this blunder won't happen again, at least not while I've got my wits about me, and you're willing to decorate my posts from time to time :D

Sincerest regards.

:cool:

Boris008
12-12-2005, 11:44 AM
Thanks Relic, this is exactly what I did and it worked like a charm! No overlap for me so I'm all set with GLU_TESS_BEGIN, GLU_TESS_VERTEX and GL_TESS_END.

Of course maybe storing a display list of the tesselated poly is just as fast as using the data in a vertex array later. It just seems that dumping it all into a vertex array is the way to go ;) especially when I have a bunch of tesselated polys in a scene.

Thanks again!

Boris008
12-13-2005, 12:51 PM
This approach does not seem to work. Even when I specify glBeing( GL_TRIANGLES ) in the GLU_TESS_BEGIN callback I don't seem to get the correct vertices. The red book says that the tessellator will choose the most efficient method (GL_TRIANGLES, GL_TRIANGLE_STRIP, etc.) So I'm assuming I can override it by explicitly calling glBegin( GL_TRIANGLES ) in the begin callback.

Then I would expect the tesselator to either produce 3 vertices PER triangle when GL_TRIANGLES is used OR produce a vertex index, otherwise there is no way to render the data later with GL_TRIANGLES in a vertex array. I must be missing something here :confused:

By the way, in order to make proper use of vertex arrays (one large array of floats) I will need to draw every triangle of each tessellated polygon with GL_TRIANGLES and not GL_TRIANGLE_FAN or _STRIP since I have several different polygons. So, I'm still a little stuck here I guess. Maybe I should just stick with display lists but this seems to be a very nice optimization if I can get it. Any thoughts on this would be most appreciated ;)

J

12-13-2005, 03:10 PM
Here's a simple demo of tessellation. Perhaps it will help you find your mistake.

http://www.opengl.org/resources/code/basics/redbook/tess.c

I knew I saw something like this! :D

Boris008
12-13-2005, 04:10 PM
<strobe>,

Thanks, I can do the tesselation just fine. :D

The problem is in the GLU_TESS_CALLBACK where we have glBegin( which ). The "which" here can be either GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN etc. so I'm trying to force the tessellator to give me GL_TRIANGLE's and not GL_TRIANGLE_STRIPs or GL_TRIANGLE_FANs. My approach is to make the following being callback:

void CALLBACK beginCallback(GLenum /*which*/)
{
glBegin( GL_TRIANGLE );
}

This should work, I think I just need to do a little debugging on my end!

I'll try to get this sorted out tomorrow and let you know what I find.

J

12-13-2005, 04:21 PM
Thanks, Boris, I understand now :D

Yeah, you can definitely do this. There's no doubt. The question is just keeping track of the primitives you're building, so they don't get tangled up.

How would I do it? Glad you asked! I'd just build the list of primitives normally, then afterwards, I'd process the list into whatever primitives I wanted. That is, build a list of indexed primitives from the tessellation, then do whatever you want with them by pulling and combining positions from the indexed pool.

I hope that's closer to the mark this time! :)

12-13-2005, 06:41 PM
In case that didn't make any sense, here's a few code snippets.


// First, create a handy struct
struct Primitive{
GLenum type;
GLushort firstVert, numVerts;
} prims[?];
int numPrims=0;

// Our vertex pool.
struct Vertex{
GLfloat position[3];
} verts[?];
int numVerts=0;

// Now on begin, create a new primitve.
void CALLBACK begin(GLenum type){
prims[numPrims].type = type;
prims[numPrims].firstVert = numVerts;
prims[numPrims].numVerts = 0;
}

// Build the primitive normally,
// whatever it is. Just emit a vertex.
void CALLBACK vertex(void* data){
GLdouble* v = (GLdouble*)data;
verts[numVerts].position[0] = v[0];
verts[numVerts].position[1] = v[1];
verts[numVerts].position[2] = v[2];
numVerts++;
}

// On end, emit a new primitve
void CALLBACK end(){
prims[numPrims].numVerts = numVerts - prims[numPrims].firstVert;
numPrims++;
}

// Now we can simply walk the list of primitves
void walkTriangles(){
for(int i=0; i<numPrims; i++){
Primitve* p = &amp;prims[i];
switch(p->type){
// Emit triangle based on type
}
}
}I hope that makes some sense. You could cache the results at any time into a display list or vertex buffer. A VBO would be cool here. You could even use an index buffer by creating an index list alongside your vertex pool!

Just some food for thought...

Boris008
12-14-2005, 09:20 AM
Thanks for the sample code

I see now that I can't just override the glBegin( which ) call since the tessellator is making this call (the whole point of a callback function) :rolleyes:

I think what I want to do is just reorganize the vertices based on the type of primitive so if "which" is a GL_TRIANGLE_FAN then I will take the vertices associated with this primative, reorganize them for GL_TRIANGLE drawing, add them to the vertex array and then make one big vertex array call later. Does that make sense or do you see a simpler way?

Right now I'm hooked on glDrawArrays( GL_TRIANGLES ...) I like the simplicity of this method. But if you have a better solution I'm all ears;)

J

Overmind
12-14-2005, 02:49 PM
You can just manually convert triangle strips or fans to triangles.

For example, if you have a triangle fan with vertices:
1 2 3 4 5 6 7 8

Just create the triangles from it by duplicating indices:
1 2 3
1 3 4
1 4 5
1 5 6
1 6 7
1 7 8

But you should be aware that rendening triangle strips or fans is almost always faster than rendering triangles, and the reverse operation (creating strips/fans out of triangles) is not easy, so perhaps you shouldn't throw this information away ;)

12-14-2005, 05:48 PM
Good point about the strips and fans, Overmind.

It's true that strips and fans are almost always a good idea, but there are cases where triangles are better. For example, if you have a bunch of small batches, it's probably better to send a single list of triangles than make a bunch of DrawArray/DrawElement calls, particularly if the list is using the same render states. A careful ordering of vertices can yield good pre-T&L cache coherency, and if you index your buffer, you'll get good post-T&L to boot (the only way to get post).

You could chain the different primitives together with degenerate triangles, if that's convenient. It's really a matter of how you intend to use the results, how frequently you need to regenerate the tessellation, and how fancy you want to get!

But I like DrawArrays too, and there's nothing wrong with that. I always say, if it ain't broke, don't fix it! :D

12-14-2005, 06:24 PM
It should probably be said that OpenGL makes no attempt to eliminate shared vertices during the tessellation, added motivation for giving your generated vertex list a good indexing, if it makes sense to do so.

All you'd need to do is modify the vertex() function above to include some code for adding a unique vertex to the list, preferably using a good hash for speed, but a linear walk and compare will do. Just compare the incoming vertex to those in the list already, add it to the list, and add its corresponding vertex pool index to the primitive's index list, instead of a vertex list. That is, a primitive has a firstIndex and numIndices, rather than firstVert and numVerts. In this way you get an index buffer along with a vertex buffer!

Boris008
12-16-2005, 10:42 AM
Removing shared vertices might be my next step, we'll see! Here's my solution (verbose version):


void CALLBACK EndCallback()
{
glEnd();
//since the tessellator chooses which type of triangle to use we need to reorganize the vertices
//so that they are in GL_TRIANGLE form. we use the g_tempVerts array to hold the tessellated vertices
//they are reorganized into GL_TRIANGLE form and added to the g_verts array which is in GL_TRIANGLE form
switch( (GLenum)(g_type) )
{
case GL_TRIANGLES:
for( int i = 0; i < g_tempVerts.size(); i++ )
g_verts.push_back( g_tempVerts[i] );
break;
case GL_TRIANGLE_FAN:
{
int nTris = g_tempVerts.size()-2;
CPoint3D vert1 = g_tempVerts[0];
int n1 = 1;
int n2 = 2;
for( int i = 0; i < nTris; i++ )
{
CPoint3D vert2 = g_tempVerts[n1];
CPoint3D vert3 = g_tempVerts[n2];
g_verts.push_back( vert1 );
g_verts.push_back( vert2 );
g_verts.push_back( vert3 );
n1 ++;
n2 ++;
}
}
break;
case GL_TRIANGLE_STRIP:
{
int nTris = g_tempVerts.size()-2;
int n0 = 1;
int n1 = 2;
int n2 = 0;
for( int i = 0; i < nTris; i++ )
{
CPoint3D vert1 = g_tempVerts[n0];
CPoint3D vert2 = g_tempVerts[n1];
CPoint3D vert3 = g_tempVerts[n2];
g_verts.push_back( vert1 );
g_verts.push_back( vert2 );
g_verts.push_back( vert3 );
if( i%2 != 0. )
{
n0 += 2;
}
else
{
n2 += 2;
}
n1 ++;
}
}
break;
default:
ASSERT( false );
}
}
The trick is to stuff the vertices in the vertex callback and then postprocess when the tessellation is done (end callback). Seems to work well for my need. Hopefully this will be helpful for others who want to consider a similar approach. Thanks again for all the help ;)