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:
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.
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:
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.
Share Improve this question edited Dec 21, 2015 at 10:53 kockburn 17.6k9 gold badges90 silver badges141 bronze badges asked Dec 15, 2015 at 12:39 neilthomneilthom 4751 gold badge4 silver badges9 bronze badges 5- Can you post the full code, also for the renderer? it's easier for us to replicate and test then – cviejo Commented Dec 18, 2015 at 16:53
- That should happen when the light is a child of the camera. In your case it is not, so you don't have to add the camera to the scene either. – gaitat Commented Dec 21, 2015 at 13:44
- I have put a little example online to check this on my mobile devices... but I'm not entirely convinced I have reproduction. The limited camera freedom coupled with the large specular highlight make it rather hard to see. If anyone else wants to take a look, the sample is here: eponalabs.com/experiments/so19086690 . – Paul-Jan Commented Dec 21, 2015 at 21:59
- Sorry for the late update. A version of the code that causes the issue can be found below. When opened on my Android device, the light will follow the camera and point in it's direction. link – neilthom Commented Dec 22, 2015 at 13:14
- Couldn't replicate on any of my devices, so it seems to be quite specific. Maybe the new answer below helps.. – cviejo Commented Dec 23, 2015 at 14:36
2 Answers
Reset to default 10 +250I 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.
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?