Skip to main content
GameDev.net gamedev.net
🔒 Locked

Shadow Mapping

Started by larson.micah Mar 29, 2011 at 5:22 AM 0 replies 1.9k views
Original Post
larson.micah
larson.micah
I'm trying to get shadow mapping implemented. So far, just one light source (directional) and two objects. I'm using a texture buffer to store the shadow map. It looks like I'm on the right track, but the shadows are hideous (see screenshot, attached). I'd also appreciate general comments on anything shady looking with respect to OpenGL.

Coding in Python with PyOpenGL.

Here's the code:



from OpenGL.GL import * #@UnusedWildImport
from OpenGL.GLU import * #@UnusedWildImport
from OpenGL.GLUT import * #@UnusedWildImport
from OpenGL.GL.framebufferobjects import * #@UnusedWildImport

import math

import coordinates
import lighting
import objects
import skybox
import textures
import util

class SpaceFlight:
def __init__(self):
#window
self.title = 'Space Flight'
self.width = 400
self.height = 400

#viewer orientation
self.camera = [10.0, 0.0, 0.0]
self.center = [ 0.0, 0.0, 0.0]
self.up = [ 0.0, 0.0, 1.0]

#UI
self.mousePosition = [0.0, 0.0]
self.moveMode = 'ZOOM'

#lighting
self.lights = {
'primary': lighting.Light(GL_LIGHT0, position=[0.0, 1000.0, 0.0],
ambient=[ 0.05, 0.05, 0.05, 1.0], #space is dark
diffuse=[ 1.0, 0.9, 0.8, 1.0], #slight orange
specular=[1.0, 0.76, 0.05, 1.0]) #very orange
}

#shadows
self.MultiTex = True
self.shadowdim = 512
self.Sdim = self.shadowdim
self.Tdim = self.shadowdim
self.NoAmbient = False
self.frameBufferID = 0
self.S = [] #texture plane S
self.T = [] #texture plane T
self.R = [] #texture plane R
self.Q = [] #texture plane Q

#perspective
self.zNear = 1.0
self.zFar = 1000.0

#miscellaneous
self.drawAxes = True #press 'a' to toggle

def display(self):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()

glDisable(GL_LIGHTING) #disable lighting for first pass
self.enable_lighting(False)

#set viewer orientation
gluLookAt(*(self.camera + self.center + self.up))

# BLOCK OF INCOMPREHENSION 1 ###########################################
self.lights['primary'].commit_position()
########################################################################

#TODO: shadow pass?

#set up shadow texture comparison
if self.MultiTex:
glActiveTexture(GL_TEXTURE1)
glEnable(GL_TEXTURE_2D)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
GL_COMPARE_R_TO_TEXTURE)

#set up the eye plane for projecting the shadow map on the scene
glEnable(GL_TEXTURE_GEN_S); glTexGenfv(GL_S, GL_EYE_PLANE, self.S)
glEnable(GL_TEXTURE_GEN_T); glTexGenfv(GL_T, GL_EYE_PLANE, self.T)
glEnable(GL_TEXTURE_GEN_R); glTexGenfv(GL_R, GL_EYE_PLANE, self.R)
glEnable(GL_TEXTURE_GEN_Q); glTexGenfv(GL_Q, GL_EYE_PLANE, self.Q)
if self.MultiTex:
glActiveTexture(GL_TEXTURE0)

#draw objects in scene
self.enable_lighting(True)
self.draw_objects()

if self.drawAxes:
objects.draw('axes', emissive=[1.0, 1.0, 1.0, 0.0])

#disable textures and texture generation
if self.MultiTex:
glActiveTexture(GL_TEXTURE1)
glDisable(GL_TEXTURE_2D)
glDisable(GL_TEXTURE_GEN_S)
glDisable(GL_TEXTURE_GEN_T)
glDisable(GL_TEXTURE_GEN_R)
glDisable(GL_TEXTURE_GEN_Q)
if self.MultiTex:
glActiveTexture(GL_TEXTURE0)

#disable alpha test if the shadow pass is done
if self.NoAmbient:
glDisable(GL_ALPHA_TEST)

#draw background
skybox.draw_skybox(self.zFar)

util.print_to_screen(
'Camera: (%0.2f, %0.2f, %0.2f); Light: (%0.2f, %0.2f, %0.2f)' %
tuple(self.camera + self.lights['primary'].position))

glFlush()
glutSwapBuffers()

def draw_objects(self):
objects.draw('asteroid', translation=[0.0, 0.0, 0.0],
scale=[5.0, 5.0, 5.0])
objects.draw('asteroid', translation=[0.0, 10.0, 3.0],
scale=[5.0, 5.0, 5.0])
#objects.draw('tie_fighter', diffuse=[0.3, 0.3, 0.3, 1.0],
# shininess=30.0)

def timer(self, value):
"""Called when time triggers."""
#TODO: update objects' positions
#TODO: update lights' positions
self.draw_shadow_map()

def toggle_axes(self):
"""Toggles in-scene (x, y, z) axes."""
self.drawAxes = not self.drawAxes
glutPostRedisplay()

def init_scene(self):
"""Initialize lighting, textures, etc."""
glEnable(GL_NORMALIZE)
glEnable(GL_POLYGON_SMOOTH)
glShadeModel(GL_SMOOTH)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
glClearColor(0.0, 0.0, 0.0, 1.0)

#enable Z-buffer
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)
glPolygonOffset(4, 0)

skybox.init_cube_textures()
textures.init()
self.init_lighting()
self.init_shadow_map()
self.set_perspective(self.width, self.height, self.zNear, self.zFar)

def init_shadow_map(self):
"""Initialize the shadow map."""
#see if multi-textures are supported
self.MultiTex = (glGetIntegerv(GL_MAX_TEXTURE_UNITS) > 1)

#get maximum texture buffer size
self.shadowdim = glGetIntegerv(GL_MAX_TEXTURE_SIZE)

#limit texture size to maximum buffer size
self.shadowdim = min(self.shadowdim,
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE_EXT))

#limit texture size to 2048 for performance
self.shadowdim = min(self.shadowdim, 2048)

self.init_shadow_textures()

#only use framebuffer if it is big enough
if self.shadowdim > 512:
powerOfTwo = util.upper_power_of_two(self.shadowdim)
self.Sdim = self.Tdim = self.shadowdim = powerOfTwo

self.frameBufferID = glGenFramebuffersEXT(1)
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, self.frameBufferID)

#set render buffer size to shadow map
renderBufferID = glGenRenderbuffersEXT(1)
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderBufferID)
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT32,
int(self.shadowdim), int(self.shadowdim))
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, renderBufferID)

glDrawBuffer(GL_NONE) #don't write to visible color
glReadBuffer(GL_NONE) #don't read from visible color

#sanity check (bail on fail)
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)
if status != GL_FRAMEBUFFER_COMPLETE_EXT:
raise Exception('Error setting up frame buffer')
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)

self.draw_shadow_map() #create shadow map
else:
#set shadow dim to maximum
self.shadowdim = glGetIntegerv(GL_MAX_TEXTURE_SIZE)
print >> sys.stderr, 'Insufficient framebuffer'

def init_shadow_textures(self):
"""Initialize textures used in shadow map."""
if self.MultiTex:
glActiveTexture(GL_TEXTURE1)
#allocate and bind shadow texture
shadowtex = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, shadowtex)

#map math.single depth value to RGBA (this is called intensity)
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY)

#check if ambient shadows are supported
glGetError() #clear errors, effectively
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FAIL_VALUE_ARB, 0.5)
self.NoAmbient = (glGetError() == GL_INVALID_ENUM)

#texture modulate underlying objects
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)

#set texture mapping to clamp and linear interpolation
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)

#set automatic texture generation mode to Eye Linear
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR)
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR)
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR)
glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR)

if self.MultiTex:
glActiveTexture(GL_TEXTURE0)

def draw_shadow_map(self, bounds=1000.0):
"""
Draw the shadow map.

This needs to be called whenever any object or light moves.

@param bounds bounding radius of scene

TODO: support multiple lights

"""

#save transforms and modes
glPushMatrix()
glPushAttrib(GL_TRANSFORM_BIT | GL_ENABLE_BIT)

glShadeModel(GL_FLAT) #no smoothing
glColorMask(0, 0, 0, 0) #no writing to color buffer
glEnable(GL_POLYGON_OFFSET_FILL) #overcome imprecision

#turn off lighting and set light position
self.enable_lighting(False)

#distance of light from origin, ensuring it's out of bounds
lightPos = self.lights['primary'].position
lightDistance = max(coordinates.distance(*lightPos[0:3]), 1.1 * bounds)

#set perspective view from light position
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(114.6 * math.atan(bounds / lightDistance), #TODO: why?
float(self.Sdim) / self.Tdim, #aspect ratio of plane
lightDistance - bounds,
lightDistance + bounds)

glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
gluLookAt(*(lightPos + self.center + self.up))

#size viewport to desired dimensions
glViewport(0, 0, int(self.Sdim), int(self.Tdim))

#redirect traffic to the frame buffer
if self.frameBufferID > 0:
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, self.frameBufferID)

#clear the depth buffer
glClear(GL_DEPTH_BUFFER_BIT)
self.draw_objects() #draw all objects that can cast a shadow

#copy depth values into depth texture
if self.MultiTex:
glActiveTexture(GL_TEXTURE1)
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0,
int(self.Sdim), int(self.Tdim), 0)
if self.MultiTex:
glActiveTexture(GL_TEXTURE0)

#retrieve light projection and modelview matrices
lightProjectionMatrix = glGetDoublev(GL_PROJECTION_MATRIX)
lightModelviewMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)

#set up texture matrix for shadow map projection, which will be rolled
# into the eye linear texture coordinate generation plane equations
glLoadIdentity()
glTranslated(0.5, 0.5, 0.5)
glScaled(0.5, 0.5, 0.5)
glMultMatrixd(lightProjectionMatrix)
glMultMatrixd(lightModelviewMatrix)

#retrieve result and transpose to get the s, t, r, and q rows for
# plane equations
#textureProjectionMatrix: | S1 T1 R1 Q1 |
# | S2 T2 R2 Q2 |
# | S3 T3 R3 Q3 |
# | S4 T4 R4 Q4 |
textureProjectionMatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
self.S = [row[0] for row in textureProjectionMatrix]
self.T = [row[1] for row in textureProjectionMatrix]
self.R = [row[2] for row in textureProjectionMatrix]
self.Q = [row[3] for row in textureProjectionMatrix]

#restore normal drawing state
glShadeModel(GL_SMOOTH)
glColorMask(1, 1, 1, 1)
glDisable(GL_POLYGON_OFFSET_FILL)
glPopAttrib()
glPopMatrix()
if self.frameBufferID > 0:
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0)

def enable_lighting(self, on=True):
"""Enable/disable lighting (and related)."""
if on:
glEnable(GL_LIGHTING)
glEnable(GL_COLOR_MATERIAL)
glEnable(GL_NORMALIZE)
else:
glDisable(GL_LIGHTING)
glDisable(GL_COLOR_MATERIAL)
glDisable(GL_NORMALIZE)

def init_lighting(self):
"""Initialize lighting."""
glEnable(GL_LIGHTING)
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, [0.01] * 3) #space is dark

for light in self.lights.keys():
self.lights[light].init()

def set_perspective(self, width, height, zNear, zFar, fieldOfView=40.0):
"""Set perspective."""
aspectRatio = (float(width) / height) if (height > 0) else 1
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(fieldOfView, aspectRatio, zNear, zFar)
glMatrixMode(GL_MODELVIEW)

def keyboard(self, key, x, y):
"""Called when ASCII key is pressed."""
try:
{
'\x1b': lambda : sys.exit(0), #ESC
'a': lambda : self.toggle_axes()
}[key.lower()]()
except KeyError as error:
print >> sys.stderr, 'Unhandled key: %s' % error

def motion(self, x, y):
"""Called when mouse moves."""
if ('ROTATE' == self.moveMode):
#rotate view based on mouse movement since last reference point
self.rotate((self.mousePosition[0] - x) / 100.0,
(self.mousePosition[1] - y) / 100.0)
elif ('ZOOM' == self.moveMode):
self.zoom((y - self.mousePosition[1]) / 10.0)
else:
pass #invalid move mode

#set new reference points
self.mousePosition = [x, y]

def mouse(self, button, state, x, y):
"""Called when mouse button is clicked/released."""
self.mousePosition[0] = x
self.mousePosition[1] = y
if GLUT_DOWN == state:
if GLUT_LEFT_BUTTON == button:
self.moveMode = 'ROTATE'
elif GLUT_RIGHT_BUTTON == button:
self.moveMode = 'ZOOM'
glutPostRedisplay() #refresh drawing

def reshape(self, width, height):
"""Called when window is resized."""
(self.width, self.height) = (width, height)
glViewport(0, 0, width, height)
self.set_perspective(width, height, self.zNear, self.zFar)
glLoadIdentity()

def rotate(self, azimuth, inclination):
"""Move the camera about the origin."""
#calculations based on current camera position:
(rho, theta, phi) = coordinates.spherical(*self.camera)

#adjust phi (azimuth) and theta (inclination)
phi = (phi + azimuth) % (math.pi * 2)
theta = min(max(0.01, theta + inclination), math.pi)

#update camera position
self.camera = coordinates.cartesian(rho, theta, phi)
glutPostRedisplay() #refresh drawing

def zoom(self, dist):
"""Move the camera closer to or farther from the origin."""
#calculations based on current camera position:
(rho, theta, phi) = coordinates.spherical(*self.camera)

#adjust rho (distance from origin)
rho = max(10.0, min(100.0, rho + dist))

#update camera position
self.camera = coordinates.cartesian(rho, theta, phi)

glutPostRedisplay() #refresh drawing

def special(self, key, x, y):
"""Called when 'special' key (up, down, etc.) is pressed."""
try:
{
GLUT_KEY_UP: lambda : self.rotate( 0.00, 0.05),
GLUT_KEY_DOWN: lambda : self.rotate( 0.00, -0.05),
GLUT_KEY_LEFT: lambda : self.rotate( 0.05, 0.00),
GLUT_KEY_RIGHT: lambda : self.rotate(-0.05, 0.00)
}[key]()
except KeyError as error:
print >> sys.stderr, 'Unhandled special key:', key
glutPostRedisplay()

def main(self):
"""Set up and execute the main loop."""
glutInit(sys.argv)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
glutInitWindowSize(self.width, self.height)
glutCreateWindow(self.title)

#set up callbacks
glutDisplayFunc(self.display) #put things on screen
glutReshapeFunc(self.reshape) #window resize
glutKeyboardFunc(self.keyboard) #ASCII keys
glutSpecialFunc(self.special) #non-printing keys (e.g. arrows)
glutMouseFunc(self.mouse) #mouse clicks
glutMotionFunc(self.motion) #mouse movement
glutTimerFunc(50, self.timer, 0) #activate timer at most every 50 ms

self.init_scene() #initialize lighting, perspective, etc.

glutMainLoop()
return


if __name__ == '__main__':
SpaceFlight().main()

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.