I am trying to create a little shadow mapping demo.
My code is currently divided into three rendering passes:
- Pass 1 - Create the depth texture that will be used for shadow mapping on an offscreen framebuffer
- Pass 2 - (Attempt to) render the scene with shadows using that depth texture
- Pass 3 - Display the shadow map in the upper right corner (debug purposes)
I have successfully created and rendered a depth texture (passes 1 and 3). However, I am struggling to render the scene with shadows from the camera's POV. There is some flickering at the edges of thick floor plane as well as on one cube corner. The results look nothing like shadows and the scene looks fully lit. I am thinking that either my second pass or my shaders for that pass that I am using to display the scene are incorrect, but I cannot seem to find the error. It is a relatively short demo written using python 2.7, opengl 2.1, and GLSL 120. Below is the main method that includes most of the relevant code (aside from some window creation, shader compilations, and primitive matrix math libraries I wrote):
from window import Window
from shader import Shader
from mat4 import Mat4
from vec3 import Vec3
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import numpy as np
from PIL import Image
#Loads my special model file format
#Basically a super-simplified obj without indexing
def loadAA7(dataUrl):
vData = []
tData = []
nData = []
inFile = open(dataUrl, "r")
for line in inFile.readlines():
lineList = line.strip().split("\t")
vData.extend([float(v) for v in lineList[0:3]])
tData.extend([float(v) for v in lineList[3:5]])
nData.extend([float(v) for v in lineList[5:]])
vertexData = np.array(vData, dtype=np.float32)
texCoordData = np.array(tData, dtype=np.float32)
normalData = np.array(nData, dtype=np.float32)
return vertexData, texCoordData, normalData
def createMeshBuffers(vertices, texCoords, normals):
v, t, n = vertices, texCoords, normals
vbo, tbo, nbo = glGenBuffers(3)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, len(v)*4, v, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, tbo)
glBufferData(GL_ARRAY_BUFFER, len(t)*4, t, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, nbo)
glBufferData(GL_ARRAY_BUFFER, len(n)*4, n, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, 0)
return vbo, tbo, nbo
if __name__ == "__main__":
window = Window("Shadow Mapping Test", 800, 600, 60)
glClearColor(0.0, 0.0, 0.0, 1.0)
glEnable(GL_CULL_FACE)
glEnable(GL_TEXTURE_2D)
glEnable(GL_DEPTH_TEST)
time = 0
v, t, n = loadAA7("./data/blockworld.aa7")
vbo, tbo, nbo = createMeshBuffers(v, t, n)
shadowMapShader = Shader("./shaders/shadowMap.vert", "./shaders/shadowMap.frag")
shadowMapShader.compile()
displayShader = Shader("./shaders/display.vert", "./shaders/display.frag")
displayShader.compile()
img = Image.open("./data/blockworld.png")
imgWidth, imgHeight = img.size
imgData = np.array(img)
modelTex = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, modelTex)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
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_RGBA, imgWidth, imgHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, imgData)
glBindTexture(GL_TEXTURE_2D, 0)
rendertarget = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, rendertarget)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 512, 512, 0, GL_DEPTH_COMPONENT, GL_FLOAT, None)
fbo = glGenFramebuffers(1)
glBindFramebuffer(GL_FRAMEBUFFER, fbo)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rendertarget, 0)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
lightPos = Vec3(150, 150, 0)
cameraPos = Vec3(0, 200, -300)
while True:
window.update()
time += 1
#Pass 1: Render to Texture
shadowMapShader.enable()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
lightProj = Mat4().perspective(60, float(512)/512, 1, 1000)
lightView = Mat4().lookAt(lightPos, Vec3(0, 0, 0), Vec3(0, 1, 0))
modelMatrix = Mat4().rotateY(time)
glMatrixMode(GL_PROJECTION)
glLoadMatrixf(lightProj.data)
glMatrixMode(GL_MODELVIEW)
glLoadMatrixf(lightView.data)
glMultMatrixf(modelMatrix.data)
glBindFramebuffer(GL_FRAMEBUFFER, fbo)
glViewport(0, 0, 512, 512)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glEnableClientState(GL_VERTEX_ARRAY)
glVertexPointer(3, GL_FLOAT, 0, v)
glDrawArrays(GL_TRIANGLES, 0, len(v)/3)
glDisableClientState(GL_VERTEX_ARRAY)
shadowMapShader.disable()
#Pass 2: Render the scene with shadows
bias = Mat4([0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0])
biasMVPMatrix = bias.mul(lightProj).mul(lightView).mul(modelMatrix)
glViewport(0, 0, 800, 600)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
cameraProj = Mat4().perspective(30, float(800)/600, 1, 1000)
cameraView = Mat4().lookAt(cameraPos, Vec3(0, 0, 0), Vec3(0, 1, 0))
modelMatrix = Mat4().rotateY(time)
glMatrixMode(GL_PROJECTION)
glLoadMatrixf(cameraProj.data)
glMatrixMode(GL_MODELVIEW)
glLoadMatrixf(cameraView.data)
glMultMatrixf(modelMatrix.data)
displayShader.enable()
glActiveTexture(GL_TEXTURE1)
glBindTexture(GL_TEXTURE_2D, rendertarget)
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, modelTex)
displayShader.setUniform("u_modelTexture", "sampler2D", 0)
displayShader.setUniform("u_shadowMap", "sampler2D", 1)
displayShader.setUniform("u_biasMVPMatrix", "mat4", biasMVPMatrix.data)
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
glVertexPointer(3, GL_FLOAT, 0, v)
glTexCoordPointer(2, GL_FLOAT, 0, t)
glDrawArrays(GL_TRIANGLES, 0, len(v)/3)
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_TEXTURE_COORD_ARRAY)
displayShader.disable()
#DEBUG: Display the render texture
glViewport(0, 0, 800, 600)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(-1, 1, -1, 1, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, rendertarget)
glBegin(GL_QUADS)
glColor3f(1,1,1)
glTexCoord2f(0, 0); glVertex3f(0.5, 0.5, 0)
glTexCoord2f(1, 0); glVertex3f(1, 0.5, 0)
glTexCoord2f(1, 1); glVertex3f(1, 1, 0)
glTexCoord2f(0, 1); glVertex3f(0.5, 1, 0)
glEnd()
I am also including my shadows for pass 1 (shadowMap.vert/frag) and pass 2 (display.vert/frag) in case the error is in one of these, but they seem to make sense to me (pass 1 outputs linearizes fragment depth while pass 2 transforms the vertices with a biased light space matrix before performing a depth comparsion between the depth texture and the scene).
shadowMap.vert
#version 120
void main()
{
gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;
}
shadowMap.frag
#version 120
void main()
{
float z = gl_FragCoord.z;
float n = 1.0;
float f = 1000.0;
//convert to linear values
//formula can be found at www.roxlu.com/2014/036/rendering-the-depth-buffer
float c = (2.0 * n) / (f + n - z * (f - n));
gl_FragDepth = c;
}
display.vert
#version 120
uniform mat4 u_biasMVPMatrix;
varying vec4 v_shadowCoord;
void main()
{
mat4 bias = mat4(0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0);
gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;
v_shadowCoord = u_biasMVPMatrix * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
}
display.frag
#version 120
uniform sampler2D u_modelTexture;
uniform sampler2D u_shadowMap;
varying vec4 v_shadowCoord;
void main()
{
vec3 projCoords = v_shadowCoord.xyz/v_shadowCoord.w;
float closestDepth = texture2D(u_shadowMap, projCoords.xy).r;
float currentDepth = projCoords.z;
float shadow = currentDepth > closestDepth ? 1.0 : 0.0;
gl_FragColor = shadow * texture2D(u_modelTexture, gl_TexCoord[0].xy);
}
Update: Here is a picture of the results achieved when using this as the pass 2 fragment shader:
gl_FragColor = vec4(v_shadowCoord.xyz/v_shadowCoord.w, 1.0);
I also found it interesting that I get "shadows" when I use this as the shader instead (but I have no idea what it mean):
#version 120
uniform sampler2D u_modelTexture;
uniform sampler2D u_shadowMap;
varying vec4 v_shadowCoord;
void main()
{
vec3 projCoords = v_shadowCoord.xyz/v_shadowCoord.w;
float closestDepth = texture2D(u_shadowMap, projCoords.xy).z;
gl_FragColor.rgb = vec3(closestDepth);
gl_FragColor.a = 1.0;
}
gl_FragColor = vec4(v_shadowCoord.xyz, 1.0);
to see what is going on. \$\endgroup\$currentDepth > closestDepth
comparison you have. You were converting to linear in vertex shader, do you need to do the same in fragment? \$\endgroup\$