我的three.js场景中的聚光灯为什么在摄像机视角下保持居中,但只在安卓Chrome浏览器上出现?

39

我目前正在使用AngularJS和Three.js尝试开发一个小型虚拟现实应用的示例。 我根据用户代理是否为移动设备定义了控件,这是一种不太可靠的方法,但只是一个例子。 在非移动设备上使用OrbitControls,在其他情况下使用DeviceOrientationControls。

var controls = new THREE.OrbitControls(camera, game.renderer.domElement);
controls.noPan  = true;
controls.noZoom = true;

controls.target.set(
    camera.position.x,
    camera.position.y,
    camera.position.z
);

// Really dodgy method of checking if we're on mobile or not
if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
    controls = new THREE.DeviceOrientationControls(camera, true);
    controls.connect();
    controls.update();
}

return controls;

我还创建了一些对象来实际显示。

this.camera     = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.001, 1000);

this.camera.position.set(0, 15, 0);

    this.textGeometry = new THREE.TextGeometry("Hello World", { size: 5, height: 1 });

    this.textMesh = new THREE.Mesh(this.textGeometry, new THREE.MeshBasicMaterial({
        color: 0xFF0000, opacity: 1
    }));

    this.textMesh.position.set(-20, 0, -20);

    this.light = new THREE.SpotLight(0x999999, 3, 300);
    this.light.position.set(50, 50, 50);

    this.floorGeometry              = new THREE.PlaneBufferGeometry(1000, 1000);
    this.floorTexture               = THREE.ImageUtils.loadTexture('img/textures/wood.jpg');
    this.floorTexture.wrapS         = THREE.RepeatWrapping;
    this.floorTexture.wrapT         = THREE.RepeatWrapping;
    this.floorTexture.repeat        = new THREE.Vector2(50, 50);
    this.floorTexture.anisotropy    = this.game.renderer.getMaxAnisotropy();

    this.floorMaterial = new THREE.MeshPhongMaterial({
        color: 0xffffff,
        specular: 0xffffff,
        shininess: 20,
        shading: THREE.FlatShading,
        map: this.floorTexture
    });

    this.floor = new THREE.Mesh(this.floorGeometry, this.floorMaterial);
    this.floor.rotation.x = -Math.PI / 2;

    this.scene.add(this.textMesh);
    this.scene.add(this.light);
    this.scene.add(this.floor);
    this.scene.add(this.camera);

在Mac OS的Chrome、Mac OS的Safari和iPad上的Safari上(包括适当的设备方向控制),这个功能运行良好。

问题出现在Android的Chrome上运行应用程序时。场景中添加的聚光灯将始终指向与相机相同的方向。以下是Android上运行应用程序的屏幕截图:

Spotlight image

在我尝试使用的所有其他浏览器上,光源位置正确地位于(50, 50, 50),并保持静态。在上面的例子中,光源被渲染在相机的中心,并且在移动或旋转相机时继续跟随相机。实际的方向控制工作正常。

这只发生在一个浏览器中的事实让我非常头疼,因为演示需要在Android的Chrome上运行。

谢谢。

更新:尝试了许多不同的解决方案,从不同的控制方法到不同类型的照明都无济于事。


你能发布完整的代码吗?包括渲染器?这样我们更容易复制和测试。 - cviejo
这应该发生在光线是相机的子级时。在您的情况下,它不是,因此您也不必将相机添加到场景中。 - gaitat
我已经在线上放了一个小例子来检查我的移动设备...但我并不完全相信我有复制品。有限的相机自由度加上大的高光使得它很难看清楚。如果其他人想要看一下,样本在这里:http://eponalabs.com/experiments/so19086690/。 - Paul-Jan
抱歉更新有些晚。引起问题的代码版本如下所示。在我的 Android 设备上打开后,光线会跟随相机并指向它的方向。链接 - neilthom
无法在我的任何设备上复制,所以似乎相当特定。也许下面的新答案有帮助... - cviejo
2个回答

10

我看到了我的Moto G 1代(Qualcomm Snapdragon 400)上的问题,但在我的Project Tango平板电脑(nVidia Tegra K1)上没有这个问题,因此很可能这是一个GPU驱动程序错误或某些硬件不支持的功能。

我能够编写一个简单的可重复测试用例,并使用它确定了计算在我的两个平台之间分歧的位置。结果发现在这部分Three.js GLSL片段着色器中(从Three.js脚本中提取),导致了差异(由我添加注释):

#ifndef FLAT_SHADED
  // Smooth shaded - use the interpolated normal.
  vec3 normal = normalize( vNormal );

#ifdef DOUBLE_SIDED
  normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );
#endif

#else
  // Flat shaded - generate the normal by taking the cross
  // product of derivatives that lie tangent to the surface.
  // This is the code that produces inconsistent results on
  // some mobile GPUs.
  vec3 fdx = dFdx( vViewPosition );
  vec3 fdy = dFdy( vViewPosition );
  vec3 normal = normalize( cross( fdx, fdy ) );

#endif

这段代码确定了一个片段的法线。您的材质设置导致启用了FLAT_SHADED块。显然,对于由GL_OES_standard_derivatives扩展提供给WebGL的导数函数dFdx()dFdy()的调用产生了不一致的结果。这表明该扩展可能被错误地实现或在引起问题的平台上不受支持。 此Mozilla bug 支持了这个假设,特别指出了高通硬件:

许多设备都暴露了OES_standard_derivatives,但它们的实现已经损坏了。

简单的解决方法是避免使用平面着色代码路径。您的floorMaterial包含参数:

shading: THREE.FlatShading,

删除这一行将默认为平滑着色(或者您可以明确地将值更改为 THREE.SmoothShading )。您的网格已经提供了顶点法线,所以这应该可以正常工作。

我尝试克隆了您的测试站点并注释掉那一行,这在我的Moto G上看起来好多了。我还创建了这个 jsfiddle,其中显示了两个四边形,一个使用平滑着色(左边),一个使用平面着色(右边)。这些四边形应该会相互反射,但如果平台无法处理平面着色器,则不会如此。


3

根据您的说法,Chrome桌面版可以正常使用,问题只出现在Android上, 您可以尝试在Android上以桌面模式运行Chrome,就像您可以为Chrome请求“桌面站点”一样。 看看这是否有帮助。 Chrome的“请求桌面站点”选项如何工作?


请求桌面版网站仍然存在与移动版相同的问题,遗憾地。 - neilthom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接