Jump to content

  • Log In with Google      Sign In   
  • Create Account

Shader Unlimited Lights


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
30 replies to this topic

#1 Medo3337   Members   -  Reputation: 665

Like
0Likes
Like

Posted 17 July 2013 - 02:01 PM

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?


Edited by Medo3337, 17 July 2013 - 07:04 PM.


Sponsor:

#2 belfegor   Crossbones+   -  Reputation: 2613

Like
5Likes
Like

Posted 17 July 2013 - 02:21 PM


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.



#3 MJP   Moderators   -  Reputation: 11343

Like
3Likes
Like

Posted 17 July 2013 - 02:29 PM

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.

 



#4 Medo3337   Members   -  Reputation: 665

Like
0Likes
Like

Posted 17 July 2013 - 02:37 PM

@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?



#5 Migi0027   Crossbones+   -  Reputation: 1726

Like
5Likes
Like

Posted 17 July 2013 - 02:50 PM

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?


Hi! Cuboid Zone
The Rule: Be polite, be professional, but have a plan to kill everyone you meet, ohh, AND STEAL ALL ZE TRIANGLES FROM ZHEM!

#6 Medo3337   Members   -  Reputation: 665

Like
0Likes
Like

Posted 17 July 2013 - 03:19 PM

@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?



#7 Medo3337   Members   -  Reputation: 665

Like
0Likes
Like

Posted 17 July 2013 - 03:59 PM

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



#8 Migi0027   Crossbones+   -  Reputation: 1726

Like
3Likes
Like

Posted 17 July 2013 - 05:05 PM

 

 

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


Hi! Cuboid Zone
The Rule: Be polite, be professional, but have a plan to kill everyone you meet, ohh, AND STEAL ALL ZE TRIANGLES FROM ZHEM!

#9 Medo3337   Members   -  Reputation: 665

Like
0Likes
Like

Posted 17 July 2013 - 05:34 PM

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.


Edited by Medo3337, 17 July 2013 - 07:04 PM.


#10 Adam_42   Crossbones+   -  Reputation: 2506

Like
1Likes
Like

Posted 17 July 2013 - 06:47 PM

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.



#11 Chris_F   Members   -  Reputation: 2369

Like
0Likes
Like

Posted 17 July 2013 - 06:52 PM

Unless I'm mistaken, you've never actually stated you are working exclusively with D3D9, so if you are so concerned with the number of lights you can support, then why not use deferred shading or some type of tiled forward renderer? Even if you are limited to D3D9, you could still use deferred shading.



#12 Medo3337   Members   -  Reputation: 665

Like
0Likes
Like

Posted 17 July 2013 - 07:00 PM

@Chris_F: Not planning to get into deferred shading now, and I would run into some problems for example (transparent mesh)

 

@Adam_42: Hmm.. I could make directional light max: 2, other lights max: 8, do you think that's okay?

 

I think even 10 should be fine, because I will only calculate the lights that I passed to the shader.



#13 MJP   Moderators   -  Reputation: 11343

Like
3Likes
Like

Posted 17 July 2013 - 11:27 PM

 

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

 

Have you looked at the generated assembly? It looks like this:

ps_3_0
def c46, -4, -5, -6, -7
def c47, 0, 1, 2, 3
dcl_texcoord v0.xyz
dcl_texcoord1 v1.xy
dcl_texcoord2 v2.xyz
dcl_texcoord3 v3.xyz
dcl_2d s0
nrm r0.xyz, v3
dp3 r0.w, v2, v2
rsq r0.w, r0.w
mov r1, c47.x
mov r2.x, c47.x
rep i0
  add r3, r2.x, -c47
  add r4, r2.x, c46
  mov r5.x, c47.x
  cmp r2.yzw, -r3_abs.x, c0.xxyz, r5.x
  cmp r2.yzw, -r3_abs.y, c5.xxyz, r2
  cmp r2.yzw, -r3_abs.z, c10.xxyz, r2
  cmp r2.yzw, -r3_abs.w, c15.xxyz, r2
  cmp r2.yzw, -r4_abs.x, c20.xxyz, r2
  cmp r2.yzw, -r4_abs.y, c25.xxyz, r2
  cmp r2.yzw, -r4_abs.z, c30.xxyz, r2
  cmp r2.yzw, -r4_abs.w, c35.xxyz, r2
  add r2.yzw, r2, -v0.xxyz
  cmp r5.y, -r3_abs.x, c4.x, r5.x
  cmp r5.y, -r3_abs.y, c9.x, r5.y
  cmp r5.y, -r3_abs.z, c14.x, r5.y
  cmp r5.y, -r3_abs.w, c19.x, r5.y
  cmp r5.y, -r4_abs.x, c24.x, r5.y
  cmp r5.y, -r4_abs.y, c29.x, r5.y
  cmp r5.y, -r4_abs.z, c34.x, r5.y
  cmp r5.y, -r4_abs.w, c39.x, r5.y
  rcp r5.y, r5.y
  mul r2.yzw, r2, r5.y
  dp3 r5.y, r2.yzww, r2.yzww
  add r5.z, -r5.y, c47.y
  max r6.x, r5.z, c47.x
  rsq r5.y, r5.y
  mul r2.yzw, r2, r5.y
  mad r5.yzw, v2.xxyz, r0.w, r2
  nrm r7.xyz, r5.yzww
  dp3_sat r2.y, r0, r2.yzww
  dp3_sat r2.z, r0, r7
  pow r5.y, r2.z, c44.x
  cmp r7, -r3_abs.x, c1, r5.x
  cmp r7, -r3_abs.y, c6, r7
  cmp r7, -r3_abs.z, c11, r7
  cmp r7, -r3_abs.w, c16, r7
  cmp r7, -r4_abs.x, c21, r7
  cmp r7, -r4_abs.y, c26, r7
  cmp r7, -r4_abs.z, c31, r7
  cmp r7, -r4_abs.w, c36, r7
  mad r7, r6.x, r7, c45
  cmp r8, -r3_abs.x, c2, r5.x
  cmp r8, -r3_abs.y, c7, r8
  cmp r8, -r3_abs.z, c12, r8
  cmp r8, -r3_abs.w, c17, r8
  cmp r8, -r4_abs.x, c22, r8
  cmp r8, -r4_abs.y, c27, r8
  cmp r8, -r4_abs.z, c32, r8
  cmp r8, -r4_abs.w, c37, r8
  mul r8, r8, c41
  mul r8, r2.y, r8
  mul r8, r6.x, r8
  mad r7, c40, r7, r8
  cmp r8, -r3_abs.x, c3, r5.x
  cmp r8, -r3_abs.y, c8, r8
  cmp r8, -r3_abs.z, c13, r8
  cmp r3, -r3_abs.w, c18, r8
  cmp r3, -r4_abs.x, c23, r3
  cmp r3, -r4_abs.y, c28, r3
  cmp r3, -r4_abs.z, c33, r3
  cmp r3, -r4_abs.w, c38, r3
  mul r3, r3, c43
  mul r3, r5.y, r3
  cmp r3, -r2.y, c47.x, r3
  mad r3, r3, r6.x, r7
  add r1, r1, r3
  add r2.x, r2.x, c47.y
endrep
texld r0, v1, s0
mul oC0, r0, r1

Because of the constant indexing limitation it has to do a compare and select for every single constant register. It's just a different variant of what I mentioned. Basically it's like doing this:

for(uint i = 0; i < NumLights; ++i)
{
    float3 LightPos = Lights[0].Position;
    if(i == 1)
        LightPos = Lights[1].Position;
    else if(i == 2)
        LightPos = Lights[2].Position;
    else if(i == 3)
        LightPos = Lights[3].Position;
    ...
    else if(i == 7)
        LightPos = Lights[7].Position;
        
    float3 LightColor = Lights[0].Color;
    if(i == 1)
        LightColor = Lights[1].Color;
    else if(i == 2)
        LightColor = Lights[2].Color;
    else if(i == 3)
        LightColor = Lights[3].Color;
    ...
    else if(i == 7)
        LightColor = Lights[7].Color;
        
    // and so on
}

Edited by MJP, 17 July 2013 - 11:27 PM.


#14 unbird   Crossbones+   -  Reputation: 4977

Like
3Likes
Like

Posted 18 July 2013 - 03:42 AM

One can break the constant limit - and force to use a proper loop - in SM 3.0 by encoding stuff in textures, using dynamic textures and uploading your data with LockRectangle. 

 

*digging up my D3D9 stuff*

 

This is a quick check with 100 point lights. The texture is 4-channel float, 3 texels are needed per point light. 

 

83d765266027189.jpg

 

Is this a good idea ? I doubt it. Though I'm surprised it still runs smooth I wonder how that scales (this was just one model). Rather go with a deferred approach and cull the lights by distance/visibility as others suggested. 



#15 Migi0027   Crossbones+   -  Reputation: 1726

Like
0Likes
Like

Posted 18 July 2013 - 12:20 PM

One can break the constant limit - and force to use a proper loop - in SM 3.0 by encoding stuff in textures, using dynamic textures and uploading your data with LockRectangle. 

 

*digging up my D3D9 stuff*

 

This is a quick check with 100 point lights. The texture is 4-channel float, 3 texels are needed per point light. 

 

83d765266027189.jpg

 

Is this a good idea ? I doubt it. Though I'm surprised it still runs smooth I wonder how that scales (this was just one model). Rather go with a deferred approach and cull the lights by distance/visibility as others suggested. 

 

It may not be a good idea, but one thing it is, it's interesting! huh.png


Hi! Cuboid Zone
The Rule: Be polite, be professional, but have a plan to kill everyone you meet, ohh, AND STEAL ALL ZE TRIANGLES FROM ZHEM!

#16 unbird   Crossbones+   -  Reputation: 4977

Like
0Likes
Like

Posted 18 July 2013 - 01:01 PM

Off topic:

 

It has it's use, e.g. here it's used for skinning a massive amount of characters (vertex texture fetch, not pixel shader this time). This approach is also useful if you have a model with a hell of a lot of bones.

 

I'm posting since you are using D3D11: The constant limit is considerably higher and even if you're hitting the limit, you don't need to use this approach - directly. Have a look into structured buffers. The usage is quite convenient: you really just define the struct in both shader and C++ and use with an array syntax. Under the hood something similar is happening as above: a "texture buffer" is used (HLSL register t#).



#17 MJP   Moderators   -  Reputation: 11343

Like
2Likes
Like

Posted 18 July 2013 - 06:48 PM

One can break the constant limit - and force to use a proper loop - in SM 3.0 by encoding stuff in textures

I think there's an echo in here tongue.png



#18 Medo3337   Members   -  Reputation: 665

Like
0Likes
Like

Posted 18 July 2013 - 07:48 PM

Okay guys, I want a direct answer...

 

Is it OK to do the following?

PointLight pointLights[6];
SpotLight spotLights[6];
DirectionalLight directionalLights[2];


#19 Steve_Segreto   Crossbones+   -  Reputation: 1530

Like
0Likes
Like

Posted 18 July 2013 - 10:57 PM

 

Okay guys, I want a direct answer...

 

Is it OK to do the following?

PointLight pointLights[6];
SpotLight spotLights[6];
DirectionalLight directionalLights[2];

Sure why not?



#20 Medo3337   Members   -  Reputation: 665

Like
0Likes
Like

Posted 18 July 2013 - 10:59 PM

@Steve_Segreto: So, If I don't want to run into performance issues for most graphic cards out there and If I don't want to run into constant limits problems.

 

What is the maximum number that I should use for point, spot, directional lights in the shader array?


Edited by Medo3337, 18 July 2013 - 11:11 PM.





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS