glDrawElements: Vertices and Normals

Started by
6 comments, last by Aikavanak 17 years, 8 months ago
Hello. I'm fairly new to OpenGL, so up until now I've been doing all my rendering in immediate mode, which I understand to be slow. I've got the code to load and render 3D models from files, but right now I only have immediate-mode render code, and I'm trying to write a version that uses vertex arrays. It looks like the gl*Pointer functions and glDrawElements are the way to go. However, I've run into a problem. My 3D model data is stored as usual with a list of vertices, and then a list of faces (triangles) that each refer to 3 vertices via an index number. There are also 3 normal vectors stored per face. But it appears that glDrawElements uses only one index array to reference both the array of vertices (set by glVertexPointer) and the array of normals (set by glNormalPointer). That means that there can only be one normal per vertex, but there could be more than one described in my 3D model file. How can I use vertex arrays with multiple normals per vertex? Or will I be forced to average all the normals on each vertex so there's only one per vertex? Thanks! EDIT: Sorry about that block of text. Split it into paragraphs for readability. [Edited by - Aikavanak on August 13, 2006 8:19:13 PM]
Advertisement
You are correct that OpenGL uses a single index array to index all active vertex arrays (vertex, normal, color, etc.).

You may have to adapt your model data to reflect this. How to do so depends on whether you want smooth or faceted shading. For the latter, you 'detach' the faces so that each has its own copies of the vertex data. For the former, you average the normals associated with each vertex to yield a single normal (as you mentioned).
firstly, paragraphs can help make post clearer [smile]

As I see it, you can go about it in one of two ways;

1 - average the normals as you suggested, however this can lead to sharp corners becoming rounded

2 - duplicate the vertex information for each normal as required. This will increase the vertex data size however chances are in a model of any real size this kind of sharing is rare as such the size increase shouldn't be a huge problem..
Thanks for the quick replies.

I think I'll go with duplicating the vertex information, because it seems like averaging the normals could distort my 3D modeler's artwork a bit too much for my tastes.

But I have another question about vertex arrays. In my code, I load the model data into my own custom arrays (I use a custom Vector class to help store the data). I obviously can't send my own Vector classes to gl*Pointer functions directly, so I could copy all the data from my Vectors to a new array of floats (float[]) that I can send directly to gl*Pointer. Is this how its generally done? If so, it seems like it could be really bad for performance, since I would basically be copying all my model data every frame. And, it would take more memory.

Should I load the model data directly into float[] arrays that I can pass to gl*Pointer functions? Will this give me a noticeable boost in framerate?

(I would try it both ways myself, but it would require a lot of re-working of code, and I don't feel like doing it right now. Just wondering if any of you have experience with this already.)
There are two pretty simple solutions:

First you could make your vector class only store the 2 / 3 / whatever floats and tell the compiler not to add any unused bytes for packing it into a power of two boundry, so your vector class is memory equivalent to the floats it stores. This also means you have to turn down the abillity to use virtual functions and other syntactic sugar, which increases your classes size, but that shouldn't be any problem for any vector class implementation.

The second one, which takes up more memory and is a little less efficient than the first one, but definitely more than copying all the data back and forth, would be to let your vector class hold a pointer to the first of its floats which is actually stored in the vertexarray and the dimension of the vector itself ( can be templated, so that it doesn't need any additional space ).

You should stick with the first solution as long as you don't need any extra variables in your vector class or something that takes up memory, because the second solution adds an additional 4 bytes per vertex which normally is 12 bytes of size ( increase of 33% ) or per normal which is 8 bytes ( 50% ! ) plus the runtime overhead of resolving the pointers ( not that much ^^ ) and the need of initialising all pointers and keeping track of their validity.



t0}{!c
while vector classes are good at holding date when it comes to locations, directions and other singlar data I don't see the point in having them when it comes to model information, you won't be perform per-vertex changes on them in any meaningfull way once your load step is completed, as such while you might want to initally load your data into your customs classes once you've done any jiggery pokery you need to it you might as well dump it into a normal array.

Well, a normal array might well be overkill, instead consider a plain struct consisting of the data you'll want per vertex.

struct terraindata {	float x,y,z;	GLubyte r,g,b,a;	// use four bytes so we dont botch the alignment up	float u,v;			// texture U,V coords	float nx,ny,nz;		// normal vector for vertex};


Thats one I've used before now.

You can even make a std::vector of them and render from that, just pass the gl*Pointer() functions the address of the first element as the data location (gl*Pointer(...,static_cast&ltvoid*>(&myVec[0]));).

Oh, and interleaving the data as above is a good idea on all hardware and maintaining 4btye alignment on the vertex elements is critical if you don't want to it die on ATI hardware.
Actually my first solution behaves exactly the like phantoms. consider

struct TerrainData{vector< float, 3 > Vertex;color< desc_rgba > Color;vector< float, 2 > TexCoord;vector< float, 3 > Normal;};// Now later you can perform stuff likebla->getTerrainData()[234].Normal.normalize();



While this code would behave the same as your code it still allows you to perform all your vector algorithms on the data. You surely wont need them that often but once you will, you'll be glad to not beeing forced to rewrite code or extract the data and then put it in again.
Thanks again for the informative replies.

I suppose I should have mentioned that I'm actually using Java and JOGL for my programs, I didn't post this in the Java forum because it's not really a Java-specific topic. But as you all probably know, Java doesn't give the coder as much control over pointers as C++ does, so I'm not sure how to implement your examples with Java.

Technically, JOGL requires NewIO Buffer objects for the gl*Pointer() calls. Any Java masters out there who might have some insight into adapting toxic's first solution to Java and FloatBuffers? (a ByteBuffer could probably also be used.)

From my look through the FloatBuffer API docs, I can't see an easy way to keep my data in a custom class and get it to a gl*Pointer function via Buffers without copying my custom data to a Buffer every frame.

Oh well, maybe I'll just have to do without my vector operations. Like toxic said, they won't be needed often, but it'd be nice to have the ability if the need comes up.


BTW, I've also decided to write two versions of my loader, one that averages the normals and one that duplicates the vertices to preserve normals when needed, just to cover all possibilities.

This topic is closed to new replies.

Advertisement