Shader Unlimited Lights

Started by
29 comments, last by DwarvesH 10 years, 8 months ago

I don't know have many lights will be used in the scene, so I'm looking for a way to be able to pass any number of lights.

I thought that I could create array with high number of lights in the shader and then tell the shader how many lights was passed for light calculation, for example:


// Shader
#define MAX_LIGHTS 8 // *** I don't know how many lights, so I will assume 1000 is the maximum possible ***
PointLight pointLights[MAX_LIGHTS];
DirectionalLight directionalLights[MAX_LIGHTS];
SpotLight spotLights[MAX_LIGHTS];

float numOfLights;

// Then loop in the shader
(for int i = 0; i < numOfLights; i++)
{
     // Code to calculate light here...
}

Is this way a good idea?

Advertisement


Is this way a good idea?

No, you will run out of instruction and constants limit that you can use in shader.

For "forward renderer" i would recommend to set some reasonable number of lights, like 3-4 tops. Note that you can still have "unlimited light count", you just limit how many lights can affect certain object. Choose light by some priority, for example in outdoor scenes sun would have highest priority so it would be always included, and for others you could just take closest ones.

D3D9 pixel shaders have a major limitation, which is that they can't dynamically index into shader constants. This means that it can't use an actual loop construct in assembly to implement your for loop, instead it has to unroll it and do something like this:

if(numLights > 0)
    CalcLight(Light0);
 
if(numLights > 1)
    CalcLight(Light1);
 
if(numLights > 2)
    CalcLight(Light2);
 
// ...and so on

The only way to dynamically index into your light data would be to use textures to store your light properties. This is a major reason why shader permutations were very popular for this era of hardware.

Now even if you get this working, you have to keep in mind that it's very important for performance to not just blindly apply every light to every mesh that you draw. Even a dozen or so lights can be a huge strain on the GPU if those lights affect every pixel on the screen. This means either doing work on the CPU to selectively bind lights to certain meshes/draw calls, or switching to a deferred approach that uses the GPU to figure out which lights affect which pixels.

@belfegor: That wouldn't be efficient, some important lights MIGHT not affect the mesh, since I wouldn't be able to figure out 100% which is important, what if more than 4 light sources affecting the mesh?

As MJP said correctly, the way to approach this would be to use deferred rendering.

One way that you could check which lights are "important", is using one of my previous posts, to you actually:

From seeing your comments, I can easily see that you aren't happy about the performance happy.png

As other people recommended, you can check whether the object is affected by the light, here's how with a Point Light!

Bounding Spheres

Bounding Spheres is if you were to collect all the vertices in the mesh and make a sphere encapsulate them, like this:

530px-Tighter_bounding_sphere.pngRABBITS!biggrin.png

To get this result, you need two things:

  1. The center of the mesh
  2. The radius of the mesh from the center

Calculating the center:

  • Declare the variable ohmy.png
  • Loop over all your vertices and add them to the center
  • Divide the center by the number of vertices.
  • THAT'S IT!
D3DXVECTOR3 center = D3DXVECTOR3(0, 0, 0);

for(int v = 0; v < mVertices.size(); v++ )
{
center += mVertices[v];
}

center /= mVertices.size();

Calculating the radius: (With the center)

  • Declare the variable laugh.png
  • Loop over all the vertices
  • Get the distance between the vertices and the center
  • Find the greatest length
float radius = 0.0f;
for (int v = 0; v < mVertices.size(); v ++)
{
D3DXVECTOR3 diff = mVertices[v] - center;

float length = sqrtf(D3DXVec3Dot(&diff, &diff));

if (length > radius)
radius = length;
}

To visualize the result, you just need to translate a sphere (or stuff) to this location: sphere->Translate(mesh->Position + mesh->Center), and also scale it by the meshes radius.

Testing for collision between Sphere-Sphere

As you know the point light has a radius, and so does, hopefully, the mesh with these calculations. To see if they collide, just do the following

  • Find the vector between the mesh center and the point light's position.
  • Get the length of that vector
  • Check whether that length is smaller than both the point light's radius and the mesh's radius.
  • If true, they collide!!!
D3DXVECTOR3 mDistance = mesh->center + pointLight.Position;
float d = sqrtf( D3DXVec3Dot( &mDistance, &mDistance) );

if (d < (mesh->BoundingSphere.radius + pointLight.Range) )
// They collide!!!

In other words, if they collide, the light will affect the mesh.

Mini Tutorials! laugh.png

And "important" lights are those who affect the mesh, and for example: a global light, that lits everything, you can't exclude that.

With the sun (by belfegor ), where you said: "some important lights MIGHT not affect the mesh", are you trying to approach shadows?

FastCall22: "I want to make the distinction that my laptop is a whore-box that connects to different network"

Blog about... stuff (GDNet, WordPress): www.gamedev.net/blog/1882-the-cuboid-zone/, cuboidzone.wordpress.com/

@Migi0027: Don't you think that I can just calculate the distance between the light source and the mesh? I guess that's easier and faster...

What If more than 4 lights affecting the mesh while the shader only allow 4 lights per pass?

D3D9 pixel shaders have a major limitation, which is that they can't dynamically index into shader constants. This means that it can't use an actual loop construct in assembly to implement your for loop, instead it has to unroll it and do something like this:

It's possible to use a loop with D3D9, take a look at this sample:

http://www.dhpoware.com/demos/d3d9NormalMappingWithManyLights.html

What If more than 4 lights affecting the mesh while the shader only allow 4 lights per pass?

Then if you don't want to go the deferred way, then this might suit you: (Very rough code)


void RenderMyMesh(Mesh *m, PointLights *p, int pointCount)
{
   ApplyDiffuseShader();

   m->Prepare();
   diffuseShader.Render();

   EnableAdditiveBlending();
   ApplyPointLightShader();
   for (int i = 0; i < pointCount; i++)
   {
      if (intersects(m->boundingSphere, p[i].boundingSphere))
      {
          pointShader.setLight(p[i]);
          pointShader.Render();
      }
   }
}

What I do here:

  • Render the mesh in diffuse mode
  • Enable additive blending for the lights
  • Loop over all lights
  • Check if they matter
  • If they do, render them.

I do think this is how it works, but then, I never used this method, so this is just from my memory.

Don't you think that I can just calculate the distance between the light source and the mesh? I guess that's easier and faster...

You are a bit too fast there, think of this: (This is made in 60 seconds, so there may be errors)

30uvdhz.png

FastCall22: "I want to make the distinction that my laptop is a whore-box that connects to different network"

Blog about... stuff (GDNet, WordPress): www.gamedev.net/blog/1882-the-cuboid-zone/, cuboidzone.wordpress.com/

Okay, how about I use 8 lights for each type of light?
Is that good?

#define MAX_LIGHTS 8
PointLight pointLights[MAX_LIGHTS];
DirectionalLight directionalLights[MAX_LIGHTS];
SpotLight spotLights[MAX_LIGHTS];

Since 99% of the cases the lights would never exceed 8 lights on a single mesh.

Do you really need that many directional lights? I suspect you can probably get away with just one for sunlight.

Does 8 lights per object look any better than 4 lights per object in the scenes you'll be rendering?

What's the performance like with 8 lights on your target hardware spec?

Essentially what I'm saying is that whatever number you come up with may have to change later. Don't worry too much about that yet, other than to make sure you can easily adjust it later when you know what your actual requirements are.

This topic is closed to new replies.

Advertisement