Home » Community » Forums » » Soft-Edged Shadows
  Intel sponsors gamedev.net search:   
[Control Panel] [Register] [Bookmarks] [Who's Online] [Active Topics] [Stats] [FAQ] [Search]

Add Forum to Favorites |  Send Topic To a Friend | View Forum FAQ | Track this topic

Page:   1 2 »»

 Last Thread Next Thread 
 Soft-Edged Shadows
Post Reply 
Nice. It's very close to what i described in ShaderX2 two years ago, in the chapter about rendering Soft Shadows, except that i did it with shadow volumes instead of shadow mapping.

I'm a bit disapointed that you didn't even mention (unless i misread it) the biggest drawback of this technique IMO: the "halo" effect around the silhouette of the objects. This might or not be acceptable, but because everything is done in screen space, increasing the amount of blur (to get "smoother" shadows) makes this problem more and more noticeable, up to the point where it becomes a real problem. In my article i tried to fight against it by only blurring the areas in the penumbra, using a custom kind of stenciling. It improved it a bit but there was still some glitches.

Y.


 User Rating: 1731   |  Rate This User  Send Private MessageView ProfileView Journal Report this Post to a Moderator | Link

I'm still too much of a newbie in this area, but the article is a good read.
I do have one question... The effect looks great but given the example, shouldn't the blurring of the shadow increase along it's length?
The diffracted light from the head of the statue has farther to travel than of the feet. At least I that's what I've noticed in real life.

Is it difficult to add to the effect?

-A clueless Noob

[Edited by - Matthew Chastain on January 18, 2005 6:38:37 PM]

 User Rating: 1041   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

i have to agree on the query of Matthew, above. i like the blurred shadows.. looks nice as they are, but since we're on the topic of believable shadows.. the blur shudn't be uniform, right?

how could we enhance the blur mechanism to support this?

 User Rating: 1078   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

To do that you would need to correctly calculate the shadow's penumbra and go from there. This is just a simple "dumb" blur so obviously it's forcing every shadow to have a constant sized penumbra area. Like he says on the first page, this technique does not do that (calculate penumbra) and the ones that do are very slow for complex scenes.

One thing that you can do for a speed up is to only blur on the shadow's edge. But to detect this and only blur on the edge would require dynamic branching in the fragment processor. NVIDIA has a demo that does this very thing (as do I, hehe).

-SirKnight

 User Rating: 1145   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

I (think I've) made an improvement on the example that could be used to expand it to "real" soft shadows (penumbra)

I've changed the GetGaussianDestrobution method to only take one "axis" as input. I did this because either x or y is 0 and is therefore not needed in this example anyway. It didn't really matter, but KISS.
Another distrobution of sample points (blur kernel) could be used instead to trade visual quality for better efficiency (I think). In that case the original GetGaussinDistrobution method would have to be used.

I changed GetGaussianOffset to eliminate the offset entirely. This is moved to the .fx file instead. In retrospect I think I've done this in an extremely complicated way. I'll explain later. I've also changed the method to output the weights in a more linear way. It now goes from -8 to +7.

C++
float GetGaussianDistribution( float amount, float rho )
{
   float g = 1.0f / sqrt( 2.0f * 3.141592654f * rho * rho );
   return g * exp( -( amount * amount ) / (2 * rho * rho) );
}

void GetGaussianOffsets( bool bHorizontal, D3DXVECTOR2 vViewportTexelSize,
                         D3DXVECTOR2* vSampleOffsets, float* fSampleWeights )
{
   // Get the weights for the remaining taps
   for( int i = 0; i < 15; i += 2 )
   {
      fSampleWeights[i] = 2.0f * GetGaussianDistribution( 0.0f, float(i - 8 ), 3.0f );
   }
   // Get the center texel offset and weight
   fSampleWeights[8] = 1.0f * GetGaussianDistribution( 0, 0, 2.0f );
}




I've made several changes in the .fx file. First I added the vViewportTexelSize's as properties(?).

I then changed the accumulators to calculate the texture coordinates on the fly. It's also possible to add a scalar to increase the size of the blur. The mistake I think I've made is that it would probably have been as easy as multiplying the g_vSampleOffsets[i] by the scalar instead, and thus get the same effect without making the changes I did at all. In my defense this is about the first time I've read shader code, and I couldn't understand anything of the code the first couple of times I read it. I didn't think of it until after I started to write on this post.

Anyway, the changed code should produce exactly the same result as the original (I hope), though it adds a couple of mults in place of a lookup :(
Also, I just changed the spots that I could see in the code on the web. There are probably some other spots in the source that would have to be changed to make it compile (notably to assign vViewportSize)

The scalar could be put in place of "2.0f" in my code

vAccum += tex2D( ScreenSampler, IN.vTexCoord + float2( 2.0f * (i-8) * g_viewPortSizeX, 0.0f ) * g_fSampleWeights[i];

..or at the marked spot in the original code.

vAccum += tex2D( ScreenSampler, IN.vTexCoord + g_vSampleOffsets[i] HERE) * g_fSampleWeights[i];


.fx file
float g_viewPortTexelSizeX;  // vViewportTexelSize.x
float g_viewPortTexelSizeY;  // vViewportTexelSize.y


// Gaussian filter vertex shader
struct VSOUTPUT_BLUR
{
   float4 vPosition    : POSITION;
   float2 vTexCoord    : TEXCOORD0;
};

VSOUTPUT_BLUR VS_Blur( float4 inPosition : POSITION, float2 inTexCoord : TEXCOORD0 )
{
   // Output struct
   VSOUTPUT_BLUR OUT = (VSOUTPUT_BLUR)0;
   // Output the position
   OUT.vPosition = inPosition;
   // Output the texture coordinates
   OUT.vTexCoord = inTexCoord;
   return OUT;
}
// Horizontal blur pixel shader
float4 PS_BlurH( VSOUTPUT_BLUR IN ): COLOR0
{
   // Accumulated color
   float4 vAccum = float4( 0.0f, 0.0f, 0.0f, 0.0f );
   // Sample the taps (g_vSampleOffsets holds the texel offsets
   // and g_fSampleWeights holds the texel weights)
   for(int i = 0; i < 15; i++ )
   {
//      vAccum += tex2D( ScreenSampler, IN.vTexCoord + g_vSampleOffsets[i] ) * g_fSampleWeights[i];
      vAccum += tex2D( ScreenSampler, IN.vTexCoord + float2( 2.0f* (i-8) * g_viewPortSizeX, 0.0f ) * g_fSampleWeights[i];

   }
   return vAccum;
}
// Vertical blur pixel shader
float4 PS_BlurV( VSOUTPUT_BLUR IN ): COLOR0
{
   // Accumulated color
   float4 vAccum = float4( 0.0f, 0.0f, 0.0f, 0.0f );
   // Sample the taps (g_vSampleOffsets holds the texel offsets and
   // g_fSampleWeights holds the texel weights)
   for( int i = 0; i < 15; i++ )
   {
//      vAccum += tex2D( BlurHSampler, IN.vTexCoord + g_vSampleOffsets[i] ) * g_fSampleWeights[i];
      vAccum += tex2D( BlurHSampler, IN.vTexCoord + float2( 0.0f, 2.0f * (i-8) * g_viewPortSizeX ) * g_fSampleWeights[i];
   }
   return vAccum;
}





To create a natural looking soft shadow the we must create another texture map with values from 0.0f to perhaps about 10.0f (150 pixels) for the distance from the shadow caster to reciever. I don't know enough about shadow mapping to know how this can be done, but I imagine it would be possible since the way it currently works IIRC is to transform the light perspective to the camera perspective and compare z-maps. If this is done in pixel shader then I believe it is possible to calculate the distance.

This texture map would then be used to calculate change the size of the blur using the scalar I mentioned before.

I hope that it's possible to do it without having to change the weights as well.

PS as a final option, it would be possible to use a 2d lookup map for offsets and weights, so it should work no matter what. I think scalars are better though, since they take less space and it's possible to get a much smoother transition from min blur to max blur. With a 2d lookup map we'd get steps for each blur size change. It does work as a last resort though.

 User Rating: 1186   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Why does it run at 3FPS in my FX5200? After switching to tiger.x model it went to 12fps. Is that th standar performance?

Guimo

 User Rating: 1256   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Quote:
Original post by Ysaneya
the biggest drawback of this technique IMO: the "halo" effect around the silhouette of the objects.
Y.


Does the article do anything to fight this? I did not see anything in the screenshots.

~Wave

 User Rating: 1068   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Hi,

I dont know why, but when I start the demo, it just shows black :-?
I have a Radeon 9800Pro with 5.1 Drivers.

Bye Enrico

 User Rating: 1141   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Quote:
Original post by frostburn
To create a natural looking soft shadow the we must create another texture map with values from 0.0f to perhaps about 10.0f (150 pixels) for the distance from the shadow caster to reciever. I don't know enough about shadow mapping to know how this can be done, but I imagine it would be possible since the way it currently works IIRC is to transform the light perspective to the camera perspective and compare z-maps. If this is done in pixel shader then I believe it is possible to calculate the distance.
Haven't been able to play with shaders for a while now after my computer fried, but the first thing that came to mind after reading the article and this thread was if it was possible to simply encode that information in the shadow map. Rather than simply duplicating the depth across xyz, storing it only in one, and using the remainder for perhaps some 2D vectors to modify the strength of the blur.

 User Rating: 1151   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Quote:
Original post by Enrico
I dont know why, but when I start the demo, it just shows black :-?
I have a Radeon 9800Pro with 5.1 Drivers.


Works fine on mine with 6.14.10.6497 and 6.14.10.6505 catalyst 5.1 drivers (I upgraded them today).

 User Rating: 1430   |  Rate This User  Send Private MessageView ProfileView Journal Report this Post to a Moderator | Link

Quote:
Original post by Guimo
Why does it run at 3FPS in my FX5200? After switching to tiger.x model it went to 12fps. Is that th standar performance?

Guimo

Same here and the same setup. It is probally because FX5200s are crappy. I would like to know how this works on a higher end card so if anyone is getting decent performance please tell me.

______________________________________________________________________________________
With the flesh of a cow.

 User Rating: 1218   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

GFX5200 are slow but n0ot THAT slow. The demo its just rendering one object of 30000 polys and making a blur... get 3FPS just for that???? No way...

The offsceen texture is 512*512 so no problem there. Its a small RT.

I even changed the rendering mode so it uses other model and does no blurring but no use... if this is the real performance then I guess this effect cant be used for a real game.

Luck!
Guimo

 User Rating: 1256   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Look into the blur pixel shader - FX5200 are crappy, but they are particularly crappy at doing pixel shaders 2.0 !


 User Rating: 1731   |  Rate This User  Send Private MessageView ProfileView Journal Report this Post to a Moderator | Link

Quote:
Original post by Enrico
Hi,

I dont know why, but when I start the demo, it just shows black :-?
I have a Radeon 9800Pro with 5.1 Drivers.

Bye Enrico

I had the same problem with an X800Pro. What fixed it for me was switching to release d3d-runtime.

 User Rating: 1052   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Switching to the retail version of d3d also fixed the black screen problem on my GeForce 6800GT, latest drivers, December sdk.

 User Rating: 1018   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

I'm wondering, could this method be improved such that the amount of blurring is not constant? One hacky way of doing this could be that when rendering to the screen-sized buffer, the distance of the light is recorded. If you're rendering to the RGB channels, the Alpha channel is a good choice. The distance value will then modify the grain of the blurring filter. Really close objects will therefore have very sharp shadows, and since they're so close, the shadow mapping aliasing won't be very strong. Of course, this isn't the correct way of doing things, as the sharpness depends on the distance from the shadow-casting object rather than the light, but it's still going to look better, surely? ~phil

 User Rating: 1154   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Quote:
Original post by JonnyQuest
I'm wondering, could this method be improved such that the amount of blurring is not constant? One hacky way of doing this could be that when rendering to the screen-sized buffer, the distance of the light is recorded. If you're rendering to the RGB channels, the Alpha channel is a good choice. The distance value will then modify the grain of the blurring filter. Really close objects will therefore have very sharp shadows, and since they're so close, the shadow mapping aliasing won't be very strong.

Of course, this isn't the correct way of doing things, as the sharpness depends on the distance from the shadow-casting object rather than the light, but it's still going to look better, surely?

~phil


What about making the variance (or standerd deviation (rho˛), can't rememer which) in the normal (Gaussian) distribution proportional to the distance between the occluder to the recieving surface divided by the distance between the light and the occluder.

In the pass that renders the shadowed to the screen (Step 2) both the distance from the light to the occluder (A) and the distance from the light to recieving surface (A) are known. The distance between the occluder and the recieving surface is then B-A so the amount of blurring should be proportional to (B-A)/A. That value can be stored in one of the components of the out value of the pixel shader.

 User Rating: 1015    Report this Post to a Moderator | Link

I thought the article was quite interesting. Does anyone know any other good links for more of the theory beind lighting and shadowing techniques? I think I would like to learn more of the theory before I delve too far into it.

 User Rating: 1620   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Quote:
Original post by Anonymous Poster
What about making the variance (or standerd deviation (rho˛), can't rememer which) in the normal (Gaussian) distribution proportional to the distance between the occluder to the recieving surface divided by the distance between the light and the occluder.

In the pass that renders the shadowed to the screen (Step 2) both the distance from the light to the occluder (A) and the distance from the light to recieving surface (A) are known. The distance between the occluder and the recieving surface is then B-A so the amount of blurring should be proportional to (B-A)/A. That value can be stored in one of the components of the out value of the pixel shader.

Well, that's going to be a lot more involved. For one, you need to divide by A, and since it's a texture lookup and not a coordinate like B, you can't even do it outside the pixel shader.

Secondly, using a per-sample weighting of the blur function isn't going to be straightforward, especially as the weights still have to add to 1.

It probably can be done, but it's not going to be easy/fast.

~phil

 User Rating: 1154   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Well done! However:

The light seemed to flicker on my computer (800Mhz, NVidia GeForce FX 5200), but it was only running at about 8 fps so perhaps it is not usually noticable.

In your pshader code I noticed:
   // Sample each of them checking whether the pixel under test is shadowed or not
   float fShadowTerms[9];
   float fShadowTerm = 0.0f;
   for( int i = 0; i < 9; i++ )
   {
      float A = tex2Dproj( ShadowSampler, vTexCoords[i] ).r;
      float B = (IN.fDepth – 0.1f);

      // Texel is shadowed
      fShadowTerms[i] = A < B ? 0.0f : 1.0f;
      fShadowTerm     += fShadowTerms[i];
   }
   // Get the average
   fShadowTerm /= 9.0f;
   return fShadowTerm;




fShadowTerms is only used as a temporary variable but why when
fShadowTerm += A < B ? 0.0f : 1.0f would suffice?

------------------------------------
Development of Rock, Paper, Scissors - The Third Dimension

 User Rating: 1201   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Thanks for the article! I'm fairly new to shadows, but not so new to lighting, and I have a few efficiency notes.

First, in the comment from DrGUI, he recommends getting rid of the fShadowTerm temporary array. Good idea, but simpler and better is (in the inner loop)

if (A < B)
. fShadowTerm += (1.0f / 9.0f);

and get rid of the divide at the end of the loop.


Probably most importantly, in your PS_scene function, you're always calculating the specular component, which is pretty expensive. You want to put this off until you know you need it. E.g.
   // NB!! I use "p=" for plus equals, since the plus sign wasn't showing up
   //  in my previews
   float4 result = ambient * vColor;
   float nDotL = dot( IN.vNormal, IN.vLightVec )
   float diffuse  = 0.0f;
   if (nDotL > 0.0f)
   {
     // Grab the shadow term
     float fShadowTerm = tex2Dproj( BlurVSampler, IN.vScreenCoord );
     // Grab the spot term
     float fSpotTerm = tex2Dproj( SpotSampler, IN.vProjCoord );
     float intensity = fShadowTerm * fSpotTerm;
     if (intensity > (1.0f / 255.0f)) // minimum to effect 8-bit color
     {
       // add the diffuse term we know exists
       result += diffuse * intensity * g_vLightColor.a * vColor;

       float theta = dot (2 * nDotL * IN.vNormal - IN.vLightVec, IN.vEyeVec);
       if (theta > 0.0f)
       {
         float specular = pow(theta, 8);

         // add the specular term we know exists
         result += specular * intensity * g_vLightColor.a * vColor;
       }
     }
   }
  
   return result;



There are other ways to improve this, e.g., if you know the shininess has a minimum value you can increase the threshold for theta, it's possible the intensity branch isn't worth it (although I doubt it), it's possible you could factor out vColor (don't know my way around a shader well enough to say), you've fixed the ambient intensity to zero.... Still it should boost the speed considerably. The power function is quite expensive.

It would be interesting to know where the bottlenecks are in this -- of course it will vary depending on the hardware, but I imagine there are some general trends.

Cheers,
Ben


 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

has anyone tried to implement the sample in OpenGL/ GLSL?

 User Rating: 997   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Has anyone use GLSL and opengl turned out that programme?

 User Rating: 1015   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Can anyone explain this matrix for me:

// Generate the texture matrix
float fTexOffs = 0.5 + (0.5 / (float)SHADOW_MAP_SIZE);
D3DXMATRIX matTexAdj( 0.5f, 0.0f, 0.0f, 0.0f,
0.0f, -0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
fTexOffs, fTexOffs, 0.0f, 1.0f );


What I understood so far is that this matrix takes a vertex from it's local (mesh) coordinates system to the texture coordinates system of the light.

But can someone explain briefly the math behind it, because I believe my demo isn't working correctly because of this matrix.

Thanks


 User Rating: 1018   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link
Page:   1 2 »»
All times are ET (US)

Post Reply
 Last Thread Next Thread 
Forum Rules:
You may not post new threads
You may post replies
You may not edit your posts
You may not use HTML in your posts
Jump To:
Administrative Options: