Shadow maps do not take perspective into account?

Started by
3 comments, last by Morloth 9 years, 4 months ago

Hello everybody,

I'm messing about with my 3D engine and decided that I want to add volumetric lighting to it. The technique I want to use is decribed here (http://hub.jmonkeyengine.org/forum/topic/volumetric-lighting-filter-wip/), it looks simple and is easy to understand. However, when I started to delve into shadow maps I came accross something that I cannot figure out.

The main question I want to ask is:

When rendering a flat surface that is orthogonal from a pointlight's direction should I expect all the depth values in the depth buffer to be the same?

Here is an image to describe my problem and expected v.s. received outcome (sorry to everybody I offend with my horrible drawing skills).

pLer2Qc.png

To test the depth buffer I create a FrameBuffer attachment as detailed below. GL_TEXTURE_COMPARE_MODE is set to GL_NONE because I want to write the depth values to a texture and use them later.


// Initialise the texture to be used for shadow mapping.
glGenTextures(1, &texture_id_);
glBindTexture(GL_TEXTURE_2D, texture_id_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, depth_texture_size_, depth_texture_size_, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Set up depth comparison mode.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
// Set up wrapping mode.
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	// Set the texture id for the depth texture.
	glActiveTexture(GL_TEXTURE0 + light_->getDepthTextureId());
	glBindTexture(GL_TEXTURE_2D, texture_id_);

// Create FBO to render depth into.
glGenFramebuffers(1, &fbo_id_);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
// Attach the depth texture to it.
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texture_id_, 0);
// Disable colour rendering.
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);

The surface is a plane with a point in (0, 0, 0). The light is 5.2 units above this plane and faces downwards (see above image). The render function is defined as such:


void ShadowRenderer::render(const FrustumCaster& cam)
{
	// Gather all the leaf nodes.
	unsigned int pre_rendered_objects_ = 0;

	glCullFace(cull_mode_);
	glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
 	glViewport(0, 0, depth_texture_size_, depth_texture_size_);

	glClearDepth(1.0f);
 	glClear(GL_DEPTH_BUFFER_BIT);
	
	glm::vec3 camera_location = cam.getLocation();
 	glm::mat4 view_matrix = cam.getViewMatrix();
	glm::mat4 perspective_matrix = cam.getPerspectiveMatrix();

	ShadowShader& shader = ShadowShader::getShader();
	frustum_->setFrustum(perspective_matrix * view_matrix);
	
	/ / New rendering method.
	Region* region = scene_manager_->getRoot().findRegion(cam.getLocation());
	if (region != NULL)
	{
		std::stringstream ss;
		std::vector<const Portal*> processed_portals;
 		region->preRender(*frustum_, cam.getLocation(), *this, false, pre_rendered_objects_, 0, processed_portals, ss);
		for (std::vector<SceneNode*>::const_iterator ci = scene_manager_->getPlayers().begin(); ci != scene_manager_->getPlayers().end(); ++ci)
		 {
 			(*ci)->preRender(*frustum_, cam.getLocation(), *this, false, pre_rendered_objects_);
 		}
 	}
 	// If we cannot find a region we fall back on the true and tested... Although this
 	// is more a debuf feature and should be removed in future versions of this rendering
 	// engine.
 	else
 	{
 		SceneNode& root = scene_manager_->getRoot();
 		root.preRender(*frustum_, cam.getLocation(), *this, false, pre_rendered_objects_);
 	}

 	for (std::vector<const RenderableSceneLeaf*>::const_iterator ci = active_entities_.begin(); ci != active_entities_.end(); ++ci)
 	{
 		const RenderableSceneLeaf* leaf = *ci;

		if (leaf->getShadowType() == ShadowRenderer::NO_SHADOW || !leaf->isInFrustum(*frustum_))
		{
			continue;
		}

		if (leaf->isDoubleSided())
		{
			glDisable(GL_CULL_FACE);
		}
		else
		{
			glEnable(GL_CULL_FACE);
		}

		leaf->draw(view_matrix, perspective_matrix, active_lights_, &shader);
	}
	// Unbind.
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glBindTexture(GL_TEXTURE_2D, 0);

	// Reset viewport.
	int orgWidth, orgHeight;
	glfwGetWindowSize(&orgWidth, &orgHeight);
	glViewport(0, 0, orgWidth, orgHeight);
}

The shader used for rendering the depth texture is very simple, as one would expect:


#version 420 core

uniform mat4 modelviewprojection_matrix;

layout (location = 0) in vec4 a_Vertex;

void main(void)
{
	gl_Position = modelviewprojection_matrix * a_Vertex;
}


#version 420 core

layout (location = 0) out vec4 colour;

void main(void)
{
	colour = vec4(0.0, 0.0, 0.0, 1.0);
}

modelviewprojection_matrix is calculated as: projection_matrix * view_matrix * model_matrix, where:

  • projection_matrix = glm::perspective(light_angle_ * 2, 1.0f, 0.1f, 60.0f).
  • view_matrix =
  • 
    glm::vec3 camera_location(-location_);
    glm::mat4 view_matrix = glm::rotate(glm::mat4(1.0f), -pitch_, glm::vec3(1.0f, 0.0f, 0.0f));
    view_matrix = glm::rotate(view_matrix, -yaw_, glm::vec3(0.0f, 1.0f, 0.0f));
    view_matrix = glm::rotate(view_matrix, -roll_, glm::vec3(0.0f, 0.0f, 1.0f));
    view_matrix = glm::translate(view_matrix, camera_location);
    

    , where location = (0, 5.2, 0), pitch = -90, yaw = 0, roll = 0

  • model_matrix = I

Now, using a debugger I checked the values of the depth texture created in this scenario. To my surprise the texture contains the same depth value in every pixel (something like 0.9618)! I would expect this if the projection matrix was orthographical, but not when using a perspective matrix.

To highlight the problem I have created a shape that starts at the light's position and usesd the depth values to create a light volume. As you can see from the image belowthe length in the centre is perfect, but the length of the edge are too short. I attribute this to the depth map containing the wrong values, but I could be wrong.

WmLvKp8.png

Can somebody explain why this result is either correct or if I should expect different values and perhaps give me a pointer where things might have gone wrong.

Many thanks for your help!

Bram

Advertisement

Yes, it is correct that the values are all the same. In fact, it only saves the z-value (the standard z-buffer works in the same way) of the actual difference vector and it is enough to determine if a point in shadow or not.

You can reconstruct the real vector by the texture coord and the shadow depth value, like the position reconstruction in a deferred shader. Once you have the vector, just take the length and you have what you want , thought it is really unnecessary if you want to determine if a point is in shadow.

Understood, that makes sense. I want to create volumetric lighting, so I need more than just checking whether a point is in shadow or not :).

The light calculation is independently of shadows, the shadow is just a (soft) mask for the light calculation. When it come to volumentric light, you will need to trace rays through the volume, the shadow map helps to define irregular volumes (eg when a box is blocking the light), a shadow map is in fact just a special type of volume texture. Doing the ray (cone) tracing is the more complexe and expensive part.

Yes, it all works now that I know what valus are actually stored in the shadow map :). Thanks for your help!

fYAKJlR.jpg

This topic is closed to new replies.

Advertisement