How To Render Combined VBO Meshes - DrawElementsBaseVertex

Started by TeamworkGuy2, October 22, 2012, 00:29:15

Previous topic - Next topic

TeamworkGuy2

LWJGL 2.8.4
nVidia GTS 450 - driver 306.97

I have a LWJGL based program with working shaders (yay!), IBOs, and VBOs.
Everything is working, I can render 1 model with DrawElements and it looks great!
But there is a problem when I try to combine models into one large VBO.

Here are 3 examples of trying to render two models stored in the same VBO.
1. When multiple models are combined into one VBO, only the first model renders correctly.
Models are combined by adding one model after another into the VBO:
VBO [vertex a.1][vertexb a.2]...[vertex a.n][vertex b.1][vertex b.2]...[vertex b.n] (is implemented as interleaving vertex attributes)
IBO [a.0, a.1, ..., a.n, b.0, b.1, ..., b.n] (format of element/index array)
Rendering with glDrawElementsBaseVertex:
// 2 quads, 4 vertices each, 6 indices each
glDrawElementsBaseVertex(GL_TRIANGLES​, 6, GL_UNSIGNED_INT, 0, 0); // Quad 1
glDrawElementsBaseVertex(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 6, 4); // Quad 2 with 'a.n' offset
...

However, only the first draw call renders model 'a' correctly, the other models either disappear or are jumbled messes of vertices.

2. Another idea is to setup the IBO format [a.0, a.1, ..., a.n, a.n+b.0, a.n+b.1, ..., a.n+b.n]
Where indices are pre-offset before being added to the IBO.
Rendered like this:
glDrawElementsBaseVertex(GL_TRIANGLES, 6​, GL_​UNSIGNED_INT, 0, 0); // Quad 1
glDrawElementsBaseVertex(GL_TRIANGLES, 6​, GL_UNSIGNED_INT​, 6, 0); // Quad 2 (assuming 4 vertices and 6 indices)
...

This does not work, only the first draw call renders correctly.

3. However, using the pre-offset index format and rendering everything in one call (no index offset or base vertex):
glDrawElementsBaseVertex(GL_TRIANGLES, 12​, GL_​UNSIGNED_INT, 0, 0); // Render both quads in the same call
...

The result are correct (two quads).

Attached are images of drawing two models (a cube and a quad),
The first image draws the cube first, quad second, I checked and all sides of the cube are rendered correctly, but as you can see, the quad is 'broken'.


The second image draws the quad first, cube second (notice the one 'broken' triangle on the top of the cube, further 'broken' triangles cannot be seen on the back of the cube)


The third image draws the cube and quad in one call (using offset indices), and both render correctly.


I could work around this problem in very simple programs (by drawing everything in one call with pre-offset index arrays).
But this work around is not manageable for more complex rendering, such as when materials are involved. 
Any help or insite someone can provide would be greatly appreciated.
I assume I'm just calling DrawElements* incorrectly, since the third example works great.
Any ideas are welcome!

Update:
See the below post for solution, the index offset in the glDrawElements* calls was being treated incorrectly like an integer offset, and needed to be treated like a byte offset.

TeamworkGuy2

Checking Open GL's documentation reveals the following.
Quote
[ void glDrawElementsBaseVertex(GLenum mode​, GLsizei count​, GLenum type​, GLvoid *indices​, GLint basevertex​); ]
...
indices
    Specifies a pointer to the location where the indices are stored.
...


Looking at the variable names for the LWJGL version of glDrawElements*, the variable "indices_buffer_offset" comes into question.
Quote
glDrawElementsBaseVertex(int mode,
                                          int indices_count,
                                          int type,
                                          long indices_buffer_offset,
                                          int basevertex)

Assuming that "indices_buffer_offset" is a byte offset, not an "index-array-type-size" offset.
The problem with the original code is apparent, the index offsets were treated like integer offsets, not correctly treated like byte offsets.

Changes to example 1. of the original post (bold)
Quote
glDrawElementsBaseVertex(GL_TRIANGLES, 6​, GL_​UNSIGNED_INT, 0, 0); // Quad 1
glDrawElementsBaseVertex(GL_TRIANGLES, 6​, GL_UNSIGNED_INT​, 6*4, 4); // Quad 2 (index array offset of 6*4 because an "UNSIGNED_INT" is 4 bytes long) (keep base vertex value the same)
...

The index offset is multiplied by the byte size of the index type (GL_UNSIGNED_BYTE=1​, GL_UNSIGNED_SHORT​=2, GL_UNSIGNED_INT=4)

It works!
Multiple models in one VBO are now possible!

I do not blame LWJGL for the my misunderstanding, but it would be great if LWJGL had notes about these types of API conventions, so that developers did not have to make assumptions and do extensive testing just to understand how the API behaves.

Edit:
Found thread with similar problem here:
http://www.gamedev.net/topic/569562-vbo---drawing-part-of-buffer/
Solution was the same, use byte offset.

CodeBunny

LWJGL in general does not have the greatest documentation/tutorials, I agree. Thankfully the library more or less lets you use straight OpenGL so it's not as bad as it could be.

TeamworkGuy2

I agree, I am starting to see the pattern of similarities between Open GL (C/C++) and LWJGL functions/methods.

My biggest problem is that if I ever try to copy-paste an example from a Open GL tutorial and test it in Java, I end up with hundreds of lines of pointer usage, arrays, and such that I have to replace with object references, Buffers, etc. in Java.
So in the end it seems like LWJGL developers (at least beginners) get the raw end of the deal compared to C/C++; fewer tutorials/blogs/forum posts/stackoverflow answers/etc and almost no documentation.
Although I guess C has wide usage in the programming world, so it has more resources than other languages.

At least I solved my current problem, hopefully the next one doesn't cost quite so much time.

CodeBunny

LWJGL is a serious attempt to give you the exact same API as C/C++ OpenGL via JNI. So yes,

QuoteI agree, I am starting to see the pattern of similarities between Open GL (C/C++) and LWJGL functions/methods.

is 100% true, because all of the actual graphics calls (other than window management/etc stuff) is intended to be identical.

That said, there are some fundamental differences between the JVM and C/C++ environments. Certain things don't work the way in Java, and so LWJGL uses some workarounds (the heavy use of ByteBuffers being the most obvious). Additionally, a lot of the basic types that OpenGL uses (GLfloat, GLint, etc) are just wrapped up in float/int/their obvious respective type.

jakj

I happen to agree with the general sentiment.

In my opinion, the one and only thing LWJGL is lacking is really good documentation, and a good bit of the time, we have to discover the little things ourselves.

However, that is a small price to pay for the ability to use a bare-metal library like this in Java. Java's greatest strength is cross-platform compatibility, and yet somehow (mostly for performance, I'd wager) it's not taken seriously as a programming tool for games, so we're left with no real good engines like Ogre. (The Java port of Ogre was abandoned long ago.)

I think LWJGL could use a bit of work here and there (like proper undecorated-window support), but overall, is the greatest thing since sliced bread.

TeamworkGuy2

I can agree with you there, LWJGL is a God send for Java developers.
I previously thought I would have to learn a lot of confusing C Win32 stuff to ever work with graphics.
When I found LWJGL, I was so excited. 
So thumbs up, +1, like, and all the rest for LWJGL giving the Java community such a great tool!

I'm just starting to discover all the parts of LWJGL and stuff like integrated AL, JInput, and the Vector and Matrix manipulation classes are so helpful.

zaippa

Ran into the same problem today. Would be really nice if the glDrawElements(int mode, int count, int type, long indices_buffer_offset) method had javadoc stating that indices_buffer_offset is specified in number of bytes, and not in number of indices. The latter is probably what most people expect, especially since the "count" parameter is given in number of indices and not in number of bytes.
(Instead of javadoc, the parameter could also just be renamed to indices_buffer_offset_in_bytes or something)

Anyway, LWJGL rocks and thanx for making it! :)

quew8

This is happening for LWJGL 3.0. The lovely developers are taking the official OpenGL documentation and sticking it into the javadocs of the OpenGL methods. So in the future, hopefully no more problems. In the mean time, I recommend (and I do) looking up each and every function before you use it. http://www.opengl.org/sdk/docs/man4/

Cornix

I second that.
Always have the openGL reference pages open. It saves you a lot of trial and error when working with those functions.