许多带有three.js阴影的灯会导致Fragment着色器错误

时间:2021-12-21 03:20:45

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(除了以后的灯光)

许多带有three.js阴影的灯会导致Fragment着色器错误

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种方法

  1. Looping through all the lights and setting .castShadow = true or .castShadow = false dynamically.

    循环遍历所有灯光并动态设置.castShadow = true或.castShadow = false。

  2. 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 castShadow = true (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).

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所说,你可以只为最近的灯启用阴影。只需在渲染循环中执行以下操作:

  1. Disable all shadows: light.castShadow = false;
  2. 禁用所有阴影:light.castShadow = false;

  3. Seek nearest lights
  4. 寻找最近的灯

  5. Enable shadow for N nearest lights: light.castShadow = true;
  6. 为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 ...

许多带有three.js阴影的灯会导致Fragment着色器错误

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 castShadow = true (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).

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所说,你可以只为最近的灯启用阴影。只需在渲染循环中执行以下操作:

  1. Disable all shadows: light.castShadow = false;
  2. 禁用所有阴影:light.castShadow = false;

  3. Seek nearest lights
  4. 寻找最近的灯

  5. Enable shadow for N nearest lights: light.castShadow = true;
  6. 为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 ...

许多带有three.js阴影的灯会导致Fragment着色器错误

Check out this fiddle for a full implementation (only 4 shadow maps are used).

查看这个小提琴以获得完整的实现(仅使用4个阴影贴图)。