# NeHe Tutorial Lesson: 41 - Volumetric Fog
#
# Ported to PyOpenGL 2.0 by Brian Leair 18 Jan 2004
#
# This code was created by Jeff Molofee 2000
#
# The port was based on the PyOpenGL tutorials and from
# PyOpenGLContext (tests/glprint.py)
#
# If you've found this code useful, feel free to let me know
# at (Brian Leair telcom_sage@yahoo.com).
#
# See original source and C based tutorial at http://nehe.gamedev.net
#
# Note:
# -----
# This code is not an ideal example of Pythonic coding or use of OO techniques.
# It is a simple and direct exposition of how to use the Open GL API in
# Python via the PyOpenGL package. It also uses GLUT, a high quality
# platform independent library. Due to using these APIs, this code is
# more like a C program using procedural based programming.
#
# To run this example you will need:
# Python - www.python.org (v 2.3 as of 1/2004)
# PyOpenGL - pyopengl.sourceforge.net (v 2.0.1.07 as of 1/2004)
# Numeric Python - (v.22 of "numpy" as of 1/2004) numpy.sourceforge.net
# Python Image Library - http://www.pythonware.com/products/pil/
#
# Make sure to get versions of Numeric, PyOpenGL, and PIL to match your
# version of python.
#
#
#
#
# Topics demonstrated in this tutorial:
# using PIL (Python Image Library) to load a texture from an image file
# (see doc - http://www.pythonware.com/library/pil/handbook/index.htm)
# accessing the extension FOG_COORDINATE_EXTENSION
# (see doc - http://pyopengl.sourceforge.net/documentation/opengl_diffs.html)
#
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import Image # PIL
import sys
from OpenGL.GL.EXT.fog_coord import *
# *********************** Globals ***********************
# Python 2.2 defines these directly
try:
True
except NameError:
True = 1==1
False = 1==0
# Some api in the chain is translating the keystrokes to this octal string
# so instead of saying: ESCAPE = 27, we use the following.
ESCAPE = '\033'
# Number of the glut window.
window = 0
fogColor = (0.6, 0.3, 0.0, 1.0); # // Fog Colour
camz = None # // Camera Z Depth
lastTickCount = 0.0
texture = None
def next_p2 (num):
""" If num isn't a power of 2, will return the next higher power of two """
rval = 1
while (rval<num):
rval <<= 1
return rval
def BuildTexture (path):
""" // Load Image And Convert To A Texture
path can be a relative path, or a fully qualified path.
returns False if the requested image couldn't loaded as a texture
returns True and the texture ID if image was loaded
"""
# Catch exception here if image file couldn't be loaded
try:
# Note, NYI, path specified as URL's could be access using python url lib
# OleLoadPicturePath () supports url paths, but that capability isn't critcial to this tutorial.
Picture = Image.open (path)
except:
print "Unable to open image file '%s'." % (path)
return False, 0
glMaxTexDim = glGetIntegerv (GL_MAX_TEXTURE_SIZE)
WidthPixels = Picture.size [0]
HeightPixels = Picture.size [1]
if ((WidthPixels > glMaxTexDim) or (HeightPixels > glMaxTexDim)):
# The image file is too large. Shrink it to fit within the texture dimensions
# support by our rendering context for a GL texture.
# Note, Feel free to experiemnt and force a resize by placing a small val into
# glMaxTexDim (e.g. 32,64,128).
if (WidthPixels > HeightPixels):
# Width is the domainant dimension.
resizeWidthPixels = glMaxTexDim
squash = float (resizeWidthPixels) / float (WidthPixels)
resizeHeightPixels = int (HeighPixels * squash)
else:
resizeHeightPixels = glMaxTexDim
squash = float (resizeHeightPixels) / float (HeightPixels)
resizeWidthPixels = int (WidthPixels * squash)
else:
# // Resize Image To Closest Power Of Two
if (WidthPixels > HeightPixels):
# Width is the domainant dimension.
resizeWidthPixels = next_p2 (WidthPixels)
squash = float (resizeWidthPixels) / float (WidthPixels)
resizeHeightPixels = int (HeighPixels * squash)
else:
resizeHeightPixels = next_p2 (HeightPixels)
squash = float (resizeHeightPixels) / float (HeightPixels)
resizeWidthPixels = int (WidthPixels * squash)
#
# Resize the image to be used as a texture.
# The Python image library provides a handy method resize ().
# Several filtering options are available.
# If you don't specify a filtering option will default NEAREST
Picture = Picture.resize ((resizeWidthPixels, resizeHeightPixels), Image.BICUBIC)
lWidthPixels = next_p2 (resizeWidthPixels)
lHeightPixels = next_p2 (resizeWidthPixels)
# Now we create an image that has the padding needed
newpicture = Image.new ("RGB", (lWidthPixels, lHeightPixels), (0, 0, 0))
newpicture.paste (Picture)
# Create a raw string from the image data - data will be unsigned bytes
# RGBpad, no stride (0), and first line is top of image (-1)
pBits = newpicture.tostring("raw", "RGBX", 0, -1)
# // Typical Texture Generation Using Data From The Bitmap
texid = glGenTextures(1); # // Create The Texture
glBindTexture(GL_TEXTURE_2D, texid); # // Bind To The Texture ID
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); # // (Modify This For The Type Of Filtering You Want)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); # // (Modify This For The Type Of Filtering You Want)
# // (Modify This If You Want Mipmaps)
glTexImage2D(GL_TEXTURE_2D, 0, 3, lWidthPixels, lHeightPixels, 0, GL_RGBA, GL_UNSIGNED_BYTE, pBits);
# Cleanup (python actually handles all memory for you, so this isn't necessary)
# // Decrements IPicture Reference Count
Picture = None
newpicture = None
return True, texid # // Return True (All Good)
def Extension_Init ():
""" Determine if the fog coord extentsion is availble """
# After calling this, we will be able to invoke glFogCoordEXT ()
if (not glInitFogCoordEXT ()):
print "Help! No GL_EXT_ForCoord"
sys.exit(1)
return False
return True
# // Any GL Init Code & User Initialiazation Goes Here
def InitGL(Width, Height): # We call this right after our OpenGL window is created.
global fogColor
global camz
if (not Extension_Init ()): # // Check And Enable Fog Extension If Available
return False; # // Return False If Extension Not Supported
if (not BuildTexture("wall.bmp")): # // Load The Wall Texture
return False; # // Return False If Loading Failed
glEnable(GL_TEXTURE_2D); # // Enable Texture Mapping
glClearColor (0.0, 0.0, 0.0, 0.5); # // Black Background
glClearDepth (1.0); # // Depth Buffer Setup
glDepthFunc (GL_LEQUAL); # // The Type Of Depth Testing
glEnable (GL_DEPTH_TEST); # // Enable Depth Testing
glShadeModel (GL_SMOOTH); # // Select Smooth Shading
glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); # // Set Perspective Calculations To Most Accurate
# // Set Up Fog
glEnable(GL_FOG); # // Enable Fog
glFogi(GL_FOG_MODE, GL_LINEAR); # // Fog Fade Is Linear
glFogfv(GL_FOG_COLOR, fogColor); # // Set The Fog Color
glFogf(GL_FOG_START, 0.0); # // Set The Fog Start
glFogf(GL_FOG_END, 1.0); # // Set The Fog End
glHint(GL_FOG_HINT, GL_NICEST); # // Per-Pixel Fog Calculation
glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FOG_COORDINATE_EXT) # // Set Fog Based On Vertice Coordinates
camz = -19.0; # // Set Camera Z Position To -19.0f
return True; # // Return TRUE (Initialization Successful)
# The function called when our window is resized (which shouldn't happen if you enable fullscreen, below)
def ReSizeGLScene(Width, Height):
if Height == 0: # Prevent A Divide By Zero If The Window Is Too Small
Height = 1
glViewport(0, 0, Width, Height) # Reset The Current Viewport And Perspective Transformation
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
# // field of view, aspect ratio, near and far
# This will squash and stretch our objects as the window is resized.
gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
# The main drawing function.
def DrawGLScene():
global camz
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); # // Clear Screen And Depth Buffer
glLoadIdentity (); # // Reset The Modelview Matrix
glTranslatef(0.0, 0.0, camz); # // Move To Our Camera Z Position
glBegin(GL_QUADS); # // Back Wall
glFogCoordfEXT( 1.0); glTexCoord2f(0.0, 0.0); glVertex3f(-2.5,-2.5,-15.0);
glFogCoordfEXT( 1.0); glTexCoord2f(1.0, 0.0); glVertex3f( 2.5,-2.5,-15.0);
glFogCoordfEXT( 1.0); glTexCoord2f(1.0, 1.0); glVertex3f( 2.5, 2.5,-15.0);
glFogCoordfEXT( 1.0); glTexCoord2f(0.0, 1.0); glVertex3f(-2.5, 2.5,-15.0);
# PyOpenGL 2.0.1.07 has a bug. Swig generated code for FogCoordfExt ()
# uses wrong error check macro. Macro falsely sets exception due to
# call during glBegin (). Fixed in later versions.
try:
glEnd();
except:
pass
glBegin(GL_QUADS); # // Floor
glFogCoordfEXT( 1.0); glTexCoord2f(0.0, 0.0); glVertex3f(-2.5,-2.5,-15.0);
glFogCoordfEXT( 1.0); glTexCoord2f(1.0, 0.0); glVertex3f( 2.5,-2.5,-15.0);
glFogCoordfEXT( 0.0); glTexCoord2f(1.0, 1.0); glVertex3f( 2.5,-2.5, 15.0);
glFogCoordfEXT( 0.0); glTexCoord2f(0.0, 1.0); glVertex3f(-2.5,-2.5, 15.0);
try:
glEnd();
except:
pass
glBegin(GL_QUADS); # // Roof
glFogCoordfEXT( 1.0); glTexCoord2f(0.0, 0.0); glVertex3f(-2.5, 2.5,-15.0);
glFogCoordfEXT( 1.0); glTexCoord2f(1.0, 0.0); glVertex3f( 2.5, 2.5,-15.0);
glFogCoordfEXT( 0.0); glTexCoord2f(1.0, 1.0); glVertex3f( 2.5, 2.5, 15.0);
glFogCoordfEXT( 0.0); glTexCoord2f(0.0, 1.0); glVertex3f(-2.5, 2.5, 15.0);
try:
glEnd();
except:
pass
glBegin(GL_QUADS); # // Right Wall
glFogCoordfEXT( 0.0); glTexCoord2f(0.0, 0.0); glVertex3f( 2.5,-2.5, 15.0);
glFogCoordfEXT( 0.0); glTexCoord2f(0.0, 1.0); glVertex3f( 2.5, 2.5, 15.0);
glFogCoordfEXT( 1.0); glTexCoord2f(1.0, 1.0); glVertex3f( 2.5, 2.5,-15.0);
glFogCoordfEXT( 1.0); glTexCoord2f(1.0, 0.0); glVertex3f( 2.5,-2.5,-15.0);
try:
glEnd();
except:
pass
glBegin(GL_QUADS); # // Left Wall
glFogCoordfEXT( 0.0); glTexCoord2f(0.0, 0.0); glVertex3f(-2.5,-2.5, 15.0);
glFogCoordfEXT( 0.0); glTexCoord2f(0.0, 1.0); glVertex3f(-2.5, 2.5, 15.0);
glFogCoordfEXT( 1.0); glTexCoord2f(1.0, 1.0); glVertex3f(-2.5, 2.5,-15.0);
glFogCoordfEXT( 1.0); glTexCoord2f(1.0, 0.0); glVertex3f(-2.5,-2.5,-15.0);
try:
glEnd();
except:
pass
glutSwapBuffers() # // Flush The GL Rendering Pipeline
return True
# The function called whenever a key is pressed. Note the use of Python tuples to pass in: (key, x, y)
def keyPressed(*args):
global window
global camz
global lastTickCount
tickCount = glutGet (GLUT_ELAPSED_TIME)
milliseconds = (tickCount - lastTickCount)
lastTickCount = tickCount
if (milliseconds > 200):
lastTickCount = tickCount
milliseconds = 20
# If escape is pressed, kill everything.
if args[0] == ESCAPE:
sys.exit ()
if ((args[0] == GLUT_KEY_UP) and (camz < 14.0)):
camz += milliseconds / 100.0 # // Move Object Closer (Move Forwards Through Hallway)
if (args[0] == GLUT_KEY_DOWN and camz > -19.0):
camz -= milliseconds / 100.0 # // Move Object Closer (Move Backwards Through Hallway)
return
def main():
global window, lastTickCount
# pass arguments to init
glutInit(sys.argv)
# Select type of Display mode:
# Double buffer
# RGBA color
# Alpha components supported
# Depth buffer
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
# get a 640 x 480 window
glutInitWindowSize(640, 480)
# the window starts at the upper left corner of the screen
glutInitWindowPosition(0, 0)
# Okay, like the C version we retain the window id to use when closing, but for those of you new
# to Python, remember this assignment would make the variable local and not global
# if it weren't for the global declaration at the start of main.
window = glutCreateWindow("NeHe's Volumetric Fog & IPicture Image Loading Tutorial")
# Register the drawing function with glut, BUT in Python land, at least using PyOpenGL, we need to
# set the function pointer and invoke a function to actually register the callback, otherwise it
# would be very much like the C version of the code.
glutDisplayFunc(DrawGLScene)
# Uncomment this line to get full screen.
#glutFullScreen()
# When we are doing nothing, redraw the scene.
glutIdleFunc(DrawGLScene)
# Register the function called when our window is resized.
glutReshapeFunc(ReSizeGLScene)
# Register the function called when the keyboard is pressed.
# The call setup glutSpecialFunc () is needed to receive
# "keyboard function or directional keys."
glutKeyboardFunc(keyPressed)
glutSpecialFunc(keyPressed)
# We've told Glut the type of window we want, and we've told glut about
# various functions that we want invoked (idle, resizing, keyboard events).
# Glut has done the hard work of building up thw windows DC context and
# tying in a rendering context, so we are ready to start making immediate mode
# GL calls.
# Call to perform inital GL setup (the clear colors, enabling modes, and most releveant -
# consturct the displays lists for the bitmap font.
InitGL(640, 480)
# Start Event Processing Engine
glutMainLoop()
# Print message to console, and kick off the main to get it rolling.
if __name__ == "__main__":
print "Hit ESC key to quit."
main()
|