Sign in to follow this  
b_thangvn

Need help setup light volume for deferred shading

Recommended Posts

Hi guys, So I want to implement deferred shading, since it's very popular now and I think I should know how to do it :D. Anyway, I read around and realize that before doing deferred shading, I need to setup some light volume for my lighting. Ok, so I read the 6800 Leagues and it said: - Render convex bounding geometry: * Spot light = cone * Point light = sphere * Directional light = quad or box I understand that for a point light, I'll draw a sphere that represent its lighting area of effect, and use it to optimize a bunch of stuff. However, that's all I know. I have no idea on how to actually do it. I mean, I can draw a sphere which has origin right at the point light location, but how do I determine the range of the point light and hence calculate the sphere radius ? Right now my pointlight looks something like this:
struct PointLight
{
   D3DXCOLOR ambient;
   D3DXCOLOR diffuse;
   D3DXCOLOR spec;
   float intensity;
   D3DXVECTOR4 posWorld;         // Position in world space
   D3DXVECTOR4 posWorldView;     // Position in view space, since I do lighting calculation in view-space
};

All helps will be greatly appreciated.

Share this post


Link to post
Share on other sites
You can give your pointlight any range you like. In the shader you can than make it so that the light faints away as it approaches the limits of the range, if you know what I mean. Its usually referred to as attenuation.
Just pick any reasonable range you like for now and go from there.

I am wondering what you do with the ambient color in the point light, though?

Share this post


Link to post
Share on other sites
Hi,

For the point-light ambient, honestly I teach myself DirectX through books and online tutorials. So I built my lighting system bases on a model where ambient is calculated like this:

ambient = light.ambient * material.ambient;

The above model was built for a directional light, so I guess for point-light I don't need it. Anyway, now for the point light, my HLSL code is like this:


... Calculating diffuse and spec

// Scale according to distance from the light
float fDistance = distance(g_aPointLight[iLight].posWorldView, ViewPosition);

// Final intensity of light
vIntensity += (fDiffuse + fSpecular) * g_aPointLight[iLight].intensity / (fDistance * fDistance);




I'm doing lighting calculation in view-space, and I do have attenuation by just divide the intensity over square of distance from light to the pixel. Now, let say I call fRange is the radius of the sphere that represents my light affecting area, how do I relate it to the lighting calculation ? I tried something like this:


if( fDistance <= fRange ) // I put fRange = 5.0f
vIntensity += (fDiffuse + fSpecular) * g_aPointLight[iLight].intensity / (fDistance * fDistance);




but ofcourse it will give me sharp edge between distance smaller than 5.0f and distance larger than 5.0f. I took a picture:

http://img256.imageshack.us/img256/9544/pointlight.jpg

So any thought on how I should do it ? Thanks.

Share this post


Link to post
Share on other sites
Hm. Something you could try to see if it works is this:


float3 lightVec = g_aPointLight[iLight].posWorldView - ViewPosition;

lightVec /= fRange;

float att = max(1.f - dot(lightVec, lightVec), 0.f);

//multiply att with your diffuse and see what you get, no if ()




Regarding the ambient light I would suggest that you have one global value that gets applied exactly once to every surface regardless of the other lights. Otherwise materials suddenly get lighter once you introduce several lights although they might only be influenced by one or less lights.

[Edited by - B_old on August 16, 2009 1:25:45 AM]

Share this post


Link to post
Share on other sites
Hi,

Thanks for the reply. I follow your suggest but not sure if I get it right:


float3 lightVec = g_aPointLight[iLight].posWorldView - ViewPosition;

lightVec /= 5.0f; // I just choose the range = 5.0f for now

float att = max(1.0f - dot(lightVec, lightVec), 0.0f);

//if(fDistance <= 15.0f)
vIntensity += (fDiffuse + fSpecular) * g_aPointLight[iLight].intensity * att / (fDistance * fDistance);



Did I do it right? Because for small range value ( < 10.0f ), the area cover by the light with and without using the "att" formula are relatively close. However, let say I increase the range to 15.0f, now the light without the "att" formula will cover larger area than the light with "att" formula ( I would say the difference is somewhere between 15-20% ). Any thought on this ? Thanks.

Share this post


Link to post
Share on other sites
Hi,

try this:

vIntensity += (fDiffuse + fSpecular) * g_aPointLight[iLight].intensity * att;


Does it work any better?
Out of curiosity, how is vIntensity calculated further up?

Share this post


Link to post
Share on other sites
Hey B_old,

Well, that formula wouldn't work better because without dividing the vItensity by (fDistance * fDistance), the light won't fade away at distance far from the point light source. The vItensity is just multiplied by the color from texture map after the calculation.

Share this post


Link to post
Share on other sites
Hm. The att term just do just what you are describing. It works for me and a few others I assume, as I didn't come up with it. Can you maybe show another screenshot of whats going on?

Share this post


Link to post
Share on other sites
Hey b_Old,

Don't get me wrong that the formula doesn't work. It works, but like I said, only with small range radius. With larger range radius, the lights start to show differences in the range they cover.

I read on MSDN and see that attenuation is calculated like this:

Att = 1 / (att0 + att1 * d + att2 * d^2)

I guess I don't need att0 and att1, so I just simplify it into:

Att = 1 / ( att2 * d^2 )

with att2 is the attenuation quadratic factor. Now, I need to figure out this att2 factor so that Att will be close to 0.0 (no light) when d gets close to the Range. Also, Att has to be close 1 ( full light ) for those d that close to the light source. Any suggestion ?

[Edited by - b_thangvn on August 16, 2009 2:57:07 PM]

Share this post


Link to post
Share on other sites
Att = att2 * d^2

att2 is a property of the light that you set.

if you want to compute how big the bounding volume of the light should be, solve for d when Att = 0.01 or some other number small enough that eliminates the hard edge

Share this post


Link to post
Share on other sites
Hey Dragon,

Sorry, I mess up with the formula. The right one is :

Att = 1 / ( attFactor * d^2 )

Regarding your suggestion, what I want to do is to limit the light range into some pre-define radius, not calculate the light range. Let say right now I have a light with range = 15.0f . Now, I want to calculate the Att so that when I multiply it with the overall light intensity, the light range can be limited to any range that I set, let say 5.0f.

I'm thinking of reducing the intensity of the light source with regards to the pre-defined Range, but haven't figure out a formula yet.

Share this post


Link to post
Share on other sites
The formula is exactly the formula you have for attenuation:

Att = 1 / ( attFactor * d^2 )

If you want the light to have Att of 0.01 at d = 5, then solve for attFactor:

0.01 = 1 / ( attFactor * 5^2 )

0.01 = 1 / ( x * 5^2 )

0.01 = 1 / 25x

0.25x = 1

x = 1 / 0.25

x = 4

Share this post


Link to post
Share on other sites
Hi Dragon,

Stupid me you are right! I got that formula before and it worked great with Range = 5.0f. However, when I tested Range = 10.0f, the light with Att covered more area than the light without it did. Let me show you some screen shot ( btw do you know how I can load the pictures straight in here instead of posting a link? ):

First is the light without att:

http://img199.imageshack.us/img199/1939/pointlightwithoutatt.jpg

Second is the light with att:

http://img190.imageshack.us/img190/1482/pointlightwithatt.jpg


Notice how the light with att covers some pillars that the light without att doesn't. I figure this is because of the 1% (0.01) of the light intensity falloff at d = 10.0f.

So now I think I will try to scale the light intensity with regards to the light range, since that would be more physically correct. Again, I calculate the overall lighting like this:

vIntensity = (spec + diffuse) * pointLight.intensity * att; ( with att = 1 / ( factor * d^2 )

Now how should I relate pointLight.intensity and some pre-define Range? All help is greatly appreciated.

Share this post


Link to post
Share on other sites
sorry, I don't understand what you mean by "with att" and "without att", and can't easily see the difference between the images that you're trying to point out

Share this post


Link to post
Share on other sites
Hi Dragon,

My bad, let me try it again. I think I used the wrong words here. Ok, from the beginning, I calculate my overall light light this:


float attenuation = 1 / ( d^2 ); // My old formula

if( fDistance <= Range ) // This is just to see the range that the light is supposed to cover
light += (spec + diffuse) * pointLight.intensity * att;



Then, later, using your formula, the lighting calculation become this:


float factor = 100.0f / ( Range^2 ); // This is your formula

float attenuation = 1 / ( factor * d^2 );

light += (spec + diffuse) * pointLight.intensity * att;



Now, it works great with Range = 5.0f. But when I test it with Range = 10.0f, I notice that the light with your formula covers more area than the light with my old formula. I added some ambient light so it's easier for you to see:

First is light with my old formula:

http://img9.imageshack.us/img9/1661/lightpointwithoutusingf.jpg

Second is the light with your formula:

http://img27.imageshack.us/img27/5451/lightpointusingformula.jpg

I can see them clearly on my screen, but maybe your screen is darker than mine. Please let me know if that's the case, I will increase the light intensity more so you can see.

Notice how the light with your formula cover some pillar (you can tell by look at the specular light), while light with my formula doesn't. Like I said, I figure this is because of the 1% (0.01) of the light intensity falloff at d = 10.0f.

So I think I will try to scale the light intensity with regards to the light range, since that would be more physically correct. Now I'm working on a formula to relate them. Any thoughts on this? Thanks.

Share this post


Link to post
Share on other sites
Sorry, what's the problem? The first image you show has a noticeable bad artifact (the hard line where the light stops abruptly) whereas the 2nd looks correct.

One method is to calculate the range based on the light intensity and falloff, but you asked for how to modify the falloff based on a constant range.

Both approaches are probably useful for an artist. Also useful is specifying a combination of a linear falloff and a quadratic one, for more artistic control. Calculating the range based on this or keeping the range constant and affecting the falloff are probably both useful in that situation, too.

It all depends on who's doing the lighting and what they feel is the most natural and productive way to specify lighting.

Remember that games can be art, you don't have to always strictly follow real-world rules. Sometimes real life isn't aesthetically pleasing, too. If it looks cool, you're good.

Share this post


Link to post
Share on other sites
Hey Dragon,

I think you're right. I shouldn't spend the whole day trying to figure out the correct formula, just to realize it didn't look good in my application. I will just use formula from b_Old with some twist. Thanks for all the posters who reply.

So I'll read on more about the next step in deferred shading regards the light volume. I remember for a point light I will draw a sphere which represent the light volume. Is it possible to have a .X sphere with radius = 1.0f and then just scale it according to pre-defined Range ? I know how to create a .X sphere but I don't know how to scale it in DirectX. Any suggestion? Thanks.

Share this post


Link to post
Share on other sites
Yes, that is possible. In theory a mesh of a sphere with radius 1 will probably not cover the exactly same space as a perfect sphere with radius 1. This probably won't be noticeable though if you use attenuation, depending on the tessellation of the sphere.
And scaling it is very easy. You pass a world matrix to the shader now before you draw your sphere, right? Just scale it to whatever size you need.

Share this post


Link to post
Share on other sites
Hi,

Ok so I got the scaling for the sphere. Now, I read from a thread that for each light you should do this:

l-pass, for each light do:
- if camera lies outside the light volume
- render front faces of the light volume and mask them out in the stencil buffer;
- render back faces of the light volume comparing to stencil
- if camera lies inside the light volume
- render back faces of the light volume (ignore stenciling)

I don't quite get the process. How do I mask out front face of the light volume in stencil ? What does it mean to "render back faces of the light volume comparing to stencil" ? Is it the right way to do it ? Thanks.

Share this post


Link to post
Share on other sites
I was actually hoping that someone else would answer this question for you, as I haven't thought about the stencil optimization a lot yet. But I believe I can you some hints.

First for the case inside the light volume:
You render the lightvolume, but instead of culling back-faces as most of the time you cull the front-faces. (Otherwise you would not see anything as you are inside the lightvolume.) You also set the depth-comparison to Greater-Equal.
That way the volume (and thus the light) will only get drawn in areas that are closer to you than the edge of the light volume which can save some time compared to just drawing a fullscreen quad.

The case where you are outside the light volume:
You render lightvolume with backface culling and depth-comparsion to Less-Equal. That way you get occlusion culling in case the light is behind a wall or something. While drawing you don't do the lighting yet but only write a certain value to the stencil buffer.
Now you do the same as for the first case. But you also check for the stencil value you just wrote for the buffer. That way you have both optimizations.

I am not entirely sure about the latter case, as I don't do the second stencil part yet. But I believe it works something like that.

What I wonder is, whether it is OK to increase the stencil value and in the first pass and then decrease in the second pass and assume that all the stencil values are exactly the same as before (no more, no less).

EDIT: I just realized you wanted to know what about stencil operations specifically. You have to have a stencil buffer and enable stencil operations. Than you can specify which stencil value to set if a pixel is drawn (or not) and which stencil value it has to be compared to before it is drawn. There surely is some tutorial around that can explain this better than me.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this