Three.JS - GLTF model loads slow. How to speed up load time? - javascript

Im loading a GLTF model (9mb) into ThreeJS. It's definitely loading slow. It takes about 4-5 seconds to load on my PC and about 11 seconds to load on my IPhone. How can i speed up the rendering times? My PC and IPhone load examples from the ThreeJS website faster than my project. My project has only one object being loaded so I feel like it should load faster than the examples on ThreeJS website.
My example project is located here # http://flowolfsworld.com/
Code
var ourObj;
var ourObj2;
// Instantiate a loader
var loader = new THREE.GLTFLoader();
// Optional: Provide a DRACOLoader instance to decode compressed mesh data
var dracoLoader = new THREE.DRACOLoader();
dracoLoader.setDecoderPath( '/js/draco/' );
loader.setDRACOLoader( dracoLoader );
let scene, camera, renderer, stars, starGeo;
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000)
camera.position.z = 25;
//renderer = new THREE.WebGLRenderer();
renderer = new THREE.WebGLRenderer();
renderer.setClearColor("#000000");
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
starGeo = new THREE.Geometry();
for(let i=0;i<6000;i++) {
star = new THREE.Vector3(
Math.random() * 600 - 300,
Math.random() * 600 - 300,
Math.random() * 600 - 300
);
star.velocity = 0;
star.acceleration = 0.02;
starGeo.vertices.push(star);
}
let sprite = new THREE.TextureLoader().load( 'star.png' );
let starMaterial = new THREE.PointsMaterial({
color: 0xaaaaaa,
size: 0.7,
map: sprite
});
stars = new THREE.Points(starGeo,starMaterial);
scene.add(stars);
// window.addEventListener("resize", onWindowResize, false);
var hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444 );
hemiLight.position.set( 0, 300, 0 );
scene.add( hemiLight );
var dirLight = new THREE.DirectionalLight( 0xffffff );
dirLight.position.set( 75, 300, -75 );
scene.add( dirLight );
loader.load(
// resource URL
'objs/dracowolf.gltf',
// called when the resource is loaded
function ( gltf ) {
scene.add( gltf.scene );
ourObj = gltf.scene;
animate();
},
// called while loading is progressing
function ( xhr ) {
console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
},
// called when loading has errors
function ( error ) {
console.log( 'An error happened' );
}
);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
if(ourObj){
ourObj.rotation.y -= .01;
}
starGeo.vertices.forEach(p => {
p.velocity += p.acceleration
p.y -= p.velocity;
if (p.y < -200) {
p.y = 200;
p.velocity = 0;
}
});
starGeo.verticesNeedUpdate = true;
stars.rotation.y +=0.002;
}
init();

A few suggestions on this particular model:
Use .glb, not .gltf. The binary form of glTF will be 25-30% smaller than a .gltf with embedded binary data, and doesn't have to be decoded from a Data URI. Using .gltf with a separate binary .bin is also an option. Use glTF-Pipeline to make these changes.
Preload the Draco decoder by calling dracoLoader.preload() before your model starts loading. On my test of your page, that would save 500ms spent fetching the decoder after the model has already been downloaded.
Consider using https://github.com/zeux/meshoptimizer#installing-gltfpack to simplify the model, or at least to quantize it, and then gzip it. This is an alternative to Draco, and may not compress the file quite as well, but can sometimes decrease overall loading time despite that.

Related

Three Js GLTF loader model not showing up

I am trying to use Three js to load in a 3d heart model and attach a picture to the front of the heart but it seems the heart isn’t showing up at all even without loading the image in. I am new at Three js so I might be doing it all wrong but I tried using the code straight from the documents and it still isn’t working. I am getting no errors and I can see AxesHelper also I have loaded in a cube and that works so I don't think there is a problem with my scene.
function handleHeart(img) {
document.getElementById("divRight").innerHTML = ""
let renderer = new THREE.WebGLRenderer();
document.getElementById("divRight").appendChild(renderer.domElement);
let w = document.getElementById("divRight").clientWidth
let h = 600
renderer.setSize( w, h)
let camera = new THREE.PerspectiveCamera(35, w / h, 0.1, 3000 );
const controls = new THREE.OrbitControls( camera, renderer.domElement );
camera.position.set( 0, 0, 10 );
camera.lookAt(new THREE.Vector3(0, 0, 0))
controls.update();
let scene = new THREE.Scene();
scene.background = new THREE.Color( 'grey' );
light = new THREE.AmbientLight(0xffffff);
scene.add(light);
const loader = new THREE.GLTFLoader();
loader.load(
// resource URL
'models/heart_v1.glb',
// called when the resource is loaded
function ( gltf ) {
let material = new THREE.MeshBasicMaterial( { map: img } );
let model = gltf.scene || gltf.scenes[0];
//model.scale.set(1000,1000,1000)
model.material = material
scene.add(model)
model.position.z = -10
},
// called while loading is progressing
function ( xhr ) {
console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
},
// called when loading has errors
function ( error ) {
console.log(error)
console.log( 'An error happened' );
})
scene.add(new THREE.AxesHelper(100))
renderer.render(scene, camera)
}
here is a replit : https://repl.it/#AlecStein44/Threejs-help#javascript.js
model.material = material
This line is incorrect. It should be:
model.traverse( function( object ) {
if ( object.isMesh ) object.material = material;
} );
Notice that applying a texture will still not work since your model heart_v1.glb has no texture coordinates.

How to use three.js with React

I am trying to use three.js in a React application however even the basic sample from the three.js docs fails to load on the canvas.
I first made a vanilla.js sandbox implementation with the sample which works fine. Then I ported it over to create a react+three.js minimal sandbox implementation which fails to work.
Can anyone have a look at it and point me in the right direction ?
class Viewer extends Component {
state = {};
scene = null;
camera = null;
renderer = new WebGLRenderer();
inst = 0;
viewerRef = React.createRef();
componentDidMount() {
const { domElement } = this.renderer;
this.scene = new Scene();
this.scene.background = new Color("#ccc");
this.camera = new Camera(
75,
domElement.innerWidth / domElement.innerHeight,
0.1,
1000
);
this.renderer.setSize(domElement.innerWidth, domElement.innerHeight);
this.viewerRef.current.appendChild(this.renderer.domElement);
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshBasicMaterial({ color: 0x05ff00 });
const cube = new Mesh(geometry, material);
this.scene.add(cube);
this.camera.position.z = 5;
this.display();
}
display = () => {
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.display);
};
render = () => <div className="viewer" ref={this.viewerRef} />;
}
Here is CodeSandBox that works with a basic React wrapper code for Three.js. It also has THREE.OrbitControls integration and scale on resize code:
https://codesandbox.io/s/github/supromikali/react-three-demo
Live demo: https://31yp61zxq6.codesandbox.io/
Here is a full code snippet in the case above listed links don't work for you:
index.js code
import React, { Component } from "react";
import ReactDOM from "react-dom";
import THREE from "./three";
class App extends Component {
componentDidMount() {
// BASIC THREE.JS THINGS: SCENE, CAMERA, RENDERER
// Three.js Creating a scene tutorial
// https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
// MOUNT INSIDE OF REACT
this.mount.appendChild(renderer.domElement); // mount a scene inside of React using a ref
// CAMERA CONTROLS
// https://threejs.org/docs/index.html#examples/controls/OrbitControls
this.controls = new THREE.OrbitControls(camera);
// ADD CUBE AND LIGHTS
// https://threejs.org/docs/index.html#api/en/geometries/BoxGeometry
// https://threejs.org/docs/scenes/geometry-browser.html#BoxGeometry
var geometry = new THREE.BoxGeometry(2, 2, 2);
var material = new THREE.MeshPhongMaterial( {
color: 0x156289,
emissive: 0x072534,
side: THREE.DoubleSide,
flatShading: true
} );
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
var lights = [];
lights[ 0 ] = new THREE.PointLight( 0xffffff, 1, 0 );
lights[ 1 ] = new THREE.PointLight( 0xffffff, 1, 0 );
lights[ 2 ] = new THREE.PointLight( 0xffffff, 1, 0 );
lights[ 0 ].position.set( 0, 200, 0 );
lights[ 1 ].position.set( 100, 200, 100 );
lights[ 2 ].position.set( - 100, - 200, - 100 );
scene.add( lights[ 0 ] );
scene.add( lights[ 1 ] );
scene.add( lights[ 2 ] );
// SCALE ON RESIZE
// Check "How can scene scale be preserved on resize?" section of Three.js FAQ
// https://threejs.org/docs/index.html#manual/en/introduction/FAQ
// code below is taken from Three.js fiddle
// http://jsfiddle.net/Q4Jpu/
// remember these initial values
var tanFOV = Math.tan( ( ( Math.PI / 180 ) * camera.fov / 2 ) );
var windowHeight = window.innerHeight;
window.addEventListener( 'resize', onWindowResize, false );
function onWindowResize( event ) {
camera.aspect = window.innerWidth / window.innerHeight;
// adjust the FOV
camera.fov = ( 360 / Math.PI ) * Math.atan( tanFOV * ( window.innerHeight / windowHeight ) );
camera.updateProjectionMatrix();
camera.lookAt( scene.position );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.render( scene, camera );
}
// ANIMATE THE SCENE
var animate = function() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
}
render() {
return <div ref={ref => (this.mount = ref)} />;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
three.js code (THREE.OrbitControls import)
import * as THREE from 'three';
window.THREE = THREE; // THREE.OrbitControls expects THREE to be a global object
require('three/examples/js/controls/OrbitControls');
export default {...THREE, OrbitControls: window.THREE.OrbitControls};
Result should look like this:
I am not sure why nobody has mentioned react-three-fiber. It's a React renderer for ThreeJS.
Best part is, using rollupjs, we can even tree-shake a lot of unnecessary code, that we won't use in our React ThreeJS app. Also, using react-three-fiber in a desired way, we can achieve 60FPS for our ThreeJS animations 🥰
To learn basics, just take a look into react-three-fiber Examples
There's a few issues with your sandbox at the moment, which aren't necessarily related to React.
innerWidth and innerHeight are not defined. clientWidth and clientHeight are probably the fields you're looking for to get the dimensions of the dom element.
The dimensions of the canvas are being read before it has been added into the document, meaning the dimensions of the element will always be 0 0. Insert the element first before computing the camera aspect ratio and setting the render size.
Camera isn't intended to be used directly. The docs specify it as an abstract base class for cameras -- use PerspectiveCamera instead.
Here's a sample of the code with the above fixes
const { domElement } = this.renderer;
// Fix 1
this.viewerRef.current.appendChild(domElement);
// Fix 2
const width = domElement.clientWidth;
const height = domElement.clientHeight;
this.scene = new Scene();
this.scene.background = new Color("#ccc");
// Fix 3
this.camera = new PerspectiveCamera(
75,
width / height,
0.1,
1000
);
this.renderer.setSize(width, height);
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshBasicMaterial({ color: 0x05ff00 });
const cube = new Mesh(geometry, material);
this.scene.add(cube);
this.camera.position.z = 5;
this.display();
Hope that helps!

Use custom mesh instead of generated one in three.js

I have just discovered the world of three.js and it's amazing.
I downloaded the examples, and started checking some of them.
I have never been coding in JavaScript, so I was wondering if somebody could help me with editing one of the example files (misc_controls_trackball.html). Instead of generated geometry (mesh.position.x = ( Math.random() - 0.5 ) ...) I was wondering if I could just include an already made mesh (from 3 studio max for example)?
I think this is the part of the code which generates the mesh:
// world
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 );
var geometry = new THREE.CylinderGeometry( 0, 10, 30, 4, 1 );
var material = new THREE.MeshLambertMaterial( { color:0xffffff, shading: THREE.FlatShading } );
for ( var i = 0; i < 500; i ++ ) {
var mesh = new THREE.Mesh( geometry, material );
mesh.position.x = ( Math.random() - 0.5 ) * 1000;
mesh.position.y = ( Math.random() - 0.5 ) * 1000;
mesh.position.z = ( Math.random() - 0.5 ) * 1000;
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
scene.add( mesh );
}
In what way should this be changed, so that I could import my external mesh (in form of .3ds, .obj, .dae, does not matter)?
Thank you.
Here is the misc_controls_trackball.html example file along with "js" folder.
Tried this?
http://threejs.org/examples/webgl_loader_collada
It`s an example for Collada, but for the other formats the concept is the same, just using a different loader.
var loader = new THREE.ColladaLoader();
// Depending on how you created your model, you may need to
loader.options.convertUpAxis = true;
// Then load it:
loader.load( './models/collada/monster/monster.dae', function ( collada ) {
// All this will happen asynchronously
dae = collada.scene;
// Before displaying it, you can tweak it as necessary
dae.scale.x = dae.scale.y = dae.scale.z = 0.002;
dae.updateMatrix();
scene.add(dae);
// At the next frame, you`ll have your model loaded.
} );
EDIT, additions
First you need the links to the proper libraries, including the ColladaLoader
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r69/three.js"></script>
<script src="js/loaders/ColladaLoader.js"></script>
Then a number of things needed fixing in the code.
- scene object was missing
- Model loaded, but to be scaled up a bit
- No call to render() in the animate function, so you had no animation.
- The fog statement was broken... Best spending some time on the basics, first...
function init() {
// Create your scene first
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 500;
controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
controls.keys = [ 65, 83, 68 ];
controls.addEventListener( 'change', render );
// world
var loader = new THREE.ColladaLoader();
// Depending on how you created your model, you may need to
loader.options.convertUpAxis = true;
// Then load it:
//loader.load( './models/collada/monster/monster.dae', function ( collada ) {
loader.load( 'models/monster.dae', function ( collada ) {
// All this will happen asynchronously
dae = collada.scene;
// Give it a decent scale
dae.scale.x = dae.scale.y = dae.scale.z = 1;
dae.updateMatrix();
scene.add(dae);
// At the next frame, you`ll have your model loaded.
} );
// lights
light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
light = new THREE.DirectionalLight( 0x002288 );
light.position.set( -1, -1, -1 );
scene.add( light );
light = new THREE.AmbientLight( 0x222222 );
scene.add( light );
// renderer
renderer = new THREE.WebGLRenderer( { antialias: false } );
//renderer.setClearColor( scene.fog.color, 1 );
renderer.setSize( window.innerWidth, window.innerHeight );
container = document.getElementById( 'container' );
container.appendChild( renderer.domElement );
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
stats.domElement.style.zIndex = 100;
container.appendChild( stats.domElement );
//
window.addEventListener( 'resize', onWindowResize, false );
// The following is not necessary at this stage, as you`ll call it
// from animate later down (if you want to do an animation, of course,
// which I guess you do)
render();
}
And the animate function should look like this
function animate() {
requestAnimationFrame( animate );
controls.update();
render();
}
Hope that helps! :)

WebGl - not loaded texture

I receive an error: Uncaught SecurityError: Failed to execute 'texImage2D' on 'WebGLRenderingContext': The cross-origin image at file:///C:/Users/.../img.jpg may not be loaded.
var scene, camera, renderer;
var geometry, material, mesh;
init();
animate();
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 1000;
geometry = new THREE.CubeGeometry( 300, 300, 300 );
material = new THREE.MeshLambertMaterial({
map: THREE.ImageUtils.loadTexture('img.jpg')
});
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColorHex( 0xFFF8DC, 1 );
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
mesh.rotation.x += 0.02;
mesh.rotation.y += 0.01;
renderer.render( scene, camera );
}
I run a simple static content server called mongoose locally on my development machine. Simply point it to the folder that contains your project and you can load the page without worrying about any cross site scripting/origin issues,
You can use a converter to binary code image http://codepen.io/anon/pen/pHKyw physical file to avoid problems.
or you can upload one server, this failure happens because you're not running on a server and the file can not be found.
for example:
original
map: THREE.ImageUtils.loadTexture('img.jpg')
replace
map: THREE.ImageUtils.loadTexture('data:image/png;base64,iVBORw0KGgoAAAANSU ... ')

Rotating .obj file OBJMTLLoader three.js

I used the OBJMTLLoader class for one obj file and rotation worked well around a fixed point on the object by using object.rotation.y -= 0.5. Using the same code (minus changing the camera position), I replaced the .obj file with another and the rotation is now going in a circular motion, like around the camera instead of staying in place. Any idea why when I used the same code?
Thanks
EDIT:
var OBJLoaded;
function init()
{
container = document.getElementById('player');
camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 1, 2000);
camera.position.x = 110;
camera.position.z = -160;
camera.position.y = 15;
// camera.position.z = 40;
// camera.position.y = 2;
//scene
scene = new THREE.Scene();
var ambient = new THREE.AmbientLight( 0x444444 );
scene.add( ambient );
var directionalLight = new THREE.DirectionalLight( 0xffffff );
directionalLight.position.set( 100, 90, 200 );
scene.add( directionalLight );
//model
var loader = new THREE.OBJMTLLoader();
//loader.load('./assets/Stereo_LowPoly.obj', './assets/Stereo_LowPoly.mtl', function(object)
loader.load('./assets/studio_beats.obj', './assets/studio_beats.mtl', function(object)
{
OBJLoaded = object;
console.log(object);
scene.add( object );
});
renderer = new THREE.WebGLRenderer({alpha: true});
renderer.setClearColor(0x000000, 0);
renderer.setSize($('#player').width(), $('#player').height());
container.appendChild(renderer.domElement);
scene.add(camera);
}
function animateBoombox()
{
requestAnimationFrame(animateBoombox);
render();
}
function render()
{
var rotSpeed = 0.004;
if (OBJLoaded)
{
OBJLoaded.rotation.y -= rotSpeed;
}
renderer.render(scene, camera);
}
The parts commented (camera and object load) is for the previous object that was loaded. That works fine, but the uncommented partdoes not work the same.
The object you loaded has a pivot point which came from the model creater software... You need to change the pivot point of the object before you load it with three.js.
If you cannot, you should do it like i had in loader callback:
var loader = new THREE.OBJMTLLoader();
loader.load('your_file.obj', 'your_file.mtl', function (object) {
object.traverse(function (child) {
child.centroid = new THREE.Vector3();
for (var i = 0, l = child.geometry.vertices.length; i < l; i++) {
child.centroid.add(child.geometry.vertices[i].clone());
}
child.centroid.divideScalar(child.geometry.vertices.length);
var offset = child.centroid.clone();
child.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-offset.x, -offset.y, -offset.z));
child.position.copy(child.centroid);
child.geometry.computeBoundingBox();
});
});
Then rotate your object...

Categories

Resources