Wednesday, October 3, 2007

OpenGL...

As you may have guessed from the oh-so-classy title of this post, I will be talking about OpenGL today.

I'm taking a graphics class this quarter, which entails lots of OpenGL-type things. Thankfully, our teacher has made the class fairly language agnostic, so I am free to use OpenGL from any language of my choosing (i.e. Python). Fortunately for me, there is a Python module which wraps the raw OpenGL/GLUT/GLU calls in all of their ugly C glory.

One thing to know about OpenGL is that when you use it, there is a huge clockwork mechanism that is entirely hidden from you and your program (well... as hidden as anything in C/C++ is). Every time you make a gl call, it returns immediately, many times with a void return. Every single function in OpenGL operates purely in a side-effecting manner. When you make a glVertex3fv(...) call, a vertex is transformed and placed somewhere.

Once you realize this, however, OpenGL is very much like an assembly language for your graphics card. Yes, you have to manually specify the normals for every vertex manually (there are some exceptions), but it does mean you can have a flat surface with some vertices pointing in odd directions (which will cause their polygon to be lit differently). If you had a dense enough mesh, you could conceivably get extremely cheap bumpmaping by randomly perturbing (or perturbing by some pattern) the normals of vertices.

Unfortunately (or fortunately), all of this low-levelness exists in the Python implementation as well. Because of this, your program will be rapidly bouncing back and forth from Python-land to C-land to OpenGL-land and back, which can be a bit slow... Fortunately, OpenGL has some tricks you can use to speed up it's performance in Python. One of these methods is to store Draw Lists of common commands.

For example, say you load in an 8000 vertex model. You could have a 'draw' function that goes through a list (or array) for vertices, and makes the appropriate glVertex call for each one of them. This has the consequence of being RIDICULOUSLY SLOW, but it works. Alternately, you can tell OpenGL that you are going to be compiling a Draw List with:

# This is a class method.  s is the object that the method is executed on
def makeDl(s):

  glNewList(s.dlid, GL_COMPILE)
  glPushMatrix()
  glMatrixMode(GL_MODELVIEW)

  glTranslated(*s.kw['position'])
  glScaled(*s.kw['scale'])
  for t,axis in s.kw['rotation']: glRotated(t, *axis)
  glMaterialfv(GL_FRONT, GL_DIFFUSE, s.kw['mat'])

  # s.fn is a pre-set function which could be anything from a glut primitive
  # call to a method that enumerates the points (and possibly materials) in
  # a mesh.
  s.fn(*s.args)

  glPopMatrix()
  glEndList()
When you want to have OpenGL execute the list, you just make a call to
def draw(s):
  glCallList(s.dlid)

glCallList will pull up the elaborate draw list data structure, hidden somewhere in OpenGL-land, and execute every command in it. This prevents the Python program from becoming a horrible, horrible slideshow :-), especially for large meshes.

I'm still learning bits and pieces of OpenGL to make things faster/better/bigger/more colorful, etc. Right now I'm trying to figure out more complicated materials (i.e. textremapping, other maps (normal, bump, emission, etc.), and shaders), and also how to use things like vertex buffers to let the GPU render stuff while Python chunks along. I'll figure it all out eventually ;-)