three.js unexpected resizing on mobile devices - performance

I am getting a strange issue with scaling scene in three.js only on mobile devices.
Everything is alright when I watch it on desktop, but on mobile it always scaling and blinking...
Here is link to codepen: https://codepen.io/elliepooh/pen/evXgdE
And here is my init function:
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
camera.lookAt(scene.position);
camera.position.set(30, 15, 15);
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
renderer.setClearColor(0xF2A9B4);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
controls = new THREE.OrbitControls(camera, renderer.domElement);
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
addLights();
drawChameleon();
drawBranch();
drawFly();
document.body.appendChild(renderer.domElement);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('touchmove', onTouchMove);
window.addEventListener('resize', onResize);
}
Maybe I missed something?
Any assistance is appreciated.

Finally I solved the problem by appending domElement to existing div#world and adding this to my css:
body {
margin: 0;
}
#world {
position: absolute;
overflow: hidden;
width: 100%;
height: 100%;
}
Great thanks to #AlexFallenstedt. Once I knew the reason was in iOS, not in my code, it made me relax and resolve the problem.

I think I know what you mean. I encountered this "blinking" bug before when displaying a three.js scene on mobile. Are you testing this on iOS? If so, when you scroll down on ios, the browser's nav bar shrinks. Your code has renderer.setSize(width, height), and will resize the entire canvas because of your event listener. When your canvas resizes, weird things happen because it's both trying to animate and resize...
How I got around it was by adding a function in render()
function resizeCanvas() {
if (canvas.width != canvas.clientWidth ||
canvas.height != canvas.clientHeight) {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
}
}
It asks if the canvas' width and height is looks like the client's width and height. If it doesn't, it kind of forces the canvas to be that width and height.
You can see it in action here:
https://github.com/Fallenstedt/Fallenstedt.github.io/blob/master/_js/index.js#L128-L135

Related

Make Three Js 2D Game Responsive

Hello Community,
I am New in Three Js.I am Creating 2D Game With Three JS and I am facing Some issue to make Game Responsive and also when Size of the game play is decrease some functionalities are not working properly like touch event on object.I want to know that can we apply css to the three js objects so that we can create the responsive desing game.
Please Guide me about it.
Initialize your camera and renderer to the window size from the start and then add an event listener that will alter properties of objects that relate to the screen size. Usually something like this:
// keep track of the window size in an object
const size = { width: window.innerWidth, height: window.innerHeight };
// initialize camera based on recorded sizes
const camera = new THREE.PerspectiveCamera(
75,
size.width / size.height,
0.1,
100
);
camera.position.set(...);
scene.add(camera);
// initialize renderer based on recorded sizes
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(size.width, size.height);
renderer.setPixelRatio(Math.min(2, window.devicePixelRatio));
// adjust relevant properties on window size change
window.addEventListener('resize', () => {
size.width = window.innerWidth;
size.height = window.innerHeight;
camera.aspect = size.width / size.height;
camera.updateProjectionMatrix();
renderer.setSize(size.width, size.height);
renderer.setPixelRatio(Math.min(2, window.devicePixelRatio));
});
Hopefully this is the answer you're looking for.

rotate mesh on page scroll in three.js

I'd like to rotate an object when the page scrolls, but I cant make it work properly.
What I have so far and updates the rotation of the object on page load is:
mesh.rotation.y = window.pageYOffset;
this however only updates the object on page load and not updating it further on scroll.
I know that for the camera you have to update the projection matrix to be able to have an interactive camera, on objects/meshes however this seems to be on by default (mesh.updateMatrix), I still cant make it work. What am I doing wrong?
I know that for the camera you have to update the projection matrix to be able to have an interactive camera,
I'm afraid this is not right. In many applications the projection matrix is actually static. A camera in three.js is a 3D object like a mesh. You can modify the position or rotation properties in order to transform it.
Check out the following live example that shows how to animate a cube on scroll via GSAP:
let camera, scene, renderer;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(40, window.innerWidth / ( window.innerHeight * 2 ), 0.1, 10);
camera.position.set(0, 0, 7);
const geometry = new THREE.BoxBufferGeometry();
const material = new THREE.MeshNormalMaterial();
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(window.innerWidth, window.innerHeight * 2);
document.body.appendChild(renderer.domElement);
// gsap
gsap.registerPlugin(ScrollTrigger);
gsap.to(mesh.rotation, {
scrollTrigger: {
trigger: "#trigger",
start: "top top",
end: "bottom top",
scrub: true,
toggleActions: "restart pause resume pause"
},
y: Math.PI
});
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
body {
margin: 0px;
}
canvas {
display: block;
}
#trigger {
position: absolute;
height: 100%;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.122/build/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/ScrollTrigger.min.js"></script>
<div id="trigger">
</div>

How do you maintain make a camera move to a new position smoothy while still looking at a target?

In this question, I asked about how one would moves a camera to a different position relative to a target. A very kind gentleman #TheJim01 provided a veery nice answer.
However, I am getting issues with this where the camera is rotated and positioned strangely after I move it.
What are the strategies to keep the camera rotated and maintaining its view of the target smoothly?
You can use any tweening library (Tween.js, GSAP) to move your camera smoothly from current position to the position atop of the sphere.
Also, use THREE.Spherical() to compute the final point. And don't forget to use .makeSafe() method.
body {
overflow: hidden;
margin: 0;
}
button{
position: absolute;
font-weight: bold;
}
<button id="moveUp">MOVE UP</button>
<script src="https://cdn.jsdelivr.net/npm/gsap#3.5.1/dist/gsap.min.js"></script>
<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/three#0.121.1/build/three.module.js";
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three#0.121.1/examples/jsm/controls/OrbitControls.js";
let scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x000000, 6, 15);
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 0, 10);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
controls.maxDistance = 10;
controls.minDistance = 7;
let sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(4, 36, 18), new THREE.MeshBasicMaterial({color: "aqua", wireframe: true}));
scene.add(sphere);
moveUp.addEventListener("click", onButtonClick);
let spherical = new THREE.Spherical();
let startPos = new THREE.Vector3();
let endPos = new THREE.Vector3();
let axis = new THREE.Vector3();
let tri = new THREE.Triangle();
function onButtonClick(event){
spherical.setFromVector3(camera.position);
spherical.phi = 0;
spherical.makeSafe(); // important thing, see the docs for what it does
endPos.setFromSpherical(spherical);
startPos.copy(camera.position);
tri.set(endPos, scene.position, startPos);
tri.getNormal(axis);
let angle = startPos.angleTo(endPos);
let value = {value: 0};
gsap.to(value, {value: 1,
duration: 2,
onUpdate: function(){
camera.position.copy(startPos).applyAxisAngle(axis, angle * value.value);
controls.update();
},
onStart: function(){
moveUp.disabled = true;
},
onComplete: function(){
moveUp.disabled = false;
}
})
.play();/**/
}
renderer.setAnimationLoop(()=>{
renderer.render(scene, camera);
});
</script>
The better way to smoothly translate a camera would be to create keyframes and position them in some form of timeline defined by a emphasys curve.

Adding three.js compound object into A-Frame causing unexpected flickering of child objects

I am adding augmented reality experience to my web app which originally created using threejs
I have a compound object(which is an Object3D instance with multiple meshes). but placing it into A-Frame giving unexpected flickering s shown in below
Pic of the original web app with threejs is given below
I have the three.js code like below
scene = new THREE.Scene();
mainObj = new THREE.Object3D();
scene.add(mainObj);
renderer = new THREE.WebGLRenderer({alpha: true, antialias: true});
renderer.sortObjects = false
container = document.getElementById("canvas-container");
width = $(container).innerWidth();
height = $(container).innerHeight();
renderer.setSize(width, height);
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(75, width / height, 10, 2000);
camera.position.set(0, 67, 100);
controls = new THREE.OrbitControls( camera , container);
controls.dampingFactor = 0.2;
controls.enableDamping = true;
controls.target.set( 0, 10, -20 );
controls.maxPolarAngle = (Math.PI/2) - 0.05;
controls.maxDistance = 800;
controls.minDistance = 2;
controls.target.set(0, buildingConfig.h/2, buildingConfig.l/-2)
controls.enableKeys = false;
controls.update();
scene.add(camera);
light = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.7);
light.position.set(1, 10000, 1);
light.castShadow = true
scene.add( light );
renderer.render(scene, camera);
// logic to add child meshes to mainObj
Which I changed to include in A-Frame.
<script type="text/javascript">
initmodels();
AFRAME.registerComponent('e1', {
init:function(){
this.el.setObject3D('building', mainObj);
}
});
</script>
<a-scene e1>
</a-scene>
I guess this issue is related to the scene or renderer. Can anyone help me with a proper scene or renderer setup in A-Frame
This looks like z-fighting. Is the three.js version running on a different system than your aframe version?
Different platforms have different z-buffer precision. It may require you to make changes to the geometry to compensate for the limited resolution.
Also, I don't know about AR.s, but the THREE renderer has a "logarithmicDepthBuffer" option that can increase the resolution of your z-buffer at near scales, but may have some side effects.
I had exactly the same issue. After a few trial and errors, I was able to fix it
Added recent version of aframe
Add <a-scene embedded artoolkit='sourceType: webcam;' renderer='logarithmicDepthBuffer: true;'>
Add <a-marker-camera camera="far:100000; near:0.01" >
Main reason was the light, Remove all lights and add only the hemisphere light into the scene and improvise upon other lights later on.
Hope this would resolve your issue too.

THREE.js Orbit Controls not working

I am attempting to learn THREE and so far things are going well. I'm working to create a simple project building a golf ball on a golf tee. Eventually I'll put it inside a skybox and use some shaders to create the appearance of dimples in the ball.
For now I've been able to create the ball and the tee and get the camera and lights set up. I'm now attempting to add some orbit controls so I can rotate, pan and zoom around the scene. I added the orbitControls script and set it up how I see in the examples online. However when I attempt to orbit my camera snaps to the either 1,0,0 0,1,0 or 0,0,1 and eventually the scene completely fails and I end up looking along 0,0 with no object visible.
Here's my code in it's entirety
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>3D Sandbox</title>
<style>
html, body {
margin: 0px;
}
canvas {
background-color: #999;
position: absolute;
height: 100vh;
width: 100vw;
overflow: hidden;
}
</style>
</head>
<body>
<script src='/three.js'></script>
<script src="/orbitControls.js"></script>
<script type='text/javascript'>
let scene,
camera,
controls,
renderer,
height = window.innerHeight,
width = window.innerWidth;
window.addEventListener('load', init, false);
function init () {
createScene();
createCamera();
createLights();
createBall();
createTee();
render();
animate();
}
function createScene () {
let grid;
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
grid = new THREE.GridHelper(100, 5);
scene.add(grid);
}
function createCamera () {
camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
camera.position.z = 50;
camera.position.y = 13;
camera.up = new THREE.Vector3(0,1,0);
//camera.lookAt(new THREE.Vector3(0,13,0));
controls = new THREE.OrbitControls(camera);
controls.target = new THREE.Vector3(0, 13, 0);
controls.addEventListener('change', render);
}
function createLights () {
let directionalLight, ambientLight;
directionalLight = new THREE.DirectionalLight(0x404040, 4);
directionalLight.position.set(0, 2000, 2000);
directionalLight.target.position.set(0, 2, 0);
scene.add(directionalLight);
ambientLight = new THREE.AmbientLight(0x404040, 2);
scene.add(ambientLight);
}
function createBall () {
let ballGeom, ballMaterial, ball;
ballGeom = new THREE.SphereGeometry(5, 32, 32);
ballMaterial = new THREE.MeshPhongMaterial({
color: 0xFFFFFF
});
ball = new THREE.Mesh(ballGeom, ballMaterial);
ball.position.y = 13.3;
scene.add(ball);
}
function createTee () {
let tee, stemGeom, stem, bevelGeom, bevel, topGeom, top, teeMat;
tee = new THREE.Object3D();
teeMat = new THREE.MeshPhongMaterial({
color: 0x0000FF
});
stemGeom = new THREE.CylinderGeometry(.9, 0.75, 7);
stem = new THREE.Mesh(stemGeom, teeMat);
tee.add(stem);
bevelGeom = new THREE.CylinderGeometry(1.5, .9, 2);
bevel = new THREE.Mesh(bevelGeom, teeMat);
bevel.position.y = 3.75;
tee.add(bevel);
topGeom = new THREE.CylinderGeometry(1.5, 1.5, .25);
top = new THREE.Mesh(topGeom, teeMat);
top.position.y = 4.875;
tee.add(top);
tee.position.y = 3.5;
scene.add(tee);
}
function render () {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame( animate );
controls.update();
render();
}
</script>
</body>
</html>
In my case, the camera position was the same as OrbitControl target, and this made my OrbitControl seem "broken" (different symptoms than OP -- my OrbitControl just wouldn't work at all.)
As mentioned in this github comment, camera position and OrbitControl target cannot be the same; I repositioned my camera to fix this (camera.position.z = 10).
Remember, the camera (like any Object3D) is positioned at (0,0,0) by default, and the OrbitControl target property (a Vector3D) seems to also default that same point (0,0,0)
controls = new THREE.OrbitControls(camera);
needs to be
controls = new THREE.OrbitControls(camera, renderer.domElement);
The result is on this block.

Resources