EVSM is the best needed ?

Started by
7 comments, last by Irlan Robson 9 years, 4 months ago

Hi,

EVSM is expensive, that demand RGBA32F texture on each cascade but from all my research that looks to be the best of the best actually.

Is it really needed to blur the depth in two pass : R32F depth map then RGBA32F using gaussian blur ?

Is it the really the best of the best actually and should be the method used today ?

EVSM shadow mapping pixel shader code :


float2 WarpDepth(in float depth, in float2 exponents)
{
  depth = 2.0f * depth - 1.0f;
  float pos =  exp( exponents.x * depth);
  float neg = -exp(-exponents.y * depth);
  return float2(pos, neg);
}

float4 ShadowDepthToEVSM(in float depth)
{
  float2 warpedDepth = WarpDepth(depth, float2(40.0f, 20.0f));
  return float4(warpedDepth.xy, warpedDepth.xy * warpedDepth.xy);
}
Advertisement

I've read little bit about EVSM, but I'm using VSM and I don't think that I'm the only one doing it. I like VSM approach because is portable and cheap.

Just rendering depth to two channels instead of one you can play with texture filters because your values are linear anyway. This explanation covers in detail everything you need to know to blur the SM and some other methods. It has HLSL code. This post covers from the top to the bottom with GLSL. Today most of the lights in a scene are culled to write to the Shadow Map. I've seen games that uses just the Sun - a simple direction light - to cast shadows. Simple VSM with the Lighting Bleeding correction and some improvements can effective simulate a nice scene.

Also, if you want to take a look at my shaders you can download here an executable I did using DirectX™ 11.

Light-Bleeding correction :


float p_max = smoothstep(0.20f, 1.0f, variance / (variance + d*d));

I tried your sample but I got a weird result, far to be good, using A and D I can only move forward backward but can't rotate the camera and I got bad quality shadow.

The result I got : http://zupimages.net/up/14/48/41fw.png

Here result with my simple cascaded shadow mapping : http://zupimages.net/up/14/48/uirb.png

Light-Bleeding correction :


float p_max = smoothstep(0.20f, 1.0f, variance / (variance + d*d));

I tried your sample but I got a weird result, far to be good, using A and D I can only move forward backward but can't rotate the camera and I got bad quality shadow.

The result I got : http://zupimages.net/up/14/48/41fw.png

Here result with my simple cascaded shadow mapping : http://zupimages.net/up/14/48/uirb.png

Are you using PCF or interpolation or both in your example?

According to this I think everything is working as it should. AFAIK this is the way that VSM works, the edges are pre-filtered before can be sampled.

Also, in your picture the camera is very far, and I coudn't see if the edges are soft like the VSM approach. I'll take a look in my code anyway and see if I did something weird.

I simply use 4 cascade R32F depth map + poisson disk 8 samples on the picture I showed.

I simply use 4 cascade R32F depth map + poisson disk 8 samples on the picture I showed.

Oh, I see. That is why is much better than the brute-force VSM. I think that this approach is a little bit slower than VSM or SAVSM (though I did never implemented this one).
One problem may be that the fact you're using 8 samples instead of just the simple Variance + Chebyshev Probabilistic Upper Bound approach, and this relates to PCF in some sword of way; applying a filter to the final depth it is fine, not the depth itself before can be compared with the position in ligh-space depth; this is just an opinion you may not agree. Everything that can retire all the for loops of the pixel shader - when possible - it is good for GPU performance as you already know.
I'm running in a NVIDIA™ 8400GS and the performance it is quite good when doing pixel instructions using VSM without any filter and MS - though I have to pick one in order to have blurry shadows.

I've checked my shaders and code and everything is fine. The way it is appearing is the fact that I didn't applied any stepping filter.


/* VARIANCE SHADOW MAPPING. */
float linstep( float _fLow, float _fHigh, float _fV ) {
	return saturate( ( _fV - _fLow ) / ( _fHigh - _fLow ) );
}

float ChebyshevVariance( in float2 _vMoments, float _fComp ) {
	/* VARIANCE. */
	float fVar = max( _vMoments.y - ( _vMoments.x * _vMoments.x ), -0.005 );

	/* UPPER BOUND. */
	float fD = _fComp - _vMoments.x;
	float fP = _fComp <= _vMoments.x;
	float fPMax = fVar / ( fVar + fD * fD );
	
	return max( fP, linstep( 0.5, 1.0, fPMax ) );
}

float VsmShadowLookUp( in float2 _vP, float _fComp ) {
	return ChebyshevVariance( g_t2dShadowMap.Sample( g_ssLinearBorder, _vP ).xy, _fComp );
}

/* A POINT-TEXTURE-SPACE INTERSECTION ROUTINE. */

bool PointTextureSpace( in float2 _vP ) {
	return _vP.x >= 0.0 && _vP.x <= 1.0 &&
	       _vP.y >= 0.0 && _vP.y <= 1.0;
}

void Main( in VS_OUT _voOut, out float4 _vOutColor : SV_TARGET ) {
	float fShadow = 1.0;
	if ( PointTextureSpace( _voOut.vLightTexAndDepth.xy ) ) {
		fShadow = VsmShadowLookUp(_voOut.vLightTexAndDepth.xy, _voOut.vLightTexAndDepth.z);
	}
	_vOutColor.xyz = mMaterial.vDiffuse.xyz * fShadow;
	_vOutColor.w = 1.0;
}

The only thing I did changed is that I'm using the method explained in GPU Gems 3 that has 3 tweaks less.

After reading for a while looks like EVSM is a much better path to follow. Actually this link redirects you to an nice set of explanations of various methods, and the EVSM approach looks promising and I am going to implement tomorrow. He is saying the same I told you: avoid filtering step functions like PCF or something. The lighting bleeding issue looks gets corrected when using EVSM also.

Now I don't know where I'm going to find time to read all his publications that looks promising BTW. Anyway, I hope it helps.

There's a lot of factors that go into determining whether a particular shadowing technique is really "the best", and those factors are usually different for every project. Otherwise everyone would agree, and we would be using the same thing in every game. It's going to be up to you to weigh the pros and cons of different approaches, and try to decide what's actually the best fit for your project and target hardware.

In general, "standard" depth buffer shadow maps with various forms of PCF are still the most popular choice for games. They're cheap to render, easy to setup, they're supported on a very wide range of hardware, and they have well understood flaws (mostly related to filtering and biasing).

VSM's primary advantage over standard shadow maps is that they're pre-filterable. Unlike depth buffer shadow maps, where you to perform the depth comparison before filtering, you can filter VSM's as soon as you have them in two-component VSM format. This opens the door to things like MSAA, mipmaps, and separable blur passes. This can not only give you better quality, but can also possibly make things cheaper relative to standard shadow maps (this is especially true if you cache your shadow maps across frames). Their other main advantage is that they are much easier to bias compared to standard shadow maps. With VSM it's possible to pick a single "magic" value that will work across a wide range of conditions, whereas with standard shadow maps you typically need artists-authored offsets combined with complex techniques that adjust the bias based on the sample position and receiver slope. The main disadvantages are that light bleeding (which I'm sure that you're already familiar with), and the need to convert from "standard" depth into the variance format. Light bleeding can be reduced with a few tricks, but it will always be present to a certain extent for certain occluder/receiver configurations. The conversion requires either using a pixel shader when rendering to the shadow map, or having a conversion step after you've finished rendering to a depth buffer. The conversion can possibly be rolled into an MSAA resolve or the filtering step, if you use those things. You might see additional memory storage brought up as a concern for VSM, but in practice I've found that using an R16G16_UNORM format provides completely adequate precision, with the same footprint as a 32-bit depth buffer.

EVSM is much like VSM, except it attempts to address light bleeding by using an exponential warp. This warp can be very effective at reducing or eliminating light bleeding in most cases, but it won't fix all cases. Since it's an exponential warp you're pretty much required to use a floating-point format, which can quickly leave you with precision problems if you're not careful. 32-bit floats will give the best results, but you can get away with 16-bit if you're very careful about restricting your depth ranges and also use a more conservative warping factor. However you really need to include the negative warp for best results, so at best you're looking at R16G16B16A16_FLOAT which is double the footprint of a 32-bit depth buffer or a VSM texture. For maximum quality, you'll want 32-bit floating point which means 4x the footprint of a standard shadow map.

Having done a lot of work with EVSM myself, I will say that it's definitely a viable approach for a higher-spec hardware that has good support for floating-point formats (for instance DX11 PC video cards, or current-gen consoles). If you're targeting more modest hardware, then you're probably better off sticking to standard shadow maps or VSM. I don't currently know of any shipping games or games in development that use EVSM, with the exception of the game that I'm working on (The Order: 1886). So when it comes out, you can have a look and judge the quality for yourself. tongue.png

Light-Bleeding correction :


float p_max = smoothstep(0.20f, 1.0f, variance / (variance + d*d));

I tried your sample but I got a weird result, far to be good, using A and D I can only move forward backward but can't rotate the camera and I got bad quality shadow.

The result I got : http://zupimages.net/up/14/48/41fw.png

Here result with my simple cascaded shadow mapping : http://zupimages.net/up/14/48/uirb.png

As promised I did check the example. In the example I was using a 256x256 texture resolution. Changing to 512x512 definitely gives more precision. The results:

nn0cva.jpg

If someone see this post, I uploaded a example of ESM and VSM with a 3x3 Box Blur on this link; you can change the size of the kernel in the shaders; there is an source code of the high-level view of the shader and blur passes. I'll post a example of the EVSM when I finish. Hope can help.

Cheers,

Irlan

This topic is closed to new replies.

Advertisement