How can I avoid a maximum number of lights limit?

I am creating a scene with a cube and multiple directional lights (all casting shadows):

When I increase the number of lights to 29, the scene renders as expected. When I increase it to 30, though, the scene is not renderer:

With an error:

THREE.WebGLProgram: Shader Error 0 - VALIDATE_STATUS false

Program Info Log: Vertex info
-----------
Internal error: assembly compile error for vertex shader at offset 8576:
-- error message --
line 281, column 1:  error: too many result variable components written
-- internal assembly text --
!!NVvp5.0
OPTION NV_internal;
OPTION NV_bindless_texture;
# cgc version 3.4.0001, build date Apr 20 2022
# command line args: 
#vendor NVIDIA Corporation
#version 3.4.0.1 COP Build Date Apr 20 2022
#profile gp5vp
#program main
#semantic webgl_d76d391c2bdfd77e
#semantic webgl_b5fdc9e191406c19
#semantic webgl_db1af00792297ccf
#semantic webgl_454edae1e91d2ee9
#semantic webgl_b120068de8f73d63
#semantic webgl_3f1cba4977262753
#semantic webgl_823767397440b998
#semantic webgl_ae2037cb19446e85
#semantic webgl_ebfdec2c22823b6d
#var float4 gl_Position : $vout.POSITION : HPOS : -1 : 1
#var float4x4 webgl_d76d391c2bdfd77e :  : c[0], 4 : -1 : 1
#var float4x4 webgl_b5fdc9e191406c19 :  : c[4], 4 : -1 : 1
#var float4x4 webgl_db1af00792297ccf :  : c[8], 4 : -1 : 1
#var float4x4 webgl_454edae1e91d2ee9 :  : c[12], 4 : -1 : 1
#var float3x3 webgl_b120068de8f73d63 :  : c[16], 3 : -1 : 1
#var float3 webgl_3f1cba4977262753 :  :  : -1 : 0
#var bool webgl_823767397440b998 :  :  : -1 : 0
#var float3 webgl_fa67bdd9ecec0ae : $vin.ATTR0 : ATTR0 : -1 : 1
#var float3 webgl_9ff69ee813f50905 : $vin.ATTR1 : ATTR1 : -1 : 1
#var float2 webgl_c82ec2f59ceb854e :  :  : -1 : 0
#var float3 webgl_a88cc5e715473e96 : $vout.ATTR0.xyz : ATTR0 : -1 : 1
#var float3 webgl_d63a0821229be6c6 : $vout.ATTR1.xyz : ATTR1 : -1 : 1
#var float4x4 webgl_ae2037cb19446e85[0] :  : c[19], 4 : -1 : 1
#var float4 webgl_edb372a80fc4cd73[0] : $vout.ATTR2 : ATTR2 : -1 : 1
#var float webgl_ebfdec2c22823b6d[0].webgl_6f2c4cad9288d0c6 :  : c[139] : -1 : 0
#var float webgl_ebfdec2c22823b6d[0].webgl_1411c2de04b10a3a :  : c[140] : -1 : 1
#var float webgl_ebfdec2c22823b6d[0].webgl_2c5595cfeb09fb5e :  : c[141] : -1 : 0
#var float2 webgl_ebfdec2c22823b6d[0].webgl_a44dc924e2f7657b :  : c[142] : -1 : 0
PARAM c[259] = { program.local[0..258] };
ATTRIB vertex_attrib[] = { vertex.attrib[0..1] };
OUTPUT result_attrib[] = { result.attrib[0..31] };
TEMP R0, R1, R2, R3, R4, R5, R6;
TEMP T;
MUL.F R0.xyz, vertex.attrib[1].y, c[17];
MAD.F R0.xyz, vertex.attrib[1].x, c[16], R0;
MAD.F R5.xyz, vertex.attrib[1].z, c[18], R0;
MOV.F R5.w, {0, 0, 0, 0}.x;
DP4.F R1.z, R5, c[14];
DP4.F R1.x, R5, c[12];
DP4.F R1.y, R5, c[13];
DP3.F R0.x, R1, R1;
RSQ.F R1.w, R0.x;
MUL.F R6.xyz, R1.w, R1;
MUL.F R0, vertex.attrib[0].y, c[1];
MAD.F R0, vertex.attrib[0].x, c[0], R0;
MAD.F R0, vertex.attrib[0].z, c[2], R0;
ADD.F R0, R0, c[3];
MUL.F R1.xyz, R6, c[140].x;
MOV.F R1.w, {0, 0, 0, 0}.x;
ADD.F R1, R0, R1;
MUL.F R2, R1.y, c[20];
MAD.F R2, R1.x, c[19], R2;
MAD.F R2, R1.z, c[21], R2;
MAD.F result.attrib[2], R1.w, c[22], R2;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[144].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[24];
MAD.F R3, R1.x, c[23], R3;
MAD.F R3, R1.z, c[25], R3;
MAD.F result.attrib[3], R1.w, c[26], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[148].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[28];
MAD.F R4, R2.x, c[27], R4;
MAD.F R4, R2.z, c[29], R4;
MAD.F result.attrib[4], R2.w, c[30], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[152].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[32];
MAD.F R3, R1.x, c[31], R3;
MAD.F R3, R1.z, c[33], R3;
MAD.F result.attrib[5], R1.w, c[34], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[156].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[36];
MAD.F R4, R2.x, c[35], R4;
MAD.F R4, R2.z, c[37], R4;
MAD.F result.attrib[6], R2.w, c[38], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[160].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[40];
MAD.F R3, R1.x, c[39], R3;
MAD.F R3, R1.z, c[41], R3;
MAD.F result.attrib[7], R1.w, c[42], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[164].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[44];
MAD.F R4, R2.x, c[43], R4;
MAD.F R4, R2.z, c[45], R4;
MAD.F result.attrib[8], R2.w, c[46], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[168].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[48];
MAD.F R3, R1.x, c[47], R3;
MAD.F R3, R1.z, c[49], R3;
MAD.F result.attrib[9], R1.w, c[50], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[172].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[52];
MAD.F R4, R2.x, c[51], R4;
MAD.F R4, R2.z, c[53], R4;
MAD.F result.attrib[10], R2.w, c[54], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[176].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[56];
MAD.F R3, R1.x, c[55], R3;
MAD.F R3, R1.z, c[57], R3;
MAD.F result.attrib[11], R1.w, c[58], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[180].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[60];
MAD.F R4, R2.x, c[59], R4;
MAD.F R4, R2.z, c[61], R4;
MAD.F result.attrib[12], R2.w, c[62], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[184].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[64];
MAD.F R3, R1.x, c[63], R3;
MAD.F R3, R1.z, c[65], R3;
MAD.F result.attrib[13], R1.w, c[66], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[188].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[68];
MAD.F R4, R2.x, c[67], R4;
MAD.F R4, R2.z, c[69], R4;
MAD.F result.attrib[14], R2.w, c[70], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[192].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[72];
MAD.F R3, R1.x, c[71], R3;
MAD.F R3, R1.z, c[73], R3;
MAD.F result.attrib[15], R1.w, c[74], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[196].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[76];
MAD.F R4, R2.x, c[75], R4;
MAD.F R4, R2.z, c[77], R4;
MAD.F result.attrib[16], R2.w, c[78], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[200].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[80];
MAD.F R3, R1.x, c[79], R3;
MAD.F R3, R1.z, c[81], R3;
MAD.F result.attrib[17], R1.w, c[82], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[204].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[84];
MAD.F R4, R2.x, c[83], R4;
MAD.F R4, R2.z, c[85], R4;
MAD.F result.attrib[18], R2.w, c[86], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[208].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[88];
MAD.F R3, R1.x, c[87], R3;
MAD.F R3, R1.z, c[89], R3;
MAD.F result.attrib[19], R1.w, c[90], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[212].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[92];
MAD.F R4, R2.x, c[91], R4;
MAD.F R4, R2.z, c[93], R4;
MAD.F result.attrib[20], R2.w, c[94], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[216].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[96];
MAD.F R3, R1.x, c[95], R3;
MAD.F R3, R1.z, c[97], R3;
MAD.F result.attrib[21], R1.w, c[98], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[220].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[100];
MAD.F R4, R2.x, c[99], R4;
MAD.F R4, R2.z, c[101], R4;
MAD.F result.attrib[22], R2.w, c[102], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[224].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[104];
MAD.F R3, R1.x, c[103], R3;
MAD.F R3, R1.z, c[105], R3;
MAD.F result.attrib[23], R1.w, c[106], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[228].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[108];
MAD.F R4, R2.x, c[107], R4;
MAD.F R4, R2.z, c[109], R4;
MAD.F result.attrib[24], R2.w, c[110], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[232].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[112];
MAD.F R3, R1.x, c[111], R3;
MAD.F R3, R1.z, c[113], R3;
MAD.F result.attrib[25], R1.w, c[114], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[236].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[116];
MAD.F R4, R2.x, c[115], R4;
MAD.F R4, R2.z, c[117], R4;
MAD.F result.attrib[26], R2.w, c[118], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[240].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[120];
MAD.F R3, R1.x, c[119], R3;
MAD.F R3, R1.z, c[121], R3;
MAD.F result.attrib[27], R1.w, c[122], R3;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[244].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[124];
MAD.F R4, R2.x, c[123], R4;
MAD.F R4, R2.z, c[125], R4;
MAD.F result.attrib[28], R2.w, c[126], R4;
MOV.F R1.w, {0, 0, 0, 0}.x;
MUL.F R1.xyz, R6, c[248].x;
ADD.F R1, R0, R1;
MUL.F R3, R1.y, c[128];
MAD.F R3, R1.x, c[127], R3;
MAD.F R3, R1.z, c[129], R3;
MAD.F result.attrib[29], R1.w, c[130], R3;
MUL.F R1.xyz, R6, c[256].x;
MOV.F R1.w, {0, 0, 0, 0}.x;
ADD.F R3, R0, R1;
MUL.F R1, R3.y, c[136];
MAD.F R1, R3.x, c[135], R1;
MOV.F R2.w, {0, 0, 0, 0}.x;
MUL.F R2.xyz, R6, c[252].x;
ADD.F R2, R0, R2;
MUL.F R4, R2.y, c[132];
MAD.F R4, R2.x, c[131], R4;
MAD.F R4, R2.z, c[133], R4;
MAD.F result.attrib[30], R2.w, c[134], R4;
MUL.F R2, vertex.attrib[0].y, c[5];
MAD.F R0, vertex.attrib[0].x, c[4], R2;
MAD.F R2, R3.z, c[137], R1;
MAD.F result.attrib[31], R3.w, c[138], R2;
MAD.F R0, vertex.attrib[0].z, c[6], R0;
ADD.F R0, R0, c[7];
MUL.F R1, R0.y, c[9];
MAD.F R1, R0.x, c[8], R1;
DP3.F R2.x, R5, R5;
MAD.F R1, R0.z, c[10], R1;
RSQ.F R2.x, R2.x;
MAD.F result.position, R0.w, c[11], R1;
MUL.F result.attrib[1].xyz, R2.x, R5;
MOV.F result.attrib[0].xyz, -R0;
END
# 236 instructions, 7 R-regs

three.module.js:18758
    WebGLProgram three.module.js:18758
    acquireProgram three.module.js:19257
    getProgram three.module.js:26496
    setProgram three.module.js:26687
    renderBufferDirect three.module.js:25836
    renderObject three.module.js:26433
    renderObjects three.module.js:26402
    renderScene three.module.js:26343
    render three.module.js:26155
    <anonymous> script.js:483
    <anonymous> bundle.013e3bcaf3471013.js:57540
    <anonymous> bundle.013e3bcaf3471013.js:57542

I had a look at this thread, which may be related:

A deferred renderer is suggested, but it seems the WebGLDeferredRenderer example got removed from Three.js.

Is there a way to allow a higher number of directional lights casting shadows in the scene?

I’d like to increase that number to compare performance between multiple simultaneous lights and light/shadow map generation.

1 Like

As the number of lights in all directions goes up, you’re increasingly just doing a very expensive approximation of ambient occlusion. Have you considered using screen-space ambient occlusion, or baking ambient occlusion to a texture, instead?

You will have to modify three.js itself to increase the limit, having too many lights will make the renderer much slower.

4 Likes

Thanks for your reply!

For what I have read about screen-space ambient occlusion, since precision is important here, most probably this technique would not be viable. Is there any difference between “baking ambient occlusion to a texture” and the light /shadow map generation examples I shared above?

Do you have any recommended pointers to share about baking ambient occlusion to a texture with Three.js? (i.e.: documentation or code examples) :blush:

For now, the idea is to first get a solution in gray scale and then convert it to a heat map where warmer colors mean higher insolation and colder colors mean lower insolation.

1 Like

Just throwing out an idea here.
Have you considered making multiple passes and merging the final image? That is, capture a frame with 29 lights sources, then another frame with 29 other light sources until all have been generated. Then, merge the images together (perhaps through transparency or opacity)

1 Like

I don’t know if this would work, but I like the creative approach!

1 Like

Have you considered making multiple passes and merging the final image?

Thanks for the suggestion @anidivr ! :blush:

Yeah, I think that is equivalent to the light/shadow map generation examples I shared. Most probably, that is the way to go, but I would have loved to be able to compare performance between having a scene with 1000 lights and getting to the same result progressively with light/shadow map generation.

Do the lights move? If yes, do they only rotate around the object? There might be a way to fake the shadows by pre-computing them as a texture on the floor.

Lights are stationary. :blush:

Maybe 2 scenes. render first scene with 16 lights to texture 1. Then render second scene 10 lights to texture 2. And merge textures into postprocess if colors be right with calculation like texture_1*texture_2 or texture_1+texture_2.

If you’re really aiming to have an unlimited amount of light sources in your scene, then I think using a deferred renderer is the way to go, if performance is important as well.

I’m not sure if there is still a working deferred renderer out there though. Writing one isn’t trivial either :sweat_smile:

Here’s one I just found from a quick Google search: https://webgl-three-js-deferred-rendering.vercel.app/

1 Like