Dual Paraboloid Shadow Mapping

701 views Asked by At

I am using OpenGL 4.0, GLSL 4.0, not GLEW or similar, not GLU or similar, not GLUT or similar. OpenCL or CUDA are also used, but they are not involved in this case.

I've been trying to solve my problem for weeks without success, and now I hope somebody who has experience with dual paraboloid shadow mapping could give me advice. Let's go straight to the problem and inspect some pictures (picture 1):

Picture_1

Picture 1 contains some colored arrows I need to explain. Red arrows show the only correct shadows we should see. Other arrows indicate shadowing errors: yellow arrows show spots caused by tesselation, perhaps blue arrows also, but their location is on the border of front/back hemispheres. And the green arrow points to the sawtooth pattern which should't exist (it is absent in examples we see lately). Now it is important to notice that the picture above was computed with the following code line (code 1):

"swap.z=-sign(swap.z)*swap.z;\n" //mostly right results in the main project, but wrong way

This code line is located in GLSL shader program and it is one of the four candidates I've been trying without success in the main Project, from where the pictures are taken. However, code 4 does work in a separate test program, as we will see. Code 1 is actually completely wrong way to do DPSM, but it is the only way my main project gets shadowing. Next we look the same scene computed a little different, but still wrong code line (picture 2 and code 2):

Picture_2

"swap.z=sign(swap.z)*swap.z;\n" //almost dark picture, wrong way

Again we look at the same scene, but now we use completely different, an orthodox code line (picture 3 and code 3):

Picture_3

"swap.z=-(teDistance-n)/(f-n);\n" //lightning is mainly working but no shadows, should be the right way

Finally we look the scene computed by a code line which works (almost) perfectly in examples we see lately (picture 4 and code 4):

Picture_4

"swap.z=(teDistance-n)/(f-n);\n" //almost dark picture, doesn't work in the main project, but works in the test program, right way

If someone suspects that artifacts seen in pictures above are due to the phenomenon called "shadow acne", well no, I think they're not. Below is a picture which has shadow acne pattern I intentionally made by setting SHADOW_EPSILON = 0.000005f and switching off blurring (picture 5):

Picture_5

At this point I need to say, that I've run the program on two separate Windows 7.1 laptops, one with nVIDIA GeForce GT 525M and another AMD Radeon R6. The results were identical. Compiler is Visual Studio 2010. It seems to me that this is a purely OpenGL related problem.

To solve the problem, I wrote a separate, small test program, and finally got shadow mapping working. As far as I can see, the test program works pretty much exactly as the program which made pictures 1-5, but there aren't optimizations and a number of matrix multiplications has been moved from host to the shaders. The relevant parts of the test program source are below. Shaders first:

    static const char *vertex1=
"#version 400 core\n"

"layout (location=1) in vec3 vertexLocation;\n"

"out vec3 vPosition;\n"

"void main() {\n"
    "vPosition=vertexLocation;\n"
"}\0";




static const char *tessIn1=
"#version 400 core\n"

"layout (vertices=3) out;\n"

"in vec3 vPosition[];\n"
"out vec3 tcPosition[];\n"

"void main() {\n"
    "tcPosition[gl_InvocationID]=vPosition[gl_InvocationID];\n"
    "if (gl_InvocationID==0) {\n"

        "gl_TessLevelOuter[0]=max(distance(vPosition[1], vPosition[2]), 1.0);\n"
        "gl_TessLevelOuter[1]=max(distance(vPosition[2], vPosition[0]), 1.0);\n"
        "gl_TessLevelOuter[2]=max(distance(vPosition[0], vPosition[1]), 1.0);\n"
        "gl_TessLevelInner[0]=max(0.33*(gl_TessLevelOuter[0]+gl_TessLevelOuter[1]+gl_TessLevelOuter[2]), 1.0);\n"

    "}\n"
"}\0";


static const char* tessOut1=
"#version 400 core\n"

"layout(triangles, equal_spacing, ccw) in;\n"

"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 lightOrientation;\n"

"in vec3 tcPosition[];\n"
"out float teDistance;\n"
"out float teClip;\n"

"const float n=0.5;\n"
"const float f=20000.0;\n"

"void main() {\n"

    "vec3 accum=vec3(0.0);\n"
    "accum=accum+gl_TessCoord.x*tcPosition[0];\n"
    "accum=accum+gl_TessCoord.y*tcPosition[1];\n"
    "accum=accum+gl_TessCoord.z*tcPosition[2];\n"

// Transform position to the paraboloid's view space
    "vec4 swap=lightOrientation*model*vec4(accum, 1.0);\n"

//store the distance and other variables
    "teDistance=abs(swap.z);\n"
    "teClip=swap.z;\n"

//calculate and set X and Y coordinates
    "swap.xyz=normalize(swap.xyz);\n"
    "if (swap.z<=0.0) {\n"
        "swap.xy=swap.xy/(1.0-swap.z);\n"
    "} else {\n"
        "swap.xy=swap.xy/(1.0+swap.z);\n"
    "}\n"

//calculate and set Z and W coordinates
//  "swap.z=-sign(swap.z)*swap.z;\n"  //Wrong way
//  "swap.z=sign(swap.z)*swap.z;\n"  //Wrong way
//  "swap.z=-(teDistance-n)/(f-n);\n"      //Wrong way
    "swap.z=(teDistance-n)/(f-n);\n"      //Right way
    "swap.w=1.0;\n"

    "gl_Position=swap;\n"
"}\0";


static const char* geometry1=
"#version 400 core\n"

"layout(triangles) in;\n"
"layout(triangle_strip, max_vertices=3) out;\n"

"in float teDistance[];\n"
"in float teClip[];\n"
"out float gDistance;\n"

"void main() {\n"
    "for (int i=0; i<3; i++) {\n"
        "gDistance=teDistance[i];\n"
        "if (teClip[i]<=0.0) {\n"
            "gl_Layer=0;\n"
        "} else {\n"
            "gl_Layer=1;\n"
        "}\n"
        "gl_Position=gl_in[i].gl_Position;\n"
        "EmitVertex();\n"
    "}\n"
    "EndPrimitive();\n"
"}\0";


static const char* fragment1=
"#version 400 core\n"

"in float gDistance;\n"

"out vec2 fragmentVari;\n"

"void main() {\n"

    "fragmentVari=vec2(gDistance, gDistance*gDistance);\n"

"}\0";


const char *vertex2=
"#version 400 core\n"

"layout (location=1) in vec3 vertexPosition;\n"
"layout (location=2) in vec2 vertexTexCoord;\n"
"layout (location=3) in vec3 vertexNormal;\n"

"const float n=0.5;\n"
"const float f=20000.0;\n"

"uniform vec4 colour;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 normal;\n"
"uniform mat4 projection;\n"
"uniform mat4 lightOrientation;\n"

"out vec2 texKoord;\n"
"out vec3 pointNormal;\n"
"out vec3 point;\n"
"out vec4 color;\n"
"out vec4 vOriginPoint;\n"

"void main() {\n"
    "texKoord=vertexTexCoord;\n"
    "pointNormal=normalize(vec3(normal*vec4(vertexNormal, 1.0)));\n"
    "point=vec3(model*vec4(vertexPosition, 1.0));\n"
    "color=colour;\n"
    "vOriginPoint=vec4(vertexPosition, 1.0);\n"
    "gl_Position=projection*view*model*vec4(vertexPosition, 1.0);\n"
"}\0";


const char *fragment2=
"#version 400 core\n"

"uniform sampler2DArray tex1;\n"

"uniform vec4 colour;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 normal;\n"
"uniform mat4 projection;\n"
"uniform mat4 lightOrientation;\n"

"in vec2 texKoord;\n"
"in vec3 pointNormal;\n"
"in vec3 point;\n"
"in vec4 color;\n"
"in vec4 vOriginPoint;\n"
"out vec4 fragmentColor;\n"

"const float SHADOW_EPSILON = 0.05f;\n"
"const vec3 Ka=vec3(0.05, 0.05, 0.05);\n"  //Ambient reflectivity
"const vec3 Kd=vec3(1.0, 1.0, 1.0);\n"  //Diffuse reflectivity
"const float At=0.4;\n"                 //Light attenuation

"vec3 ads(in vec3 position, in vec3 normal) {\n"
    "vec3 l=vec3(lightOrientation*model*vOriginPoint);\n"
    "vec3 s=normalize(l - position);\n"
    "vec3 intensity=vec3(0.5, 0.5, 0.5)*10.0;\n"
    "float attenuation=1.0/(1.0+At*max(length(l), 1.0));\n"
    "intensity=intensity*attenuation*Kd*abs(dot(s, normal));\n"
    "return intensity;\n"
"}\n"

"float drawShadow() {\n"
    "vec3 texKoord;\n"
    "vec4 textureDepth;\n"

    "vec4 originPoint=vec4(lightOrientation*model*vOriginPoint);\n"
    "float distance=abs(originPoint.z);\n"
    "vec3 normalized=normalize(originPoint.xyz);\n"

    "if (normalized.z<=0.0) {\n"
        "texKoord.xy=normalized.xy/(1.0-normalized.z);\n"
        "texKoord.xy=0.5*texKoord.xy+0.5;\n"
        "texKoord.z=0.0;\n"
        "textureDepth=texture(tex1, texKoord);\n"
    "} else {\n"
        "texKoord.xy=normalized.xy/(1.0+normalized.z);\n"
        "texKoord.xy=0.5*texKoord.xy+0.5;\n"
        "texKoord.z=1.0;\n"
        "textureDepth=texture(tex1, texKoord);\n"
    "}\n"

    "if (textureDepth.x+SHADOW_EPSILON>=distance) {\n"
        "return 1.0;\n"
    "} else {\n"
        "return 0.0;\n"
    "}\n"
"}\n"

"void main() {\n"
    "vec4 lightning=vec4(Ka, 1.0);\n"
    "swap2=swap2*drawShadow();\n"
    "lightning=lightning+swap2;\n"
    "fragmentColor=color*lightning;\n"
"}\0";


const char *vertexLight=
"#version 400 core\n"

"layout (location=1) in vec3 vertexPosition;\n"

"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"uniform mat4 lightOrientation;\n"

"void main() {\n"
    "gl_Position=projection*view*lightOrientation*vec4(vertexPosition, 1.0);\n"
"}\0";


const char *fragmentLight=
"#version 400 core\n"

"out vec4 fragmentColor;\n"

"void main() {\n"
    "fragmentColor=vec4(1.0, 1.0, 1.0, 1.0);\n"
"}\0";

And here is the Display function:

void TForm1::display()
{
    GLuint loc1, loc2, loc3, loc4, loc5, loc6;
    float swap[16];
    float normal[16]={0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0};

//first we render a shadow map
    {
        glUseProgram(shaderT1);
        glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer[0]);
        glClearColor(20000.0f, 0.0f, 0.0f, 0.0f);
        glDepthMask(GL_TRUE);
        glDepthRange(0, 1);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glViewport(0, 0, textureDim.x, textureDim.y);
        glPatchParameteri(GL_PATCH_VERTICES, 3);
        loc1=glGetUniformLocation(shaderT1, "model\0");
        loc2=glGetUniformLocation(shaderT1, "lightOrientation\0");
        loc3=glGetUniformLocation(shaderT1, "view\0");
        swap[0]=1.0; swap[1]=0.0; swap[2]=0.0; swap[3]=0.0;
        swap[4]=0.0; swap[5]=1.0; swap[6]=0.0; swap[7]=0.0;
        swap[8]=0.0; swap[9]=0.0; swap[10]=1.0; swap[11]=0.0;
        swap[12]=-lightMatrix[12]; swap[13]=-lightMatrix[13]; swap[14]=-lightMatrix[14]; swap[15]=1.0;
        glUniformMatrix4fv(loc1, 1, GL_FALSE, triangleMatrix);
        glUniformMatrix4fv(loc2, 1, GL_FALSE, swap);
        glUniformMatrix4fv(loc3, 1, GL_FALSE, view);
        glBindVertexArray(VAO[1]);
        glDrawArrays(GL_PATCHES, 0, 3);
        glUniformMatrix4fv(loc1, 1, GL_FALSE, identity);
        glUniformMatrix4fv(loc2, 1, GL_FALSE, swap);
        glUniformMatrix4fv(loc3, 1, GL_FALSE, view);
        glBindVertexArray(VAO[0]);
        glDrawArrays(GL_PATCHES, 0, 6);
    }

//then we render the world and make use of that rendered shadow map
    {
        glUseProgram(shaderT2);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glDepthMask(GL_TRUE);
        glDepthRange(0, 1);
        glClearColor(0.0f, 1.0f, 0.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glUniform1i(glGetUniformLocation(shaderT2, "tex1"), 1);
        glActiveTexture(GL_TEXTURE0+1);
        glBindTexture(GL_TEXTURE_2D_ARRAY, texID[0]);
        glViewport(0, 0, 512, 512);
        loc1=glGetUniformLocation(shaderT2, "model\0");
        loc2=glGetUniformLocation(shaderT2, "view\0");
        loc3=glGetUniformLocation(shaderT2, "normal\0");
        loc4=glGetUniformLocation(shaderT2, "colour\0");
        loc5=glGetUniformLocation(shaderT2, "projection\0");
        loc6=glGetUniformLocation(shaderT2, "lightOrientation\0");

//render a rectangle where the shadow is drawn onto
        glUniformMatrix4fv(loc1, 1, GL_FALSE, identity);
        glUniformMatrix4fv(loc2, 1, GL_FALSE, view);
        matrixMultiply4D(swap, view, identity);
        inverseMatrix4D(swap, swap);
        transpose4D(normal, swap);
        glUniformMatrix4fv(loc3, 1, GL_FALSE, normal);
        glUniform4fv(loc4, 1, red);
        glUniformMatrix4fv(loc5, 1, GL_FALSE, projection);
        swap[0]=1.0; swap[1]=0.0; swap[2]=0.0; swap[3]=0.0;
        swap[4]=0.0; swap[5]=1.0; swap[6]=0.0; swap[7]=0.0;
        swap[8]=0.0; swap[9]=0.0; swap[10]=1.0; swap[11]=0.0;
        swap[12]=-lightMatrix[12]; swap[13]=-lightMatrix[13]; swap[14]=-lightMatrix[14]; swap[15]=1.0;
        glUniformMatrix4fv(loc6, 1, GL_FALSE, swap);
        glBindVertexArray(VAO[0]);
        glDrawArrays(GL_TRIANGLES, 0, 6);

//render the triangle which makes a shadow
        glUniformMatrix4fv(loc1, 1, GL_FALSE, triangleMatrix);
        glUniformMatrix4fv(loc2, 1, GL_FALSE, view);
        matrixMultiply4D(swap, view, triangleMatrix);
        inverseMatrix4D(swap, swap);
         transpose4D(normal, swap);
        glUniformMatrix4fv(loc3, 1, GL_FALSE, normal);
        glUniform4fv(loc4, 1, yellow);
        glUniformMatrix4fv(loc5, 1, GL_FALSE, projection);
        swap[0]=1.0; swap[1]=0.0; swap[2]=0.0; swap[3]=0.0;
        swap[4]=0.0; swap[5]=1.0; swap[6]=0.0; swap[7]=0.0;
        swap[8]=0.0; swap[9]=0.0; swap[10]=1.0; swap[11]=0.0;
        swap[12]=-lightMatrix[12]; swap[13]=-lightMatrix[13]; swap[14]=-lightMatrix[14]; swap[15]=1.0;
        glUniformMatrix4fv(loc6, 1, GL_FALSE, swap);
        glBindVertexArray(VAO[1]);
        glDrawArrays(GL_TRIANGLES, 0, 3);
    }

//finally render a white triangle which represents a location of the light
    {
        glUseProgram(shaderT3);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glDepthMask(GL_TRUE);
        glDepthRange(0, 1);
        glViewport(0, 0, 512, 512);
        loc1=glGetUniformLocation(shaderT3, "view\0");
        loc2=glGetUniformLocation(shaderT3, "projection\0");
        loc3=glGetUniformLocation(shaderT3, "lightOrientation\0");
        glUniformMatrix4fv(loc1, 1, GL_FALSE, view);
        glUniformMatrix4fv(loc2, 1, GL_FALSE, projection);
        glUniformMatrix4fv(loc3, 1, GL_FALSE, lightMatrix);
        glBindVertexArray(VAO[2]);
         glDrawArrays(GL_TRIANGLES, 0, 3);
    }

    glFinish();

//rotate a light on it's orbit
    matrixMultiply4D(lightMatrix, rotationMatrix, lightMatrix);

}

I have taken four movies representing the test program and code lines 1-4 above. Below is movie 1 done with code 1:

Movie_1

Movie 2 with code 2:

Movie_2

Movie 3 with code 3:

Movie_3

Movie 4 with code 4:

Movie_4

You can see that Movie 4 is the only one where shadow mapping works (there is no shadow in the border zone between the two hemispheres, but I can live with that. However, if someone knows how to fix it, I'd be happy if you tell me). But it's not working in the main project! I hope you could give me advice about what could be wrong, what should I check or give me your working dual paraboloid shadow mapping code samples...

1

There are 1 answers

1
mamannon On

Well, answering to myself...

I found my bug: while rendering a shadow map I also rendered the white ball representing the light. It occluded everything and that's the reason word was dark...

Now my main project works as my test Project, but there's still annoying thin dark line between the hemispheres. Does anybody know what can I do for that?