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

时间:2017-03-18 17:38:12

标签: javascript three.js glsl

假设有一个街道上有许多路灯(更多20个)的场景,你移动一个靠近它们的物体,你会想到一个阴影。

灯光,简单

var light = new THREE.PointLight(0xffffff, 0.5, 6.0);

只有街道有.receiveShadow = true且只有汽车有.castShadow = true(除了后来的灯光)

example

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

幸运的是,在小时场景中,我们只需要一些(最多4个)投射阴影,因为大多数灯都无法到达。

我尝试使用2种方法

  1. 循环播放所有灯光并动态设置.castShadow = true.castShadow = false

  2. 完全添加和移除灯光,但设置时没有阴影或阴影。

  3. 他们两个都有同样的错误。

    还有哪种方法可行?

    更新

    @neeh为它创建了一个小提琴here(使错误更改为var numLightRows = 8;更高的数字)。但要注意错误,会出现另一个错误,因为同样的问题不会引起太多的灯光

    他还指出我们看到here即使在未使用时也会创建pointShadowMap。这解释了为什么没有变化的更聪明的"做法。现在这是在GLSL代码中。

    所以我们受到GPU的限制,在我的情况下有16个IMAGE_UNITS但是并非所有GPU的情况都是如此(我的CPU实际上工作得更好)。您可以使用renderer.capabilities.maxTextures检查系统。但如上所述,我们真的只需要4。

    问题仍然存在。

1 个答案:

答案 0 :(得分:2)

问题

new shadow map will be created对于castShadow = true 的每一盏灯(事实上,情况并非如此,请检查this issue。绘制阴影贴图,在阴影贴图上计算阴影,然后在曲面上进行混合。

  

gl.getProgramInfoLog片段着色器采样器计数超过MAX_TEXTURE_IMAGE_UNITS(16)。

这意味着您的设备每次通话可以发送不超过16个纹理。通常情况下,您想要投放阴影的汽车(街道?)是1次抽奖。

要绘制接收阴影的对象,所有阴影贴图应与漫反射贴图混合。因此,这需要使用 N + 1 纹理单位进行一次绘制调用。 ( N 是可以投射阴影的灯光数量。)

如果你深入了解Three.js着色器,你会发现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

检查this tool以查看浏览器可以处理多少纹理单元(片段着色器> 最大纹理图像单元)。

解决方案?

动态创建和删除灯光很糟糕,因为它占用大量内存(分配阴影贴图......)。

但是,正如gaitat所述,您可以仅为最近的灯启用阴影 。只需在渲染循环中执行以下操作:

  1. 禁用所有阴影:light.castShadow = false;
  2. 寻找最近的灯光
  3. N 最近的灯启用阴影:light.castShadow = true;
  4. 改进

    这种算法孤独很糟糕,因为它为每个光分配一个阴影贴图。除了耗费内存之外,每次穿过没有分配阴影贴图的新灯时,渲染会冻结一点......

    因此,我们的想法是重用相同的阴影贴图以获得最近的灯光。你可以像这样处理阴影贴图:

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

    您可以使用renderer.capabilities.maxTextures获取最大纹理单位数。所以你可以根据它来计算要创建的阴影贴图的数量,但是记得留下一些更常规的贴图,比如diffuseMap,normalMap ......

    enter image description here

    查看this fiddle以获得完整实施(仅使用4张阴影贴图)。