PDA

View Full Version : VBO problem



blackwind
04-13-2007, 09:41 AM
Hi, i implemented vertex buffer objects for drawing an octree world, and i seems to work fine, but i have a question:

when i init my program, i do something like this:

// bind and generate
glGenBuffersARB( 1, &uiVBODataID);
glBindBufferARB( GL_ARRAY_BUFFER_ARB, uiVBODataID );

// Load The Data
glBufferDataARB( GL_ARRAY_BUFFER_ARB, octreeWorldNumberOfVerts*3*sizeof(float), pOctreeWorldVertices, GL_STREAM_DRAW_ARB ); then, the code for the drawing the octree is something like this:

void COctree::DrawOctree(COctree *pOctreeNode)
{
if( pOctreeNode->IsSubdivided() )
{
// draw recursively all the nodes
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_LEFT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_LEFT_BACK] );
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_RIGHT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_RIGHT_BACK] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_LEFT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_LEFT_BACK] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_RIGHT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_RIGHT_BACK] );
}

else
{
if( !pOctreeNode->m_pvVertices )
return;

glBindBufferARB( GL_ARRAY_BUFFER_ARB, uiVBODataID );
glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL );
glEnableClientState( GL_VERTEX_ARRAY );
glDrawArrays( GL_TRIANGLES, 0, octreeWorldNumberOfVerts );
glDisableClientState( GL_VERTEX_ARRAY );
}
}Before using VBO, i used vertex arrays for doing it, and if i have more than one node (i.e the octree is subdivided), the framerate decreased a lot because it was drawing ALL the vertices while the octree was still subdivided.

With VBO's it seems to be the same problem


Because i have this problem, i tried to do what i had when i was using vertex arrays, and that was creating the necessary arrays for every subdivided node in my Init code, so then, when in every frame i call the draw function, it only drawed the vertex data that is in every node.

I'm doing it like this:

void COctree::CreateVBOs(COctree *pOctreeNode, UINT uiVBOoffsetID)
{
if( !pOctreeNode )
return;

if( pOctreeNode->IsSubdivided() )
{
CreateVBOs( pOctreeNode->m_pOctreeNode[TOP_LEFT_FRONT ],uiVBOoffsetID );
CreateVBOs( pOctreeNode->m_pOctreeNode[TOP_LEFT_BACK ],uiVBOoffsetID );
CreateVBOs( pOctreeNode->m_pOctreeNode[TOP_RIGHT_FRONT],uiVBOoffsetID );
CreateVBOs( pOctreeNode->m_pOctreeNode[TOP_RIGHT_BACK ],uiVBOoffsetID );
CreateVBOs(pOctreeNode->m_pOctreeNode[BOTT_LEFT_FRONT],uiVBOoffsetID );
CreateVBOs( pOctreeNode->m_pOctreeNode[BOTT_LEFT_BACK],uiVBOoffsetID );
CreateVBOs( pOctreeNode->m_pOctreeNode[BOTT_RIGHT_FRONT],uiVBOoffsetID);
CreateVBOs( pOctreeNode->m_pOctreeNode[BOTT_RIGHT_BACK],uiVBOoffsetID );
}

else
{
if( !pOctreeNode->m_pvVertices )
return;

// increase the VBO ID
pOctreeNode->m_uiVBODataID += uiVBOoffsetID;

// bind and generate
glGenBuffersARB( 1, &pOctreeNode->m_uiVBODataID);
glBindBufferARB( GL_ARRAY_BUFFER_ARB, pOctreeNode->m_uiVBODataID );

// Load The Data
glBufferDataARB( GL_ARRAY_BUFFER_ARB,
pOctreeNode->GetTriangleCount()*sizeof(float),
pOctreeNode->m_pvVertices, GL_STREAM_DRAW_ARB );
}
}and then, when drawing:


if( pOctreeNode->IsSubdivided() )
{
// draw the nodes recursively
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_LEFT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_LEFT_BACK] );
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_RIGHT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_RIGHT_BACK] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_LEFT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_LEFT_BACK] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_RIGHT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_RIGHT_BACK] );
}

else
{
if( !pOctreeNode->m_pvVertices )
return;
glBindBufferARB( GL_ARRAY_BUFFER_ARB, pOctreeNode->m_uiVBODataID );

glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL );

glEnableClientState( GL_VERTEX_ARRAY );

glDrawArrays( GL_TRIANGLES, 0, pOctreeNode->m_TriangleCount/3 );

glDisableClientState( GL_VERTEX_ARRAY );
}
} But if i do that, it only draws some portions of the world, then...
what am i doing wrong?

Ido_Ilan
04-13-2007, 03:17 PM
Hi,

1. Don't create vbo each time while drawing.
2. Use index array, update only this array for view culling/ordering

Ido

yooyo
04-13-2007, 04:12 PM
1. Store all vertices in one VBO
2. Split all faces into octree-nodes. Create one index buffer and store each octree-node faces set on this index buffer.
3. Calculate boundary box or sphere for each octree-node.


IsNodeVisible checks boundary box or sphere against viewing frustum.
Result can be: 100% visible, partially visible and invisible.
100% visible node means that all it's child nodes are 100% visible too.
Partially visible nodes must check visibility for each child. Invisible
nodes are ignored.

Each node contains bytes offset in index buffer, and number of faces in it.


void DrawNode(node* root, int iVisible)
{
// parent node can be partially visible... in that case let check this child node
if (iVisible == PARTIALY_VISIBLE)
iVisible = IsNodeVisible(frustum, root);

// if this child node not visible, nothing to draw
if (iVisible == NOT_VISIBLE) return;

if (IsLeaf(node))
{
// draw node
glDrawArrays( GL_TRIANGLES, node->offset_in_indexbuffer, node->m_TriangleCount/3 );
}
else
{
// roll recursion
for (int i=0; i<8; i++)
DrawNode(node->child[i], iVisible);
}
}

glBindBufferARB( GL_ARRAY_BUFFER_ARB, vertexVBO);
glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL );
glEnableClientState( GL_VERTEX_ARRAY );

glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexVBO);

DrawNode(scene_root, PARTIALLY_VISIBLE);

Cab
04-14-2007, 02:13 PM
Originally posted by blackwind:

glDrawArrays( GL_TRIANGLES, 0, pOctreeNode->m_TriangleCount/3 ); But if i do that, it only draws some portions of the world, then...
what am i doing wrong? m_TriangleCount/3? Is it what you are trying to do? If m_TriangleCount means #of triangles you want to render, then it should be m_TriangleCount*3

Hope this helps.

speed
04-17-2007, 07:36 AM
I think you can use only one VBO, an before you call DrawOctree do this:


glBindBufferARB( GL_ARRAY_BUFFER_ARB, uiVBODataID );
glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL );
glEnableClientState( GL_VERTEX_ARRAY );
DrawOctree(octree->root())
glDisableClientState( GL_VERTEX_ARRAY );Now you can call for each Leaf Node the subset of triangles you need using:


glDrawArrays( GL_TRIANGLES, pOctreeNode->firstVertex(), pOctreeNode->lastVertex() );So, you need to have on the VBO the triangles sorted by octree's nodes. If you don't want to repeat the vertexs, you need a double VBO.

blackwind
04-17-2007, 03:02 PM
Originally posted by Cab:
m_TriangleCount/3? Is it what you are trying to do? If m_TriangleCount means #of triangles you want to render, then it should be m_TriangleCount*3

Hope this helps. [/QB]Nop, unfurtonatelly thats not the problem.


Originally posted by speed:
I think you can use only one VBO, an before you call DrawOctree do this:


glBindBufferARB( GL_ARRAY_BUFFER_ARB, uiVBODataID );
glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL );
glEnableClientState( GL_VERTEX_ARRAY );
DrawOctree(octree->root())
glDisableClientState( GL_VERTEX_ARRAY );Now you can call for each Leaf Node the subset of triangles you need using:


glDrawArrays( GL_TRIANGLES, pOctreeNode->firstVertex(), pOctreeNode->lastVertex() );So, you need to have on the VBO the triangles sorted by octree's nodes. If you don't want to repeat the vertexs, you need a double VBO. I tried doing that something like this:


void COctree::DrawOctree(COctree *pOctreeNode)
{


// checamos que existan datos del nodo
if( !pOctreeNode )
return;

static int numTriangles = 0;
if( pOctreeNode->IsSubdivided() )
{
numTriangles += pOctreeNode->GetTriangleCount();
pOctreeNode->m_TriangleOffset += numTriangles;
// dibujamos recursivamente todos los nodos
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_LEFT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_LEFT_BACK] );
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_RIGHT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[TOP_RIGHT_BACK] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_LEFT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_LEFT_BACK] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_RIGHT_FRONT] );
DrawOctree( pOctreeNode->m_pOctreeNode[BOTTOM_RIGHT_BACK] );
}

else
{
glDrawArrays( GL_TRIANGLES, pOctreeNode->GetTriangleOffset(),pOctreeNode->GetTriangleCount()*3 );

}
} but it doesnt work either.... what may be wrong?

as for what Ido Ilan and yooyo said... i read about index arrays, and understand how call them with the opengl functios, but i dont undertand how can i create the indices? can you give me an example for doing that with my octree?

yooyo
04-17-2007, 11:15 PM
Imagine you have array of vertices.. v0... vN and array of triangles. Triangle is set of 3 indices in vertex array (ex. 0,1,2). Index buffer looks like:
0,1,2, 0,1,3, 0,2,4, ...

In octree, store all vertices in VBO and store indicies in IBO node by node. Usually, app will send to render whole node.

Your code in first post is wrong. It send whole octree to render. It's better to send only visible nodes.

blackwind
04-18-2007, 12:03 AM
greetings,


Originally posted by yooyo:
Imagine you have array of vertices.. v0... vN and array of triangles. Triangle is set of 3 indices in vertex array (ex. 0,1,2). Index buffer looks like:
0,1,2, 0,1,3, 0,2,4, ...

In octree, store all vertices in VBO and store indicies in IBO node by node. Usually, app will send to render whole node.
ok, i think i get it, but i have some question:

so if i have this verts:
verts[0] = {12,15,11}
verts[1] = {12,5,12}

i will have this indices:
indices = {0,1,2,0,3,0}

1.-is that correct?

2.-whats the advantage of doing that? isnt that harder and more memory consumption for long meshes?

3.- isnt it possible solving my problem the way that speed said?, i yes, then what could be wrong with my code? (the last i posted)


Originally posted by yooyo:

Your code in first post is wrong. It send whole octree to render. It's better to send only visible nodes. it isnt wrong hehe, i just cut down that code to keep the post simple and focus on the problem, it only draws the visbles nodes, but thanks for your advice.

yooyo
04-18-2007, 01:41 PM
Index buffers allows to "reuse" vertices in mesh. Difference between glDrawArrays and glDrawElements calls is in how they work.
Imagine cube... It have 8 vertices and 12 triangles.
- Using glDrawArrays VBO must have 36 vertices (3*12).

glDrawArrays(GL_TRIANGLES, 0, 36) - Using glDrawElements VBO must have 8 vertices and 12 triangles (36 indices).

// bind your vertex VBO
...
// bind index buffer
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexVBO);

glDrawElements(GL_TRIANGLES, 0, 36, GL_UNSIGNED_INT, OFSPTR(0));Anyway... in both cases same number of vertices will be transformed, but in case of indexed vertex buffer, memory footprint is smaller and (im not 100% sure in this) indexed vertex buffers can be more vertex-cache-friendly.

Bottleneck in VBO can be pointer setup. glVertexPointer call is very expensive, and try to avoid to call it frequently. Bottleneck can be vertex memory aligment. Avoid odd offsets for vertex attributes.

edit: typo

blackwind
04-20-2007, 11:17 AM
ok thank you ,but... so there isnt a way to it like speed said?

Relic
04-22-2007, 03:41 AM
Of course there is, but as yooyo already explained with DrawArrays you would need to store the vertices per cube, which means in a single subdivision you would have nine cubes, the parent and 8 children = 9 * 8 vertices!

If the subdivided cubes can reuse the vertices of the parent, each subdivision would only add one vertex in the cube's center for each subdivision! Keep that in a STATIC_DRAW VBO, you only need to store the indices to these for rendering with glDrawElements.

Depending on the number of total cubes these indices should be unsigned short, so rather small compared to whole vertex structs.

If you generate the index array dynamically in your recursive function depending on visibility you can render the whole thing with one glDrawElements call.
Switching VBOs and pointers is not for free!

The only slight coding problem would be to calculate the vertex index correctly if the subdivisions are not uniformly deep.
Well, not too difficult if you store the indices with each cube while generating the vertices. Adding the cube's indices to the render index array would just be a small memcpy then. Almost too easy. ;)
You should use a STREAM_DRAW VBO for that index array.

On top of that space saving, the GPU also uses the indices to check if it has already seen that vertex recently and saves transform operations.

blackwind
04-23-2007, 09:58 PM
ok, so i'm trying to make a simple example for using indices, i want to draw a cube, but the verts are all messed up.
this is the code:

first i create the verts and the indices:

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

static float corners[] = { -25.0f, 25.0f, 25.0f, // 0 // Front of cube

25.0f, 25.0f, 25.0f, // 1

25.0f, -25.0f, 25.0f,// 2

-25.0f, -25.0f, 25.0f,// 3

-25.0f, 25.0f, -25.0f,// 4 // Back of cube

25.0f, 25.0f, -25.0f,// 5

25.0f, -25.0f, -25.0f,// 6

-25.0f, -25.0f, -25.0f };// 7



// Array of indexes to create the cube

static GLubyte indexes[] = { 0, 1, 2, 3, // Front Face

4, 5, 1, 0, // Top Face

3, 2, 6, 7, // Bottom Face

5, 4, 7, 6, // Back Face

1, 5, 6, 2, // Right Face

4, 0, 3, 7 }; // Left Facenow i bind the data:

// first the verts
glGenBuffersARB(1, &amp;g_uiVBOVertexDataID);
glBindBufferARB(GL_ARRAY_BUFFER_ARB,g_uiVBOVertexD ataID);
glBufferDataARB(GL_ARRAY_BUFFER_ARB,24*sizeof(floa t), corners, GL_STATIC_DRAW);

// now the indices
glGenBuffersARB(1, &amp;g_uiVBOIndexDataID); // <-- i have commented that line and the result is the same
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, g_uiVBOIndexDataID);
glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 24*sizeof(GLubyte), indexes, GL_STATIC_DRAW);now, the drawing:

glVertexPointer( 3, GL_FLOAT, 0, (char *) NULL );
glEnableClientState( GL_VERTEX_ARRAY );
glDrawElements(GL_TRIANGLES, 24*sizeof(GLubyte), GL_UNSIGNED_BYTE, BUFFER_OFFSET(0)); thats the code and it draws the cube all messed up (it doesnt draw everything), so ..... whats wrong?

and btw, when i'm trying to use this call:
glDrawRangeElements(GL_TRIANGLES, x, y, z, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));
the compiler tells me that glDrawRangeElements, the identifier is not found.... i have already included glext, gl, etc... why can this be happening?

thanks for your help....

Relic
04-24-2007, 01:23 AM
Because you don't know what you're doing. :rolleyes:

1.) You define your cube as six QUADS using 24 indices, but render it as TRIANGLES. Triangles would need 6*6 = 36 indices.
2.) The count parameter in glDrawElements is not a stride, it should never contain a sizeof().
3.) Don't use GL_UNSIGNED_BYTES in the index array when using GL_UNSIGNED_SHORTS for rendering. Use unsigned shorts for both, as I told you.
4.) glDrawRangeElements() is in the OpenGL specs. Search for newer headers if yours don't have them. This official one contains it: http://www.opengl.org/registry/api/glext.h
You must use wglGetProcAddress to load it or use GLEW (GL Extension Wrapper, it's really simple.)

blackwind
04-24-2007, 10:48 PM
ooops, my error,thank you, i changed to unsigned short, and i changed this:

glDrawElements(GL_TRIANGLES, 24*sizeof(GLubyte), GL_UNSIGNED_BYTE, BUFFER_OFFSET(0)); for:

glDrawElements(GL_QUADS, 24, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0))and it now works perfectly.

Now, i'm trying to do it with glDrawRangeElements for drawing just a part of the cube,if i do this:

glDrawRangeElementsEXT(GL_QUADS,0,0,24,GL_UNSIGNED _SHORT, BUFFER_OFFSET(0) );it draws all the cube.
Its supossed that the first 3 numbers are:
start: The first index of the index range that will be used.
end: The last index of the index range that will be used.
count: The byte offset between coordinates in the array. A value of 0 indicates that the data is tightly packed.

If change the end to any number, and the start to any number below than "end", it still draws all the cube (but i have to keep the "count" in 24)..... why?

now, if i do this:

glDrawRangeElementsEXT(GL_QUADS,0,0,12,GL_UNSIGNED _SHORT, BUFFER_OFFSET(0) );it draws 3 sides of the cube, but if change to this:

glDrawRangeElementsEXT(GL_QUADS,0,4,12,GL_UNSIGNED _SHORT, BUFFER_OFFSET(0) );it still draws the same 3 sides of the cube....why?
i dont really understand how glDrawRangeElementsEXT works, can somebody explain me?
what should i do i just only want sides of the cube that i want?

thanks for for your help....

Relic
04-25-2007, 12:19 AM
This is from the OpenGL 2.1 spec:

void DrawRangeElements( enum mode, uint start,
uint end, sizei count, enum type, void *indices );
is a restricted form of DrawElements. mode, count, type, and indices match the
corresponding arguments to DrawElements, with the additional constraint that all
values in the array indices must lie between start and end inclusive.That should be absolutely clear. As long as your count and indices are the same, the same geometry is used in glDrawElements or glDrawRangeElements.

Think of the glDrawRangeElements' start and end indices as hints to help the implementation to optimize access of the index array data.
Using wrong values for start and end like you do will only confuse the implementation.

In your count == 24 case, start should be 0 and end should be 7, because that's lowest and highest index used.
Same as for the count == 12 case. If you look at your "indexes" array, the first 12 indices contain 0 and 7 as min and max.

The "Range" in the name doesn't mean this function is specifically designed to render only parts of a vertex array.

If you only want to render a single side of a cube defined in your index array, use a count of 4 and an VBO offset (resp. pointer for non-VBO arrays) into the index array in the glDraw(Range)Elements last parameter.

The stride of the indices is always defined by the data type, indices are tightly packed.

For optimal performance you don't want to render individual faces of cubes.
Remember, I was recommending to generate the optimium subset of indices dynamically if your geometry changes per view due to frustum culling etc. and render the whole thing in one glDrawElements call. There's probably not much to gain using glDrawRangeElements. Reducing calling overhead is important.

BTW, explaining specs isn't a topic for "OpenGL coding: advanced".