Depth Render Problem

Hello,

I am trying to implement shadow mapping using GLSL, the problem is, even I am using pretty reasonable depth render precision my depth render result is suffering from artifacts, it even looks like a wireframe render. Here is an image of the result, depth render texture is pretty much similar to the shadow here. That is why I believe it is not a z-fighting issue here, I already experiemented adding and subtracting different values(commented out later).

Here is a full test case using Python and only PyOpenGL
shader is included in the file, you can see it in build method of shader class.


# shadow mapping test
# utkualtinkaya at gmail 
# shader is from http://www.fabiensanglard.net/shadowmapping/index.php

from OpenGL.GL import *
from OpenGL.GLU import * 
from OpenGL.GLUT import *
from OpenGL.GL.shaders import *
from OpenGL.GL.framebufferobjects import *
import math

class Camera:
    def __init__(self):
        self.rotx, self.roty = math.pi/4, math.pi/4
        self.distance = 100
        self.moving = False
        self.ex, self.ey = 0, 0
        self.size = (800, 600) 

    def load_matrices(self):
        glViewport(0, 0, *self.size)
        y = math.cos(self.roty) * self.distance
        x = math.sin(self.roty) * math.cos(self.rotx) * self.distance
        z = math.sin(self.roty) * math.sin(self.rotx) * self.distance

        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(45.0, self.size[0]/float(self.size[1]), 1, 1000)

        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        gluLookAt(x,y,z, 0,0,0, 0,1,0)

    def on_mouse_button (self, b, s, x, y):
        self.moving = not s
        self.ex, self.ey = x, y
        if b in [3, 4]:
            dz = (1 if b == 3 else -1)
            self.distance += self.distance/15.0 * dz;

    def on_mouse_move(self, x, y, z = 0):
        if self.moving:            
            self.rotx += (x-self.ex) / 300.0
            self.roty += -(y-self.ey) / 300.0
            self.ex, self.ey = x, y

    def set_size(self, w, h):
        self.size = w, h

class Shader():
    def __init__(self):
        self.is_built = False
        self.uniforms = {}

    def build(self):
        self.program = compileProgram(
        compileShader('''
            uniform mat4 camMatrix;
            uniform mat4 shadowMatrix;
            varying vec4 depthProjection;
            uniform bool useShadow;

            void main() {
                gl_Position = camMatrix * gl_ModelViewMatrix * gl_Vertex;
                depthProjection = shadowMatrix * gl_ModelViewMatrix * gl_Vertex;
                gl_FrontColor = gl_Color;
            }
        ''',GL_VERTEX_SHADER),
        compileShader('''
            varying vec4 depthProjection;
            uniform sampler2D shadowMap;
            uniform bool useShadow;

            void main () {
                float shadow = 1.0;
                if (useShadow) {
                    vec4 shadowCoord = depthProjection / depthProjection.w ;
                    // shadowCoord.z -= 0.0003;            
                    float distanceFromLight = texture2D(shadowMap, shadowCoord.st).z;                                
                    if (depthProjection .w > 0.0)
                        shadow = distanceFromLight < shadowCoord.z ? 0.5 : 1.0 ;            

                    }
                gl_FragColor = shadow * gl_Color;
              }
        ''',GL_FRAGMENT_SHADER),)
        self.is_built = True

        self.uniforms['camMatrix'] = glGetUniformLocation(self.program, 'camMatrix')
        self.uniforms['shadowMatrix'] = glGetUniformLocation(self.program, 'shadowMatrix')
        self.uniforms['shadowMap'] = glGetUniformLocation(self.program, 'shadowMap')
        self.uniforms['useShadow'] = glGetUniformLocation(self.program, 'useShadow')
        print self.uniforms

    def use(self):
        if not self.is_built:
            self.build()
        glUseProgram(self.program)

class Test:
    def __init__(self):
        glutInit(sys.argv)
        glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
        glutInitWindowSize(800, 600)
        glutInitWindowPosition(1120/2, 100)
        self.window = glutCreateWindow("Shadow Test")
        self.cam = Camera()
        self.light = Camera()
        self.cam.set_size(800, 600)
        self.light.set_size(2048, 2048)
        self.light.distance = 100
        self.shader = Shader()
        self.initialized = False        

    def setup(self):
        self.initialized = True
        glClearColor(0,0,0,1.0);
        glDepthFunc(GL_LESS)
        glEnable(GL_DEPTH_TEST)

        self.fbo = glGenFramebuffers(1);
        self.shadowTexture = glGenTextures(1)

        glBindFramebuffer(GL_FRAMEBUFFER, self.fbo)

        w, h = self.light.size

        glActiveTexture(GL_TEXTURE5)         
        glBindTexture(GL_TEXTURE_2D, self.shadowTexture)

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
        glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );

        glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, w, h, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, None)

        glDrawBuffer(GL_NONE)
        glReadBuffer(GL_NONE)

        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, self.fbo, 0)

        FBOstatus = glCheckFramebufferStatus(GL_FRAMEBUFFER)
        if FBOstatus != GL_FRAMEBUFFER_COMPLETE:
            print ("GL_FRAMEBUFFER_COMPLETE_EXT failed, CANNOT use FBO
");

        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        #glActiveTexture(GL_TEXTURE0)                

    def draw(self):
        glPushMatrix()
        glTranslate(0, 10 ,0)
        glColor4f(0, 1, 1, 1)
        glutSolidCube(5)
        glPopMatrix()

        glPushMatrix()
        glColor4f(0.5, 0.5, .5, 1)
        glScale(100, 1, 100)
        glutSolidCube(1)
        glPopMatrix()

    def apply_camera(self, cam):
        cam.load_matrices()
        model_view = glGetDoublev(GL_MODELVIEW_MATRIX);
        projection = glGetDoublev(GL_PROJECTION_MATRIX);
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glMultMatrixd(projection)
        glMultMatrixd(model_view)        
        glUniformMatrix4fv(self.shader.uniforms['camMatrix'], 1, False, glGetFloatv(GL_MODELVIEW_MATRIX))
        glLoadIdentity()     

    def shadow_pass(self):
        glUniform1i(self.shader.uniforms['useShadow'], 0)

        glBindFramebuffer(GL_FRAMEBUFFER, self.fbo)
        glClear(GL_DEPTH_BUFFER_BIT)
        glCullFace(GL_FRONT)
        self.apply_camera(self.light)        
        self.draw()
        glBindFramebuffer(GL_FRAMEBUFFER, 0)

    def final_pass(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        self.light.load_matrices()
        model_view = glGetDoublev(GL_MODELVIEW_MATRIX);
        projection = glGetDoublev(GL_PROJECTION_MATRIX);        
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        bias = [ 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]
        glLoadMatrixd(bias)
        glMultMatrixd(projection)
        glMultMatrixd(model_view)
        glUniformMatrix4fv(self.shader.uniforms['shadowMatrix'], 1, False, glGetFloatv(GL_MODELVIEW_MATRIX))

        glActiveTexture(GL_TEXTURE5)
        glBindTexture(GL_TEXTURE_2D, self.shadowTexture)                
        glUniform1i(self.shader.uniforms['shadowMap'], 5)

        glUniform1i(self.shader.uniforms['useShadow'], 1);

        self.apply_camera(self.cam)
        glLoadIdentity()
        glCullFace(GL_BACK)
        self.draw()

    def render(self):
        if not self.initialized: self.setup()
        self.shader.use()        
        self.shadow_pass()
        self.final_pass()        
        glutSwapBuffers()

    def mouse_move(self, *args):
        self.cam.on_mouse_move(*args)
        self.light.on_mouse_move(*args)

    def mouse_button(self, b, *args):
        if b==0:
            self.light.on_mouse_button(b, *args)
        else:
            self.cam.on_mouse_button(b, *args)

    def main(self):
        glutDisplayFunc(self.render)
        glutIdleFunc(self.render)
        glutMouseFunc(self.mouse_button)
        glutMotionFunc(self.mouse_move)
        glutReshapeFunc(self.cam.set_size)
        #self.setup()
        glutMainLoop()

if __name__ == '__main__':
    test = Test()
    test.main()

shadowCoord.z -= 0.0003; might want to put it back as
shadowCoord.z += 0.0005;

gets rid of the moire pattern problem so it should have a positive affect anyways.


glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, self.fbo, 0)

your passing the wrong handle for the texture. It shouldn’t be self.fbo but self.shadowTexture. I’m surprised anything got put into the shadow texture at all.

Yes I tried, but since the shadow texture is not correct, it is almost impossible to find a good value. First pass seems o be the problem, to make it easier to see, here is the shadow texture I have been able to save using GPU PerfStrudio of AMD:

you are absolutely correct, but since the handles where both 1, it did not create a problem, I have fixed that, but the result is unfortunately the same.

Yeah, with garden variety light-space front-face casted shadow maps with no biasing, you’re going to get this. See the diagrams on pg. 36-37 in this PDF:

It’s a projection issue. With standard projection shadow maps, front-face casting, and no bias, you’ll only get rid of the acne when your shadow texels are infinitely small and when you have no loss due to floating point precision (i.e. when hell freezes over :wink: )

There are several ways to deal with this shadow texel projection acne issue. The simplest way, when your caster objects are volumetric closed solids, is to only cast light-space “back” faces into the shadow map. You can do that for your ground plane too, since it’ll never cast shadows into the scene ( assuming you never let the light source be below the ground plane. The implementation for this is to change your glCullFace setting so it culls away light-space front faces while rendering the shadow map, leaving the light-space back faces.

What this does is gets rid of all the light-space front-facing caster acne. It’s then pushed to the light-space back-facing sides. And if you only use shadows to attenuate diffuse and specular lighting (which, using the standard Blinn lighting equations, is by definition 0 for light-space back faces), then you won’t even see shadow acne on the back sides. The only place you might see it is near light-space tangent faces, and you can deal with that other ways.

When your objects aren’t volumetric closed solids though, you just need a little shadow bias. Several ways to do that while rendering the shadow map, including glPolygonOffset, projection matrix tricks, and others.

Yeah, with garden variety light-space front-face casted shadow maps with no biasing, you’re going to get this. See the diagrams on pg. 36-37 in this PDF:

It’s a projection issue. With standard projection shadow maps, front-face casting, and no bias, you’ll only get rid of the acne when your shadow texels are infinitely small and when you have no loss due to floating point precision (i.e. when hell freezes over :wink: )

There are several ways to deal with this shadow texel projection acne issue. The simplest way, when your caster objects are volumetric closed solids, is to only cast light-space “back” faces into the shadow map. You can do that for your ground plane too, since it’ll never cast shadows into the scene ( assuming you never let the light source be below the ground plane. The implementation for this is to change your glCullFace setting so it culls away light-space front faces while rendering the shadow map, leaving the light-space back faces.

What this does is gets rid of all the light-space front-facing caster acne. It’s then pushed to the light-space back-facing sides. And if you only use shadows to attenuate diffuse and specular lighting (which, using the standard Blinn lighting equations, is by definition 0 for light-space back faces), then you won’t even see shadow acne on the back sides. The only place you might see it is near light-space tangent faces, and you can deal with that other ways.

When your objects aren’t volumetric closed solids though, you just need a little shadow bias. Several ways to do that while rendering the shadow map, including glPolygonOffset, projection matrix tricks, and others.

Thanks Dark Photon. I used front face culling in the test already, and added some bias. I will move to a better implementation(PCF/VSM) as soon as get basics working.

But those parameters only effect the 2nd pass of the rendering right ? Can you confirm that my framebuffer content(I posted as a reply to previous poster) is ok ? Since I am already seeing the artifacts in the image file, I cannot be sure about that.

Indeed the depth texture itself does not look like any depth map I have ever seen. Especially the subtly different detail of each black square is most surprising…

My 2 cents:
From his postings I assume he’s got an ATI card.

@Utkua: What exact hardware and driver version do you use?

I know that older ATI cards have a bug when sampling from a GL_DEPTH24_STENCIL8 texture (i.e. the sampled depth values seem to have regular artifacts that look like patterns).
Since you create the texture without providing a specific internal format, it could be that the texture is promoted to a depth-stencil internal format and then exposes this artifacts.

Please try GL_DEPTH_COMPONENT24 as internal format and see if you still get the artifacts.

Oh, sorry. Missed seeing you were already culling out front faces for the shadow view. Given that, I agree: the results you’re getting and that shadow map debug dump make no sense. Sounds like skynet may know what’s going on.

And no, all of those tweaks are for when rendering the shadow map from the light’s perspective (first pass), not rendering the scene with the shadow maps from the camera’s perspective (second pass). Looks like you’re already doing this.

I wouldn’t jump into elaborate any filtering just yet either. You’ve apparently got underlying shadow map rendering problems they won’t fix, just lessen.

I am using a 4870 ATI card with 8.791.0.0 driver version. While using GL_DEPTH_COMPONENT24 did not change the result, I also tested in my laptop which has an nvidia card, and I am receiving frame buffer creation errors(not even an error on mac os, texture is just plain black). Error is “The combination of internal formats of the attached images violates an implementation-dependent set of restrictions.” as the document states, so I guess there is something definitely wrong with my formats.

What GPU is in your laptop? (“glxinfo | grep renderer”). And what are the formats and resolutions of your FBO attachments?

Perhaps you could post a short GLUT test pgm illustrating your problem, so folks could advise and try on their hardware.

gpu is 9400M, my textures are 2048x2048 gl_depth_components, you can see the details in the test case above:

glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, w, h, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, None)

Perhaps you could post a short GLUT test pgm illustrating your problem, so folks could advise and try on their hardware.

you mean different test case, probably in c++ ?

Yeah, just something everyone can run. Here if I try to run your Python test program, these two import lines fail:


from OpenGL.GL.shaders import *
from OpenGL.GL.framebufferobjects import *

with:


ImportError: No module named shaders
ImportError: No module named framebufferobjects

Python 2.6.5 and PyOpenGL 3.0.0 release 5.2 here.

Ok, if I go grab and install PyOpenGL 3.0.1, then it runs and I get your shadow acne results.

A few seconds of playing with it reveals that while you set the CullFace attribute:

glCullFace(GL_FRONT)

you never enabled the CullFace “mode”:

glEnable(GL_CULL_FACE)

so you weren’t actually culling anything.

Add that last line, and your light-space front-facing shadow acne goes away.

The back-face acne will go away when you only use the shadow result to attenuate diffuse and specular, which by definition is 0 on light-space back faces.

I was wrong, the problem was different on nvidia, it was about drawbuffer, my framebuffer was incomplete, when I set drawbuffer to none, voila !, it works on Nvidia as expected.

It is funny ATI did not have any problems with that… But the fixed version still is not working on ATI, just the same results, weird patterns everywhere.

I updated drivers etc, but no go, I read somewhere people having trouble with specifically HD4870 of ATI, but hardware problem does not seem likely to me, especially while modern games are running on this system pretty well.

There are some example binaries which run well also. So only thing I can think of is the pyopengl, it is a thin wrapper of opengl, and works well with nvidia, I do not think something in library can be a problem for a specific hardware.

confused…

Thanks, you are right, but I was not able to really test that because of the depth map problem.

Thanks everyone, specially Dark Photon. I’ve solved it, it is the binding issue, in shadow pass fragment shader I simply checked a boolean value to disable reading the texture, but that was not enough. I should have unbind the texture before shadow pass, that is mentioned in documentation as:

Special precautions need to be taken to avoid attaching a texture image to the currently bound framebuffer while the texture object is currently bound and potentially sampled by the current vertex or fragment shader.

NVIDIA ignores this, ATI behaves pretty much “undefined” as the documentation says.

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.