Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the spotlight in my three.js scene remain centered in the camera perspective, but only on Chrome for Android?

I'm currently using AngularJS and Three.js to try to develop a small example of a VR application. I have defined controls based on whether the user agent is a mobile device or not. It's a dodgy method, but it's just an example. OrbitControls are used on non-mobile devices, and DeviceOrientationControls are used otherwise.

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;

I also created a few objects to actually display.

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

This works fine on Chrome for OSX, Safari for OSX & Safari on iPad (including device orientation controls where appropriate).

The problem occurs when running the application on Chrome for Android. The spotlight that has been added to the scene will constantly be pointed in the same direction as the camera. Here is a screenshot of the application running on Android:

Spotlight image

On every other browser I have tried to use, the light is positioned at (50, 50, 50) correctly and remains static. In the example above, the light is being rendered in the middle of the camera, and continues to follow the camera when it is moved or rotated. The actual orientation controls work just fine.

The fact that this only happens in one browser is really giving me headaches, as the demo needs to run on Chrome for Android.

Thanks.

Update: Have been attempting many different solutions from different control methods to different types of lighting to no avail.

like image 757
neilthom Avatar asked Dec 15 '15 12:12

neilthom


2 Answers

I see the problem on my Moto G 1st gen (Qualcomm Snapdragon 400) but not on my Project Tango tablet (nVidia Tegra K1), so it is likely that this is either a GPU driver bug or an unsupported feature on certain hardware.

I was able to write a simple repeatable test case and use it to determine where the computation diverged between my two platforms. It turned out to happen in this portion of the Three.js GLSL fragment shader (extracted from the Three.js script) that causes the discrepancy (comments added by me):

#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

This section of code determines the normal at a fragment. Your material settings result in enabling the FLAT_SHADED block. Apparently the calls to the derivative functions dFdx() and dFdy(), which are provided by the GL_OES_standard_derivatives extension to WebGL, are producing inconsistent results. This suggests that the extension is either implemented incorrectly or not supported on the platforms causing problems. This Mozilla bug supports this hypothesis, specifically pointing out Qualcomm hardware:

A number of devices expose OES_standard_derivatives, but have broken implementations of it

The simple workaround is to avoid the flat shading code path. Your floorMaterial contains the parameter:

shading: THREE.FlatShading,

Removing this single line will default to smooth shading (or you can explicitly change the value to THREE.SmoothShading). Your mesh already provides vertex normals so this should just work.

I tried to clone your test site and commenting out that one line looks much better to me on my Moto G. I also created this jsfiddle that shows two quads, one with smooth shading (left) and one with flat shading (right). The quads should appear to be reflections of each other but won't if the platform has problems with the flat shader.

like image 74
rhashimoto Avatar answered Nov 02 '22 17:11

rhashimoto


Going by your words that it works fine in chrome desktop, but issue is only with android, you may try running chrome in desktop mode in android. just like you can do "request desktop site" for chrome. See if this helps. How does Chrome's "Request Desktop Site" option work?

like image 24
Ashish Rawat Avatar answered Nov 02 '22 16:11

Ashish Rawat