Blogs PythonFlashWindows C++
October 25 2014


Lapmaster Download
Path Finding

Rocketeer Online


Elite, The New Kind




Python, Pygame, and PyOpenGL
July 23 2019
Python is an interpretive programming language that is easy to learn and use. With pygame, you can get control of the display using a straightforward well documented interface. Pygame is primarily a 2D graphics tool. It allows you to open a window, display images, draw lines and polygons, including shaded polygons upon the screen. It does not provide functions to perform line clipping, texturing, or projection of a 3d object onto the screen. You could conceivably write python code to perform all these functions, but not only would it be a lot of code, performance would suffer since 3d graphics involves a lot of computation and python is too slow for that level of arithmetical manipulation. With PyOpenGL, however, the amount of additional code required to do 3D graphics is greatly reduced and performance is enhanced enormously by moving most of the cpu intensive computational work from the cpu to the graphics card.

This means that you can write some very complex 3D video games in python and still achieve frame rates of 30 fps or better.

There are alot of tutorials available for beginners on opengl. First they show you how to draw lines, then how to move the camera, then how to shade surfaces with vertex colors. A lot of beginner tutorials stop here, but to make really cool graphics ou need to be able to apply texures to your solid objects. So this is my beginners tutorial on that subject.

Basic questions:
1. How to define objects
2. How to draw objects
3. How to move objects
4. How to move the camera
5. How to define and manage textures

Here is a python program to draw a square on the screen using pygame and pyopengl. The square is assumed to be 40 feet long and 24 feet wide. The camera is looking along the minus z axis (into the screen) from a height of 4 feet above the square. The up and down keys move the square forward and back relative to the observer.

import pygame, OpenGL
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import time
pygame.init()
pygame.display.set_mode((640,480), DOUBLEBUF|OPENGL)
# x positive right
# y positive up
# z positive back
lanewidth = 12 # typical roadway lanes are 12 feet wide
height = 4     # height of camera above roadway
aspect = 1.0   # aspect ratio  (width/height)
length = 40    # length of a section of roadway
vertices = (
    (-lanewidth,-height,0),
    (-lanewidth,-height,-length),
    (lanewidth,-height,-length),
    (lanewidth,-height,0)
    )
# function to draw a line between a pair of vertices at location x,y,z
def line(n,m,x,y,z):
    (x1,y1,z1) = vertices[n]
    (x2,y2,z2) = vertices[m]
    glVertex3f(x1+x,y1+y,z1+z)
    glVertex3f(x2+x,y2+y,z2+z)
def slab(z):
    glLineWidth(2)
    glBegin(GL_LINES)
    line(0,1,x,y,z)
    line(1,2,x,y,z)
    line(2,3,x,y,z)
    line(3,0,x,y,z)
    glEnd()
clock = pygame.time.Clock()
(x,y,z) = (0,0,10)
twait = 0
tstart = time.time()
i = 0
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            tstop = time.time()
            elapsed = tstop-tstart
            print 'elapsed time = ',elapsed
            print 'wait time = ',twait
            print 'pct free = ',100*twait/elapsed
            exit()
#            pygame.quit()
        elif event.type == KEYDOWN:
            if event.key == K_UP:
                i = i + 1
                print i,i*length
            elif event.key == K_DOWN:
                i = i - 1
                print i,i*length
    glLoadIdentity()
    gluPerspective(45,aspect,1,1000)
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
    glTranslate(0,-height,0)
#    for i in range(500):
#        slab(-i*length)
    slab(-i*length)
    pygame.display.flip()
    t1 = time.time()
    clock.tick(30)
    t2 = time.time()
    twait = twait + t2-t1
#    print z

The display screen shows:

Pressing the up arrow move the square forty feet away from the camera and you see this.

As you move the square forward it gets smaller and smaller due to perspective. The clipping region for the camera is set to 1000 so if you push it out beyond that distance, the square disappears.

Next, here is a program to display a textured square on the screen.

import pygame, OpenGL
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import time
pygame.init()
pygame.display.set_mode((640,480), DOUBLEBUF|OPENGL)
img = pygame.image.load('image1.png')
textureData =pygame.image.tostring(img,"RGB",1)
width = img.get_width()
height = img.get_height()
im = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D,im)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, textureData)
glEnable(GL_TEXTURE_2D)
# x positive right
# y positive up
# z positive back
lanewidth = 12 # typical roadway lanes are 12 feet wide
height = 4     # height of camera above roadway
aspect = 1.0   # aspect ratio  (width/height)
length = 40    # length of a section of roadway
vertices = (
    (-lanewidth,-height,0),
    (-lanewidth,-height,-length),
    (lanewidth,-height,-length),
    (lanewidth,-height,0)
    )
# Draw a shaded polygon
def centerline(x,y,z):
    glLineWidth(2)
    glBegin(GL_LINES)
    glColor3f(1,1,1)
    (x1,y1,z1) = (0,-height,z-40)
    glVertex3f(x1,y1,z1)
    (x2,y2,z2) = (0,-height,z-30)
    glVertex3f(x2,y2,z2)
    glEnd()
    
def slab(x,y,z):
    glBegin(GL_QUADS)
    glTexCoord2f(0,0)
    glVertex3f(-lanewidth,-height,z)
    glTexCoord2f(0,1)
    glVertex3f(-lanewidth,-height,-length+z)
    glTexCoord2f(1,1)
    glVertex3f(lanewidth,-height,-length+z)
    glTexCoord2f(1,0)
    glVertex3f(lanewidth,-height,z)
    glEnd()
clock = pygame.time.Clock()
(x,y,z) = (0,0,10)
twait = 0
tstart = time.time()
i = 0
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            tstop = time.time()
            elapsed = tstop-tstart
            print 'elapsed time = ',elapsed
            print 'wait time = ',twait
            print 'pct free = ',100*twait/elapsed
            exit()
#            pygame.quit()
        elif event.type == KEYDOWN:
            if event.key == K_UP:
                i = i + 1
                print i,i*length
            elif event.key == K_DOWN:
                i = i - 1
                print i,i*length
    glLoadIdentity()
    gluPerspective(45,aspect,1,1000)
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
    glTranslate(0,-height,0)
#    for i in range(500):
#        slab(-i*length)
    slab(x,y,-i*length)
    pygame.display.flip()
    t1 = time.time()
    clock.tick(30)
    t2 = time.time()
    twait = twait + t2-t1
#    print z

The display screen shows:

If you press the up arrow to move the slab away from the camera, you see this:

Return to Main Page
08691