Help with Shadow Mapping and Multi-Texturing

Started by
22 comments, last by Terin 11 years, 8 months ago
Hello again,

Trying to make a more specific topic to my problem, since I've realized my lighting was actually correct and has been correct (thank you, everyone!), but that my Shadow Mapping leaves something to be desired (as it's not working at all). It looks like the FBO/Shadow Map is rendering/creating the Shadow Map correctly, but that my use of it in my final Shader.

The basic idea is that I have two shader programs -- one renders the scene without shadows into the depth buffer of an FBO (or into a texture if the hardware isn't GL 3.0), and the second mixes that with the way the scene should be rendered.

Here's what the scene looks like, presently (normal view):

2012.07.21%20Shadow%20Mapping%20I%20%28Normal%20View%29.png

And this is the what the scene looks like from the Light's View:

2012.07.21%20Shadow%20Mapping%20II%20%28Light%27s%20View%29.png

The second uses the same projection and modelview matrices used by the Depth Buffer capture to render -- so it should be spot on, but here it looks like it's flipped the wrong way.

In the bottom right of both images, I'm just rendering the resulting Shadow Map on top of the scene post-final shader-render.

The code process is essentially:

1) Setup Shader Program to Grab Shadow Map
2) Run through rendering of all objects that generate shadows
3) Setup Shader Program to render everything AND shadow map on top of it
4) Render the Shadow Map in the bottom right corner and the other text data on the top-left.

Let's assume that I created the Shadow Map right -- and we can see the rendering of it on the bottom right. The rendering of it seems to be problematic.

Here's what my overall code looks like.

[source lang="csharp"] Rendering.ActivateShadowTextureUnit(); //Set's active Texture Unit to Unit 1
Rendering.EnableTextures(); //Enable Textures
Gl.glBindTexture( Gl.GL_TEXTURE_2D, ShadowMapTextureID[0] ); //Uses the image that is rendered in the bottom right hand corner

Rendering.ActivatePrimaryTextureUnit(); //Now activate the primary texture unit, where we render our normal textures in (Unit 0)

Gl.glUseProgram( ViewerRenderProgram ); //Use the rendering GL Program

Int32 LightModelViewProjectionMatrixLocation = Gl.glGetUniformLocation( ViewerRenderProgram, "LightModelViewProjectionMatrix" );
Gl.glUniformMatrix4fv( LightModelViewProjectionMatrixLocation, 1, Gl.GL_FALSE, LightProjectionViewMatrix ); //This is the Projection Matrix used to render light from (poorly named, sorry)

Int32 WorldMatrixLocation = Gl.glGetUniformLocation( ViewerRenderProgram, "WorldMatrix" );
Gl.glUniformMatrix4fv( WorldMatrixLocation, 1, Gl.GL_FALSE, WorldMatrix ); //This is the World ModelView Matrix

Int32 DiffuseMapLocation = Gl.glGetUniformLocation( ViewerRenderProgram, "DiffuseMap" );
Gl.glUniform1i( DiffuseMapLocation, 0 ); //Texture Unit 0 (where our normal textures are loaded and rendered through

Int32 ShadowMapLocation = Gl.glGetUniformLocation( ViewerRenderProgram, "ShadowMap" );
Gl.glUniform1i( ShadowMapLocation, 1 ); //Texture Unit 1 (where the Shadow Map is currently bound to)

Int32 MinimumShadowLocation = Gl.glGetUniformLocation( ViewerRenderProgram, "MinimumShadow" );
Gl.glUniform1f( MinimumShadowLocation, 0.8f ); //This is to run against the lambert as the "max value" -- eventually, it will be variable[/source]
Here's how I grab my Lighting Matrices...

[source lang="csharp"] Gl.glPushMatrix();

Gl.glMatrixMode( Gl.GL_PROJECTION );
Gl.glLoadIdentity();

Gl.glOrtho( -Configuration.Instance.RenderingSettings.Depth,
Configuration.Instance.RenderingSettings.Depth,
-Configuration.Instance.RenderingSettings.Depth,
Configuration.Instance.RenderingSettings.Depth,
-Configuration.Instance.RenderingSettings.Depth * 2.0d * Configuration.Instance.RenderingSettings.Zoom,
Configuration.Instance.RenderingSettings.Depth * 2.0d * Configuration.Instance.RenderingSettings.Zoom );

Gl.glMatrixMode( Gl.GL_MODELVIEW );
Gl.glLoadIdentity();

Gl.glScaled( Tile.DIMENSION * Configuration.Instance.RenderingSettings.Zoom,
Tile.DIMENSION * Configuration.Instance.RenderingSettings.Zoom,
Tile.DIMENSION * Configuration.Instance.RenderingSettings.Zoom );

Gl.glRotated( -90.0d, 1.0d, 0.0d, 0.0d );

Gl.glRotated( Configuration.Instance.RenderingSettings.LightAngle, 1.0d, 0.0d, 0.0d );
Gl.glRotated( -90.0d, 0.0d, 0.0d, 1.0d );

Gl.glGetFloatv( Gl.GL_PROJECTION_MATRIX, LightProjectionViewMatrix );

Gl.glPopMatrix();[/source]

And this is the World Matrix:

[source lang="csharp"] Gl.glPushMatrix();

Gl.glMatrixMode( Gl.GL_MODELVIEW );
Gl.glLoadIdentity();

Gl.glScaled( Tile.DIMENSION * Configuration.Instance.RenderingSettings.Zoom,
Tile.DIMENSION * Configuration.Instance.RenderingSettings.Zoom,
Tile.DIMENSION * Configuration.Instance.RenderingSettings.Zoom );
Gl.glRotated( -Configuration.Instance.RenderingSettings.Tilt, 1.0f, 0.0f, 0.0f );
Gl.glRotated( Configuration.Instance.RenderingSettings.Rotation, 0.0f, 0.0f, 1.0f );

Gl.glTranslated( -pX, -pY, -pZ ); //pX, pY, pZ is the "center" of the scene that I render at

Gl.glGetFloatv( Gl.GL_MODELVIEW_MATRIX, WorldMatrix );

Gl.glPophMatrix();[/source]

And finally, my Shader Code looks something like this:

[source lang="cpp"]//VERTEX

uniform mat4 LightModelViewProjectionMatrix;
uniform mat4 WorldMatrix;

varying vec3 Normal;
varying vec4 LightCoordinate; // Position in model view projection space from the lights view.
varying vec4 WorldPosition; // Position in world space.

void main()
{
Normal = gl_Normal;
WorldPosition = WorldMatrix * gl_Vertex;
LightCoordinate = LightModelViewProjectionMatrix * gl_Vertex;
gl_Position = ftransform(); // Transform via fixed function into the viewer's view
gl_TexCoord[0] = gl_MultiTexCoord0;
}



//FRAGMENT

uniform sampler2D DiffuseMap;
uniform sampler2D ShadowMap;
uniform mat4 WorldMatrix;
uniform float MinimumShadow;

varying vec3 Normal;
varying vec4 LightCoordinate;
varying vec4 WorldPosition;

void main()
{
// Direct lighting
// ------------------------------
vec4 Color = gl_LightSource[0].ambient;
vec3 l = normalize(gl_LightSource[0].position.xyz); // direction to the light source

vec3 view_normal = normalize(gl_NormalMatrix * Normal);
vec3 view_light_direction = normalize(vec3(gl_LightSource[0].position)); //(WorldMatrix * vec4( LightPosition.x, LightPosition.y, LightPosition.z, 0 ) ).xyz;

//float lambert = max(dot(view_normal, view_light_direction), MinimumShadow);
float lambert = max(dot(Normal, l), MinimumShadow);
Color.xyz *= lambert;

//Blend in Color from primary texture unit
Color.wxyz *= texture2D(DiffuseMap, vec2(gl_TexCoord[0])).wxyz;

// Shadow mapping
// ------------------------------
vec4 lcoord = LightCoordinate; // Fragment position in light space.
lcoord /= lcoord.w; // Project to cartesian space
lcoord.xy = lcoord.xy * 0.5 + 0.5; // Scale since light clipping space is in [-1,1] but texture space is [0,1]

float fragmentDepth = lcoord.w; // Depth of the fragment in light space.
float shadowMapDepth = texture2D(ShadowMap, lcoord.xy).x; // Depth in the shadow map.

float eps = 0.1; // depth bias
float shadow = fragmentDepth - eps > shadowMapDepth ? 0.5: 1.0;

gl_FragColor = Color * shadow;[/source]

I had assumed before, when I had a fixed position light (not a directional one), that I had been showing shadows -- this was just an error on my part. If I commented out everything under "Shadow Mapping" in the Fragment Shader, no difference was made! So, my lighting is correct. Getting the Shadow Map appears to be correct. The problem appears to be in how I'm rendering it. Perhaps I'm making a mistake? Also going to include my Shadow Map Capture stuff as well:

[source lang="cpp"]//VERTEX

varying vec4 LightPosition;

void main()
{
gl_Position = ftransform();
LightPosition = gl_Position;
}

//FRAGMENT

varying vec4 LightPosition;

void main()
{
gl_FragColor = vec4(LightPosition.z / LightPosition.w);
}[/source]

Hopefully someone can give me some insight into what I'm doing wrong. I've been struggling to get this working for a while and I've had a lot of help from various members in the community. The issue I have appears to be the *actual* shadow mapping. And again, the resulting texture of the Shadow Map is showing in the bottom right hand corner of the screen (with glColor3f(1.0f, 1.0f, 1.0f);).

Here's also how I'm constructing the actual FBO and using it...

[source lang="csharp"] Gl.glGenTextures( 1, ShadowMapTextureID );
Gl.glBindTexture( Gl.GL_TEXTURE_2D, ShadowMapTextureID[0] );
Gl.glTexImage2D( Gl.GL_TEXTURE_2D, 0, Gl.GL_RGBA, Configuration.Instance.RenderingSettings.ShadowDimension, Configuration.Instance.RenderingSettings.ShadowDimension, 0, Gl.GL_RGBA, Gl.GL_FLOAT, IntPtr.Zero );
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR );
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR );
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_CLAMP_TO_BORDER );
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_CLAMP_TO_BORDER );

Gl.glTexParameterfv( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_BORDER_COLOR, Video.Instance.ClearColor );

Gl.glGenTextures( 1, DepthMapTextureID );
Gl.glBindTexture( Gl.GL_TEXTURE_2D, DepthMapTextureID[0] );
Gl.glTexImage2D( Gl.GL_TEXTURE_2D, 0, Gl.GL_DEPTH_COMPONENT24, Configuration.Instance.RenderingSettings.ShadowDimension, Configuration.Instance.RenderingSettings.ShadowDimension, 0, Gl.GL_DEPTH_COMPONENT, Gl.GL_UNSIGNED_BYTE, IntPtr.Zero );
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_NEAREST );
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_NEAREST );

ShadingMethodsAllowed = ShadingMethods.None | ShadingMethods.NonFBO;

Gl.glGenFramebuffersEXT( 1, ShadowMapFramebuffer );
Gl.glBindFramebufferEXT( Gl.GL_FRAMEBUFFER_EXT, ShadowMapFramebuffer[0] );
Gl.glFramebufferTexture2DEXT( Gl.GL_FRAMEBUFFER_EXT, Gl.GL_COLOR_ATTACHMENT0_EXT, Gl.GL_TEXTURE_2D, ShadowMapTextureID[0], 0 );
Gl.glFramebufferTexture2DEXT( Gl.GL_FRAMEBUFFER_EXT, Gl.GL_DEPTH_ATTACHMENT_EXT, Gl.GL_TEXTURE_2D, DepthMapTextureID[0], 0 );

string ExtensionsSupported = Gl.glGetString( Gl.GL_EXTENSIONS );

if ( Gl.glCheckFramebufferStatusEXT( Gl.GL_FRAMEBUFFER_EXT ) != Gl.GL_FRAMEBUFFER_COMPLETE_EXT )
{
ShadingMethodsAllowed |= ShadingMethods.FBO;
}

Gl.glBindFramebufferEXT( Gl.GL_FRAMEBUFFER_EXT, 0 );[/source]

Thanks for all the help in advance -- if someone can point me in a direction so that I can progress forward, I would greatly appreciate it! I apologize for all of the funky spaghetti code!
Advertisement
The line "lcoord /= lcoord.w;" is probably resulting 'w' always being 1 which then used later on. But this isn't all that wrong, I think.

If you are in fact multiplying the input vertex by the Light mvP matrix to get "LightCoordinate", you don't need any extra division by 'w' when using an Ortho matrix as it'll be one anyway. Just use .z as "fragmentDepth" (still do the scale on xy) . Make sure that the shadow texture being sampled is actually a depth component texture (it looks like it is in what you posted, I think). Also, I suggest using the .z off of the texture2D call (even though it probably returns the same in all xyzw for depth texture) just because it looks more "correct".

Hope that helps smile.png

New C/C++ Build Tool 'Stir' (doesn't just generate Makefiles, it does the build): https://github.com/space222/stir

Thank you for the response!

That didn't fix it at all, unfortunately. It didn't do anything, and I do have some valleys and peaks in the scene, so there should be something there.

So, a good question (since I've been getting a lot of help) -- is something maybe wrong with the format of it? ShadowMapTexture is :

[source lang="csharp"] Gl.glGenTextures( 1, ShadowMapTextureID );
Gl.glBindTexture( Gl.GL_TEXTURE_2D, ShadowMapTextureID[0] );
Gl.glTexImage2D( Gl.GL_TEXTURE_2D, 0, Gl.GL_RGBA, Configuration.Instance.RenderingSettings.ShadowDimension, Configuration.Instance.RenderingSettings.ShadowDimension, 0, Gl.GL_RGBA, Gl.GL_FLOAT, IntPtr.Zero );
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR );
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR );
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_CLAMP_TO_BORDER );
Gl.glTexParameteri( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_CLAMP_TO_BORDER );

Gl.glTexParameterfv( Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_BORDER_COLOR, Video.Instance.ClearColor );[/source]

I also have the DepthMapTexture, which I'm not sure what it really is for (if you look in the code above). I'm not sure why I have a ShadowMap and a DepthMap.

The fragment shader of the shadow map capture gl_FragColor = vec4(LightPosition.z / LightPosition.w); should just put the "depth" as a scalar across all channels of RGBA -- correct?

There has to be something wrong with the Shadow Map projection here in the Fragment shader -- either I'm not setting up something correctly or the code isn't projecting things correctly... Or should I be passing in the DepthMapTexture instead of Shadow?
Use the depth component texture instead. Ditch the RGBA one. Using only a depth buffer will render it faster, and you can use simpler shaders. The fragment shader can just an empty main ("void main(){}") and the vertex shader main contain just the ftransform() line. I think the call is "glDrawBuffer(GL_NONE);" in order to not have a color buffer in the framebuffer object.

I'm not sure but I think using an RGBA texture like that should work how you have it, but using a depth texture should just work (easier and faster).

New C/C++ Build Tool 'Stir' (doesn't just generate Makefiles, it does the build): https://github.com/space222/stir

Well, that still doesn't solve the issue of the shadows... Is there a way to just render shadows only? I was assuming I could get rid of all the texture stuff at the top to calculate light and just set the variable Color as 1,1,1,1 -- then multiply by the Shadow Map to see if shadows are correctly rendered. When I do that, though, the screen just stays a solid color.
For some reason, it's not letting me edit my prior post, but I did this:

[source lang="cpp"] varying vec3 Normal;
varying vec4 LightCoordinate;
varying vec4 WorldPosition;

void main()
{
// Direct lighting
// ------------------------------
vec4 Color = gl_LightSource[0].ambient;
vec3 l = normalize(gl_LightSource[0].position.xyz); // direction to the light source

vec3 view_normal = normalize(gl_NormalMatrix * Normal);
vec3 view_light_direction = normalize(vec3(gl_LightSource[0].position)); //(WorldMatrix * vec4( LightPosition.x, LightPosition.y, LightPosition.z, 0 ) ).xyz;

//float lambert = max(dot(view_normal, view_light_direction), MinimumShadow);
float lambert = max(dot(Normal, l), MinimumShadow);
Color.xyz *= lambert;

//Blend in Color from primary texture unit
Color.wxyz *= texture2D(DiffuseMap, vec2(gl_TexCoord[0])).wxyz;

// Shadow mapping
// ------------------------------
vec4 lcoord = LightCoordinate; // Fragment position in light space.
//lcoord /= lcoord.w; // Project to cartesian space
lcoord.xy = lcoord.xy * 0.5 + 0.5; // Scale since light clipping space is in [-1,1] but texture space is [0,1]

float fragmentDepth = lcoord.z; // Depth of the fragment in light space.
float shadowMapDepth = texture2D(ShadowMap, lcoord.xy).z; // Depth in the shadow map.

float eps = 0.001; // depth bias
float shadow = fragmentDepth - eps > shadowMapDepth ? 0.5: 1.0;

gl_FragColor = vec4(1,1,1,1) * shadow;
}[/source]

So basically I avoid the color stuff at all -- just calculate the shadow. My screen is pure white, and that's it.
At this point, it looks like the shader should work. After taking a closer look at the matrix code:

glPush/PopMatrix have different stacks per MatrixMode, so technically I think there should be Push/Pop around altering both MODELVIEW and PROJECTION, although if both are going to be overwritten immediately for the next pass/render it doesn't really matter.

It appears that the projection matrix for the light is being retrieved, but the modelview is not even though it appears to be modified. They need to be multiplied manually before being assigned to the LightModelViewProjectionMatrix uniform. I'm not sure about this though, as I only use GL3+ which deprecates the matrix functions.

New C/C++ Build Tool 'Stir' (doesn't just generate Makefiles, it does the build): https://github.com/space222/stir

Would it help to confirm that the Matrix is the exact same going in at both locations when I get them?

My goal is to have this code working on a netbook in addition to a high-end machine -- so unfortunately, I have to stick with GL 2.0 and before -- with few optimizations here and there for GL3.
And that being said -- do I need the Model View * Projection to be multiplied for the actual display? That may be my problem... I'm not sure if that's the correct parameter, because I think I remember it being Projection -- not Projection * ModelView -- but if it is both, that should be a relatively simple fix, yes? :-)
Yes, you need the full MVP that was used for rendering the shadow map for the actual display otherwise you haven't transformed the display vertex into the right light-relative space in order to be comparing with the shadow map. It should be a simple fix, just get the modelview as well as the projection; simply send the modelview separately into the vertex shader and do the multiply there (lightProj*lightMV*vertex).

New C/C++ Build Tool 'Stir' (doesn't just generate Makefiles, it does the build): https://github.com/space222/stir

This topic is closed to new replies.

Advertisement