Three.js project crashes mobile - javascript

I've been working on a three.js project to try and learn the framework. Got a basic model floating around that works fine on the desktop browser but will crash repeatedly on mobile. I uploaded the project on my server http://threedeesneaker.404vanity.com/
Is there any way to optimize this for mobile devices? I tried both chrome and safari for iPhone and iPad.
The code it self:
(function() {
var scene, camera, renderer;
var geometry, material, mesh, sneaker;
init();
animate();
function init() {
scene = new THREE.Scene();
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
var ambient = new THREE.AmbientLight( 0x444444 );
scene.add( ambient );
camera = new THREE.PerspectiveCamera( 3, WIDTH / HEIGHT, 1, 20000 );
camera.position.z = 1000;
window.addEventListener('resize', function() {
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
geometry = new THREE.BoxGeometry( 200, 200, 200 );
material = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: true } );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
// prepare loader and load the model
var oLoader = new THREE.OBJMTLLoader();
oLoader.load('models/sneaker.obj', 'models/sneaker.mtl', function(object) {
object.scale.set(1, 1, 1);
object.rotation.y = 600;
object.rotation.z= 600;
sneaker = object;
scene.add(sneaker);
});
// var loader = new THREE.OBJLoader();
// loader.load('models/sneaker.obj', function(object) {
// sneaker = object;
// sneaker.scale.set(1,1,1);
// sneaker.rotation.y = 600;
// sneaker.rotation.z= 600;
// scene.add(sneaker);
// });
renderer = new THREE.WebGLRenderer();
renderer.setSize( WIDTH, HEIGHT );
renderer.setClearColor(0x333F47, 1);
var light = new THREE.PointLight(0xffffff);
light.position.set(-100,200,100);
scene.add(light);
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
sneaker.rotation.x += 0.01;
sneaker.rotation.y += 0.02;
renderer.render( scene, camera );
}
})();

First a comment on your js : check if typeof sneaker !== 'undefined' in your render loop before asking to rotate your mesh, before loading it generates errors.
Your scene crashes because you are using too detailed materials, I can see a 4096x4096 bump map for instance. It strongly increases frame rendering time on desktop and is probably the reason why the page is irresponsive on mobile : the fragment shader computations become too big.
However it would be a shame to completly delete those details you spent time on. What you can do is to add a device detector in your js. You can use that to display two different models on desktop and on mobile.
But there are further important improvements you can bring. As they are part of my original post I let them there :) :
Resize your textures. You are using two 4096 x 4096 jpg of 4.5MB, this is heavy (note that there are webgl-enabled smartphones with only 500Mo RAM that get realeased these days). Moreover you have very few details that justifies it. You could change your uv to reduce a lot the parts with no details, and probably resize the picture to 512x512. Finally, use a JPG compressor that will reduce the weight by 70-80%. Depending on your picture PNG can be a better choice also. The device's GPU memory is still something else, and if you still need to improve performance you can check in the script if the client supports .pvr or .ktx texture formats, optimized for GPU memory.
An important problem that makes your visualization unappropriate for mobile devices is that you have ... 23 render calls, because you are using 15 textures and 23 geometries.
What it means is that, for each frame, you will have to bind 23 different geometries before the final frame renders. Some mobile CPU-GPU couples cannot do that 60 times per second. Don't plan more than 10 render calls for average mobile devices. That means less geometries with less materials. Merge.
I have not inspected your .obj file in detail to understand how you get 23 geometries in the end, neither where your 13 textures come from, up to you.
A lot of 3D apps (OpenGL) on the stores have more than 23 objects of course. But stores know the apps and they know your phone so they can do the compatibility job and hide the app to low devices.
Here is the tip to check your render calls, geometries and materials in the scene : in your main function, after having set the renderer, include a pointer to it in the window object window.renderer = renderer. Now at runtime in your console, once resources have been loaded, type renderer.info. It will return those data in an object.

Related

ThreeJS my GLTF loading speed is too slow on host server?

I'm currently creating a Website with a 3D Market Place feature in it. Alas, I'm having trouble implementing the main feature of this website.
This is the website (alpha state): https://www.openstring-studios.com/
All the code is in the index.html file there so you can see how it's made, don't mind the mess though, my issue is only with the loading of 3D models into the scene.
There is this website called sketchfab.com and they have this amazing feature of previewing PBR Assets within a WebGL Window. And it loads reasonably fast.
Here's my problem. I want to create a similar feature for my website and got the ThreeJS Library to do it. However, even though everything loads quite quickly on my xampp localhost as soon as I upload it on my host server, the models take forever to load (probably because of the 4k textures).
This is the code I use:
function init() {
camera.position.z = 0.3;
camera.position.y = 0.15;
camera.position.x = 0.4;
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth/sceenDivide, 600 );
renderer.physicallyCorrectLights = true;
renderer.gammaOutput = true;
renderer.gammaFactor = 2.8;
new THREE.RGBELoader().setDataType( THREE.UnsignedByteType )
.setPath( '' ).load( 'white_cliff_top_1k.hdr', function ( texture ) {
var cubeGenerator = new THREE.EquirectangularToCubeGenerator( texture, { resolution: 512 } );
var pmremGenerator = new THREE.PMREMGenerator( cubeGenerator.renderTarget.texture );
var pmremCubeUVPacker = new THREE.PMREMCubeUVPacker( pmremGenerator.cubeLods );
envMap = pmremCubeUVPacker.CubeUVRenderTarget.texture;
cubeGenerator.update( renderer );
pmremGenerator.update( renderer );
pmremCubeUVPacker.update( renderer );
loader = new THREE.GLTFLoader().setPath( '' );
loadModel( 'Promo_ProductSoil.gltf');
loadModel( 'Promo_ProductCover.gltf');
loadModel( 'StraightPot_OrangePaintedIron.gltf');
pmremGenerator.dispose();
pmremCubeUVPacker.dispose();
scene.background = cubeGenerator.renderTarget;
} );
}
function loadModel( src ) {
loader.load( src , function ( gltf ) {
gltf.scene.traverse( function ( child ) {
if ( child.isMesh ) {
child.material.envMap = envMap;
child.position.y -= 0.12
mesh = child;
}
gltfSource = gltf;
} );
scene.add( gltf.scene );
},
function ( xhr ) {
onProgress( xhr )
} );
}
Is there a solution how I can achieve a similar loading speed as seen on the website sketchfab.com because at the moment my simple pot model takes over a minute before it is displayed.
The only reason why the planes load so fast is that their material has only a low-resolution BaseColor and nothing else.
Any ideas on how to solve this. My website is hosted through domain.com, should I ask them or is it more a ThreeJS issue and if so are there alternatives?
Is there a solution how I can achieve a similar loading speed as seen on the website sketchfab.com because at the moment my simple pot model takes over a minute before it is displayed.
As mentioned in the comments, your glTF assets are just too big. E.g. StraightPot_OrangePaintedIron.gltf has file size of 41 MB. There are several way in order to speed up transmission time.
You can compress the geometry data of your assets with DRACO and use THREE.DRACOLoader in combination with THREE.GLTFLoader to decompress the asset on the client side. The following live example demonstrates the usage of both loaders. Use a tool like glTF Pipeline to compress existing glTF assets.
Reduce the resolution of your textures. Also consider to use texture compression to further reduce the file size. However, it's still a bit inconvenient to use platform independent texture compression with WebGL. Basis will probably make things easier in the future.
Reduce the complexity of your asset in general by simplifying the geometry. Sometimes it's also possible to lower the amount of materials and to merge the respective textures.
three.js R108

three.js progressive rendering of large mesh

I would like to render a static mesh that is arbitrarily large using three.js. The mesh could be 2 GB with tens of millions of polygons.
I want to stream the mesh geometry buffers into indexedDB and progressively read them out and render them to the screen, while maintaining an interactive frame rate. I will create a MemoryManager class that makes sure we do not crash the browser by loading the data into a fixed-size buffer from indexedDB. In my animation loop I will render as many geometries as I can within 16ms, until the user stops interacting, then continuously render meshes until there are no more.
That is the high-level approach I want to take, of course there are many optimizations that will need to be done. ( object pools, octree, occlusion queries, etc)
My question is this: is there a better way to do this, and has it been done before? ( with WebGL1, I know WebGL2 occlusion queries would make this much simpler)
Also, what is the best way to customize the Three.js WebGLRenderer class? There are private closure vars ( like WebGLState ) that I will need access to tweak the performance for my use case.
I don't believe WebGL2 occlusion queries will help you a whole lot here. It might be possible to use them, but doing culling on the cpu is probably a better option, especially if your scene is largely static.
If you want to progressively render in geometry while the user has stopped interacting, then you can just stop clearing the render buffer. With THREE.js you can do this by enabling preserveDrawingBuffer on the renderer and setting autoClear to false.
If the camera moves or the scene otherwise changes and you need to start over, you can clear the view again by calling renderer.clear().
Regarding streaming data in, I would use BufferGeometry and modify the necessary BufferAttributes every frame. And it sounds like you have already have some ideas around optimizing and deciding which geometry needs to be rendered.
Example
Scene redraws on the same frame over and over. Click the scene to clear it and start drawing again. The important js lines are NOTE'd
// NOT: enabling preserveDrawingBuffer
const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
renderer.setClearColor(0x263238);
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.clear();
document.body.appendChild( renderer.domElement );
// NOTE: disabling autoclear
renderer.autoClear = false;
// Scene setup
const scene = new THREE.Scene();
const dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(.4, 1, .1);
scene.add(dirLight);
// Camera setup
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000)
camera.position.z = 10;
const geom = new THREE.TorusKnotGeometry(.5, .2, 200, 100);
const mesh = new THREE.Mesh(geom, new THREE.MeshPhongMaterial({ color: 0xE91E63 }));
scene.add(mesh);
// NOTE: Clearing on user interaction
renderer.domElement.addEventListener('click', () => renderer.clear());
(function renderLoop() {
mesh.position.y = Math.sin(performance.now() * 0.001);
renderer.render( scene, camera );
requestAnimationFrame( renderLoop );
})();
<script type="text/javascript" src="https://rawgit.com/mrdoob/three.js/r92/build/three.js"></script>
<style>
canvas {
width: 100%;
height: 100%;
}
* {
margin: 0;
padding: 0;
font-family: sans-serif;
}
div {
color: white;
position: absolute;
text-align: center;
width: 100%;
}
</style>
<div>Click to clear</div>
Hope that helps!

Make a scene a 3D texture in THREE.js (3D Render Targets)

I'm looking at the THREE.js example located here and wondering how to prevent the 'flattening' of scenes rendered as textures. In other words, the scene loses the illusion of having depth when set as a WebGLRenderTarget.
I have looked everywhere, including in THREE.js documentation, and have found no mention of this kind of functionality, probably because it would put a significant load on the user's processor unnecessarily (except for in very particular cases). Perhaps this is possible in pure WebGL, though?
EDIT: Downvoters - why is this question poor? I have done significant research into this matter, but since I'm new to WebGL, I can't exactly spout senseless code... How do I improve my query?
I think you want to use screenspace projections instead of UV projections if that makes sense. Given your tv example, the screen would have the UV points that do get transformed as you move the camera around. You want something that stays put, ie. no matter how much you move, you're looking at the same thing. I'm not sure how this is done without shaders, but in fragment shaders you have gl_FragCoord
Because THREE.js "flattens" every scene it renders, all that's needed is a change of perspective (relative to the main camera of the main scene) to maintain the illusion of depth in render targets. Here's a skeleton of something that would do that:
var scene = new THREE.Scene(),
rtScene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera( ..... ),
rtCamera = new THREE.PerspectiveCamera( ..... ),
rtCube = new THREE.Mesh(new THREE.CubeGeometry(1,1,1), new THREE.MeshSimpleMaterial({ color: 0x0000ff }),
rtTexture = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBFormat }),
material = new THREE.MeshBasicMaterial({ map: rtTexture }),
cube = new THREE.Mesh(new THREE.CubeGeometry(1,1,1), material);
function init() {
//manipulate cameras
//add any textures, lighting, etc
rtScene.add( rtCube );
scene.add( cube );
}
function update() {
//some function of cube.rotation & cube.position
//that changes the rtCamera rotation & position,
//depending on the desired effect.
}
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
renderer.clear();
update();
renderer.render( rtScene, rtCamera, rtTexture, true );
renderer.render( scene, camera );
}
init();
animate();
I assumed in my code that camera would remain stationary while cube rotates around the y axis. Each face has its own updating instance of material. The update() function for each face is a bunch of trigonometric gibberish that can be derived easily with the law of cosines. I will post a jsFiddle example as soon as I have my local working properly.

WebGL & Three.js - Render a scene successively in two renderers

I'm trying to render a scene in two different renderers (successively not at the same time) but it leads to the error "GL_INVALID_OPERATION".
Here is a sample script:
var scene1 = new THREE.Scene();
var camera1 = new THREE.PerspectiveCamera( ... );
var renderer1= new THREE.WebGLRenderer( ... );
var renderer2= new THREE.WebGLRenderer( ... );
var camera2 = new THREE.PerspectiveCamera( ... );
//Render scene1 in renderer1
renderer1.render( scene1, camera1 );
//[After some user event...]
//Render scene1 in renderer2
renderer2.render( scene1, camera2 ); //This fails. getError()=1282 (i.e. GL_INVALID_OPERATION)
I know it often deprecated to render a scene in two different renderers even not at the same time, but I could think of no other way of solving my issue as it is part of a very big project.
I understand there are GL data associated to scene1 that are linked to renderer1 but how can I remove those data so that I could render the scene1 again in an other renderer ???
Beware that I am not trying to render the scene in the two renderes simutaneously (which is different than https://github.com/mrdoob/three.js/issues/189).
Thanks for the help.
Regards.
The problem was related to objects/material/textures being bound to specific OpenGL buffers. The solution is thus to unbind from any buffer all the children of the object to be removed from a specific scene, before adding it to an other scene.
I'll post the code of my solution asap.
Regards.

How do I use texture on a sphere in three.js

I've downloaded a sphere example from: http://aerotwist.com/lab/getting-started-with-three-js/ and I can see the nice red sphere. I'd like to use a texture on it. I've tried this:
var texture = THREE.ImageUtils.loadTexture("ball-texture.jpg");
texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;
texture.repeat.set( 125, 125 );
texture.offset.set( 15, 15 );
texture.needsUpdate = true;
var sphereMaterial = new THREE.MeshBasicMaterial( { map: texture } );
var sphere = new THREE.Mesh(new THREE.Sphere(radius, segments, rings),sphereMaterial);
but I can't see anything, all is black. Does anyone have a working example for sphere texture?
You might have two problems.
First, try loading it like this:
var texture = THREE.ImageUtils.loadTexture('ball-texture.jpg', {}, function() {
renderer.render(scene, camera);
});
texture.needsUpdate = true;
Make sure that the texture size is a power of two (512x512px for IE).
Are you using Firefox? This could be a problem in your browser. Firefox uses some kind of cross-site-blocker for textures. The result is black instead. Take a look at this site for more info: http://hacks.mozilla.org/2011/06/cross-domain-webgl-textures-disabled-in-firefox-5/
Do you have a rendering loop, or did you render the scene just once?
You need to have a rendering loop so that when the THREE.ImageUtils loads the image and updates the texture, you re-render the scene with the now updated texture.
All the three.js examples seem to rely on this technique. I.e., Fire off several async operations involving a fetch of a remote resource, start rendering loop, let scene be updated as remote resources arrive.
IMHO this is Three.js's biggest gotcha for Javascript newbs (like me) who are not familiar with how async operations work.
I had this problem, but if you are loading the html as a file (i.e. locally not a webserver), many browsers (chrome for e.g.) will not allow you to load images in the standard three.js way as it is a security violation.

Categories

Resources