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()