If you are like me then you tend to write small chunks of prototype code to test various aspects of your current project before implementing them in the full build. The decreased complexity is both easier to trace and faster to compile. When projects start requiring a dedicated build machine it’s time you can’t make a quick change and recompile for poops and giggles.
What you see below is an example of how to use PyOpenGL to quickly test a shader. In this case I’ve included an xray shader that shows a dodecahedron inside the teapot.
[What You'll Need(More specifically: What I used)]
- PyOpenGL 3.0.1 (Be sure to get PyOpenGL accelerate as well)
- Python 2.6.5
The raw code….:
#! /usr/bin/env python
'''
Quaint little Python/OpenGL/Shader example
Uses the x-ray shader found in MeshLab
Code is a slightly modified version of http://www.pygame.org/wiki/GLSLExample
'''
import OpenGL
OpenGL.ERROR_ON_COPY = True
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
# PyOpenGL 3.0.1 introduces this convenience module...
from OpenGL.GL.shaders import *
import time, sys
program = None
global falloffValue
global rotY
# A general OpenGL initialization function. Sets all of the initial parameters.
def InitGL(Width, Height): # We call this right after our OpenGL window is created.
glClearColor(0.0, 0.0, 0.0, 0.0) # This Will Clear The Background Color To Black
glClearDepth(1.0) # Enables Clearing Of The Depth Buffer
glShadeModel(GL_SMOOTH) # Enables Smooth Color Shading
glMatrixMode(GL_PROJECTION)
glLoadIdentity() # Reset The Projection Matrix
# Calculate The Aspect Ratio Of The Window
gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0)
glMatrixMode(GL_MODELVIEW)
if not glUseProgram:
print 'Missing Shader Objects!'
sys.exit(1)
global program
program = compileProgram(
compileShader('''
// Application to vertex shader
varying vec3 P;
varying vec3 N;
varying vec3 I;
void main()
{
//Transform vertex by modelview and projection matrices
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
// Position in clip space
P = vec3(gl_ModelViewMatrix * gl_Vertex);
// Normal transform (transposed model-view inverse)
N = gl_NormalMatrix * gl_Normal;
// Incident vector
I = P;
// Forward current color and texture coordinates after applying texture matrix
gl_FrontColor = gl_Color;
gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
}
''',GL_VERTEX_SHADER),
compileShader('''
varying vec3 P;
varying vec3 N;
varying vec3 I;
uniform float edgefalloff;
void main()
{
float opacity = dot(normalize(N), normalize(-I));
opacity = abs(opacity);
opacity = 1.0 - pow(opacity, edgefalloff);
gl_FragColor = opacity * gl_Color;
}
''',GL_FRAGMENT_SHADER),
)
# 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()
gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0)
glMatrixMode(GL_MODELVIEW)
# The main drawing function.
def DrawGLScene():
global rotY
# Clear The Screen And The Depth Buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity() # Reset The View
# Move Left 1.5 units and into the screen 6.0 units.
glTranslatef(-1.5, 0.0, -6.0)
# Spin this business.
glRotatef(rotY,0.0,1.0,0.0)
# Enable blending and disable depth masking (x-ray shader only applies the opacity falloff.
glEnable(GL_BLEND);
glDepthMask(GL_FALSE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
# Load something with some curves
glutSolidTeapot(1.0)
# Include a lovely dodecahedron
glScalef(0.3,0.3,0.3)
glutSolidDodecahedron()
# Swap buffers
glutSwapBuffers()
rotY += 1.0
def mod_falloff(val):
global falloffValue
if program:
edgefalloff = glGetUniformLocation(program, "edgefalloff")
if not edgefalloff in (None,-1):
falloffValue = falloffValue + val
glUniform1f(edgefalloff,falloffValue)
# The function called whenever a key is pressed. Note the use of Python tuples to pass in: (key, x, y)
def keyPressed(*args):
# If escape is pressed, kill everything.
if args[0] == '\x1b':
sys.exit()
elif args[0] == 'c':
print "Decreasing falloff"
mod_falloff(1.0)
elif args[0] == 'x':
print "Increasing falloff"
mod_falloff(-1.0)
def main():
global window
global falloffValue
global rotY
# For now we just pass glutInit one empty argument. I wasn't sure what should or could be passed in (tuple, list, ...)
# Once I find out the right stuff based on reading the PyOpenGL source, I'll address this.
glutInit(sys.argv)
# Select type of Display mode:
# Double buffer
# RGBA color
# Alpha components supported
# Depth buffer
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | 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 (like myself), 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("Jeff Molofee's GL Code Tutorial ... NeHe '99")
# 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.
glutKeyboardFunc(keyPressed)
# Initialize our window.
InitGL(800, 600)
#Start using our program
glUseProgram(program)
#Set defaults
falloffValue = 1.0
rotY = 0.0
#Trigger a fall off modify to update the shader
mod_falloff(0.0)
#glEnable (GL_BLEND)
#glBlendFunc (GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
# 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."
print "x - increase shader falloff"
print "c - decrease shader falloff"
main()
OpenGL made some waves w/ their recent posting of the 4.1 specifications. Most notably the ability to save and load binary shader programs via GL_ARB_get_program_binary.
According to NVIDIA, via Ars Technica:
NVIDIA says that it will release OpenGL 4.1 drivers on or before Wednesday, with AMD (ATI) releasing them shortly. As is now traditional, Apple has given no indication of when, if ever, it will update Mac OS X’s OpenGL drivers to support the new standard; the company does not have full support for OpenGL 3.0. – ars technica
Additionally it appears that OpenGL 3.3 offers some support for this feature. OpenGL 4.1 doesn’t have any hardware specific requirements.
Usage, Ripped directly from the ARB specs.:
void retrieveProgramBinary(const GLchar* vsSource, const GLchar* fsSource,
const char* myBinaryFileName,
GLenum* binaryFormat)
{
GLuint newFS, newVS;
GLuint newProgram;
const GLchar* sources[1];
GLint success;
GLint binaryLength;
GLvoid* binary;
FILE* outfile;
//
// Create new shader/program objects and attach them together.
//
newVS = glCreateShader(GL_VERTEX_SHADER);
newFS = glCreateShader(GL_FRAGMENT_SHADER);
newProgram = glCreateProgram();
glAttachShader(newProgram, newVS);
glAttachShader(newProgram, newFS);
//
// Supply GLSL source shaders, compile, and link them
//
sources[0] = vsSource;
glShaderSource(newVS, 1, sources, NULL);
glCompileShader(newVS);
sources[0] = fsSource;
glShaderSource(newFS, 1, sources, NULL);
glCompileShader(newFS);
glProgramParameteri(newProgram, PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
glLinkProgram(newProgram);
glGetProgramiv(newProgram, GL_LINK_STATUS, &success);
if (!success)
{
//
// Fallback to simpler source shaders? Take my toys and go home?
//
}
glUseProgram(newProgram);
//
// Perform rendering and state changes likely to be encountered.
//
DoRendering(newProgram);
//
// Retrieve the binary from the program object
//
glGetProgramiv(newProgram, GL_PROGRAM_BINARY_LENGTH, &binaryLength);
binary = (GLvoid*)malloc(binaryLength);
glGetProgramBinary(newProgram, binaryLength, NULL, binaryFormat, binary);
//
// Cache the program binary for future runs
//
outfile = fopen(myBinaryFileName, "wb");
fwrite(binary, binaryLength, 1, outfile);
fclose(outfile);
free(binary);
//
// Clean up
//
glDeleteShader(newVS);
glDeleteShader(newFS);
glDeleteProgram(newProgram);
}
void loadProgramBinary(const char* myBinaryFileName, GLenum binaryFormat,
GLuint progObj)
{
GLint binaryLength;
GLvoid* binary;
GLint success;
FILE* infile;
//
// Read the program binary
//
infile = fopen(myBinaryFileName, "rb");
fseek(infile, 0, SEEK_END);
binaryLength = (GLint)ftell(infile);
binary = (GLvoid*)malloc(binaryLength);
fseek(infile, 0, SEEK_SET);
fread(binary, binaryLength, 1, infile);
fclose(infile);
//
// Load the binary into the program object -- no need to link!
//
glProgramBinary(progObj, binaryFormat, binary, binaryLength);
free(binary);
glGetProgramiv(progObj, GL_LINK_STATUS, &success);
if (!success)
{
//
// Something must have changed since the program binaries
// were cached away. Fallback to source shader loading path,
// and then retrieve and cache new program binaries once again.
//
}
}

A joke poster I made for Frosty after an amusing game where he spent his free time jacking my kills. It was all in good fun and it was by far one of our better games. I freely admit I still love spending an evening killing zombies with friends. I don’t know what other folks do but I can’t imagine a better way to unwind.

From the feedback I got it seems people aren’t keen on doing things themselves. So I’ve uploaded a full solution:
url: http://www.daksystems.net/svn/public/Spline/
Login: Guest
password: guest
As always you can access this via an SVN client or by simply using your browser, your choice. I’ve modified things slightly to use a nehe demo that includes arcball rotation for moving your scene around to view the spline. Points are still hardcoded, I’m not going to write you lot an importer. I’ve added a keystroke for switching between Bezier(‘b’) and Catmull-Rom(‘c’) splines. This will give you an idea of how these two curves are different.
It was noted that my implementation doesn’t properly include end points. That is sort of true, to connect to the start and end curve points just double them up. IE, have 2 copies of your start and 2 copies of your end.
As an aside I’d like to thank Makeeva for the wonderful private comment that reads as follows:
“Dont post exampels if you dont have everything there!!! ive spent a hour now and havent gotten it working. you’re program is like you, dumb.”
Allow me to take this moment to apologize for not providing the proper hand holding to go along with my 4:00a.m. post. As proper punishment for my transgression I will hand convert all of my code to assembly before recompiling my next example.
.model small
.stack
.data
message db "Not bloody likely", "$"
.code
main proc
mov ax,seg message
mov ds,ax
mov ah,09
lea dx,message
int 21h
mov ax,4c00h
int 21h
main endp
end main
I love coding, I really do. I’m not saying I want to spend the rest of my life writing code but there is something cathartic about the process. Our lives are filled with so many instabilities that it is nice to sit down w/ a system based purely on logic and know that following the rules will lead to an expected result.
Some posts back I made a reference to catmull-rom splines but the code was messed up during the copy process to the blog. Here today I’m going to attach a single .cpp file that will create a spline and render it in OpenGL. Easy right?
Splines.cpp
95% of this code is NeHe’s wonderfully simplistic OpenGL tutorial. That’s how little code it takes to spline. Given that it is 3:30am. you’ll have to forgive my lack of comments in the Spline.cpp file. But these should suffice:
///catmull-rom spline (t should range from 0-1)
double catmullrom(double t, double p0,double p1,double p2,double p3){
double t2 = t*t;
double t3 = t2 * t;
return (0.5 *( (2 * p1) + (-p0 + p2) * t +(2*p0 - 5*p1 + 4*p2 - p3) * t2 +(-p0 + 3*p1- 3*p2 + p3) * t3));
}
///create a dummy point vector, points here are chosen @ random
void FillPointVector(){
pointList.push_back(vector3(-500,700,10));
pointList.push_back(vector3(-300,12,10));
pointList.push_back(vector3(-300,12,10));
pointList.push_back(vector3(-100,12,10));
pointList.push_back(vector3(-50,45,0));
pointList.push_back(vector3(0,0,20));
pointList.push_back(vector3(50,0,15));
pointList.push_back(vector3(200,350,30));
pointList.push_back(vector3(350,0,20));
pointList.push_back(vector3(356,35,60));
pointList.push_back(vector3(400,67,15));
pointList.push_back(vector3(424,122,0));
pointList.push_back(vector3(450,1,0));
pointList.push_back(vector3(500,0,0));
}
///Build full path;
void BuildPath(){
FillPointVector();
printf("Point vector filled: %d\n",pointList.size());
float granularity = 0.1f; // This refers to the step size between points along a spline.
printf("Filled line list...");
for(int c = 0; c < pointList.size()-3; c++){
vector3 p0,p1,p2,p3;
p0 = pointList[c];
p1 = pointList[c+1];
p2 = pointList[c+2];
p3 = pointList[c+3];
for(float i = 0; i <= 1; i+=granularity){
vector3 pt;
pt.x = catmullrom(i,p0.x,p1.x,p2.x,p3.x);
pt.y = catmullrom(i,p0.y,p1.y,p2.y,p3.y);
pt.z = catmullrom(i,p0.z,p1.z,p2.z,p3.z);
///ignore this crap
if(i==0){
minPt = maxPt = pt;
} else {
minPt = vector3::Min(minPt,pt);
maxPt = vector3::Max(maxPt,pt);
}
lineList.push_back(pt);
}
printf(".");
}
printf("Complete[%d]\n",lineList.size());
printf("Sample: %f %f %f\n",lineList[0].x,lineList[0].y,lineList[0].z);
//make sure line list is EVEN or we'll run in to problems w/ OpenGL.
if(lineList.size() % 2 != 1 && lineList.size() != 0)lineList.pop_back();
}
Take note that splines are made up of 4 control points and we merge these to form one cohesive path.