I'm working on shadow maps in OpenGL (using C#).

First, I've created a framebuffer and attached a depth texture as follows:

// Generate the framebuffer.
var framebuffer = 0u;

glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

// Generate the depth texture.
var shadowMap = 0u;

glGenTextures(1, &shadowMap);
glBindTexture(GL_TEXTURE_2D, shadowMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, 1024, 1024, 0, GL_DEPTH_COMPONENT, GL_FLOAT, null);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowMap, 0);

// Set the read and draw buffers.

Later (after rendering to the shadow map and preparing the main scene), I sample from the shadow map in a GLSL fragment shader as follows:

float shadow = texture(shadowMap, shadowCoords.xy).r;

Where vec3 shadowCoords is the coordinates of the fragment from the perspective of the global directional light source (the one used to create the shadow map). The result is shown below. As expected, shadow edges are jagged due to using GL_NEAREST filtering.


To improve smoothness, I tried replaced the shadow map's filtering with GL_LINEAR, but the results haven't changed. I understand there are other avenues I could take (like Percentage-Closer Filtering), but I'd like to answer this question first, if only for my sanity. I've also noticed that other texture parameters (like GL_CLAMP_TO_EDGE rather than GL_REPEAT for wrapping) don't function for the shadow map, which hints that this may be a limitation of depth textures in general.

To reiterate: Is linear filtering possible using depth textures in OpenGL?


1 Answer 1


Shadow maps, the way you're using them, don't filter well. The linear filtering does what it's supposed to, it's just not the thing you actually want for this context.

Take a sample point halfway between an occluded sample (say, depth from light = 5 vs my depth from light = 10), and a non-occluded sample (depth from light = far plane). We somehow want to interpolate these two adjacent samples to get a "half-shadowed" anti-aliased effect, rather than a stairstep.

If we just linearly interpolate these two depth values, with a far plane that's say 1000 units out, we get:

   0.5 * (occluderDepth) + 0.5 * (farPlaneDepth)
 = 0.5 * 5 + 0.5 * 1000
 = 502.5 

That 502.5 is much farther away from the light than my position, so we conclude we're not shadowed at all!

A similar thing happens if we interpolate with a sample at the receiver's depth:

  0.5 * (occluderDepth) + 0.5 * (receiverDepth)
= 0.5 * 5 + 0.5 * 10
= 7.5

Now we have a depth that's substantially closer to the light than our receiver point, so we conclude we're entirely in shadow!

You can see that even when we work out the math by hand, instead of a smooth falloff when we sample between depth points, we still get a hard stairstep edge between completely shadowed and not at all shadowed.

So: interpolating depths does not do the same thing as interpolating "shadowed-ness".

You can store your shadow maps in an alternative format, like Variance Shadow Maps. These store not just a depth value, but some distribution information designed so that filtering the shadow map does filter "shadowed-ness" - or something similar to it, at least. You get a different type of artifact with these, which is light leaking - areas of the shadow map with large depth discontinuities trick the filtering into thinking some light could get through, even if no light should.

  • \$\begingroup\$ Excellent answer. That all seems obvious in hindsight (given the binary nature of in-shadow vs. not), but I guess that's true of many technical problems. Thank you so much! :) \$\endgroup\$
    – Grimelios
    Commented Nov 26, 2020 at 20:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .