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.
假设有一个带有许多路灯(更多20个)的街道的场景,你移动一个靠近它们的物体,你会想到一个阴影。
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)
只有街道有.receiveShadow = true,只有汽车有.castShadow = true(除了以后的灯光)
In three.js adding .castShadow = true
to all of the lights causes following error
在three.js中,将.castShadow = true添加到所有灯光会导致跟随错误
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.
幸运的是,在小时场景中,我们只需要一些(最多4个)投射阴影,因为大多数灯光无论如何都是遥不可及的。
I tried to use 2 approaches
我尝试使用2种方法
-
Looping through all the lights and setting
.castShadow = true
or.castShadow = false
dynamically.循环遍历所有灯光并动态设置.castShadow = true或.castShadow = false。
-
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?
还有什么其他方法可行?
Update
@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
@neeh在这里为它创建了一个小提琴(导致错误更改var numLightRows = 8;更高的数字)。但要注意错误,会有另一个错误,太多的灯光不是由同一个问题引起的
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.
他还指出,我们在这里看到即使在不使用时也会创建一个pointShadowMap。这解释了为什么“智慧”方法没有变化。现在这是在GLSL代码中。
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.
所以我们受到GPU的限制,在我的情况下有16个IMAGE_UNITS,但并非所有GPU的情况都是如此(我的CPU实际上工作得更好)。您可以使用renderer.capabilities.maxTextures检查系统。但如上所述,我们真的只需要4。
The problem remains.
问题仍然存在。
1 个解决方案
#1
2
The problem
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
是的,将为每个具有castShadow = true的灯光创建一个新的阴影贴图(实际上,情况并非如此,请查看此问题)。绘制阴影贴图,在阴影贴图上计算阴影,以便之后在曲面上进行混合。
gl.getProgramInfoLog Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (16).
gl.getProgramInfoLog片段着色器采样器计数超过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.
这意味着您的设备每次绘制调用可以发送不超过16个纹理。通常情况下,您想要放置阴影的汽车(街道?)是1次平局调用。
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.)
要绘制接收阴影的对象,应将所有阴影贴图与漫反射贴图混合在一起。因此,这需要在一次绘制调用中使用N + 1个纹理单元。 (N是可以投射阴影的灯光数量。)
If you dig into Three.js shaders, you'd find this :
如果你深入了解Three.js着色器,你会发现:
#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).
选中此工具以查看浏览器可以处理的纹理单位数(片段着色器>最大纹理图像单位)。
The solution ?
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 :
但是,正如gaitat所说,你可以只为最近的灯启用阴影。只需在渲染循环中执行以下操作:
- Disable all shadows:
light.castShadow = false;
- Seek nearest lights
- Enable shadow for N nearest lights:
light.castShadow = true;
禁用所有阴影:light.castShadow = false;
寻找最近的灯
为N个最近的灯启用阴影:light.castShadow = true;
Improvement
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...
您可以使用renderer.capabilities.maxTextures获取最大数量的纹理单位。因此,您可以根据它计算要创建的阴影贴图的数量,但请记住留下一些更常规的贴图,如diffuseMap,normalMap ...
Check out this fiddle for a full implementation (only 4 shadow maps are used).
查看这个小提琴以获得完整的实现(仅使用4个阴影贴图)。
#1
2
The problem
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
是的,将为每个具有castShadow = true的灯光创建一个新的阴影贴图(实际上,情况并非如此,请查看此问题)。绘制阴影贴图,在阴影贴图上计算阴影,以便之后在曲面上进行混合。
gl.getProgramInfoLog Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (16).
gl.getProgramInfoLog片段着色器采样器计数超过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.
这意味着您的设备每次绘制调用可以发送不超过16个纹理。通常情况下,您想要放置阴影的汽车(街道?)是1次平局调用。
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.)
要绘制接收阴影的对象,应将所有阴影贴图与漫反射贴图混合在一起。因此,这需要在一次绘制调用中使用N + 1个纹理单元。 (N是可以投射阴影的灯光数量。)
If you dig into Three.js shaders, you'd find this :
如果你深入了解Three.js着色器,你会发现:
#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).
选中此工具以查看浏览器可以处理的纹理单位数(片段着色器>最大纹理图像单位)。
The solution ?
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 :
但是,正如gaitat所说,你可以只为最近的灯启用阴影。只需在渲染循环中执行以下操作:
- Disable all shadows:
light.castShadow = false;
- Seek nearest lights
- Enable shadow for N nearest lights:
light.castShadow = true;
禁用所有阴影:light.castShadow = false;
寻找最近的灯
为N个最近的灯启用阴影:light.castShadow = true;
Improvement
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...
您可以使用renderer.capabilities.maxTextures获取最大数量的纹理单位。因此,您可以根据它计算要创建的阴影贴图的数量,但请记住留下一些更常规的贴图,如diffuseMap,normalMap ...
Check out this fiddle for a full implementation (only 4 shadow maps are used).
查看这个小提琴以获得完整的实现(仅使用4个阴影贴图)。