VBO - Example with Vertices, Normals, Textures, Colors

Because I’ve not been able to find completely working examples that show the use of VBOs with vertices and normals, etc I decided to list some snippets of code here from I finally was able to get to work. I hope this is helpful to others.

I’ll try to explain what needs to occur where and what things mean as best as I could decipher them. BTW, I am trying to improve the renderer in the 3DS part of JOGLUTILS so this is related to the 3DS structures that I have. 3DS files contain ‘objects’ which each object has arrays of vertices and arrays of normals, textures and materials/colors.

So, for VBO the first thing is the generation of the VBO themselves. So say you have some ‘objects’ that have a size of ‘objects.size()’, well you want to create a VBO for each of the objects…actually since we want normals, vertices, colors, and textures we have to create 4 VBOs for each object. This code simply creates the necessary VBO ids and generates the necessary buffers. You would do this in the ‘init()’ method, typically. Note, there are routines to check if VBOs are available for use on the current machine but I’ve not listed this check here.


// Global variable 
private int VBO[] = null; 

// Note that I'm creating twice the VBOs since I want 
// one VBO for vertices and one for normals.  In our VBO 
// array, the vertices will be first, and the normals second
// .. ie. vertices will be VBO[4*i]
//        normals  will be VBO[4*i+1]
//        textures will be VBO[4*i+2]
//        colors   will be VBO[4*i+3]
VBO = new int[objects.size()*4];

// Generate the buffers for all of the VBOs
gl.glGenBuffers(objects.size()*4, VBO, 0);

Now we need to populate the VBOs in the init() method.


// Global variables
private FloatBuffer vertices, normals, textures, colors;

// Loop through all of the objects we have
for (int i=0; i<objects.size(); i++) {
    // Get the current object from which we'll retrieve the vertices and normals
    Obj tempObj = objects.get(i);


    // First create the VERTICES buffer for each object
    //  ...corresponding to the VBO[4*i] ids
    // Bind the buffer to represent which buffer we are currently working with
    gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBO[4*i]);

    // Allocate the float buffer for the vertices.  The size of the 
    // buffer of vertices corresponds to how many faces/triangles 
    // each object has times 3 vertices per triangle times 3 floats
    // per vertex:  #faces * 3 * 3
    int totalBufferSize = tempObj.numOfFaces*3*3;
    vertices = FloatBuffer.allocate(totalBufferSize);

    // Create the float buffer for the vertices by looping over each
    // face and adding the coordinates of each vertex to the buffer
    // in the order that the vertices would normally be called in 
    // a direct draw method.  Yes, you will be duplicating the vertices
    // across the buffer because vertices are typically shared by 
    // faces/triangles, but that's how it is supposed to be done.
    for (int j=0; j<tempObj.numOfFaces; j++) {
        // Loop through the 3 vertices of each face
        for (int whichVertex=0; whichVertex<3; whichVertex++) {
            int index = tempObj.faces[j].vertIndex[whichVertex];

            // add the X, Y, Z float values of the current vertex to 
            // the 'vertices' float buffer 
            vertices.put(tempObj.verts[index].x);
            vertices.put(tempObj.verts[index].y);
            vertices.put(tempObj.verts[index].z);
        }  
    }

    // Rewind buffer ... necessary step
    vertices.rewind();

    // Write out vertex buffer to the currently bound VBO.
    // Use 'totalBufferSize*4' because we have 4 bytes for a float.
    gl.glBufferData(GL.GL_ARRAY_BUFFER, totalBufferSize *4, vertices, GL.GL_STATIC_DRAW);

    // Free the vertices buffer since we are done with them
    vertices = null;


    // Second create the NORMALS buffer for each object
    //  ...corresponding to the VBO[4*i+1] ids
    // Bind the buffer to represent which buffer we are currently working with
    gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBO[4*i+1]);

    // Allocate the float buffer for the normals.  The size of the 
    // buffer of normals corresponds to the same number of floats
    // as the vertices since each vertex has a normal and each normal
    // also has 3 floats so the same buffer size is used.
    totalBufferSize = tempObj.numOfFaces*3*3;
    normals = FloatBuffer.allocate(totalBufferSize);

    // Create the float buffer for the normals by looping over each
    // face and each face's vertex and adding the coordinates of 
    // each corresponding normal to the buffer...same concept as for
    // vertices
    for (int j=0; j<tempObj.numOfFaces; j++) {
        // Loop through the 3 normals of each face
        for (int whichVertex=0; whichVertex<3; whichVertex++) {
            int index = tempObj.faces[j].vertIndex[whichVertex];

            // add the X, Y, Z float values of the current normal to 
            // the 'normals' float buffer 
            normals.put(tempObj.normals[index].x);
            normals.put(tempObj.normals[index].y);
            normals.put(tempObj.normals[index].z);
        }  
    }

    // Rewind buffer
    normals.rewind();

    // Write out the normals buffer to the currently bound VBO.
    // Use 'totalBufferSize*4' because we have 4 bytes for a float.
    gl.glBufferData(GL.GL_ARRAY_BUFFER, totalBufferSize *4, normals, GL.GL_STATIC_DRAW);

    // Free the normals
    normals = null;


    // Third create the TEXTURES buffer for each object (if the object 
    // has a texture...skip it if it does not)
    //  ...corresponding to the VBO[4*i+2] ids
    if(tempObj.hasTexture) {
        // Bind the buffer to represent which buffer we are currently working with
        gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBO[4*i+2]);  

        // Textures have 2 coordinates (u,v) per vertex and 3 vertices per triangle/face
        totalBuffer = (tempObj.numOfFaces*3*2);

        // Allocate the float buffer for the textures (again, 4 bytes per float so multiply by 4)
        textures = ByteBuffer.allocateDirect(totalBuffer*4).order(ByteOrder.nativeOrder()).asFloatBuffer();

        for (int j=0; j<tempObj.numOfFaces; j++) {
            for (int whichVertex=0; whichVertex<3; whichVertex++) {
                int index = tempObj.faces[j].vertIndex[whichVertex];

                // put the u,v coordinates of the texture vertices into the textures FloatBuffer
                textures.put(tempObj.texVerts[index].x);
                textures.put(tempObj.texVerts[index].y);
            }  
        }

        // Rewind buffer
        textures.rewind();  

        // Write out the textures buffer to the currently bound VBO.
        // Use 'totalBufferSize*4' because we have 4 bytes for a float.
        gl.glBufferData(GL.GL_ARRAY_BUFFER, totalBuffer*4, textures, GL.GL_STATIC_DRAW); 

        // Free the textures
        textures = null;
    }


    // Fourth create the COLORS buffer for each object (if the object has 
    // a texture then you don't need to set a color buffer for that object...but 
    // make sure not to use the empty buffer during rendering by accident...you
    // could just put in fake colors for the buffer just in case...I don't do that
    // here)
    //  ...corresponding to the VBO[4*i+3] ids
    // Bind the buffer to represent which buffer we are currently working with
    gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBO[4*i+3]);

    // Allocate the float buffer for the colors.  The size of the 
    // buffer of normals corresponds to the same number of floats
    // as the vertices.
    totalBufferSize = tempObj.numOfFaces*3*3;
    colors = FloatBuffer.allocate(totalBufferSize);

    // Create the float buffer for the colors by looping over each
    // face and each face's vertex and adding the color of 
    // each corresponding vertex to the buffer
    for (int j=0; j<tempObj.numOfFaces; j++) {
        // if the material id is in the materials list
        if (tempObj.faces[j].materialID < materials.size()) {
            // Get the color (I'm getting the color from a material in my object)
            byte aColor[] = materials.get(tempObj.faces[j].materialID).color;
            // I convert the byte color to float color using my own methods...so
            // inttofloat(), unsignedByteToInt() won't work for you.
            // You basically need a way to get the float components of the color. (0.0f to 1.0f)
            float R = inttofloat(unsignedByteToInt(aColor[0]));
            float G = inttofloat(unsignedByteToInt(aColor[1]));
            float B = inttofloat(unsignedByteToInt(aColor[2]));

            // First vertex of face
            colors.put(R);
            colors.put(G);
            colors.put(B);
            //colors.put(1.0f);   // if using a 4 float color then you need a fourth value

            // Second vertex of face
            colors.put(R);
            colors.put(G);
            colors.put(B);
            //colors.put(1.0f);   // only if using 4 floats

            // Third vertex of face
            colors.put(R);
            colors.put(G);
            colors.put(B);
            //colors.put(1.0f);   // only if using 4 floats
        // else no material id exists and so just put in default color of white
        } else {
            for (int z=0; z<9; z++) {
                colors.put(1.0f);
            }
        }
    }

    // Rewind buffer
    colors.rewind();

    // Write out the colors buffer to the currently bound VBO.
    // Use 'totalBufferSize*4' because we have 4 bytes for a float.
    gl.glBufferData(GL.GL_ARRAY_BUFFER, totalBufferSize*4, colors, GL.GL_STATIC_DRAW);

    // Free colors
    colors = null;
}

Note that in the code above we bound the buffer for the vertices first, then
we added the vertices to the vertex buffer and finally added buffer to the
VBO using the glBufferData() call. And then we did the same for the normals
but we did it separately…yes, you could create the buffers together but you
need to perform the glBindBuffer() and glBufferData() separtely for the vertices
and normals…ie:
glBindBuffer(vertices)
glBufferData(vertices)

  glBindBuffer(normals)
  glBufferData(normals)

And NOT the following (had me stuck for a while…the glBindBuffer() basically tells
the system which is the current buffer that we are working with.
glBindBuffer(vertices)
glBindBuffer(normals)

  glBufferData(vertices)
  glBufferData(normals)

OK, so now we’ve created the VBOs and we are ready for rendering…again, note that
in my case I have several objects and so I will loop through each object to render:


// Enable the client states for the normals and vertices since we use these
// in all cases...if you were not using them then you should not enable the
// states until you do (I had it crash when I had them enabled unnecessarily)
gl.glEnableClientState(GL.GL_NORMAL_ARRAY);
gl.glEnableClientState(GL.GL_VERTEX_ARRAY);

// Loop over all of the objects and bind the buffers for the normals and 
// vertices and draw...
for (int i=0; i<objects.size(); i++) {
      // Get the current object
      Obj tempObj = objects.get(i);   
      int numFaceIndices = tempObj.numOfFaces*3;

      /////////////////////////////////////////////////////////////////
      // Clear/Initialize the display
      //
      // Clear the color so the previous color won't be used to override 
      // anything that you didn't have a color for.
      gl.glColor3f(1.0f, 1.0f, 1.0f);
          
      // Disable the use of textures so that the previous texture's
      // properties are not used to color the current object that
      // doesn't have a texture.
      gl.glDisable(GL.GL_TEXTURE_2D);

      // Same as above, disable the color material until it is used
      gl.glDisable(GL.GL_COLOR_MATERIAL);


      /////////////////////////////////////////////////////////////////
      // Start the rendering for each object here
      //
      // USING textures (if the object has a texture)
      if(tempObj.hasTexture) {
            // Enable the client state for the texture coordinate array 
            // and for the textures in general.
            gl.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY);
            gl.glEnable(GL.GL_TEXTURE_2D);           

            // I have a Texture array ('texture[]') from which I retrieve 
            // the texture object to bind it.                                   
            gl.glBindTexture(GL.GL_TEXTURE_2D, texture[tempObj.materialID].getTextureObject());
            gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBO[4*i+2]);
            gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, 0);

      // ELSE using colors
      } else {  
            gl.glEnableClientState(GL.GL_COLOR_ARRAY);
            gl.glEnable(GL.GL_COLOR_MATERIAL);
            gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBO[4*i+3]);

            // If using a 4 float color (then this '3' has to be a '4')
            gl.glColorPointer(3, GL.GL_FLOAT, 0, 0);       
      }

      // Bind the normal buffer to work with
      gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBO[4*i+1]);
      gl.glNormalPointer(GL.GL_FLOAT, 0, 0);

      // Bind the vertex buffer to work with
      gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBO[4*i]);
      gl.glVertexPointer(3, GL.GL_FLOAT, 0, 0);
               
      // Draw the contents of the VBO...either filled triangles
      // or triangle outlines (wireframe)
      if (fill) {
            gl.glDrawArrays(GL.GL_TRIANGLES, 0, numFaceIndices);
      } else {
            gl.glDrawArrays(GL.GL_LINE_LOOP, 0, numFaceIndices);
      }

      if(tempObj.hasTexture) {
            gl.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY);
      } else {
            gl.glDisableClientState(GL.GL_COLOR_ARRAY);
      }
}

// Disable the client states for the normals and vertices
gl.glDisableClientState(GL.GL_NORMAL_ARRAY);
gl.glDisableClientState(GL.GL_VERTEX_ARRAY);

btw, yes I know there are examples in the DEMO code but some examples I found are just too darn complicated to understand, some mix and match using the “_ARB” suffixes which people should know that the “_ARB” are no different than the calls withouth the “_ARB”…I believe the with “_ARB” calls were old version that were not in the main spec so they were experimental (so I’ve read somewhere).

If anyone has corrections to anything I wrote then I would appreciate those comments - if you want to criticize me then keep those comments to yourself. :slight_smile:

I’m a real big fan of supper simple tests and example myself, so thank you very much.

For decent performance, change
FloatBuffer.allocate(totalBufferSize)
into
BufferUtils.createFloatBuffer(totalBufferSize)
which is a util-method for
ByteBuffer.allocateDirect(bytes*4).order(ByteOrder.nativeOrder()).asFloatBuffer();

What’s the import for the ‘BufferUtils’ that you suggest?

ok…finally figured out colors and textures so I’m updating the first and second post to include that info.

btw…we need to increase the message limit on these forums from 10,000 characters to something more (not just 10,001)…I have to freaking split my message over multiple posts and it is annoying to try to limit yourself to 10,000 (actually less since there is other html characters that are filled in for you that you normally don’t count but the forum does.

That’s rather this: com.sun.opengl.util.BufferUtil

An example from the source code of TUER:

StaticVertexBufferObject(GL gl,float[] array){
this.mode=GL.GL_TRIANGLES;
this.gl=gl;
this.buffer=BufferUtil.newFloatBuffer(array.length);
this.buffer.put(array);
this.buffer.position(0);
this.id=new int[1];
gl.glGenBuffersARB(1,id,0);
gl.glBindBufferARB(GL.GL_ARRAY_BUFFER,id[0]);
gl.glBufferDataARB(GL.GL_ARRAY_BUFFER,BufferUtil.SIZEOF_FLOAT*buffer.capacity(),buffer,GL.GL_STATIC_DRAW_ARB);
this.buffer.position(0);
}

If you plan to use VBO, treat the case of it unavailability too…

I do check for VBO unavailability…I simply do this:


            VBOSupported = gl.isFunctionAvailable("glGenBuffers") &&
                           gl.isFunctionAvailable("glBindBuffer") &&
                           gl.isFunctionAvailable("glBufferData") &&
                           gl.isFunctionAvailable("glDeleteBuffers");

           if (VBOSupported) {
               // Make VBOs
           }

Regarding the creation of VBOs in general has anyone had worse performance on a MAC (10.4)? In windows and Linux my VBO loader works fast, but on a MAC it works horribly slow…not the rendering but the creation part (the part in the init() code).

Every init() call rebuilds the VBOs and for some reason it is just horrible. I can’t seem to run the Netbeans profiler on it either because it gives an exception on whatever code I run (so I know this is a netbeans profiler issue and not code issue).

Not sure what else to do here…anyone?

Are you sure it is because of MAC? Which graphics card do you have?

I can’t be 100% sure but I have no reason to suspect anything else. I don’t personally have a MAC so this is tested on two MAC laptops of my co-workers and I don’t have the specs…I only know that these two MAC laptops were purchased about 6-9 months ago and at the time were the most expensive MACs with the best available graphics cards and 2 GB of RAM. That’s not saying much but this is all I have as far as info right now. I’m using the exact same JOGL release on MAC, Windows and Enterprise Linux (64) and only the MAC has huge slowdowns in creating the VBOs.

I obviously need to do some more testing to narrow down the problem so I’m sorry if I asked a question without having all the necessary information for you at this time.

Please check they use an Intel graphics cards. I’m very worried as I use VBOs too and I don’t want Mac users to have bad performance.

they have the nVIDIA GeForce 8600M GT 256MB.

I’m waiting for them to upgrade to using Leopard OS so that they can get the latest Java and be able to run the Netbeans profiler correctly so I can see where my problem is occurring. Either the issue is with the graphics card or something to do with the VBOs. At one time I thought they ran my initial VBO code that didn’t include textures and colors and it was fine so I’m going to try and temporarily remove those 2 VBOs out and see if that improves their performance.

I am using VBOs on OS X 10.5 with a ATI X1600 card (MacBook Pro), and I haven’t noticed any performance problems

Ok, I have to clarify because I did some more investigation and it turns out that it is not the loading that is the cause but that doesn’t mean I know the reason yet.

Since I don’t have a profiler capability right now I had my co-workers comment things as out as we went to try to see the slowdown.

The initialization and creation of the VBOs is fast so that is not the problem.

The rendering is where I get the slowdown…except that this happens only ONCE - the first rendering call and not subsequent calls and also after an init() it happens again just once. It makes no sense to me yet so anyone with VBO knowledge please chime in. The following code is what I have in my rendering loop that gets called by the display() method repeatedly, and as I said, only the very first time this rendering method is called do I have the huge slowdown. Also, when closing the application, I have a huge slowdown before the application actually closes (MAC only as well). I’m guessing the first is do the buffer binding? though not sure why this happens only on the first time, and the closing delay is due to the release of the buffers? Note that I do not do anything yet with releasing the buffers or clearing them, etc, I just let them die off.


    render(GL gl) {
        ...
            // Enable the client states
            gl.glEnableClientState(GL.GL_NORMAL_ARRAY);
            gl.glEnableClientState(GL.GL_VERTEX_ARRAY);

            // Bind the VBOs (loop over the objects)
            for (int i=0; i<objects.size(); i++) {

                // Use the normals
                gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBO[4*i+1]);
                gl.glNormalPointer(GL.GL_FLOAT, 0, 0);
                
                // Use the vertices
                gl.glBindBuffer(GL.GL_ARRAY_BUFFER, VBO[4*i]);
                gl.glVertexPointer(3, GL.GL_FLOAT, 0, 0);

                // Render the triangles (or lines)
                if (fill) {
                    gl.glDrawArrays(GL.GL_TRIANGLES, 0, numFaceIndices);
                } else {
                    gl.glDrawArrays(GL.GL_LINE_LOOP, 0, numFaceIndices);
                }
            }

            // Disable the client states
            gl.glDisableClientState(GL.GL_VERTEX_ARRAY);
            gl.glDisableClientState(GL.GL_NORMAL_ARRAY);
         ...
    }

Again, this only happens on a MAC…and not on Windows or Linux (using the same JOGL release 1.1.1)

Your render code looks ok, I would suggest binding your buffer to 0 once you are done with VBOs, but thats really all i can come up with. I don’t have that much knowledge of the inner workings of VBOs or what wacky stuff apple is up to in their OpenGL drivers, so I can’t really be of any service here.

thanks for the info.

I’m wondering if it has something to do with the MAC having Opengl 1.5 while on my PC and Linux I have OpenGL 2.0+

Also, I was thinking the issue may have been my code because I believe an previous version that didn’t include the colors/textures buffers seemed to work on the MAC fine though the 3DS model being loaded was likely a smaller one so that may be just be a perception issue.

I’m still kind of curious why the first time rendering the effect would cause a delayed response and a delay in rendering but not all subsequent rendering calls…displaying the model on PC/Linux is instantaneous, displaying this same model on MAC causes about a 5 second delay on the first pass. If I hide the model then everything runs fine and if I show it again it runs fine, but the very very very first time the rendering tells the model to unhide it has that long delay…is there something that I should move to the init() method that maybe occurs during the first rendering pass?