How to add blur to VSM? :)

BoH_Havoc

Newcomer
Hi there,
in the last few days i was fiddeling around with variance shadow maps. I tried hard to implement them in the engine i'm using and finally managed to do it :)
I then added shadow transparency and the colormap. Looking really good so far :)

Now i want to blur the shadows, but to be honest i don't have a clue on how to do that. I tried to render the shadowmap to a separate view, then blur it and project it back to the scene, but as you may have already guessed by now i get nasty halo effects. As far as i know, there should be a way better way to blur the shadow. If I didn't get that wrong, then that's the whole thing about vsm: having a good base to blur the shadows.

Here's my code (basically AndyTX's code, with some minor changes so the engine can use it)

Code:
//------------------------------------------------------------------------------
// Lighting Pass Shaders
//------------------------------------------------------------------------------
struct LightingVSOutput
{
	float4 position_screen : POSITION;
	float3 position_world  : TEXCOORD0;
	float3 normal_world    : TEXCOORD1;
	float3 light_position  : TEXCOORD2;
	float3 light_direction : TEXCOORD3;
	float3 TexCoords : TEXCOORD4;
};

LightingVSOutput lighting_VS(
		float4 position : POSITION,
		float3 normal   : NORMAL,
		uniform float4x4 tshadow)
{      
   LightingVSOutput output = (LightingVSOutput)0;
   
   //--------------------------------------------
   //calculate ViewInv
   //find an other way to do this!  
   tshadow[0].x = matMtl[0].x;
  	tshadow[0].y = matMtl[1].x;
  	tshadow[0].z = matMtl[2].x;
  	tshadow[0].w = matMtl[0].w;
  	
  	tshadow[1].x = matMtl[0].y;
  	tshadow[1].y = matMtl[1].y;
  	tshadow[1].z = matMtl[2].y;
  	tshadow[1].w = matMtl[1].w;
  	
  	tshadow[2].x = matMtl[0].z;
  	tshadow[2].y = matMtl[1].z;
  	tshadow[2].z = matMtl[2].z;
  	tshadow[2].w = matMtl[2].w;
        
   tshadow[3].x = vecSkill5;
  	tshadow[3].y = vecSkill9;
  	tshadow[3].z = vecSkill13;
  	tshadow[3].w = matMtl[3].w;
   //-------------------------------------------
    
    output.position_screen  = mul(position, matWorldViewProj);
    output.position_world   = mul(position, matWorld).xyz;
    output.normal_world     = mul(float4(normal, 0), matWorld).xyz;
    
    // Work out the light position and direction in world space
    output.light_position   = float3(tshadow._41, tshadow._42, tshadow._43);
    output.light_direction  = float3(tshadow._31, tshadow._32, tshadow._33);
        
    //colormap texcoords
    output.TexCoords = mul(position, matWorldView).xyz;
    
    
    return output;
}

struct LightingPSOutput
{
	float4 color : COLOR;
};

LightingPSOutput lighting_PS(
		LightingVSOutput input,
		uniform float4x4 shadow_view_projection)
{
	LightingPSOutput output = (LightingPSOutput)0;

	// Renormalize
    float3 normal_world = normalize(input.normal_world);
    float3 light_direction = normalize(input.light_direction);

    // Sum the contributions from all lights
    float3 lit_color = float3(0, 0, 0);
      
  	// Light Shader:
  	float3 light_contrib;
  	float3 dir_to_light;
  	float  dist_to_light;
  	float  n_dot_l;
  	 	
  	{  		  	
    	// Unnormalized light vector
    	dir_to_light = input.light_position - input.position_world;
    	dist_to_light = length(dir_to_light);
		
		float atten_amount =
			clamp((dist_to_light   - light_atten_begin) /
        	      (light_atten_end - light_atten_begin),
        	       0.0, 1.0);
		
    	// Radial attenuation
    	dir_to_light = normalize(dir_to_light);
    	float2 cos_angle_atten = cos_light_angle_atten;
    	float  cos_angle = dot(-dir_to_light, light_direction);
    	
    	float angle_atten_amount = 
        	clamp((cos_angle         - cos_angle_atten.x) /
                  (cos_angle_atten.y - cos_angle_atten.x),
            	   0.0, 1.0);
            	   
    	// Compose the light shader outputs
    	light_contrib = (1.0 - atten_amount) * (1.0 - angle_atten_amount) * vecLightColor[0];
    	n_dot_l       = dot(normal_world, dir_to_light);
  	}
  	
  	
  	// Variance Shadow Mapping:
  	{
  		// Transform the surface into light space and project
  		// NB: Could be done in the vertex shader, but doing it here keeps the "light
  		// shader" abstraction and doesn't limit # of shadowed lights.
  		float4 surf_tex = mul(float4(input.position_world, 1.0), shadow_view_projection);
    	surf_tex = surf_tex / surf_tex.w;
    	
    	// Rescale viewport to be [0,1] (texture coordinate space)
    	float2 shadow_tex = surf_tex.xy * float2(0.5, -0.5) + 0.5;
	
    	float4 moments;
    	// TODO: Emulate bilinear filtering on unsupporting hardware
      	moments = tex2D(light_shadow_map, shadow_tex);
 	
    	// Rescale light distance and check if we're in shadow
    	float rescaled_dist_to_light = dist_to_light / light_atten_end;
    	rescaled_dist_to_light -= light_shadow_bias;
    	float lit_factor = (rescaled_dist_to_light <= moments.x);
    	
    	// Variance shadow mapping
    	float E_x2 = moments.y;
    	float Ex_2 = moments.x * moments.x;
    	float variance = min(max(E_x2 - Ex_2, 0.0) + (light_vsm_epsilon), 1.0);
    	float m_d = (moments.x - rescaled_dist_to_light)*4000;
    	float p = variance / (variance + m_d * m_d);
    	
    	// Adjust the light color based on the shadow attenuation
    	light_contrib *= max(lit_factor, p);
  	}
     
      
  	// Evaluate basic diffuse lighting
  	float self_shadow = clamp(n_dot_l * 2.0, 0.0, 1.0);
  	float3 direct_contrib = diffuse_color * n_dot_l;
  	
  	lit_color += (light_contrib) * self_shadow * direct_contrib;

   //output.color = float4(lit_color, 1.0)+ShadowAmbient;
   
   //colormap
   float2 tPos = input.TexCoords.xy/input.TexCoords.z;
	tPos.y = 1 - (tPos.y*0.657 + 0.5);
	tPos.x = tPos.x*0.499 + 0.5;
	//output.color *= tex2D(color_map, tPos.xy)*ColorAmbient;


	output.color = (float4(lit_color, 1.0)+ShadowAmbient)*(tex2D(color_map, tPos.xy)*ColorAmbient);   
 
   return output;
}


Is someone able to help me out here? ;)
 
All you need is to 'blur' the shadow map is to blur the first and the second moment, technically speaking if you don't blur them you don't even have a first and second moment to use in your chebyshev's inequality computation as you didn't build any statistics over your shadow map, thus computing occlusion using this method doesn't make matematically and visually sense.
VSMs are extremely simple to implement:
1) render your depth, and depth squared into a texture
2) blur this texture, both components!! (the main benefit over PCF filtering is that you can use a separable filter here, so blur in the x direction and then in y direction, even a simple box filter works great)
3) sample your variance shadow map, extract first and second raw moments, derive from them mean and variance and use chebyshev's inequality to compute the probability that each pixel has to be in shadow or not. (this is basically what lighting_ps() is doing)

have fun!
 
Alright, here's what i got so far:

Shadow before bluring the depthmap...
vsm_noBlur.jpg


... and after blurring it
vsm_blur.jpg


depthmap
vsm_blur.jpg



Well, while this looks better than before, it isn't exactly what i was trying to archive. The thing i'm after is softshadows ;) Maybe i was unclear in my first post, sorry for that. Or maybe i did something wrong and therefor got the wrong result ? All i did is blurring the depthmap.

render your depth, and depth squared into a texture
So i have to render 2 depthmaps (one normal, one squared) and then multiply/add them into a single texture? Do i have to blur them before ore after combining them? Sorry for beeing such a noob, but sadly im not that much of a shader guru :cry: *hides*

Thanks for your help so far nAo :smile:
 
(alright, i tried six time to post a reply, but it never worked...maybe im not allowed to quote/ijnclude pics/links ? so here we go again, without pics/links/quotes)

Alright, i blurred the depthmap, and while the result looks better than before (the shadows have less "stairs" ) this isn't exactly what i'm after. What i'm trying to archive are softshadows. Sorry if i was unclear in my first post.

Or maybe i did something wrong? All i did was blurring the depthmap. In your reply you mentioned that i have to render 2 depthmaps, one normal and one squared and then render them into 1 texture (additive?). Is that right? If it is, do i have to blur those 2 maps before i combine them, or after? Or gets all this already done by the pixel shader (float E_x2 = moments.y; float Ex_2 = moments.x * moments.x;)?

Sorry for beeing a noob here, but im not that much of a shader guru :cry: *hides*

And thanks for your help so far nAo
 
(alright, i tried six time to post a reply, but it never worked...maybe im not allowed to quote/ijnclude pics/links ? so here we go again, without pics/links/quotes)
Sorry I didn't see those earlier, I would have approved. Yeah, it's the possible spam catcher blocking your way, it'll leave you alone after a while :smile:
 
All i did was blurring the depthmap. In your reply you mentioned that i have to render 2 depthmaps, one normal and one squared and then render them into 1 texture (additive?). Is that right?
No, this is wrong.
What you have to do is just to render depth and depth squared to a texture (tipically you will need to render depth into the x component and depth*depth into the y component).
After that you just blur the whole texture keeping the components separate, you don't need to add them or to mix them in any way, just blur both components.
Blurring your texture let you build 2 quantites, for each pixel, called first (in thte x component) and second (in the y component) raw moments.
Once you have these quantities you can then use them to compute all the quantities needed by VSM to come up with a soft shadowing term.

Sorry for beeing a noob here, but im not that much of a shader guru :cry: *hides*
Don't worry, I read VSM paper 10 times before getting in my head why and how it works.

[edit] just had a look at your images, can't really tell what's going on from those pics
 
Works now :)

In fact i was doing everything right and all the code was already there. i just had to tweak the values and add some small extra calculations to fit it to the engine i'm using.

vsm for the win, i think i'm in love :love: ;)

oh and btw, the picture above labeled as depthmap isn't the depthmap, the link is incorrect. the correct one is
project-havoc.com/g2/vsm_depthmap.jpg , sorry for that
 
Back
Top