Sign in to follow this  
Medo Mex

Most Efficient Way for Lighting in Forward Rendering

Recommended Posts

I'm currently using Forward Lighting and trying to do my best to make sure that the FPS game will always render all the lights

 

1. In Pixel Shader 2.0, I have

DirectionalLight dirLights[maxLights];
PointLight pointLights[maxLights];
SpotLight spotLights[maxLights];

What is the appropriate number for the variable maxLights?

 

2. Since Forward Lighting is limited to a certain amount of lights, I have been thinking that I could:

- Detect collision between the light and the camera bounding frustum and only render the light if the camera can see it

- Arrange the lights from closest to farthest to the camera and remove any light if it's index is over 'maxLights' variable

 

What do you think about my idea? What is the most efficient way to work with forward lighting?

Edited by Medo3337

Share this post


Link to post
Share on other sites


What is the appropriate number for the variable maxLights?

That all depends on the scene you are rendering.  If you have a total of 4 lights in your scene, then of course it makes no sense to all for 8 of them in your shader.  I would recommend to take the advice that cozzie provided, and just have one combination for each number of lights so you can choose the appropriate one when you need to.

Share this post


Link to post
Share on other sites

@mhagain: Should I handle only one light per pass or more than one light?

 

Maybe I could set the maxLights to 4 and If the lights on a mesh exceed 4 lights, I can render it multiple times.

 

Which is better?

Edited by Medo3337

Share this post


Link to post
Share on other sites

@mhagain: Should I handle only one light per pass or more than one light?

 

Maybe I could set the maxLights to 4 and If the lights on a mesh exceed 4 lights, I can render it multiple times.

 

What's your suggestion?

 

Profile and find out.

 

This is an "it depends" answer.  How many lights does a typical scene have?  Of those, how many lights do you typically have per object?  How many per pixel?  Does having a 4 light shader hurt in cases where an object has fewer than 4 lights on it?  And are those cases common enough that you need to care?  What's your target hardware?  How does it perform with multipass versus how does it perform with more complex shaders?  And don't forget how you're going to (or not going to) handle shadows.

 

In other words I can't make a suggestion here.

Share this post


Link to post
Share on other sites

@mhagain: I'm having a problem with blending diffuse color with light using multipass, here is the Shader:

 

(I'm getting white meshes)

// ....
PS_OUTPUT PS_Diffuse( VS_OUTPUT IN )
{
         // ...
         OUT.Color = tex2D(diffuseMap, IN.UV).rgb;
}

PS_OUTPUT PS_Lighting( VS_OUTPUT IN )
{
         OUT.Color = CalcLighting();
}

//-----------------------------------------------------------------------------
// Techniques.
//-----------------------------------------------------------------------------
technique Lighting
{
    // Diffuse pass
    pass Pass0
    {
        AlphaBlendEnable = TRUE;
        DestBlend = INVSRCALPHA;
        SrcBlend = SRCALPHA;
        ZEnable = TRUE;
        VertexShader = compile vs_3_0 VS();
        PixelShader = compile ps_3_0 PS_Diffuse();
    }


    // Lighting pass
    pass Pass1
    {
        AlphaBlendEnable = TRUE;
        DestBlend = ONE;
        SrcBlend = ONE;
        ZEnable = FALSE;
        VertexShader = compile vs_3_0 VS();
        PixelShader = compile ps_3_0 PS_Lighting();
    }
}

Share this post


Link to post
Share on other sites
If you want to use forward shading and lots of lights, you almost certainly want Forward+ shading (which a lot of AAA games are moving to).

The basic gist of the idea is to use a compute shader to bin all lights into the scene into a 2D grid indicating which parts of the screen they intersect with and then using the appropriate bin for all lighting calculating for a particular fragmnet. The binning is done by breaking the screen into a grid, generated a frustrum for each part of the grid, then doing an intersection test against each light volume and the frustrum for a particular grid location. If the light intersects, add it to that bin. More advanced versions of Forward+ deal with depth more intelligently.

It's a more advanced technique than either regular forward shading or deferred shading due to the use of compute shader, but it isn't that much more complex. It's all the same math you already have to do anyway (frustrum generation, intersection tests, etc.) just now it's in a computer shader instead of CPU code.

Share this post


Link to post
Share on other sites

 

@mhagain: I'm having a problem with blending diffuse color with light using multipass, here is the Shader:

 

(I'm getting white meshes)

 

I don't see what's happening in your CalcLighting function, but you almost certainly don't want what I think you have.

 

The first thing is that the first pass isn't a diffuse pass, it's an ambient pass.  This can be black (just output 0 from your PS) if you don't have any ambient light, but it's critically important to do it even if so as it's laying down a baseline for the additive passes.  Alpha blending should be disabled, enable depth writing, do a standard depth test.

 

The second and subsequent passes are your additive passes.  In order to understand how to do each of these, you first need to look at what a single pass/multiple light setup would look like:

 

output = ambient + diffuse * (light0 + light1 + light2 + light3); // assume 4 lights for the purpose of this example

 

Using some basic mathematics, let's break that into 5 passes, ambient plus 1 for each light, and we get:

 

output = ambient; // initial ambient pass - I've already covered this above

output += diffuse * light0; // first additive pass

output += diffuse * light1; // second additive pass

output += diffuse * light2; // third additive pass

output += diffuse * light3; // fourth additive pass

 

So each additive pass needs to sample the diffuse texture as well as calculate the lighting.  The additive blend adds it to the result from the previous pass(es), enable depth testing but set the depth test to equal, disable depth writing, and you'll get the (mostly) correct result.

 

I put in "mostly" above because the one caveat is that if your lighting goes above 1.0 each pass will clamp, so it's not going to look 100% identical to a single pass/multiple light setup.  You can of course draw to a floating point rendertarget if this is a problem for you, but I'd encourage experimentation before making that decision - it may be just fine as is.

 

If you want to use forward shading and lots of lights, you almost certainly want Forward+ shading (which a lot of AAA games are moving to).

The basic gist of the idea is to use a compute shader to bin all lights into the scene into a 2D grid indicating which parts of the screen they intersect with and then using the appropriate bin for all lighting calculating for a particular fragmnet. The binning is done by breaking the screen into a grid, generated a frustrum for each part of the grid, then doing an intersection test against each light volume and the frustrum for a particular grid location. If the light intersects, add it to that bin. More advanced versions of Forward+ deal with depth more intelligently.

It's a more advanced technique than either regular forward shading or deferred shading due to the use of compute shader, but it isn't that much more complex. It's all the same math you already have to do anyway (frustrum generation, intersection tests, etc.) just now it's in a computer shader instead of CPU code.

 

I suspect that the OP is using D3D9 (PS2.0 in the first post, SM3 in the effect file code).

Edited by mhagain

Share this post


Link to post
Share on other sites

@mhagain: It's now working well, however I see Z-Fighting in the animated character (only when the animation is playing)

 

I'm using D3D9, C++

 

On some hardware you may see this if you use a different D3DPT_ in some of the passes.  I got hit by it once; thinking "If I'm only drawing 4 vertices I'll draw it as a strip or fan rather than an indexed triangle list, and save on some buffer locking".  Bad idea and Z-fighting was the result.

 

So draw everything as an indexed triangle list and it should be fine.

Edited by mhagain

Share this post


Link to post
Share on other sites

@mhagain: I'm rendering all the meshes using LPD3DXMESH, so I only use pMesh->DrawSubset() and I still have the Z-Fighting problem when animation is playing

 

Another problem, when I draw the mesh multiple times I get very bright mesh (even if there is no lighting)

 

Here is what I have in the Shader:

 

pass0 = ambient pass (render once)

pass1 = tex2D(ColorMap, IN.UV) + light (repeat rendering according to the number of lights)

    pass pass0
    {
         AlphaBlendEnable = FALSE;
         DestBlend = ONE;
         SrcBlend = ZERO;
         BLENDOP = ADD;
         ZEnable = TRUE;
         VertexShader = compile vs_3_0 VS();
         PixelShader  = compile ps_3_0 PS_AMBIENT();
    }


    pass pass1
    {
         AlphaBlendEnable = TRUE;
         SRCBLEND = ONE;
         DESTBLEND = ONE;
         BLENDOP = ADD;
         ZWRITEENABLE = TRUE;
         ZEnable = TRUE;
         VertexShader = compile vs_3_0 VS();
         PixelShader  = compile ps_3_0 PS();
    }

Share this post


Link to post
Share on other sites

I figured out that the Z-Fighting was due to the animation getting updated every time the mesh is rendered, so I changed the code to only update once every frame no matter how many times I do draw calls.

 

The problem now is that I the mesh is getting very bright when I render it multiple times as the following:

 

PS Pass 0: return Ambient;

PS Pass 1: return tex2D(ColorMap, IN.UV) + Light1();

PS Pass 1: return tex2D(ColorMap, IN.UV) + Light2();

PS Pass 1: return tex2D(ColorMap, IN.UV) + Light3();

 

I believe that it's getting very bright because tex2D(ColorMap, IN.UV) is added multiple times.

 

How do I fix it?

Share this post


Link to post
Share on other sites

return tex2D(ColorMap, IN.UV) + Light1();

 

Typically you'd use * not +.

 

I'm not sure why you're using +; that would be expected to make things excessively bright for sure.  Think about it: if a light value is 0 that means it's fully dark so you want to return black (0); on the other hand if it's 1 that means it's fully bright so you return the texture colour; it should be clear that you use:

 

return tex2D(ColorMap, IN.UV) * Light1();

 

And not:

 

return tex2D(ColorMap, IN.UV) + Light1();

 

If you go back to my earlier post where I derive the additive blending method from basic mathematics you'll see that it's correct to also read the texture for every pass, because t * (l0 + l1 + l2 + l3) == (t * l0) + (t * l1) + (t * l2) + (t * l3).  This is the same as 2 * (1 + 3) == (2 * 1) + (2 * 3).

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