Improve Three.js performance with SSR (Node,js next,js) - three.js

I'm trying to find best solution to improve my website with three.js performance.
I would like to ask someone who have tried three.js with Server Side Rendering and if the
performance improve.
My three.js project include 5 glb files in the scene. Each file is about 1MB.
So far I improved.
Speed Index First
Meaningful Paint
by using webpack
** minify and include all javascript into one file.
** use next generation image wep for all textures.
** able gzip to my sever
Also, to improve loading speed instead of load all 5 glb models at the beginning,
load it 1 or two first, then load rest of models when user needed.
To reduce fan sound( the CPU cause)
I did animate render only user interact
window.addEventListener("wheel", function(e) {
animate();
}, true);
var scene, renderer, camera, model, mixer;
function init() {
var container = document.getElementById( 'container' );
camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 1, 2, - 3 );
camera.lookAt( 0, 1, 0 );
clock = new THREE.Clock();
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xa0a0a0 );
scene.fog = new THREE.Fog( 0xa0a0a0, 10, 50 );
var hemiLight = new THREE.HemisphereLight( 0xffffff, 'rgb(100,100,100)' );
hemiLight.position.set( 0, 20, 0 );
scene.add( hemiLight );
var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2000, 2000 ), new THREE.MeshPhongMaterial( { color: 0x999999, depthWrite: false } ) );
mesh.rotation.x = - Math.PI / 2;
mesh.receiveShadow = true;
scene.add( mesh );
var grid = new THREE.GridHelper( 2000, 20, 0x000000, 0x000000 );
grid.material.opacity = 0.2;
grid.material.transparent = true;
scene.add( grid );
var textload = new THREE.TextureLoader();
var text = textload.load( require('./material/text.jpg') );
//text.jpg = 80kb;
text.flipY = false;
var textloadbbump = new THREE.TextureLoader();
var textbump = textloadbbump.load( require('./material/textbump.jpg') );
//textbump.jpg = 50kb;
textbump.flipY = false;
var loader = new GLTFLoader();
//character.glb is 1.1MB
loader.load( require('./material/character.glb'), function ( gltf ) {
model = gltf.scene;
model.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material.roughness = 1;
if(child.name=='body'){
child.material.map = text;
child.material.bumpMap = textbump;
child.material.bumpScale = 0.001;
}
}});
scene.add( model );
mixer = new THREE.AnimationMixer( model );
mixer.clipAction( gltf.animations[ 0 ] ).play();
});
renderer = new THREE.WebGLRenderer( {
// antialias: true
powerPreference: 'low-power',
//precision: 'lowp'
} );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMap.enabled = false;
container.appendChild( renderer.domElement );
}
window.onresize = function () {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
};
init();
window.addEventListener("wheel", function(e) {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}, true);

Related

Applying postprocessing steps for specific objects

Based on this example i try to create a scene where several objects get the bloom, and other objects dont.
The white cube in the middle is supposed to be just white (without the bloom)
I'm confused on how to get the result that i want. I tried for example adding a 2nd scene with the white cube but it seems i cant get the order right. Maybe my approch with different scenes is wrong?
Whats the "best" way to achieve this behaviour? I always end up just seeing one scene, just the white cube or the 4 colored ones. (example below shows everything atm)
myFiddle: https://jsfiddle.net/qwertasyx/8qw3ys4z/16/
var scene,scene2,camera, controls, pointLight, stats;
var composer, renderer, mixer;
var params = {
exposure: 1,
bloomStrength: 1.5,
bloomThreshold: 0,
bloomRadius: 0
};
var objects = [];
var clock = new THREE.Clock();
var container = document.getElementById( 'container' );
stats = new Stats();
//container.appendChild( stats.dom );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.toneMapping = THREE.ReinhardToneMapping;
container.appendChild( renderer.domElement );
scene = new THREE.Scene();
//scene2 = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 100 );
camera.position.set( 2.5,2.5, 10 );
scene.add( camera );
// scene2.add( camera );
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.maxPolarAngle = Math.PI * 0.5;
controls.minDistance = 1;
controls.maxDistance = 10;
controls.target.set(2.5,2.5,0)
controls.update()
// scene.add( new THREE.AmbientLight( 0x404040 ) );
pointLight = new THREE.PointLight( 0xffffff, 1 );
// camera.add( pointLight );
var renderScene = new THREE.RenderPass( scene, camera );
//var renderScene2 = new THREE.RenderPass( scene2, camera );
var bloomPass = new THREE.UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 1.5, 0.4, 0.85 );
bloomPass.renderToScreen = true;
bloomPass.threshold = params.bloomThreshold;
bloomPass.strength = params.bloomStrength;
bloomPass.radius = params.bloomRadius;
composer = new THREE.EffectComposer( renderer );
composer.setSize( window.innerWidth, window.innerHeight );
composer.addPass( renderScene );
composer.addPass( bloomPass );
//composer.addPass( renderScene2 );
//objects
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var material = new THREE.MeshBasicMaterial( { color: 0xffff00 } );
var cube = new THREE.Mesh( geometry, material );
cube.vrz = 0.01;
cube.position.x += 5
scene.add( cube );
objects.push(cube)
var material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
var cube = new THREE.Mesh( geometry, material );
cube.vrz = 0.01;
cube.position.x += 5
cube.position.y += 5
scene.add( cube );
objects.push(cube)
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
var cube = new THREE.Mesh( geometry, material );
cube.vrz = 0.01;
cube.position.y += 5
scene.add( cube );
objects.push(cube)
var material = new THREE.MeshBasicMaterial( { color: 0x0000ff } );
var cube = new THREE.Mesh( geometry, material );
cube.vrz = 0.01;
scene.add( cube );
objects.push(cube)
// cube thats supposed to be not bloomy
var material = new THREE.MeshBasicMaterial( { color: 0xffffff } );
var cube = new THREE.Mesh( geometry, material );
cube.vrz = 0.01;
cube.position.y += 2.5
cube.position.x += 2.5
scene.add( cube );
objects.push(cube)
var gui = new dat.GUI();
gui.add( params, 'exposure', 0.1, 2 ).onChange( function ( value ) {
renderer.toneMappingExposure = Math.pow( value, 4.0 );
} );
gui.add( params, 'bloomThreshold', 0.0, 1.0 ).onChange( function ( value ) {
bloomPass.threshold = Number( value );
} );
gui.add( params, 'bloomStrength', 0.0, 3.0 ).onChange( function ( value ) {
bloomPass.strength = Number( value );
} );
gui.add( params, 'bloomRadius', 0.0, 1.0 ).step( 0.01 ).onChange( function ( value ) {
bloomPass.radius = Number( value );
} );
window.onresize = function () {
var width = window.innerWidth;
var height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize( width, height );
composer.setSize( width, height );
};
function animate() {
requestAnimationFrame( animate );
objects.forEach(function(obj){
obj.rotation.z += obj.vrz;
});
stats.update();
composer.render();
}
animate();
I had a similar problem once. An example from this comment helped me.
Note that in that example there are 2 scenes and 2 composers (the final composer gets output of the previous composer as its input)
ppoFinal.blendPass.uniforms.tAdd.value = ppoRGB.composer.renderTarget2.texture;
and render() is called on both composers.
ppoRGB.composer.render();
ppoFinal.composer.render();
This pattern allows you to apply postprocessing effects selectively and it works well. The problem is the scalability of the method and probably performance. Because when you want to apply another object with yet different effect, you need to introduce 3rd scene and 3rd composer. For my little project in the past I ended up with 4 scenes and 4 composers...

Animating a line in Three.js with lineTo and curveTo

I'm a three.js newbie and I'm coming from Processing/p5.js (so I'm a bit spoiled on animating loops). I'm trying to create something like this simple stretching pill shape:
Stretching pill shape
This is what I cobbled together from some things I found online. I just have the 'O' shape. I'm trying to get the variable oHeight to be the variable that causes the fluxing back and forth with a Math.sin.
Do I need to update the path? Or the bufferGeometry? Or the THREE.path?
Sorry this code is so messy. Just starting out!
var camera, scene, renderer;
var curve;
var path;
var oHeight = 0;
var delta = 0;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 600;
scene = new THREE.Scene();
path = new THREE.Path();
path.lineTo( 0, 0 );
path.quadraticCurveTo( 0, 20, 20, 20 );
path.lineTo( 40, 20 );
path.quadraticCurveTo( 60,20, 60,0);
path.lineTo(60,-40-oHeight);
path.quadraticCurveTo( 60,-60-oHeight, 40,-60-oHeight);
path.lineTo(20,-60-oHeight);
path.quadraticCurveTo(0,-60,0,-40-oHeight);
path.lineTo(0,0);
var points = path.getPoints();
var geometry = new THREE.BufferGeometry().setFromPoints( points );
var material = new THREE.LineBasicMaterial( { color: 0xffffff } );
curve = new THREE.Line( geometry, material );
scene.add( curve );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
delta +=.1;
oHeight = Math.sin(delta)*20;
line.needUpdate = true; //is this where I went wrong?
renderer.render( scene, camera );
}
There is no object line in your code and there is no mystic reference from the object geometry to the oHeight. The THREE.Path is only a temporary object, which contains the information, that is needed to create the BufferGeoemtry. Note, at the end the vertices of the geometry are stored in an array buffer on the GPU.
You have to create the path in the animate function and to set it to the BufferGeoemtry. So the geometry is recreated in every frame:
function animate() {
requestAnimationFrame( animate );
delta +=.1;
oHeight = Math.sin(delta)*20;
path = new THREE.Path();
path.lineTo( 0, 0 );
path.quadraticCurveTo( 0, 20, 20, 20 );
path.lineTo( 40, 20 );
path.quadraticCurveTo( 60,20, 60,0);
path.lineTo(60,-40-oHeight);
path.quadraticCurveTo( 60,-60-oHeight, 40,-60-oHeight);
path.lineTo(20,-60-oHeight);
path.quadraticCurveTo(0,-60-oHeight,0,-40-oHeight);
path.lineTo(0,0);
geometry.dispose();
geometry.setFromPoints( path.getPoints() );
renderer.render( scene, camera );
}
var camera, scene, renderer;
var curve;
var path;
var oHeight = 0;
var delta = 0;
var geometry;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 600;
scene = new THREE.Scene();
geometry = new THREE.BufferGeometry();
var material = new THREE.LineBasicMaterial( { color: 0xffffff } );
curve = new THREE.Line( geometry, material );
scene.add( curve );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
window.onresize = resize;
}
function animate() {
requestAnimationFrame( animate );
delta +=.1;
oHeight = Math.sin(delta)*20;
path = new THREE.Path();
path.lineTo( 0, 0 );
path.quadraticCurveTo( 0, 20, 20, 20 );
path.lineTo( 40, 20 );
path.quadraticCurveTo( 60,20, 60,0);
path.lineTo(60,-40-oHeight);
path.quadraticCurveTo( 60,-60-oHeight, 40,-60-oHeight);
path.lineTo(20,-60-oHeight);
path.quadraticCurveTo(0,-60-oHeight,0,-40-oHeight);
path.lineTo(0,0);
geometry.dispose();
geometry.setFromPoints( path.getPoints() );
renderer.render( scene, camera );
}
function resize() {
var aspect = window.innerWidth / window.innerHeight;
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = aspect;
camera.updateProjectionMatrix();
}
<script src="https://threejs.org/build/three.min.js"></script>

Detecting initial display

I'm loading eight 8192 x 4096 textures on init. Naturally there's a delay prior to display as the textures load. Is there a way for me to detect when first display has taken place? With the example below, I'd like onDocumentMouseDown to fire only when I can see the textures - not while the screen is still blank as loading takes place.
function init() {
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var sphereGeometry = new THREE.SphereBufferGeometry(500, 100, 100);
sphereGeometry.scale(-1, 1, 1);
var renderList = ["DF_DC_DS", "DF_DC_LS", "DF_LC_DS", "DF_LC_LS", "LF_DC_DS", "LF_DC_LS", "LF_LC_DS", "LF_LC_LS"];
for (var i = 0; i < renderList.length; i++) {
var texture = new THREE.TextureLoader().load( "img/high-quality/" + renderList[i] + ".jpg",
function ( texture ) {
textureLoadedCount++;
if (textureLoadedCount == 8) {
allLoaded = true;
}
}
);
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
var material = new THREE.MeshBasicMaterial( { map: texture } );
var mesh = new THREE.Mesh( sphereGeometry, material );
scene.add( mesh );
}
document.addEventListener( "mousedown", onDocumentMouseDown, false );
}

How can I make three.js update a scene after adding an object or group of objects?

I am fairly new to three.js and have a problem I can't readily find an answer for.
Here is a codepen that should sum up the situation: http://codepen.io/anon/pen/PPYPzO
var container, stats;
var camera, controls, scene, renderer, raycaster, mouse;
init();
animate();
add_world();
var indie_render = true;
for(var j = 0; j < 20; j++){
add_objects(20);
indie_render = !indie_render;
console.log("adding more objects...");
if(!indie_render){render();}
}
function add_world(){
var geometry = new THREE.BoxGeometry( 1000, 1000, 1000);
var mesh = new THREE.MeshBasicMaterial( {color: 0xf5f5dc, wireframe: false, opacity: 0.2, transparent:true } );
var world = new THREE.Mesh( geometry, mesh );
scene.add( world );
render();
}
function add_objects(num, indiv){
var geometry = new THREE.SphereGeometry( 5, 32,32 );
var material = new THREE.MeshBasicMaterial( { shading: THREE.FlatShading } );
material.color.setRGB( Math.random(), Math.random(), Math.random() );
for ( var i = 0; i < num; 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 );
if(indie_render){
console.log("individual render");
render();
}
}
}
function init() {
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.set(500, 500, -1000);
camera.up.set( 0, 1, 0 );
camera.lookAt(500,500,500);
controls = new THREE.OrbitControls( camera );
controls.addEventListener( 'change', render );
//world
scene = new THREE.Scene();
// 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 = new THREE.WebGLRenderer( { antialias: false } );
renderer.setClearColor( 0x000000, 1 );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.sortObjects = false;
container = document.getElementById( 'container' );
container.appendChild( renderer.domElement );
mouse = new THREE.Vector2();
raycaster = new THREE.Raycaster();
container.addEventListener( 'mousemove', onMouseMove, false );
container.addEventListener( 'mousedown', onMouseDown, false );
window.addEventListener( 'resize', onWindowResize, false );
}
function animate() {
requestAnimationFrame( animate );
controls.update();
}
function render() {
renderer.render( scene, camera );
}
function onMouseMove( e ) {
mouse.x = ( e.clientX / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( e.clientY / renderer.domElement.height ) * 2 + 1;
}
function onMouseDown( e ) {
mouse.x = ( e.clientX / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( e.clientY / renderer.domElement.height ) * 2 + 1;
if(e.button == 2){ //right button
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children, true );
if ( intersects.length > 0 ) {
var geometry = new THREE.SphereGeometry( 5, 32,32 );
var material = new THREE.MeshBasicMaterial( { color:0xff0000, shading: THREE.FlatShading } );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.set(intersects[0].point.x, intersects[0].point.y, intersects[0].point.z);
scene.add(mesh);
render();
}
}
}
In this demo, I init() and animate() a blank scene, and then add a translucent cube, following what seems to be convention. Then I add groups of spheres to the scene in a nested for loop, randomly placing the spheres inside the cube and making a render() call after every scene.add() call.
Currently, the scene adds all the spheres and only then is visible to the user, even though I can add individual objects after the for-loop objects are added (by right-clicking on the cube). I need for the user to be able to watch as spheres are added, rather than waiting for the whole thing to be done.
I realize this may not be the most efficient way to render the scene, but it would be quite helpful if, for example, the info on the objects to be animated is arriving asynchronously from a server. Does anyone have a suggestion?
Thanks
1) First: move call render() to animate:
function animate() {
requestAnimationFrame( animate );
render();
controls.update();
}
2) Call add_objects asynchronously: setTimeout( add_objects, 0, 20 );
http://codepen.io/anon/pen/bVbEEP

Three.js ray object intersection

friends.
Here is my code. It should find intersection of ray and cube. But it does not work and makes me mad. This code is quite simple, but it is hard for me to find the error. Please, help.
jsFiddle
<script>
var container;
var camera, controls, scene, renderer;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 1000;
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
scene = new THREE.Scene();
var testObject_G = new THREE.CubeGeometry(100, 100, 100);
var testObject_M = new THREE.MeshBasicMaterial({ color: 0xBBBBBB });
var testObject_Mesh = new THREE.Mesh(testObject_G, testObject_M);
testObject_Mesh.position.x = 300;
scene.add(testObject_Mesh);
scene2 = new THREE.Object3D();
// rays
var direction = new THREE.Vector3(1, 0, 0);
direction.normalize();
var startPoint = new THREE.Vector3(0, 0, 0);
var ray = new THREE.Raycaster(startPoint, direction);
var rayIntersects = ray.intersectObjects(scene.children, true);
if (rayIntersects[0]) {
console.log(rayIntersects[0]);
var geometry = new THREE.CubeGeometry(10, 10, 10);
var material = new THREE.MeshBasicMaterial({color: 0xff0000});
var cube = new THREE.Mesh(geometry, material);
cube.position = rayIntersects[0].point;
scene2.add(cube);
}
var ray_G = new THREE.Geometry();
ray_G.vertices.push(new THREE.Vector3(0, 0, 0));
ray_G.vertices.push(direction.multiplyScalar(1000));
var ray_M = new THREE.LineBasicMaterial({ color: 0x000000 });
var ray_Mesh = new THREE.Line(ray_G, ray_M);
scene2.add(ray_Mesh);
scene.add(scene2);
// renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor(0xffffff, 1);
container = document.getElementById( 'container' );
container.appendChild( renderer.domElement );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
controls.handleResize();
render();
}
function animate() {
requestAnimationFrame( animate );
controls.update();
}
function render() {
renderer.render( scene, camera );
}
</script>
</body>
Thank you very much for replies.
During the render loop, three.js updates each object's transform matrix for you, based on your specified object.position, object.rotation/quaternion, and object.scale.
Since you are calling Raycaster.intersectObjects() before the first render call, you have to update the object matrices yourself prior to raycasting.
scene.updateMatrixWorld();
fiddle: http://jsfiddle.net/mzRtJ/5/
three.js r.64

Resources