Reputation: 4691
Assume to have a scene with a street with many streetlights (more 20), you move an object close by them and you expect a shadow.
The lights, simply
var light = new THREE.PointLight(0xffffff, 0.5, 6.0);
Only the street has .receiveShadow = true
and only the car has .castShadow = true
(besides later the lights)
In three.js adding .castShadow = true
to all of the lights causes following error
THREE.WebGLProgram: shader error: 0 gl.VALIDATE_STATUS false
gl.getProgramInfoLog Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (16).
Luckily in hour scene we only need a few (at max 4) of them to cast a shadow, as most of the lights are out of reach anyway.
I tried to use 2 approaches
Looping through all the lights and setting .castShadow = true
or .castShadow = false
dynamically.
Adding and removing the lights completely but setting them with no shadow or a shadow.
With both of them I got the same error.
What other approach would work?
@neeh created a Fiddle for it here (to cause the error change var numLightRows = 8;
to a higher number). Keep an eye on the error though, there will be another error with too many lights that isn't caused by the same problem
He also pointed out that we see here that a pointShadowMap
is created even when not in use. This explains why there is no change with a "smarter" approach. This now is within the GLSL code.
So we are limited by the GPU, which in my case has 16 IMAGE_UNITS but that isn't the case for all GPUs (my CPU actually works fine with more). You can check on your system with renderer.capabilities.maxTextures
. But as mentioned we really only need 4.
The problem remains.
Upvotes: 3
Views: 3134
Reputation: 3025
Yes a new shadow map will be created for every light having (Actually, this is not the case, check this issue). A shadow map is a drawn on which a shadow is computed in order to be blended on a surface afterwards.castShadow = true
gl.getProgramInfoLog Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (16).
It means that your device can send no more than 16 textures per draw call. Typically, the car (street?) on which you'd like to put shadows is 1 draw call.
To draw a object that receives shadows, all the shadow maps should be blended together with the diffuse map. So this requires to use N+1 texture units for one single draw call. (N being the number of lights that can cast shadow.)
If you dig into Three.js shaders, you'd find this :
#ifdef USE_SHADOWMAP
#if NUM_DIR_LIGHTS > 0
// Reserving NUM_DIR_LIGHTS texture units
uniform sampler2D directionalShadowMap[ NUM_DIR_LIGHTS ];
varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];
#endif
...
#endif
Check this tool to see how much texture units your browser can handle (Fragment shader > Max Texture Image Units).
Dynamically creating and deleting lights is bad because it's memory-intensive (allocation of a shadow map...).
But, as gaitat said, you can enable shadows only for the nearest lights. Just do the following in your render loop :
light.castShadow = false;
light.castShadow = true;
This algorithm lonely is bad because it allocates one shadow map per light. In addition to be memory-consuming, the rendering would freeze for a bit every time you cross a new light that has no shadow map allocated...
Hence, the idea is to reuse the same shadows maps for the nearest lights. You can deal with shadow maps like this :
// create a new shadow map
var shadowMapCamera = new THREE.PerspectiveCamera(90, 1, 0.5, 500);
var shadow = new THREE.LightShadow(shadowMapCamera);
// use the shadow map on a light
light.shadow = shadow;
shadow.camera.position.copy(light.position);
light.castShadow = true;
You can get the maximum number of texture units with renderer.capabilities.maxTextures
. So you can compute the number of shadow map to create based on it but remember to leave some for more regular maps like diffuseMap, normalMap...
Check out this fiddle for a full implementation (only 4 shadow maps are used).
Upvotes: 3