Trying to use Vertex Arrays and Buffer Objects

Hello,
I’m trying to improve a simple aplication used to visualize 3ds models. I coded with the glBegin()/glEnd() paradigm but I’ve read that it is not a good method.
I read then about the use of Vertex Arrays and Buffer Objects but I’ve been having problems with the coding.

Till now the algorithm was reading the data structure of the 3ds model and sending the triangles one by one like this:

glBegin(GL_TRIANGLES);
glVertex3fv(.....);
glEnd();

If I have understood well, now the idea is to put all these vertex into a Vertex Array at the moment of loading the model and in the paintGL() function, use glDrawElements() or glDrawRangeElements() or glMultiDrawElements().

When loading the model I have this:

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
//For each triangle
glVertexPointer(3, GL_FLOAT, 0, vertex);
numTriangles++;

And then, in paintGL:

glDrawRangeElements(GL_TRIANGLES, 0, numTriangles - 1, numTriangles, GL_UNSIGNED_SHORT, ???);

The first thing that I don’t understand is which version of the glDrawElements function is the most appropriate in this case, and the second is what this last parameter “const GLvoid *indices” refers to.

Can anybody clarify my mind?
Thankyou.

Well, first of all, understand how Vertex Buffer Objects extend the notion of Vertex Arrays. They merely allow the pointer arguments of vertex array calls to point to GPU memory rather than system memory. So far, you’ve got no VBO code there, only VA code.

Second, “indicies” indicates which of the elements in the array you’d like to draw. If you want them all, consider using glDrawArrays instead. I’ve heard it’s a bit slower, but for starting out it will work well enough.

I was confused about Vertex Arrays, so I wanted to clarify it before enter to deal with Buffer Objects.
In this case I’ll try glDrawArrays.

Anyway, in the case I wanted to draw only some of these elements of the array, I still don’t understand how can I guess these indices, because in the moment you build the array with glVertexPointer, it doesn’t return any index. Have I to assume that the first vertex have the index 0, the second the index 1, and so on?

And if I need to add information about the normal and the material of the vertex?

Thanks again.

glVertexPointer just tells OpenGL where the vertex data is stored. It’s indexed as a normal C array, ie first element is 0, next is 1 etc.

So what you want to do is allocate an array for all your vertexes, fill it with the data you want, and then call glVertexPointer once, passing the address of the array.

I suggest you read the “Vertex Arrays” chapter in the OpenGL specifications, available at http://www.opengl.org/documentation/specs/
It explains it rather well in my opinion.

As mentioned, VBO is just a way to store the vertex array “server side” (ie video card/driver), rather than in your program. It doesn’t change the fundamentals of how the vertex array stuff works, just where the actual memory “exists”.

You can use glNormalPointer, glColorPointer, and glTexCoordPointer to do some per-vertex things. However, I don’t think there’s a glMaterialPointer; if you want to do that, then either (a) have a separate VA for each material and just specify it beforehand, or (b) use a Cg vertex program with a Cg pointer parameter to bring in the extra properties. I don’t actually know what Cg semantics you’d use here, so maybe it’s best to just go with (a).

With regards to indices, they should be fairly obvious. The gl*Pointer calls specify precisely how OpenGL should interpret the memory you’re pointing to. The “pointer” param initially points to index 0; the “stride” parameter tells OGL how many bytes away the next index is.

There is one subtlety. “Stride” has a special case if it’s 0—it assumes that “type” and “size” then define the stride. However, do not interpret this to mean that “stride” measures the number of byes between indices. Thus,
glVertexPointer(4,GL_FLOAT,16,ptr)
is exactly equivalent to
glVertexPointer(4,GL_FLOAT,0,ptr)

Do not be confused by glIndexPointer. That’s for color-index mode, not for indexing into a VA.

I’ll try these options and tell how does it go.
Thanks for your help.

And how I define this separate VA for materials, with VertexAttribPointer?

Meanwhile I’ve tried to use Buffer Objects.
Compiling I get the error “glGenBuffers was not declared in this scope”, but the result of calling glGetString(GL_VERSION) is “2.1.0 NVIDIA 96.31”.
It’s maybe a missing include?

Are you including GL/glew.h before GL/gl.h? (Or GLee.h, if you’re using that instead.)

I know it’s not supposed to be an extension in 2.0, but it doesn’t hurt to always bring in available extensions. And you can always try using glGenBuffersARB if nothing else works; it should do the same.

I haven’t used glVertexAttribPointer, but I suspect you could use it for material properties if you wished. I was more thinking that each object which had different properties would be represented by a different VA, and you’d just call glMaterial() between each glDrawArrays().

Originally posted by arnaugm:
And how I define this separate VA for materials, with VertexAttribPointer?
Just be aware that some attributes might alias with “built in” attributes. For instance, attribute 0 is (afaik always, possibly only “sometimes”) the position. Read the documentation to be certain.

Again with the Vertex Arrays.
I suppose it’s a problem constructing the array but it’s strange that a part of the model appears correct and the rest simply doesn’t appear.

Here I show you my problem:

Simple geom drawn with glBegin()/glEnd()

Using Vertex Arrays

A dolphin drawn with glBegin()/glEnd()

The dolphin with VA

Very hard to say anything without code, but you could try to make your own glDrawElements or similar. It should accept the same data you “feed” opengl with, but use glBegin/glEnd. It’s just a few lines of code and will help narrow down where the problem is.

Fine some decent tutorial on vertex array (this page may be a good start http://nehe.gamedev.net/ ) and work your way through. It is always better to learn things step by step and not rush in. There could be so many things you got wrong in your examples: from setting bad array pointers to using bad arguments with DrawArrays/DrawElements functions…

I suspected a typical mistake using Vertex Arrays, for this reason I didn’t add any code.

The strange thing is the code is the same than using glBegin()/glEnd() but putting the vertex in the array instead of sending them with glVertex().

At loading time the code goes as follows:

void glvisor::generateVertexArrays() {

	GLfloat arrayVertexs[numTriangles * 3 * 3];
	GLfloat arrayNormals[numTriangles * 3 * 3];

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);

	indexAV = 0;
	addVertexs(arrayVertexs, arrayNormals);
	glVertexPointer(3, GL_FLOAT, 0, arrayVertexs);
	glNormalPointer(GL_FLOAT, 0, arrayNormals);

} // generateVertexArrays

void glvisor::addVertexs(GLfloat *av, GLfloat *an) {

	if(!file) return;
	Lib3dsNode *n;
	for(n = file->nodes; n; n = n->next) {
		addVertexsRec(n, av, an);
	} // for

} // addVertexs

void glvisor::addVertexsRec(Lib3dsNode *n, GLfloat *av, GLfloat *an) {

	for(Lib3dsNode *p = n->childs; p!=0; p = p->next) {
		addVertexsRec(p, av, an);
	} // for

	if(n->type != LIB3DS_OBJECT_NODE || strcmp(n->name, "$$$DUMMY") == 0)
		return;

	Lib3dsMesh *mesh = lib3ds_file_mesh_by_name(file, n->name);

	for(Lib3dsDword i = 0; i < mesh->faces; ++i) {
		Lib3dsFace *f = &mesh->faceL[i];
		
		GLfloat normal[3];
		GLfloat coords[3];
		GLfloat div;
		for(int j = 0; j < 3; j++) {
			coords[0] = (((Lib3dsPoint *)(mesh->user.i))[f->points[j]].pos[0]);
			coords[1] = (((Lib3dsPoint *)(mesh->user.i))[f->points[j]].pos[1]);
			coords[2] = (((Lib3dsPoint *)(mesh->user.i))[f->points[j]].pos[2]);

			div = sqrt(coords[0] * coords[0] +
				coords[1] * coords[1] +
				coords[2] * coords[2]);

			normal[0] = coords[0] / div;
			normal[1] = coords[1] / div;
			normal[2] = coords[2] / div;
			
			av[indexAV] = mesh->pointL[f->points[j]].pos[0];
			av[indexAV + 1] = mesh->pointL[f->points[j]].pos[1];
			av[indexAV + 2] = mesh->pointL[f->points[j]].pos[2];
			an[indexAV] = normal[0];
			an[indexAV + 1] = normal[1];
			an[indexAV + 2] = normal[2];
			indexAV += 3;
			} // if
		} // for
	} // for

} // addVertexsRec 

And the drawing uses:

 
glDrawArrays(GL_TRIANGLES, 0, numTriangles); 

I hope you understand that you declare your arrays on stack and they become invalid once the function “generateVertexArrays” returns?

I always have problems with this kind of things.
Why it’s drawn partially well then? Anyway.

I thought in declaring the arrays as a global variable but the problem is I don’t know how long they must be until the load function takes part and counts the trianges of the model.
How can I do that?

Allocate them on heap, that would be probably the best choise. Or use VBOs (which are actually pointers to server-side memory).

Or use a garbage-collected language :wink:

Problem solved.

There was certainly a mistake with the declaration of the arrays.

Now I’ve declared the pointers as members of the class (not as global variables, I said it wrong before) and in the function I get some memory and link it with the pointers.

arrayVertexs = new GLfloat[numTriangles * 3 * 3];
arrayNormals = new GLfloat[numTriangles * 3 * 3]; 

Apart from that in the glDrawArrays() I used the number of triangles in the last parameter and it needs the number of vertexs. This is why the models were partially drawn.

Sorry for annoying with such stupid problems and thanks for your help.

Just don’t forget to delete those after you’re done with them.

About the materials.

I’ve analyzed some of my models and there are a few with a lot of different materials, including one with more than 700.
Do you still think it’s a good idea to use a different vertex array for each material?

It also needs a preprocess to identify which faces have the same or different material and it slows the application a lot.

If there are special vertex arrays for normals or textures or normal color, why there aren’t for materials? There must be an easier way to add this information.