• FEATURED

View more

View more

View more

Free!

Image of the Day Submit

IOTD | Top Screenshots

The latest, straight to your Inbox.

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.

30 replies to this topic

#1Medo3337  Members

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.

#2belfegor  Members

Posted 17 July 2013 - 02:21 PM

POPULAR

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.

#3MJP  Moderators

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.

#4Medo3337  Members

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?

#5Migi0027 (肉コーダ)  Members

Posted 17 July 2013 - 02:50 PM

POPULAR

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:

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:

RABBITS!

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
• 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
• Loop over all the vertices
• Get the distance between the vertices and the center
• Find the greatest length
for (int v = 0; v < mVertices.size(); v ++)
{
D3DXVECTOR3 diff = mVertices[v] - center;

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

}

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!

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/

#6Medo3337  Members

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?

#7Medo3337  Members

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

#8Migi0027 (肉コーダ)  Members

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)
{

m->Prepare();

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


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)

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/

#9Medo3337  Members

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.

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.

#11Chris_F  Members

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.

#12Medo3337  Members

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.

#13MJP  Moderators

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
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
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
max r6.x, r5.z, c47.x
rsq r5.y, r5.y
mul r2.yzw, r2, r5.y
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
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
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
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.

#14unbird  Members

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.

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.

#15Migi0027 (肉コーダ)  Members

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.

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!

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/

#16unbird  Members

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#).

#17MJP  Moderators

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

#18Medo3337  Members

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];

#19Steve_Segreto  Members

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?

#20Medo3337  Members

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.