NeHe6 for OpenGLContext

This document discusses the NeHe6 tutorial by Jeff Molofee.  It introduces texture mapping using PIL to load Images, and the use of the OnInit customisation point for Contexts.

See NeHe5 for discussion of the Context setup procedure here...

from OpenGLContext import testingcontext
BaseContext, MainFunction = testingcontext.getInteractive()
from OpenGL.GL import *
import time

We will use PIL's "open" function to open the image we are going to use as a texture (the tutorial uses auxDIBImageLoad, PIL gives us considerably more flexibility and is fairly easy to use).  Once we've imported "open", we continue with our normal initialisation.

from Image import open

class TestContext( BaseContext ):
# set initial camera position, tutorial does the re-positioning
initialPosition = (0,0,0)

The OnInit customisation point is called after the Context has been initialised, but before any rendering passes are attempted.  It allows you to setup global resources, start new worker threads and the like.  In this case we use it to load the PIL image,  compile it to a texture ID, and store that texture ID for the rendering passes.

	def OnInit( self ):
"""Load the image on initial load of the application"""
self.imageID = self.loadImage ()

The loadImage method is just used to factor out the image loading and compiling stage, it could just as easily have been done directly in the OnInit method.  See the tutorial for explanations of the various OpenGL functions used.

	def loadImage( self, imageName = "nehe_wall.bmp" ):
"""Load an image file as a 2D texture using PIL

This method combines all of the functionality required to
load the image with PIL, convert it to a format compatible
with PyOpenGL, generate the texture ID, and store the image
data under that texture ID.

Note: only the ID is returned, no reference to the image object
or the string data is stored in user space, the data is only
present within the OpenGL engine after this call exits.
"""
im = open(imageName)
try:
# get image meta-data (dimensions) and data
ix, iy, image = im.size[0], im.size[1], im.tostring("raw", "RGBA", 0, -1)
except SystemError:
# has no alpha channel, synthesize one, see the
# texture module for more realistic handling
ix, iy, image = im.size[0], im.size[1], im.tostring("raw", "RGBX", 0, -1)
# generate a texture ID
ID = glGenTextures(1)
# make it current
glBindTexture(GL_TEXTURE_2D, ID)
glPixelStorei(GL_UNPACK_ALIGNMENT,1)
# copy the texture into the current texture ID
glTexImage2D(GL_TEXTURE_2D, 0, 3, ix, iy, 0, GL_RGBA, GL_UNSIGNED_BYTE, image)
# return the ID for use
return ID

Render is as seen before, save that we don't have a pyramid, and we call setupTexture before rendering the cube.

	def Render( self, mode = 0):
"""Render scene geometry"""
BaseContext.Render( self, mode )
glDisable( GL_LIGHTING) # context lights by default
glTranslatef(1.5,0.0,-6.0);
glRotated( time.time()%(8.0)/8 * -360, 1,0,0)
self.setupTexture()
self.drawCube()
def OnIdle( self, ):
"""Request refresh of the context whenever idle"""
self.triggerRedraw(1)
return 1

setupTexture does the render-time setup of the texture (makes it actually affect the rendered geometry).

	def setupTexture( self ):
"""Render-time texture environment setup

This method encapsulates the functions required to set up
for textured rendering. The original tutorial made these
calls once for the entire program. This organization makes
more sense if you are likely to have multiple textures.
"""
# texture-mode setup, was global in original
glEnable(GL_TEXTURE_2D)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)
# re-select our texture, could use other generated textures
# if we had generated them earlier...
glBindTexture(GL_TEXTURE_2D, self.imageID)

Drawing the cube has changed slightly, because we now need to specify the texture coordinates for each vertex. This is all just taken from the original tutorial.

	def drawCube( self ):
"""Draw a cube with texture coordinates"""
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0, 1.0);
glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0, 1.0);
glTexCoord2f(1.0, 1.0); glVertex3f( 1.0, 1.0, 1.0);
glTexCoord2f(0.0, 1.0); glVertex3f(-1.0, 1.0, 1.0);

glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0, -1.0);
glTexCoord2f(1.0, 1.0); glVertex3f(-1.0, 1.0, -1.0);
glTexCoord2f(0.0, 1.0); glVertex3f( 1.0, 1.0, -1.0);
glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0, -1.0);

glTexCoord2f(0.0, 1.0); glVertex3f(-1.0, 1.0, -1.0);
glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, 1.0, 1.0);
glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, 1.0, 1.0);
glTexCoord2f(1.0, 1.0); glVertex3f( 1.0, 1.0, -1.0);

glTexCoord2f(1.0, 1.0); glVertex3f(-1.0, -1.0, -1.0);
glTexCoord2f(0.0, 1.0); glVertex3f( 1.0, -1.0, -1.0);
glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0, 1.0);
glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0, 1.0);

glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0, -1.0);
glTexCoord2f(1.0, 1.0); glVertex3f( 1.0, 1.0, -1.0);
glTexCoord2f(0.0, 1.0); glVertex3f( 1.0, 1.0, 1.0);
glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0, 1.0);

glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0, -1.0);
glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0, 1.0);
glTexCoord2f(1.0, 1.0); glVertex3f(-1.0, 1.0, 1.0);
glTexCoord2f(0.0, 1.0); glVertex3f(-1.0, 1.0, -1.0);
glEnd()

Make the script run MainFunction with an instance of our TestContext if the module is run as a script.

if __name__ == "__main__":
## We only want to run the main function if we
## are actually being executed as a script
MainFunction ( TestContext)

You can find the complete code for this sample in OpenGLContext/tests/nehe6.py