Depth-map Shadows: Shadows in a Frame Buffer Object
#! /usr/bin/env python
import OpenGL,sys,os,traceback
Import the previous tutorial as BaseContext
from shadow_1 import TestContext as BaseContext
from OpenGLContext import testingcontext
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL.ARB.depth_texture import *
from OpenGL.GL.ARB.shadow import *
Import the PyOpenGL convenience wrappers for the FrameBufferObject
extension(s) we're going to use. (Requires PyOpenGL 3.0.1b2 or above).
from OpenGL.GL.framebufferobjects import *
class TestContext( BaseContext ):
"""Shadow rendering tutorial code"""
def OnInit( self ):
"""Scene set up and initial processing"""
super( TestContext, self ).OnInit()
We'll use the slightly more idiomatic "check if the entry
point is true" way of checking for the extension. The alternates
in the convenience wrapper will report true if there is any
implementation of the function.
if not glBindFramebuffer:
print 'Missing required extensions!'
sys.exit( testingcontext.REQUIRED_EXTENSION_MISSING )
Decide how big our depth-texture should be...
self.shadowMapSize = min(
(
glGetIntegerv( GL_MAX_TEXTURE_SIZE ),
self.shadowMapSize,
)
)
if self.shadowMapSize < 256:
print 'Warning: your hardware only supports extremely small textures!'
print 'Using shadow map of %sx%s pixels'%(
self.shadowMapSize,self.shadowMapSize
)
We override this default in the init function.
shadowMapSize = 512
Should you wish to experiment with different filtering functions,
we will parameterize the filtering operation here.
offset = 1.0
FILTER_TYPE = GL_NEAREST
def setupShadowContext( self,light=None, mode=None, textureKey="" ):
"""Create a shadow-rendering context/texture"""
shadowMapSize = self.shadowMapSize
As with the previous tutorial, we want to cache our texture (and FBO),
so we check to see if the values have already been set up.
key = self.textureCacheKey+textureKey
token = mode.cache.getData(light,key=key)
if not token:
A cache miss, so we need to do the setup.
fbo = glGenFramebuffers(1)
It has to be bound to configure it.
glBindFramebuffer(GL_FRAMEBUFFER, fbo )
The texture itself is the same as the last tutorial. We make the
texture current to configure parameters.
texture = glGenTextures( 1 )
glBindTexture( GL_TEXTURE_2D, texture )
glTexImage2D(
GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
shadowMapSize, shadowMapSize, 0,
GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, None
)
We attach the texture to the FBO's depth attachment point. There
is also a combined depth-stencil attachment point when certain
extensions are available. We don't actually need a stencil buffer
just now, so we can ignore that.
The final argument is the "mip-map-level" of the texture,
which currently always must be 0.
glFramebufferTexture2D(
GL_FRAMEBUFFER,
GL_DEPTH_ATTACHMENT,
GL_TEXTURE_2D,
texture,
0 #mip-map level...
)
if sys.platform in ('win32','darwin'):
"""Win32 and OS-x require that a colour buffer be bound..."""
color = glGenRenderbuffers(1)
glBindRenderbuffer( GL_RENDERBUFFER, color )
glRenderbufferStorage(
GL_RENDERBUFFER,
GL_RGBA,
shadowMapSize,
shadowMapSize,
)
glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color )
glBindRenderbuffer( GL_RENDERBUFFER, 0 )
glBindFramebuffer(GL_FRAMEBUFFER, 0 )
holder = mode.cache.holder(
light,(fbo,texture),key=key
)
else:
We've already got the FBO with its colour buffer, just bind to
render into it.
fbo,texture = token
Make the texture current to configure parameters.
glBindTexture( GL_TEXTURE_2D, texture )
We use the same "nearest" filtering as before. Note, we could use an alternate
texture unit and leave the parameters set, this just forces the setting back on
each rendering pass in case some clever geometry renders using our texture.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, self.FILTER_TYPE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
BUG NOTE: on AMD hardware, binding the FBO before you have unbound the
texture will cause heavy moire-style rendering artefacts due to undefined
behaviour where the FBO and the current texture are bound to the same
texture. You *must* unbind the texture before you bind the FBO.
glBindTexture( GL_TEXTURE_2D, 0 )
glBindFramebuffer(GL_FRAMEBUFFER, fbo )
Unlike in the previous tutorial, we now *know* this is a
valid size for the viewport in the off-screen context.
glViewport(0,0,shadowMapSize,shadowMapSize)
Disable drawing to the colour buffers entirely. Without this our
framebuffer would be incomplete, as it would not have any colour buffer
into which to render. Note that on Win32 we would *still* be considered
incomplete if we didn't define a color buffer.
glDrawBuffer( GL_NONE )
This function in the OpenGL.GL.framebufferobjects wrapper will
raise an OpenGL.error.GLError if the FBO is not properly configured.
try:
checkFramebufferStatus( )
except Exception, err:
traceback.print_exc()
import os
os._exit(1)
Clear the depth buffer (texture) on each pass. Our previous
tutorial didn't need to do this here because the back-buffer was
shared with the regular rendering pass and the OpenGLContext renderer
had already called glClear() during it's regular context setup.
glClear(GL_DEPTH_BUFFER_BIT)
return texture
def closeShadowContext( self, texture, textureKey="" ):
"""Close our shadow-rendering context/texture"""
This is a very simple function now, we just disable the FBO,
and restore the draw buffer to the regular "back" buffer.
glBindFramebuffer(GL_FRAMEBUFFER, 0 )
glDrawBuffer( GL_BACK )
return texture
if __name__ == "__main__":
Our display size is now irrelevant to our rendering algorithm, so we
won't bother specifying a size.
TestContext.ContextMainLoop(
depthBuffer = 24,
)
There are a number of possible next steps to take:
- create cube-maps for point light sources
- create multiple depth maps which cover successively farther "tranches" of the camera view frustum to produce higher-resolution shadows
- use shaders to combine the opaque and diffuse/specular passes into a single rendering pass
- use shaders to do "Percentage Closer Filtering" on the shadow-map values in order to antialias the shadow edges.
Shadows in a Frame Buffer Object