Main Menu

VBO rendering

Started by morris4019, January 06, 2012, 19:03:48

Previous topic - Next topic

morris4019

I am sort of new to using LWJGL and have been using lots of resources online for examples and tutorials to aid my learning. I have also been using a number of books dedicated to OpenGL such as "The Red Book", and "Beginning OpenGL Game Programming, 2e", although they are designed for use with C++ they are giving some great knowledge which crosses over.

My problem is using GL12.glDrawRangeElements(..). Everything I read explains that I can "hop" around, although I cannot seem to get it to function properly. First, my the code I am using (which is a modified version of the VBO tutorial at http://lwjgl.org/wiki/index.php?title=Using_Vertex_Buffer_Objects_%28VBO%29.

My VBO buffer initialization:
private static void loadBlockVBO() {
    
    final float[] verts = new float[] {
        0.0f, 0.0f, 0.0f,   // 0
        0.0f, 1.0f, 0.0f,   // 1
        1.0f, 1.0f, 0.0f,   // 2
        1.0f, 0.0f, 0.0f,   // 3
        1.0f, 1.0f, -1.0f,  // 4
        1.0f, 0.0f, -1.0f,  // 5
        0.0f, 0.0f, -1.0f,  // 6
        0.0f, 1.0f, -1.0f   // 7
    };
    
    final int[] inds = new int[] {
        
      0, 3, 1,  // FBL
      3, 2, 1,  // FTL
      
      3, 5, 2,  // RBL
      2, 5, 4,  // RTL
      
      5, 4, 6,  // B(ack)BL
      4, 6, 7,  // B(ack)TL
        
      7, 6, 0,  // LBL
      7, 0, 1,  // LTL
      
      0, 6, 3,  // B(ottom)BL
      3, 6, 5,  // B(ottom)TL
      
      7, 1, 2,  // TBL
      7, 2, 4   // TTL
      
    };
    
    vert_buff_id = ARBVertexBufferObject.glGenBuffersARB();
    index_buff_id = ARBVertexBufferObject.glGenBuffersARB();
    
    FloatBuffer vertBuff = BufferUtils.createFloatBuffer(verts.length);
    vertBuff.put(verts).flip();
    
    IntBuffer indBuff = BufferUtils.createIntBuffer(inds.length);
    indBuff.put(inds).flip();
    
    ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB,
                                          vert_buff_id);
    ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB,
                                          vertBuff,
                                          ARBVertexBufferObject.GL_STATIC_DRAW_ARB);
    
    ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ELEMENT_ARRAY_BUFFER_ARB,
                                          index_buff_id);
    ARBVertexBufferObject.glBufferDataARB(ARBVertexBufferObject.GL_ELEMENT_ARRAY_BUFFER_ARB,
                                          indBuff,
                                          ARBVertexBufferObject.GL_STATIC_DRAW_ARB);
    

    
  }


My Render Code:
public final void render(final float x, final float y, final float z) {
    
    if (renderable) {
    
      GL11.glMatrixMode(GL11.GL_MODELVIEW);
      GL11.glPushMatrix();
      GL11.glLoadIdentity();
      
      GL11.glTranslatef(x, y, z);
      
      GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
      ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ARRAY_BUFFER_ARB, vert_buff_id);
      GL11.glVertexPointer(3, GL11.GL_FLOAT, 0, 0);
      
      GL11.glEnableClientState(GL11.GL_INDEX_ARRAY);
      ARBVertexBufferObject.glBindBufferARB(ARBVertexBufferObject.GL_ELEMENT_ARRAY_BUFFER_ARB, index_buff_id);
      
      final int max_x = openglbook.world_blocks[0].length;
      final int max_y = openglbook.world_blocks.length;
      final int max_z = openglbook.world_blocks[0][0].length;
      
      // If x,y,z+1 (front) block id == 0 then draw front.
      if ((z + 1 >= max_z) || (z + 1 < max_z && openglbook.world_blocks[(int)y][(int)x][(int)z+1] == 0)) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 0, 5, 6, GL11.GL_UNSIGNED_INT, 0);  
      }
      
      // x, y, z - 1 (back) block id == 0 then draw back.
      if ((z-1 < 0) || (z-1 >= 0 && openglbook.world_blocks[(int)y][(int)x][(int)z-1] == 0)) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 12, 17, 6, GL11.GL_UNSIGNED_INT, 0);
      }
      
      // x, y + 1, z (top) block id == 0 then draw top.
      if ((y+1 >= max_y) || (y + 1 < max_y && openglbook.world_blocks[(int)y + 1][(int)x][(int)z] == 0)) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 30, 35, 6 , GL11.GL_UNSIGNED_INT, 0);
      }
      
      // x, y - 1, z (bottom) block id == 0 then draw bottom.
      if ((y-1 < 0) || y - 1 >= 0 && openglbook.world_blocks[(int)y-1][(int)x][(int)z] == 0) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 24, 29, 6 , GL11.GL_UNSIGNED_INT, 0);
      }
      
      // x -1, y, z (left) block id == 0 then draw left.
      if ((x-1 < 0) || x - 1 >= 0 && openglbook.world_blocks[(int)y][(int)x-1][(int)z] == 0) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 18, 23, 6 , GL11.GL_UNSIGNED_INT, 0);
      }
      
      // x +1, y, z (right) block id == 0 then draw right.
      if ((x+1 >= max_x) || x + 1 < max_x && openglbook.world_blocks[(int)y][(int)x+1][(int)z] == 0) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 6, 11, 6 , GL11.GL_UNSIGNED_INT, 0);
      }     
      
      GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
      
      GL11.glPopMatrix();
    
    }
    
  }


My world_blocks array is nothing more than a 3d array of int's storing which type of block is in the location. I'm attempting to build a sort of minecraft type sandbox world. My question is, how am i suppose to use GL12.glDrawRangeElements(..) to only draw a select number of vertices from the bound buffers? The above implementation calls glDrawRangeElements but for some reason no matter what it always draws the same portion of the cube, i.e - i can put any start and end index values in the method call and the same portion of the cube is drawn. What am i missing?

(This is by far not the best implementation of the logic between frames, i.e checking for index out of bounds and such, but for now i just want the cubes to render properly before i worry about any sort of performance issues.)

morris4019

Ok, so i made a little headway, although I do not understand exactly what these offsets mean. I made the following changes to the render code above, notice the index_buffer_offset parameters.

     
if ((z + 1 >= max_z) || (z + 1 < max_z && openglbook.world_blocks[(int)y][(int)x][(int)z+1] == 0)) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 0, 36, 6, GL11.GL_UNSIGNED_INT, 0);  
      }
      
      // x, y, z - 1 (back) block id == 0 then draw back.
      if ((z-1 < 0) || (z-1 >= 0 && openglbook.world_blocks[(int)y][(int)x][(int)z-1] == 0)) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 0, 36, 6, GL11.GL_UNSIGNED_INT, 48);
      }
      
      // x, y + 1, z (top) block id == 0 then draw top.
      if ((y+1 >= max_y) || (y + 1 < max_y && openglbook.world_blocks[(int)y + 1][(int)x][(int)z] == 0)) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 0, 36, 6, GL11.GL_UNSIGNED_INT, 120);
      }
      
      // x, y - 1, z (bottom) block id == 0 then draw bottom.
      if ((y-1 < 0) || y - 1 >= 0 && openglbook.world_blocks[(int)y-1][(int)x][(int)z] == 0) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 0, 36, 6 , GL11.GL_UNSIGNED_INT, 96);
      }
      
      // x -1, y, z (left) block id == 0 then draw left.
      if ((x-1 < 0) || x - 1 >= 0 && openglbook.world_blocks[(int)y][(int)x-1][(int)z] == 0) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 0, 36, 6 , GL11.GL_UNSIGNED_INT, 72);
      }
      
      // x +1, y, z (right) block id == 0 then draw right.
      if ((x+1 >= max_x) || x + 1 < max_x && openglbook.world_blocks[(int)y][(int)x+1][(int)z] == 0) {
        GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 0, 36, 6 , GL11.GL_UNSIGNED_INT, 24);
      }


I started playing around with the parameters to see whether or not I would either get 1) the desired effect, or 2) an exception/error. I first played around with the start/end parameters and found that they aparently do nothing. I can set them to whatever I want, even negative numbers and I the outcome does not change. So i moved on to the total_indices parameter, which had the effect of, starting at the beginning of the array and rendering that number of vertices. This was no good because each face only contains two faces, i.e 6 vertices. Then i started messing around with the index_buffer_offset (the last parameter) and I originally thought this was the offset it used as a starting point, but it does not make sense because I definately do not have 72/120/96 vertices.

The only thing i can think of is that this offset is possibly in bytes?

Either way this produced the desired goal of rendering, but I wonder if this is working the way it should. I am at a loss when it comes to render strategies. I am using backface culling but that does not fully give me the desired outcome. That only culls the back of each rendered cube. Is there another way, besides checking at the time of cube render which faces should be rendered so that, say a 100 x 100 set of cubes only draws the outside layer of cubes since the inner cubes will never be visible?

bartvbl

After you stored your data in the buffer, your buffer should look something like:
vertex, texCoord, colour, certex, texCoord, colour, etc.
They are indeed measured in bytes, as you can see on the wiki example.
You need to tell openGL how to use the data from the buffer, which can be formatted in a lot of ways. Hence the offsets.
Then you can use the index buffer to tell what vertices you would like to display.
Hidden surface removal is pretty tricky, and can be very dependent on the game you are trying to make.
And I have no actual experience with that, sorry.

morris4019

I have the following as base for Frustum culling but for some reason it does not seem to work properly. I think I may have the matrix positions messed up. Although I thought that OpenGL Matrices were column major apparently I am getting incorrect values or else it should work. Here is a small utility class which i wrote to check to see if points are inside the frustum.

If anyone out there has any experience with using this type of culling please share what you think of this. I am not exactly sure why this is not working. When rendering I see nothing. Oddly enough, if i negate the x and z coordinates before passing them to isPointInFrustum(), I get some polygons rendered although as you move throughout the world the clipping is backwards and very short in angle.

package com.mmm.basic3dworld;

import java.nio.FloatBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;

public class FrustumManager {

  public static FrustumBound current_frustum;
  
  public static FloatBuffer getFrustumMatrix() {
    
    FloatBuffer projection = BufferUtils.createFloatBuffer(16);
    FloatBuffer modelview = BufferUtils.createFloatBuffer(16);
    FloatBuffer mvp = BufferUtils.createFloatBuffer(16);
    
    GL11.glGetFloat(GL11.GL_PROJECTION_MATRIX, projection);
    GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, modelview);
    GL11.glMatrixMode(GL11.GL_MODELVIEW);
    GL11.glPushMatrix();
      GL11.glLoadMatrix(projection);
      GL11.glMultMatrix(modelview);
      GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, mvp);
    GL11.glPopMatrix();
    
    return mvp;
    
  }
  
  public static void displayFloatBuffer(final FloatBuffer fb) {
    
    for (int i = 0; i < 16; i++) {
      System.out.print(fb.get(i) + ", ");
    }
    System.out.print("\n");
    
  }

  public static void updateFrustumBound() {
    
    if (current_frustum == null) {
      current_frustum = new FrustumBound();
    }
    
    // Get the matrix first
    FloatBuffer frust_matrix = getFrustumMatrix();
    
    
    // Near
    current_frustum.planes[0] = new Plane(frust_matrix.get(3) + frust_matrix.get(2),
                                      frust_matrix.get(7) + frust_matrix.get(6),
                                      frust_matrix.get(11) + frust_matrix.get(10),
                                      frust_matrix.get(15) + frust_matrix.get(14));

    // far
    current_frustum.planes[1] = new Plane(frust_matrix.get(3) - frust_matrix.get(2),
                                     frust_matrix.get(7) - frust_matrix.get(6),
                                     frust_matrix.get(11) - frust_matrix.get(10),
                                     frust_matrix.get(15) - frust_matrix.get(14));
    
    // Left
    current_frustum.planes[2] = new Plane(frust_matrix.get(3) + frust_matrix.get(0),
                                      frust_matrix.get(7) + frust_matrix.get(4),
                                      frust_matrix.get(11) + frust_matrix.get(8),
                                      frust_matrix.get(15) + frust_matrix.get(12));
    
    // Right
    current_frustum.planes[3] = new Plane(frust_matrix.get(3) - frust_matrix.get(0),
                                       frust_matrix.get(7) - frust_matrix.get(4),
                                       frust_matrix.get(11) - frust_matrix.get(8),
                                       frust_matrix.get(15) - frust_matrix.get(12));
    
    // Bottom top
    current_frustum.planes[4] = new Plane(frust_matrix.get(3) + frust_matrix.get(1),
                                        frust_matrix.get(7) + frust_matrix.get(5),
                                        frust_matrix.get(11) + frust_matrix.get(9),
                                        frust_matrix.get(15) + frust_matrix.get(13));
    
    // Top
    current_frustum.planes[5] = new Plane(frust_matrix.get(3) - frust_matrix.get(1),
                                     frust_matrix.get(7) - frust_matrix.get(5),
                                     frust_matrix.get(11) - frust_matrix.get(9),
                                     frust_matrix.get(15) - frust_matrix.get(13));
    
  }
  
  public static boolean isPointInFrustum(float x, float y, float z) {
    
    if (current_frustum == null) {
      updateFrustumBound();
    }
    
    for (int p = 0; p < 6; p++) {
      
      float plane_eqn = current_frustum.planes[p].n_a * x + 
                        current_frustum.planes[p].n_b * y +
                        current_frustum.planes[p].n_c * z + 
                        current_frustum.planes[p].n_d;
      
      if (plane_eqn  < 0) {
        return false;
      }
    }
    
    return true;
  }
  
  
  public static class FrustumBound {
    
    Plane[] planes;
    
    public FrustumBound() {
      planes = new Plane[6];
    }
    
  }
  
  public static class Plane {
    
    final float n_a;
    final float n_b;
    final float n_c;
    final float n_d;
    
    public Plane(final float a,
                 final float b,
                 final float c,
                 final float d) {
      
      float t = (float)Math.sqrt(a * a + b * b + c * c);
      
      n_a = a / t;
      n_b = b / t;
      n_c = c / t;
      n_d = d / t;
      
    }
    
  }
  
  
}

morris4019

If anyone has any ideas as to a good strategy for rendering a 3d cube terrain I would love to hear their ideas.

I am currently using VBO's (as stated above), although at the moment I have taken out the frustum culling code because I cannot seem to get it to work right. I am looping through roughly 80x80x30 cubes - ~192000 cubes of 1x1x1 (of a total of about 7.5 billion, each stored as an int), and rendering 1-3 faces of about half of the cubes. I am getting a little lag, which is noticable when moving the mouse around mostly - it's not real crisp but I'd like it to be.

The cubes are chosen based on the current location so basically -40 in the x to +40 in the x, -40 in the z to +40 in the z, and about 15 below and above the person. There is only one main block class which is loaded, and buffers are loaded and bound at the beginning of the program so after the initial load there is no switching of buffers except colors.

I have 6 if statements which get executed and check what type of block is next to it, if it is null (end of array) or 0 (do not render) then that face is rendered with a call to glDrawRangeElements(..) directed toward the respective face. Not sure if this is the best way to achieve what I want, but I could not figure out how to only render faces which would be seen (i.e if a block is below a square of 9 blocks, it will be completely hidden so do not draw any of the faces.) This at first seemed to me that it would add overhead because of all the if statements but without it the program is very choppy.

morris4019

I thought I might put this up for everyone to read who has not figured this out yet. It took me quite alot of thinking tonight to think of this idea, but this made all the difference.

I edited my code above for rendering a cube. Instead of calling the render method on every cube (from the small selection of cubes being drawn), and subsequently each call using 1 - 4 glDrawRangeElement() calls, i decided to only save geometry inside the cube render code.

More specifically, i have one overall VBO. I use a fixed size FloatBuffer which keeps all of the vertices for all geometry being drawn (i.e. the entire rendered world). My game loop runs through each cube which needs to be drawn and calls the render method on it which now does something a little different. Specifically the "Block" class now knows how to draw a 1x1x1 cube (located at the origin). When render() is called on Block it simply puts translated vertices in to the one FloatBuffer. Then after all geometry which actually needs to be in the float buffer is collected (or the max triangle limit, which i set, is hit) one call to glDrawRangeElements is called drawing vertices 0 ~ num_verts_in_buffer. Similarly a FloatBuffer for normals, colors, etc is in place, although I will be attempting my first try at interleaving these components.

The shear increase in speed alone astounds me.

I can post some code if need be, I just thought I would put this out there because, like a lot of other people have found out, this info is sparse out there (for this specific situation) although it looks like a lot of people seem to be trying to find it.

:) Cheers!

sufimaster

I thought about this single VBO verses each mesh being its own VBO idea as well.  What is the preferred way to do this? It seems like it would save a lot of time if I just keep stuffing vertices+vertex data into a buffer, along with an index buffer, and only called the draw for the VBO once.  Is there any downside to just having one VBO?

morris4019

I have been trolling around the forums asking the same questions with little to no answer.

I originally had saved one VBO, which was a simple cube. Having everything in my "world" drawn as cubes it made it easy because i would not ever have to bind another buffer after the initial, but the downside there (my observation), was that a whole lot of overhead was used in each respective call to glDrawRangeElements.

To remedy this I chose to have a sort of global (not really) FloatBuffer which would store vertices, and each render I would push whatever vertices I needed to actually draw on to that buffer, then render with one call to glDrawRangeElements. This, as i'm noticing now, cannot be the best approach because it forces you to have a FloatBuffer of huge proportion (at least in my case) all the time. As i see from this approach as well, I am actually (every frame) overwriting the buffer data by using the original ID and sending new data. I have asked and asked on these forums but no one seems to be able to tell me, or wishes to divulge any info on the subject so I seem to be learning from my own mistakes.

I would say, simply by the speed at which frames are rendered, that my current approach far outweighs separate calls to glDrawRangeElements. However I think the problem i am having is that there are too many vertices which need to be drawn on the screen using the GL_TRIANGLES approach. I would ultimately like to use triangle strips, but cannot seem to come up with an effective algorithm for going through a list of vertices to remove duplicates, and then order them so that they are in proper order for rendering such a complicated mesh. My take is simple, each call to the object either adds the vertices for the left/top/bottom/right/front/back IF the block in that direction is not render able (meaning it is possible to actually see the face). I have not worked out my own algorithm for drawing only front facing (current front anyway) vertices either, so i am relying on the OpenGL back face culling, which as I have read does a pretty decent job. This does not change the fact that the vertices are showing up in my buffer that may never be seen.

If i come to any concrete solutions to these problems I will for sure post them here, I just work in short little time frames. I am a computer science major nearing my senior year so I have a lot of course work which requires the majority of my time.

broumbroum

hi morris
Did you look at this tutorial ? http://www.songho.ca/opengl/gl_vbo.html
I used it to pratice VBO for rendering 2D geometrical shapes. It turns out that LWJGL gets better by Mapping the V or PBO's, while providing direct jni buffers is stumbling.
...glBufferDataARB(...size*4...) //float buffer of size
will initialize your VBOs.
ByteBuffer bb = ARBVertexBufferObjects.glMapBufferARB(... size*4); // floats
creates AGP accelerated buffers that you can fill up with your vertices and indices.
Rendering is straightforward, as the usual Vertex Arrays (enable client states, pointers and drawElements..).
I hope that helps you out. :D

morris4019

So if I understand this correctly I would need to use the same code to create a FloatBuffer, or the size required, then use glBindBufferARB and glBufferDataARB to get the buffers created (as usual). After this I can immediately call glMapBufferARB() to retrieve a ByteBuffer which can be accessed directly. So the difference here is that after the initial call to bind and create the buffer I can discard the original FloatBuffer in favor of accessing the data through the ByteBuffer? And as a ByteBuffer I can use the direct access methods (get/put) such as getFloat()/putFloat() still, to change the data in the ByteBuffer?


broumbroum