Freitag, 31. Juli 2009

Fire!

This is the old-school fire effect from the 90's, rendered in OpenGL. It uses a combination of Buffer Feedback and Palette rendering, both implemented in GLSL.



Quite a few things that I deemed infeasible to implement back then are easily possible in todays GC's, among them: dynamic convolution patterns, interpolation, correct side-to-side wrapping (clamp vertically, but wrap horizontally), arbitrary "fire pixel size". The example doesn't demonstrate this though.

It even should be possible to distort the fire eg. to flow around objects, by simply not drawing a simple square in the blur/displace step but something else. However, due to the exponential nature of feedback processes, one ends up with bizarrely distorted images frequently.

It appeared necessary to use two textures, since you don't want to be overwriting the texture you're reading from. However, I'm not aware if render-to-texture would overwrite a texture while you're rendering instead of dumping all changes from a frame buffer after rendering. I don't even know if this is specified


# Pygame/PyopenGL example by Bastiaan Zapf, Jul/Aug 2009

# render a fire effect via blurring and displacing feedback
# (similar to video feedback)

# this effect was ubiquitous in the 90's, often used in
# cracker's intros, demos and the like

from OpenGL.GL import *
from OpenGL.GLU import *

from OpenGL.GL.ARB.framebuffer_object import *
from OpenGL.GL.EXT.framebuffer_object import *

from ctypes import *

from math import *
import pygame
import random
import screenshot
import time
import numpy # for black textures
from shaderlib import *

pygame.init()

screenx=400
screeny=300

pygame.display.set_mode((screenx,screeny), pygame.OPENGL|pygame.DOUBLEBUF)

glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

# Create and Compile fragment and vertex shaders

# Texturing

vertex_shader=createAndCompileShader(GL_VERTEX_SHADER,"""
varying vec3 v;
varying vec3 N;

void main(void)
{

v = gl_ModelViewMatrix * gl_Vertex;
N = gl_NormalMatrix * gl_Normal;

gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord[0]=gl_TextureMatrix[0] * gl_MultiTexCoord0;
}
""");

# Convolution shader - this is the Code that implements
# the blur between two frames
# this will be expensive.

fire_shader=createAndCompileShader(GL_FRAGMENT_SHADER,"""

const int MaxKernelSize=25;
uniform vec3 OffsetWeight[MaxKernelSize];
uniform int KernelSize;
uniform sampler2D BaseImage;
varying vec3 N;
varying vec3 v;

void main(void)
{
int i;
vec4 sum=vec4(0.0);
for (i=0;i<KernelSize;i++) {
sum+=texture2D(BaseImage,gl_TexCoord[0].st+OffsetWeight[i].st)*OffsetWeight[i].z;
}
gl_FragColor=sum;
}
""");

# build fire shader program

fire_program=glCreateProgram()
glAttachShader(fire_program,vertex_shader)
glAttachShader(fire_program,fire_shader)
glLinkProgram(fire_program)

# reference the shader variables

locOW = glGetUniformLocation(fire_program,"OffsetWeight");
locKS = glGetUniformLocation(fire_program,"KernelSize");
locBI = glGetUniformLocation(fire_program,"BaseImage");

# try to activate/enable shader program
# handle errors wisely

try:
glUseProgram(fire_program)
except OpenGL.error.GLError:
print glGetProgramInfoLog(fire_program)
raise


# Monochrome to fire colors shader

color_shader=createAndCompileShader(GL_FRAGMENT_SHADER,"""

const int MaxKernelSize=25;
uniform sampler2D BaseImage;
varying vec3 N;
varying vec3 v;

void main(void)
{
int i;
vec4 sum=vec4(0.0);
sum=texture2D(BaseImage,gl_TexCoord[0].st+vec2(0,0));

if (sum.x<0.1) {
gl_FragColor=vec4(0.0,0.0,0.0,0.0);
} else if (sum.x<0.2) {
gl_FragColor=vec4((sum.x-0.1)*10.0,0.0,0.0,0.0);
} else if (sum.x<0.3) {
gl_FragColor=vec4(1.0,(sum.x-0.2)*10.0,0.0,0.0);
} else if (sum.x<0.4) {
gl_FragColor=vec4(1.0,1.0,(sum.x-0.3)*10.0,0.0);
} else {
gl_FragColor=vec4(1.0,1.0,1.0,0.0);
}
}
""");



colors_program=glCreateProgram()
glAttachShader(colors_program,vertex_shader)
glAttachShader(colors_program,color_shader)
glLinkProgram(colors_program)

# try to activate/enable shader program
# handle errors wisely

try:
glUseProgram(colors_program)
except OpenGL.error.GLError:
print glGetProgramInfoLog(colors_program)
raise

# return to fire program to set variables

glUseProgram(fire_program);
glMatrixMode(GL_MODELVIEW);

# set a cheap convolution kernel of length 3

glUniform1i(locKS,3);
glUniform3fv(locOW,3,
[[ 0.001, 0.0 , 0.32],
[ -0.001, 0.0 , 0.32],
[ 0 , -0.001, 0.32],
]); # just some blur

glUniform1i(locBI,0); # we will be using the first texturing unit

# get a monochromatic texture of the size specified below - low resolution
# will mean large flames and cheap calculation

firew=64
fireh=64

texture=glGenTextures( 1 )

glBindTexture( GL_TEXTURE_2D, texture );
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_REPEAT);
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_REPEAT );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

glTexImage2D(GL_TEXTURE_2D, 0,GL_LUMINANCE,
firew,fireh,0,GL_LUMINANCE,GL_FLOAT,numpy.zeros([firew,fireh,1],float))

glEnable( GL_TEXTURE_2D );
glBindTexture( GL_TEXTURE_2D, texture );


# generate another texture we render the fire to, and set parameters

newtexture=glGenTextures( 1 )

glBindTexture( GL_TEXTURE_2D, newtexture );
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_REPEAT );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_REPEAT );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

glTexImage2D(GL_TEXTURE_2D, 0,GL_LUMINANCE,firew,fireh,0,GL_LUMINANCE,
GL_FLOAT, numpy.zeros([firew,fireh,1],float))


# other choices for the texture wrapping options might be
# benefitial

done = False

t=0

# prepare a frame buffer object

fbo=c_uint(1) # WTF? Did not find a way to get there easier
# A simple number would always result in a "Segmentation
# Fault" for me

glGenFramebuffers(1,fbo)

while not done:

t=t+1

# rotation and displacement during fire blur

alpha=sin(t/73.0)

x=0
y=0.004+(cos(t/43.0)+1.0)/50.0

# render to texture (render next step of fire effect)

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo)

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, newtexture, 0)

glPushAttrib(GL_VIEWPORT_BIT) # save viewport

glViewport(0, 0, firew, fireh) # set to fire effect dimensions

glUseProgram(fire_program) # use fire blur

# first draw blurred/displaced fire

glBindTexture( GL_TEXTURE_2D, texture )
glLoadIdentity()

glTranslatef(x,y,0)
glRotatef(alpha,0,0,1)

glBegin(GL_QUADS)

glTexCoord2f(0,0)
glVertex2f(-1,-1)
glTexCoord2f(1,0)
glVertex2f( 1,-1)
glTexCoord2f(1,1)
glVertex2f( 1, 1)
glTexCoord2f(0,1)
glVertex2f(-1, 1)

glEnd()

# then draw a few random pixels at the bottom to get the fire going

glUseProgram(0) # shaders interfer with pure pixel drawing

glBindTexture( GL_TEXTURE_2D, 0 ) # textures do so as well

for i in range(0,firew):
glRasterPos2f(-1+2*i/(firew+0.0),-0.999)
glDrawPixelsf(GL_LUMINANCE,[[[random.random()]]])

# restore viewport

glPopAttrib(GL_VIEWPORT_BIT)

# don't render to texture anymore

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)

# fire "palette"
glLoadIdentity()
glUseProgram(colors_program)
glBindTexture( GL_TEXTURE_2D, texture )

# draw fire once, however, clip noise from top and bottom

glBegin(GL_QUADS)

glTexCoord2f(0,0.03)
glVertex3f(-1,-1,0)
glTexCoord2f(1,0.03)
glVertex3f( 1,-1,0)
glTexCoord2f(1,0.97)
glVertex3f( 1, 1,0)
glTexCoord2f(0,0.97)
glVertex3f(-1, 1,0)

glEnd()

pygame.display.flip()

# flip textures

oldtexture=texture
texture=newtexture
newtexture=oldtexture

time.sleep(0.01)
t=t+1

Sonntag, 3. Mai 2009

Projective Texture Mapping





This script will create a Texture containing a Ring image, then it will project this on some green cubes ("project" as in "overhead projector", not as in "flat projection"). This is usually called "projective Texturing". The secret in this is the GLSL Function texture2DProj, which does nothing more than to divide by the (usually useless) last Texture coordinate. Be careful however, as, since if you specify a vec4, the homogenous fourth coordinate, which is constant, will be used. Then you'd get a Projection without perspective.

Try using "GL_REPEAT" for the projected texture's wrapping, it looks fun.

The Distance measurement between projector and projection actually is a little clumsy, i bet there's a better way, but this one works so far.


# Pygame/PyopenGL example by Bastiaan Zapf, May 2009
#
# Projective Textures
#
# Employed techniques:
#
# - Vertex and Fragment shaders
# - Display Lists
# - Texturing

from OpenGL.GL import *
from OpenGL.GLU import *
import random
from math import * # trigonometry
import numpy

import pygame # just to get a display

import Image
import sys
import time

# get an OpenGL surface

pygame.init()
pygame.display.set_mode((800,600), pygame.OPENGL|pygame.DOUBLEBUF)

def jpg_file_write(name, number, data):
im = Image.frombuffer("RGBA", (800,600), data, "raw", "RGBA", 0, 0)
fnumber = "%05d" % number
im.save(name + fnumber + ".jpg")


# Create and Compile a shader
# but fail with a meaningful message if something goes wrong

def createAndCompileShader(type,source):
shader=glCreateShader(type)
glShaderSource(shader,source)
glCompileShader(shader)

# get "compile status" - glCompileShader will not fail with
# an exception in case of syntax errors

result=glGetShaderiv(shader,GL_COMPILE_STATUS)

if (result!=1): # shader didn't compile
raise Exception("Couldn't compile shader\nShader compilation Log:\n"+glGetShaderInfoLog(shader))
return shader

vertex_shader=createAndCompileShader(GL_VERTEX_SHADER,"""

varying vec3 normal, lightDir;

void main()
{
normal = gl_NormalMatrix * gl_Normal;
vec4 posEye = gl_ModelViewMatrix * gl_Vertex;

// Put World coordinates of Vertex, multiplied by TextureMatrix[0]
// into TexCoord[0]

gl_TexCoord[0] = gl_TextureMatrix[0]*gl_ModelViewMatrix*gl_Vertex;

// LightSource[0] position is assumed to be the projector position

lightDir = vec3(gl_LightSource[0].position.xyz - posEye.xyz);
gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;
}
""");

fragment_shader=createAndCompileShader(GL_FRAGMENT_SHADER,"""
uniform sampler2D projMap;
varying vec3 normal, lightDir;

void main (void)
{
vec4 final_color = vec4(0.0,0.5,0,0.3);
vec3 N = normalize(normal);
vec3 L = normalize(lightDir);

float lambert = dot(N,L);

if( gl_TexCoord[0].z>0.0 // in front of projector?
&&
lambert>0 ) // facing projector?
{

// project texture - see notes for pitfall

vec4 ProjMapColor = texture2DProj(projMap, gl_TexCoord[0].xyz);
final_color += ProjMapColor*lambert*pow(length(L),-2.0);
}

gl_FragColor = final_color;
}
""");

# build shader program

program=glCreateProgram()
glAttachShader(program,vertex_shader)
glAttachShader(program,fragment_shader)
glLinkProgram(program)

# try to activate/enable shader program
# handle errors wisely

try:
glUseProgram(program)
except OpenGL.error.GLError:
print glGetProgramInfoLog(program)
raise

done = False

t=0

# load a cube into a display list

glNewList(1,GL_COMPILE)

glBegin(GL_QUADS)

glColor3f(1,1,1)

glNormal3f(0,0,-1)
glVertex3f( -1, -1, -1)
glVertex3f( 1, -1, -1)
glVertex3f( 1, 1, -1)
glVertex3f( -1, 1, -1)

glNormal3f(0,0,1)
glVertex3f( -1, -1, 1)
glVertex3f( 1, -1, 1)
glVertex3f( 1, 1, 1)
glVertex3f( -1, 1, 1)

glNormal3f(0,-1,0)
glVertex3f( -1, -1, -1)
glVertex3f( 1, -1, -1)
glVertex3f( 1, -1, 1)
glVertex3f( -1, -1, 1)

glNormal3f(0,1,0)
glVertex3f( -1, 1, -1)
glVertex3f( 1, 1, -1)
glVertex3f( 1, 1, 1)
glVertex3f( -1, 1, 1)

glNormal3f(-1,0,0)
glVertex3f( -1, -1, -1)
glVertex3f( -1, 1, -1)
glVertex3f( -1, 1, 1)
glVertex3f( -1, -1, 1)

glNormal3f(1,0,0)
glVertex3f( 1, -1, -1)
glVertex3f( 1, 1, -1)
glVertex3f( 1, 1, 1)
glVertex3f( 1, -1, 1)

glEnd()
glEndList()

texture=glGenTextures( 1 )

glActiveTexture(GL_TEXTURE0); # use first texturing unit
glBindTexture( GL_TEXTURE_2D, texture );

glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_CLAMP ); # try GL_REPEAT for fun
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_CLAMP );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR)

texdata=numpy.zeros((256,256,4))

# a ring-shaped projection texture

for i in range(0,256):
x=(i-128.1)/128.0
for j in range(0,256):
y=(j-128.1)/128.0
if ((x*x+y*y>0.9) | (x*x+y*y<0.3)):
texdata[i][j][0]=0
texdata[i][j][1]=0
texdata[i][j][2]=0
texdata[i][j][3]=0
else:
texdata[i][j][0]=1
texdata[i][j][1]=1
texdata[i][j][2]=1
texdata[i][j][3]=1

glTexImage2Df(GL_TEXTURE_2D, 0,GL_RGBA,0,GL_RGBA,
texdata)

loc=glGetUniformLocation(program,"projMap");
glUniform1i(loc, 0) # use first texturing unit in shader

glEnable(GL_DEPTH_TEST)

while not done:

t=t+1

# Projector position and angle - this is rather rough so far

ppos=[sin(t/260.0)*4,cos(t/240.0)*4,0]
palpha=t
pbeta=t/3.0

# the texture matrix stores the intended projection
# however, signs seem to be reversed. This somehow makes sense, as
# we're transforming the texture coordinates

glMatrixMode(GL_TEXTURE);
glLoadIdentity()

glRotate(-palpha,0,1,0);
glRotate(-pbeta ,0,0,1);
glTranslate(-ppos[0],-ppos[1],-ppos[2])

# set light source position

glLightfv(GL_LIGHT0,GL_POSITION,[ppos[0],ppos[1],ppos[2]]);

# Set view

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(90,1,0.01,1000)
gluLookAt(sin(t/200.0)*8,sin(t/500.0)*3+8,cos(t/200.0)*8,0,0,0,0,1,0)

glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

glMatrixMode(GL_MODELVIEW)

# draw a triangle to visualize the projector
# i don't have a clue how to relate the angles correctly here

glLoadIdentity()

glTranslate(ppos[0],ppos[1],ppos[2])

glBegin(GL_TRIANGLES)

glVertex3f(1,0,0)
glVertex3f(0,1,0)
glVertex3f(0,0,1)

glEnd()

# fallback

glColor3f(1,1,1)

glLoadIdentity()

# render a range of cubes

for i in range(-1,2):
for j in range(-1,2):
for k in range(-1,2):
glPushMatrix()
glTranslate(i*5,j*5,k*5)
glScale(1,1,1)
glCallList(1)
glPopMatrix()

time.sleep(0.01);

pygame.display.flip()

Mittwoch, 29. April 2009

Basic Shading





The following script demonstrates basic shading. A Vertex Shader and a Fragment (Pixel) Shader are defined. Vertex shaders are necissary to transfer important data to the Pixel shader which isn't accessible otherwise. A display list is used to keep the number of calls low. A (moving) light source position is transferred from python to GLSL via the GL_LIGHT0 variables.

I think it is curious to call the (implicit) cosine (in GLSL "dot(L,N)") "Lambert's law", which is occasionally the case. Lambert's law states that a glowing surface will look the same from every angle. This is implicit in that the color of a fragment will not change depending on the viewing angle (except if you specify it to do so). The point in this dot product is that a face will appear dimmer when struck by light at an acute angle, because it will span less of a solid angle viewed from the light source. This is not the opposite of Lambert's law or something.


# Pygame/PyopenGL example by Bastiaan Zapf, Apr 2009
#
# Draw a range of cubes, light them prettily
#
# Employed techniques:
#
# - Vertex and Fragment shaders
# - Lightsource variables
# - Display Lists


from OpenGL.GL import *
from OpenGL.GLU import *
import random
from math import * # trigonometry

import pygame # just to get a display

import Image
import sys
import time

# get an OpenGL surface

pygame.init()
pygame.display.set_mode((800,600), pygame.OPENGL|pygame.DOUBLEBUF)

def jpg_file_write(name, number, data):
im = Image.frombuffer("RGBA", (800,600), data, "raw", "RGBA", 0, 0)
fnumber = "%05d" % number
im.save(name + fnumber + ".jpg")


glEnable(GL_DEPTH_TEST)

# Create and Compile a shader
# but fail with a meaningful message if something goes wrong

def createAndCompileShader(type,source):
shader=glCreateShader(type)
glShaderSource(shader,source)
glCompileShader(shader)

# get "compile status" - glCompileShader will not fail with
# an exception in case of syntax errors

result=glGetShaderiv(shader,GL_COMPILE_STATUS)

if (result!=1): # shader didn't compile
raise Exception("Couldn't compile shader\nShader compilation Log:\n"+glGetShaderInfoLog(shader))
return shader

# Create and Compile fragment and vertex shaders
# Transfer data from fragment to vertex shader

vertex_shader=createAndCompileShader(GL_VERTEX_SHADER,"""
varying vec3 v;
varying vec3 N;

void main(void)
{

v = gl_ModelViewMatrix * gl_Vertex;
N = gl_NormalMatrix * gl_Normal;

gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

}
""");

fragment_shader=createAndCompileShader(GL_FRAGMENT_SHADER,"""
varying vec3 N;
varying vec3 v;

void main(void)
{
vec3 L = gl_LightSource[0].position.xyz-v;

// "Lambert's law"? (see notes)
// Rather: faces will appear dimmer when struck in an acute angle
// distance attenuation

float Idiff = max(dot(normalize(L),N),0.0)*pow(length(L),-2.0);

gl_FragColor = vec4(0.5,0,0.5,1.0)+ // purple
vec4(1.0,1.0,1.0,1.0)*Idiff; // diffuse reflection
}
""");

# build shader program

program=glCreateProgram()
glAttachShader(program,vertex_shader)
glAttachShader(program,fragment_shader)
glLinkProgram(program)

# try to activate/enable shader program
# handle errors wisely

try:
glUseProgram(program)
except OpenGL.error.GLError:
print glGetProgramInfoLog(program)
raise

done = False

t=0

# load a cube into a display list

glNewList(1,GL_COMPILE)

glBegin(GL_QUADS)

glColor3f(1,1,1)

glNormal3f(0,0,-1)
glVertex3f( -1, -1, -1)
glVertex3f( 1, -1, -1)
glVertex3f( 1, 1, -1)
glVertex3f( -1, 1, -1)

glNormal3f(0,0,1)
glVertex3f( -1, -1, 1)
glVertex3f( 1, -1, 1)
glVertex3f( 1, 1, 1)
glVertex3f( -1, 1, 1)

glNormal3f(0,-1,0)
glVertex3f( -1, -1, -1)
glVertex3f( 1, -1, -1)
glVertex3f( 1, -1, 1)
glVertex3f( -1, -1, 1)

glNormal3f(0,1,0)
glVertex3f( -1, 1, -1)
glVertex3f( 1, 1, -1)
glVertex3f( 1, 1, 1)
glVertex3f( -1, 1, 1)

glNormal3f(-1,0,0)
glVertex3f( -1, -1, -1)
glVertex3f( -1, 1, -1)
glVertex3f( -1, 1, 1)
glVertex3f( -1, -1, 1)

glNormal3f(1,0,0)
glVertex3f( 1, -1, -1)
glVertex3f( 1, 1, -1)
glVertex3f( 1, 1, 1)
glVertex3f( 1, -1, 1)

glEnd()
glEndList()

while not done:

t=t+1

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(90,1,0.01,1000)
gluLookAt(sin(t/260.0)*4,cos(t/260.0)*4,cos(t/687.0)*3,0,0,0,0,1,0)

glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

glMatrixMode(GL_MODELVIEW)

# calculate light source position

ld=[sin(t/16.0)*4.0,sin(t/20.0)*4.0,cos(t/16.0)*4.0]

# pass data to fragment shader

glLightfv(GL_LIGHT0,GL_POSITION,[ld[0],ld[1],ld[2]]);

# fallback

glColor3f(1,1,1)

glLoadIdentity()
# render a pretty range of cubes

for i in range(-5,5):
for j in range(-5,5):
for k in range(-5,5):
glPushMatrix()

glTranslate(i,j,k)
glScale(0.1,0.1,0.1)
glCallList(1)
glPopMatrix()

pygame.display.flip()

Samstag, 11. April 2009

Render to Texture





This Program demonstrates a "render to texture" effect. It uses the "Framebuffer Management"-Technique, which has superseded the "Render to Back buffer, GlCopyImage2D, Overwrite Back buffer"-Technique for a few years now.

# Pygame/PyopenGL example by Bastiaan Zapf, Apr 2009
#
# "Render to Texture" demonstration: Render a helix to a
# texture and project it onto the faces of a cube pattern.
#


from OpenGL.GL import *
from OpenGL.GLU import *

from OpenGL.GL.ARB.framebuffer_object import *
from OpenGL.GL.EXT.framebuffer_object import *

from ctypes import *

from math import *
import pygame

pygame.init()

pygame.display.set_mode((800,600), pygame.OPENGL|pygame.DOUBLEBUF)

glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

glMatrixMode(GL_MODELVIEW);

done = False

t=0

while not done:

# step time

t=t+1

# render a helix (this is similar to the previous example, but
# this time we'll render to a texture)

# initialize projection

glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

glMatrixMode(GL_PROJECTION);
glLoadIdentity()

gluPerspective(90,1,0.01,1000)
gluLookAt(sin(t/200.0)*2,sin(t/500.0)*2,cos(t/200.0)*2,0,0,0,0,1,0)
glMatrixMode(GL_MODELVIEW)

# generate the texture we render to, and set parameters

rendertarget=glGenTextures( 1 )

glBindTexture( GL_TEXTURE_2D, rendertarget );
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_REPEAT);
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_REPEAT );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

# occupy 512x512 texture memory

glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA,512,512,0,GL_RGBA,
GL_UNSIGNED_INT, None)

# This is the interesting part: render-to-texture is initialized here

# generate a "Framebuffer" object and bind it

fbo=c_uint(1) # WTF? Did not find a way to get there easier
# A simple number would always result in a "Segmentation
# Fault" for me
glGenFramebuffers(1,fbo)
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

# render to the texture

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, rendertarget, 0);

# In case of errors or suspect behaviour, try this:
# print "Framebuffer Status:"
# print glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

# Save Viewport configuration

glPushAttrib(GL_VIEWPORT_BIT);

# Align viewport to Texture dimensions - Try rendering
# to different dimensions than the texture has to find out
# about how your hardware handles display pitch

glViewport(0, 0, 512, 512);

glEnable(GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE);

# Black background for the Helix

glClearColor(0.0, 0.0, 0.0, 0.0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

# Fallback to white

glColor4f(1,1,1,1);

# But try a fancy texture

texture=glGenTextures( 1 )

glBindTexture( GL_TEXTURE_2D, texture );
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_REPEAT);
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_REPEAT );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

texdata=[[[1.0,0,0,1],
[1.0,1,1,1],
[0.0,1,0,1],
[1.0,1,1,1]],
[[1.0,0,0,1],
[1.0,0,0,1],
[1.0,0,0,0.5],
[0.0,0,0,1]],
[[0.0,1,0,1],
[0.0,0,0,0],
[0.0,0,1,1],
[0.0,0,0,0]],
[[0.0,0,0,1],
[0.0,0,0,1],
[0.0,0,0,1],
[0.0,0,0,1]]];

glTexImage2Df(GL_TEXTURE_2D, 0,4,0,GL_RGBA,
texdata)

glEnable( GL_TEXTURE_2D );
glBindTexture( GL_TEXTURE_2D, texture );

# The helix

glBegin(GL_TRIANGLE_STRIP);

for i in range(0,100):

r=5.0;
if (i%2==0):
glTexCoord2f(0,i);
glVertex3f( cos(i/r)*1.5, -2.5+i*0.05, sin(i/r)*1.5);
else:
glTexCoord2f(1,i);
glVertex3f( cos(i/r+2)*1.5, -2.5+i*0.05, sin(i/r+2)*1.5);


glEnd();
glPopAttrib(); # Reset viewport to screen format
# do not render to texture anymore - "switch off" rtt
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

glDisable( GL_BLEND );
glEnable( GL_TEXTURE_2D );
glEnable( GL_DEPTH_TEST );

glMatrixMode(GL_PROJECTION);
glLoadIdentity()

gluPerspective(90,1,0.01,1000)

gluLookAt(-1.4+sin(t/100.0)*2.5,
-1.4+sin(t/90.0)*0.4,
-1.4+sin(t/100.0+3.14)*0.5,
0,0,0,
0,1,0)

glMatrixMode(GL_MODELVIEW);

# use the rendered texture from above!
glBindTexture( GL_TEXTURE_2D, rendertarget );

glBegin(GL_QUADS)
glColor3f(1,1,1) # fallback

# render a range of half cubes

for x in range(-2,3,2):
for y in range(-2,3,2):
glTexCoord2f(0,0);
glVertex3f(-1-x, -1+x+y, 1-y)
glTexCoord2f(0,1);
glVertex3f(-1-x, 1+x+y, 1-y)
glTexCoord2f(1,1);
glVertex3f(1-x, 1+x+y, 1-y)
glTexCoord2f(1,0);
glVertex3f(1-x, -1+x+y, 1-y)

glTexCoord2f(1,1);
glVertex3f(1-x,-1+x+y, -1-y)
glTexCoord2f(1,0);
glVertex3f(1-x,-1+x+y, 1-y)
glTexCoord2f(0,0);
glVertex3f(1-x,1+x+y, 1-y)
glTexCoord2f(0,1);
glVertex3f(1-x,1+x+y, -1-y)

glTexCoord2f(1,1);
glVertex3f(-1-x,1+x+y, -1-y)
glTexCoord2f(0,1);
glVertex3f(-1-x,1+x+y, 1-y)
glTexCoord2f(0,0);
glVertex3f(1-x,1+x+y, 1-y)
glTexCoord2f(1,0);
glVertex3f(1-x,1+x+y, -1-y)

glEnd()
glFlush()

pygame.display.flip()

# do not leak any mem

glDeleteTextures(texture)
glDeleteTextures(rendertarget)
glDeleteFramebuffers(1,fbo)

Samstag, 4. April 2009

Screenshots from OpenGL

The shortest way I found to bring OpenGL screenshots to the hard drive is using the function "frombuffer" from the "imaging Library" "Image", which understands the same format that OpenGL outputs.

Documentation for Image
Documentation for glReadPixels


screenshot = glReadPixels( 0,0, 800, 600, GL_RGBA, GL_UNSIGNED_BYTE)
im = Image.frombuffer("RGBA", (800,600), screenshot, "raw", "RGBA", 0, 0)
im.save("test.jpg")

Bizarro Error "AttributeError: __class__"

In case of errors of the following form:


$ python test.py
No handlers could be found for logger "OpenGL.arrays.arraydatatype"
Traceback (most recent call last):
File "test.py", line 135, in
jpg_file_write("helix/helix", t, glReadPixels( 0,0, 800, 600, GL_RGBA, GL_UNSIGNED_BYTE))
File "/var/lib/python-support/python2.5/OpenGL/GL/images.py", line 319, in glReadPixels
imageData = arrayType.dataPointer(array)
File "/var/lib/python-support/python2.5/OpenGL/arrays/arraydatatype.py", line 38, in dataPointer
return cls.getHandler(value).dataPointer( value )
File "/var/lib/python-support/python2.5/OpenGL/arrays/formathandler.py", line 17, in __call__
typ = value.__class__
AttributeError: __class__


Try to install the latest PyOpenGL package.

Freitag, 3. April 2009

Rotating Helix





The following script will display a rotating Helix. It depends on both PyGame and PyOpenGL.



It demonstrates:
  • Texturing
  • Blending (Alpha Channel)
  • Projection/Modelview Basics
  • Fun with Trigonometric Functions
  • Animation (Double Buffered)
It also demonstrates missing Optimisation:

  • The Texture does not have to be reloaded every frame. Animation (see comments for Variable "pulse") would be harder, but could be achieved by blending textures (or blending display data, if blending textures isn't possible)
  • The Helix vertex data could be stored in a Display List. However, we'd lose the possibility to modify the Helix. A vertex shader might be able to do that in the Graphics Controller.
  • The shown version might act as a template for coarse benchmarks. (eg. for answering the question "How many fps will I get when i Upload a 512x512 texture every frame?")


I hope someone will find this fun and educating. Read the comments to get some suggestions about what to toy with first. Comments welcome.

# Pygame/PyopenGL example by Bastiaan Zapf, Apr 2009
#
# Draw an helix, wiggle it pleasantly
#
# Keywords: Alpha Blending, Textures, Animation, Double Buffer

from OpenGL.GL import *
from OpenGL.GLU import *

from math import * # trigonometry

import pygame # just to get a display

# get an OpenGL surface

pygame.init()
pygame.display.set_mode((800,600), pygame.OPENGL|pygame.DOUBLEBUF)

# How to catch errors here?

done = False

t=0

while not done:

t=t+1

# for fun comment out these two lines

glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

# Get a perspective at the helix

glMatrixMode(GL_PROJECTION);

glLoadIdentity()
gluPerspective(90,1,0.01,1000)
gluLookAt(sin(t/200.0)*3,sin(t/500.0)*3,cos(t/200.0)*3,0,0,0,0,1,0)

# Draw the helix (this ought to be a display list call)

glMatrixMode(GL_MODELVIEW)

# get a texture (this ought not to be inside the inner loop)

texture=glGenTextures( 1 )

glBindTexture( GL_TEXTURE_2D, texture );
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

# set sane defaults for a plethora of potentially uninitialized
# variables

glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_REPEAT);
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_REPEAT );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)

# a texture

#pulse = sin(t/30)*0.5+0.5 # try this one
pulse = 0

texdata=[[[0.0,0,1,1],
[0.0,0,0,0],
[0.0,1,0,1],
[0.0,0,0,0]],
[[0.0,0,0,0],
[pulse,pulse,pulse,1],
[pulse,pulse,pulse,1],
[0.0,0,0,0]],
[[0.0,1,0,1],
[1,pulse,pulse,1],
[pulse,pulse,0,1],
[0.0,0,0,0]],
[[0.0,0,0,0],
[0.0,0,0,0],
[0.0,0,0,0],
[0.0,0,0,0]]];

glTexImage2Df(GL_TEXTURE_2D, 0,4,0,GL_RGBA,
texdata)

glEnable(GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE); # XXX Why GL_ONE?
# alternatively:
# glEnable(GL_DEPTH_TEST);

glEnable( GL_TEXTURE_2D );
# use the texture
glBindTexture( GL_TEXTURE_2D, texture );

# vertices & texture data

glBegin(GL_TRIANGLE_STRIP);

for i in range(0,100):

r=5.0 # try other values - integers as well
d=1 # try other values

if (i%2==0):
glTexCoord2f(0,i);
glVertex3f( cos(i/r), -2.5+i*0.05, sin(i/r));
# glVertex3f( cos(i/r)*pulse2, -2.5+i*0.05, sin(i/r)*pulse2);
else:
glTexCoord2f(1,i);
glVertex3f( cos(i/r+3.14), -2.5+i*0.05+d, sin(i/r+3.14));
# glVertex3f( cos(i/r+3.14)*pulse2, -2.5+i*0.05+d+pulse2*1, sin(i/r+3.14)*pulse2);


glEnd();

glFlush()

glDeleteTextures(texture)
pygame.display.flip()