Variance shadow mapping: artifacts when using filtered shadow map

I’ve implemented standard variance shadow mapping and it works as expected if I use an unfiltered 32-bit floating-point render target during the shadow pass:

[ATTACH=CONFIG]1363[/ATTACH]

However, if I setup a render target with linear, bilinear or trilinear filtering instead, all fragments that lie behind a certain distance from the camera are in shadow:

[ATTACH=CONFIG]1364[/ATTACH]

Here I have moved a little closer:

[ATTACH=CONFIG]1365[/ATTACH]

What’s happening here?!

Common vertex shader:

void main() {
  vec4 objectSpacePosition = in_position;
  
  vec4 worldSpacePosition = in_modelMatrix*objectSpacePosition;
  vec4 cameraSpacePosition = in_viewMatrix*worldSpacePosition;


  #ifdef LIGHT_COUNT
    #if LIGHT_COUNT > 0
      for (int i = 0; i < LIGHT_COUNT; i++) {
        Light light = in_lights[i];
        
        v_lightSpacePosition[i] = light.viewMatrix * worldSpacePosition;
      }
    #endif
  #endif
  
  vec4 screenSpacePosition = in_projectionMatrix*cameraSpacePosition;
  
  gl_Position = screenSpacePosition;
}

Shadow pass shader:

void main() {
  #ifdef LIGHT_COUNT
    #if LIGHT_COUNT > 0
      Light light = in_lights[0];
      vec4 lightSpacePosition = v_lightSpacePosition[0];
      
      vec4 lightProjection = light.projectionMatrix * lightSpacePosition;
      vec2 shadowMapCoords = (lightProjection.xy/lightProjection.w)*0.5+0.5;
      
      float depth = lightProjection.z;
      
      gl_FragColor = vec4(depth, depth*depth, depth, depth*depth);
    #endif
  #endif
}

Fragment shader:

float shadowMapping(sampler2D shadowMap, vec2 texCoords, float lightDistance, int shadowIndex, float varianceOffset){
  vec4 shadow = texture2D(shadowMap, texCoords);
    
  vec2 moments = vec2(1.0, 1.0);
    
  if (shadowIndex == 0) {
    moments = shadow.rg;
  } else if (shadowIndex == 1) {
    moments = shadow.ba;
  }


  float variance = moments.y - (moments.x*moments.x);  
  variance = max(variance, varianceOffset);  


  float difference = lightDistance - moments.x;  
  float upperBound = variance / (variance + difference*difference);
    
  return max(upperBound, step(lightDistance, moments.x));
}


void main() {
  vec3 totalDiffuseColor = vec3(0.0);
    
  #ifdef LIGHT_COUNT
    #if LIGHT_COUNT > 0
      for (int i = 0; i < LIGHT_COUNT; i++) {
        Light light = in_lights[i];
                
        vec4 lightSpacePosition = v_lightSpacePosition[i];
                
        vec4 lightProjection = light.projectionMatrix * lightSpacePosition;
        vec2 shadowMapCoords = (lightProjection.xy/lightProjection.w)*0.5+0.5;

        vec3 diffuse_color = 1.0;

        /* fresnel term, attenuation, etc. */
                
        #ifdef SHADOW_MAPPING
          diffuse_color *= shadowMapping(shadowMap, shadowMapCoords, lightProjection.z, light.shadowIndex, in_varianceOffset);
        #endif


        totalDiffuseColor += diffuse_color;
      }
    #endif
  #endif
    
  gl_FragColor = vec4(totalDiffuseColor, 1.0);
}

I see you mention trilinear filtering, but have you generated mipmaps for the shadow map? It looks like the far fragments try to lookup on a texture lod that does not exist.

D’oh! Of course, that was the problem! It’s painfully obvious to me now.

Thank you!