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:
- Left-click manipulates the currently selected object with the current tool. It often does nothing.
- Middle-click (or left and right simultaneously) rotates in 3D. Control-middle-click zooms in.
- Right-click selects.
- Spacebar brings up a context-sensitive menu.
- "Object mode" lets you translate, scale, rotate entire objects (arrange models).
- "Edit mode" lets you translate, scale, rotate, extrude vertices and faces (edit polygons).
- "Sculpt mode" lets you push and pull groups of faces (smoosh polygons like clay).
- "Vertex paint" lets you apply colors to polygon vertices.
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:
- Blender internal format, extension ".blend". A Blender-proprietary binary file format.
- RAW
triangles, which really are just "X Y Z X Y Z X
Y Z \n". Very simple to read, but the triangles aren't even
indexed, so the files are huge. Plus, there's no way to add
normals or texture coordinates.
- Wavefront .obj format,
which consists of vertex lines starting with "v X Y Z", vertex texture
coordinates like "vt S T", vertex normals "vn X Y Z", and faces, which
list the 1-based index of their vertices. Faces can be either
triangles (three vertices) or quads (four vertices). Face lines
either have the simple format "f I J K" (I J and K are a 1-based vertex
index), or with texture coordinates "f I/TI J/TJ K/TK" (TI TJ and TK
are a vertex texture coordinate index), or finally if you included
normals each face has separate normals, like "f I//NI J//NJ K//NK" (NI
NJ and NK are vertex normal index). For example, here's a
two-triangle OBJ file: the vertices are numbered 1-4, and then used by
the two faces.
# written by foolib v3.7
v 0.0 0.0 0.0
v 0.0 0.1 0.0
v 0.1 0.1 0.0
v 0.1 0.0 0.0
f 1 2 3
f 2 3 4
- VRML 1.0,
extension ".wrl". It's all ASCII, but with a strange XML-like
nested structure. VMRL can represent object instances and
transforms.
- Videoscape format,
extension ".obj". This format came from the 1980's Amiga program
"Videoscape 3D". This is NOT the same as the OBJ format we've
been using, but it is a very simple ASCII format:
"3DG1"
<number of vertices>
List of vertices:
<x y z coordinates for each vertex>
List of faces:
<number of vertices for this face> <1-based vertex numbers...> <face RGB color, in hex>
Example 3-vertex, 1-face file:
3DG1
3
0.0 0.1 0.0
0.1 0.0 0.0
0.0 0.0 0.1
3 1 2 3 0xff0000
- STL (binary) format,
extension ".stl". This format is used by 3D printers to generate
hardcopy models. This is a binary file format, but it's pretty
simple: it's an 80-byte header, followed by one little-endian 32-bit
triangle count, followed by a set of "triangle records". Each
triangle record has 12 floats: an XYZ normal (sometimes all-zeros) and three XYZ
vertices in little-endian 32-bit IEEE floating-point, followed by two
zero bytes.
- DXF (ascii) format,
extension ".dxf". This is AutoCad's "Drawing eXchange Format",
but it also supports 3D models (barely!). It's basically a long
series of AutoCad commands, and so isn't very easy to read.
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:
- size, the number of floats per vertex (usually 3; it's hardcoded to 3 for glNormalPointer!)
- type, the data type (usually GL_FLOAT, occasionally GL_SHORT to save space, or even GL_BYTE for colors)
- stride, the distance in bytes between each vertex's data. This is usually sizeof(yourVertexStruct).
- pointer, the distance in bytes from the start of the buffer
to the corresponding vertex data. This is zero for the first
element, 12 for the second vec3 (because you have to skip over the
first), 24 for the third vec3, and so on. This is passed as a
"void *", and back in the pre-vertexbuffer days could point directly to
CPU data, but now you pass the buffer data separately.
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);