Reading Triangles from Files, and Vertex Buffer Objects

2010, Dr. Lawlor, CS 481/681, CS, UAF

So to model real-world objects, you need a modeling program.

Blender is a pretty capable free 3D modeling program.  It's available for all platforms.  The only downside is the bizarre, nonstandard user interface.  This is typical for 3D modeling programs, even pro versions--they're just their own weird thing.  Start with the official installer (use the .zip option on the Chapman lab machines, since the .exe installer needs admin access).

Check out the Blender Tutorials and Blender Doc Wiki.  In particular, I've worked over the Volcano Tutorial to the point where it almost makes sense.  Here's my super-compressed cheat sheet:
Blender starts you out with a cube.  To model anything with this, we need more polygons.  Press F9 to get to the Editing panel, and then hit "Add Multires" and then hit "Add Level" six times.  Zoom into the now-smoothed high-poly sphere with control-middle-click.   Switch to "Sculpt Mode".  Hit the "Sculpt" tab to get sculpting options.  Turn on symmetry about the X axis.  Use the "Add", "Grab", and "Smooth" tools to sculpt the object into something meaningful, like a potato.  Save the original as a .blend file.  To save a low-poly triangle version in a nice ASCII format, "Apply Multires", "Add Modifier" "Decimate", and set the decimation ratio to 0.1 or so.  Hit Apply, and File->Export as a RAW or Wavefront .obj file.

Exporting from 3D modelers to "Real Code"

So 3D modeling programs make it pretty easy to generate cool geometry.  The trick is then you've got to somehow move that geometry into your application (game, visualization app, etc).

The easiest way to do this is skip it entirely--just do all your modeling and rendering inside the 3D modeling program!  But the modeling performance of these programs usually isn't that good, and you often need to add some complicated features that would be easy in C++, but tricky in the 3D program.

The standard way to exchange data between programs is of course files.  We've looked at several very simple file formats, like the OBJ file format, but modeling programs usually support more than the very simplest "just the polygons" formats, because the modeling programs support way more than just polygons--they have colors, textures, "instanced" sub-pieces (like function calls), and transforms.

Blender supports a bunch of decent file formats:
To export a fully-rigged model, preserving all the animation and bone info, takes an industrial-strength file format (the 3D analog of a complicated image file format like JPEG!).  There's a new XML-based standard called COLLADA that attempts to be that format, but it looks pretty complicated, and it's evolving very quickly.

Vertex Buffer Object

Vertex Buffer Objects (VBOs) are a way to store geometry information on the graphics card, just like Textures let you store raster information on the graphics card.  VBOs are described in the ARB_vertex_buffer_object extension.

A VBO describes a series of glVertex (and optionally glColor, glNormal, and glTexCoord) calls.  The parameters for the calls start in a CPU array, and get copied into graphics card memory.  Depending on the CPU load and OS, VBOs can be a 10x speedup, or no faster than a bunch of bare glVertex calls--you've just got to try 'em to be sure!

You create a Vertex Buffer Object with (guess what!) glGenBuffersARB.  You then have to glBindBufferARB the buffer, and then you can then copy data in with glBufferDataARB.    You describe what your data contains using calls to glVertexPointer (and optionally glColorPointer, glNormalPointer, and glTexCoordPointer), which each take the same four parameters:
Here's how you'd create a vertex buffer object to store vertex locations and colors, then render it:
	static GLuint vb=0; 
if (vb==0) { /* set up the vertex buffer object */
glGenBuffersARB(1,&vb); /* make a buffer */
glBindBufferARB(GL_ARRAY_BUFFER_ARB,vb);
/* Copy our vtx array (on the CPU) into our new GPU buffer */
glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(myVertex)*vtx.size(),
&vtx[0],GL_STATIC_DRAW_ARB);

/* Tell OpenGL how our array is laid out */
glEnableClientState(GL_VERTEX_ARRAY);
  glVertexPointer(3,GL_FLOAT,sizeof(vtx[0]), (void *)0); /* myVertex.xyz is first thing in struct */
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer (3,GL_FLOAT,sizeof(vtx[0]), (void *)12); /* myVertex.color starts 12 bytes after struct start */
glBindBufferARB(GL_ARRAY_BUFFER_ARB,0); /* back to plain OpenGL */
}
/* Draw all our (GPU) points.
This is way faster than looping over vtx and calling glVertex many times! */
glBindBufferARB(GL_ARRAY_BUFFER_ARB,vb);
glDrawArrays(GL_POINTS,0,vtx.size());
glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);
You can also create an "element buffer" to store vertex indices.  For example, to make a triangle from vertices zero, seven, and thirteen, you'd put {0,7,13} into an element buffer.  Element buffers allow many triangles to point to the same vertex, which saves that vertex many trips through your vertex shader.  You upload the index data with glBufferDataARB (just like vertex buffer objects), and then use glDrawElements to look up your indices into your (already bound) vertex array:
	static GLuint eb=0; 
if (eb==0) { /* set up the element buffer object */
glGenBuffersARB(1,&eb); /* make a buffer */
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,vb);
/* Copy our idx array (on the CPU) into our new GPU buffer */
glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(int)*idx.size(),
&idx[0],GL_STATIC_DRAW_ARB);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,0); /* back to plain OpenGL */
}
glBindBufferARB(GL_ARRAY_BUFFER_ARB,vb); /* vertex data */
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,eb); /* index data */
glDrawElements(GL_TRIANGLES,idx.size(),GL_UNSIGNED_INT,0);
glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,0);