Really poor performance in my scene

Hi all.

I’ve been struggling with the performance of my Three.js scene for a number of months. Tweaking here and there when I can but I can’t seem to improve the performance.

I’ve read somehwere that I could be rebuilding my geometry once per frame but if I am, I’m not sure how to rectify it.

The code is spread across several layers but the bulk of it is here:

	if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

	var container, stats;
	var camera, scene, renderer, plaquetext1, plaquetext2;
	var globalloader;
	var globalscene;
	var plaque_mesh;
	var plaquetext_mesh;
	var frame_mesh;
	var framebacking_mesh;
	var glob_materialframe;
	var glob_materialplaque;

	init();
	//animate();
				
	function init() {
		container = document.createElement( 'div' );

	  

		scene = new THREE.Scene();
		globalscene = scene;
		scene.background = new THREE.Color( 0xFFFFFF );
		

		
		camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 500 );
		// Z is up for objects on bed.
		camera.up.set( 0, 0, 1 );
		camera.position.set( 0, -10, 10 );
		camera.add( new THREE.PointLight( 0xffffff, 0.8 ) );
		scene.add( camera );

	 
		
		renderer = new THREE.WebGLRenderer( { antialias: true } );
		renderer.setPixelRatio( window.devicePixelRatio );
		renderer.setSize( window.innerWidth, window.innerHeight );
		renderer.shadowMap.enabled = true;
		renderer.shadowMap.type = THREE.PCFShadowMap;
		renderer.domElement.setAttribute("id", "display");
		document.body.appendChild( renderer.domElement );
					
		// Loader
		var loader = new THREE.STLLoader();
		globalloader = loader;

	   
		var materialframe = new THREE.MeshStandardMaterial( 
			{ 
				color: 0x222222, 
				//specular: 0x222222, 
				metalness: 75,
				roughness:75 
			} 
		);
		glob_materialframe = materialframe;

		var materialglass = new THREE.MeshPhongMaterial( 
			{
				color: 0xffffff, 
				specular: 0x222222, 
				shininess: 200, 
				opacity: 0.1, 
				transparent: true, 
				reflectivity: true, 
				metalness:0.1 
			} 
		);
		
		var materialplaque = new THREE.MeshBasicMaterial( 
			{ 
				color: 0xd4af37, 
				//specular: 0x222222, 
				//shininess: 200, 
				//opacity: 1, 
				//transparent: false, 
				//reflectivity: true, 
				//metalness:.7 
			} 
		);
		glob_materialplaque = materialplaque;				
					
		var bottomColor = new THREE.Color( 0xFF0000 );
		var topColor = new THREE.Color( 0x00FF00);

		
	  

		  //Create a frame shape..
		  
		  function frame()	{
		  var frameshape = new THREE.Shape();
		  frameshape.moveTo(-4, -4);
		  frameshape.lineTo( 4, -4);
		  frameshape.lineTo( 4,  4);
		  frameshape.lineTo(-4,  4);
		  
		  //..with a hole:
		  var hole = new THREE.Path();
		  hole.moveTo(-3.25, -3.25);
		  hole.lineTo( 3.25, -3.25);
		  hole.lineTo( 3.25,  3.25);
		  hole.lineTo(-3.25,  3.25);
		  frameshape.holes.push(hole);
		  
		  
		  var frameback = new THREE.Shape();
		  frameback.moveTo(-4, -4);
		  frameback.lineTo( 4, -4);
		  frameback.lineTo( 4,  4);
		  frameback.lineTo(-4,  4);
		  
		  
		  //Extrude the shape into a geometry, and create a mesh from it:
		  var extrudeSettings = {
			  steps: 1,
			  depth: 1.5,
			  bevelEnabled: false,
		  };
		  
		   var extrudeSettingsFrameBack = {
			  steps: 1,
			  depth: 0.05,
			  bevelEnabled: false,
		  };
		  var geom = new THREE.ExtrudeGeometry(frameshape, extrudeSettings);
		  var geomback = new THREE.ExtrudeGeometry(frameback, extrudeSettingsFrameBack);
		  var frame = new THREE.Mesh(geom, new THREE.MeshPhongMaterial({ color: 0x222222 }));
		  var framebacking = new THREE.Mesh(geomback, new THREE.MeshPhongMaterial({ color: 0x222222 }));
		  
		  
	   
				frame.scale.set( 0.6, 0.6, 0.6 );
				framebacking.scale.set( 0.6, 0.6, 0.6 );
				frame.castShadow = true;
				framebacking.castShadow = true;
				frame.receiveShadow = true;
				framebacking.receiveShadow = true;					
				scene.add(frame);
				scene.add(framebacking);
				render();
				frame_mesh = frame;
				framebacking_mesh = framebacking;
		  }
		  frame();
		
	   
	   
		 // PLAQUE
		function plaque()	{
			loader.load( 'http://morgsol.ddns.net:8000/wp-includes/models/stl/plaque.stl', function ( geometry ) {
				var meshMaterial = materialplaque;
				if (geometry.hasColors) {
					 meshMaterial = new THREE.MeshBasicMaterial({ color: 0xd4af37 }); 
				}
				var plaque = new THREE.Mesh( geometry, meshMaterial );
				plaque.position.set( -0.75, -2.2, 0.915 );
				plaque.scale.set( 0.02, 0.02, 0.02 );
				plaque.castShadow = false;
				plaque.receiveShadow = false;
				scene.add(plaque);
				render();
				plaque_mesh = plaque;
			} );
		}
		plaque();

		previous_mesh = null;
				
		function model() {
			loader.load( 'http://morgsol.ddns.net:8000/wp-includes/models/stl/coedybrenin.stl', function ( geometry ) {		
				geometry.computeBoundingBox();
				geometry.rotateX( -Math.PI / 2 );
			  
			 var material = new THREE.MeshPhongMaterial({ color: bottomColor }); 
		
				
				var mesh = new THREE.Mesh(geometry, material);
				
				mesh.position.set( 0, 0, 0.1 );
				mesh.rotateX( Math.PI / 2 );
				mesh.receiveShadow = true;
				mesh.geometry.center();
				mesh.position.z = 0.3;						
				
				mesh.scale.set( 0.02, 0.02, 0.02 );
				scene.add(mesh);
				previous_mesh = mesh;
			} ) ;
		}
		model();	

		function removeFrame()	{
			scene.remove(frame);
		scene.remove(frame_backing);
	   // corect way to remove a mesh
		frame.geometry.dispose();
		frame.material.dispose();
		frame = undefined;
		framebacking.geometry.dispose();
		framebacking.material.dispose();
		framebacking = undefined;
		}
		
		// TEXT
		function plaquetext() {
			var loader = new THREE.FontLoader();
			loader.load( 'http://morgsol.ddns.net:8000/wp-includes/fonts/helvetiker_regular.typeface.json', function ( font ) {

				var xMid, text;
				var color = 0xFFFFFF;
				var matDark = new THREE.LineBasicMaterial( {
					color: color,
					side: THREE.DoubleSide
				} );

				var matLite = new THREE.MeshBasicMaterial( {
					color: color,
					transparent: true,
					opacity: 1,
					side: THREE.DoubleSide
				} );
						
						
				var messageLine1 = "Plaque Text Line 1\nLine 2 Plaque Text";
				var shapes = font.generateShapes( messageLine1, 0.08 );
				var geometry = new THREE.ShapeBufferGeometry( shapes );
				geometry.computeBoundingBox();
				xMid = - 0.5 * ( geometry.boundingBox.max.x - geometry.boundingBox.min.x );
				geometry.translate( xMid, 0, 0 );
				text = new THREE.Mesh( geometry, matLite );
				text.position.z = 0.9375;
				text.position.y = -2;					
				scene.add( text );
				plaquetext_mesh = text;

			} ); //end load function END TEXT
						
		}
		plaquetext();

	 
		
		var controls = new THREE.OrbitControls( camera, renderer.domElement );
		controls.addEventListener( 'change', render );
		controls.target.set( 0, 1.2, 2 );
		controls.update();
		
	   
		
		window.addEventListener( 'resize', onWindowResize, false );
		window.addEventListener( 'change', render );
		
	  
		/* Set the width of the side navigation to 250px and the left margin of the page content to 250px */
		function openNav() {
			document.getElementById("Div2").style.width = "250px";
			//document.getElementById("Div1").style.marginLeft = "-250px";
		}

		/* Set the width of the side navigation to 0 and the left margin of the page content to 0 */
		function closeNav() {
			document.getElementById("Div2").style.width = "0";
			//document.getElementById("Div1").style.marginLeft = "0";
		} 
					
					
	// END INIT

	}	
	/* Set the width of the side navigation to 0 and the left margin of the page content to 0 */
	function closeNav() {
		document.getElementById("Div2").style.width = "0";
		//document.getElementById("Div1").style.marginLeft = "0";
	} 	
	////////// FUNCTIONS //////////

	function addShadowedLight( x, y, z, color, intensity ) {

		var directionalLight = new THREE.DirectionalLight( color, intensity );
		directionalLight.position.set( x, y, z );
		scene.add( directionalLight );
		directionalLight.castShadow = true;

		var d = 1;
		directionalLight.shadow.camera.left = -d;
		directionalLight.shadow.camera.right = d;
		directionalLight.shadow.camera.top = d;
		directionalLight.shadow.camera.bottom = -d;
		directionalLight.shadow.camera.near = 1;
		directionalLight.shadow.camera.far = 4;
		directionalLight.shadow.mapSize.width = 1024;
		directionalLight.shadow.mapSize.height = 1024;
		directionalLight.shadow.bias = -0.002;

	}


	function onWindowResize() {

		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();
		renderer.setSize( window.innerWidth, window.innerHeight );
		render();
	}

	function plaqueChange(val) {
		alert("The input value has changed. The new value is: " + val);
		window.location.reload() ;
	}	

	function animate() {
		requestAnimationFrame( animate );
		render();
		stats.update();
	}

	function render() {

		//Note: this goes inside render loop
		
		requestAnimationFrame(render);
		renderer.render( scene, camera );
	//	shadowMapViewer.render(render.renderer);	// IF this is included, the resize doesn't work			
	}

The end result (unfinished) is a kind of product configurator

The primary model in the scene (an STL file loaded using the STLLoader) has a vertex count of 4,668 and has 1,556 faces. Not really that much compared to much more complex scenes I’ve seen with >50000 individual objects. The plaque is really simple having just 36 vertices and only 12 faces. Finally the frame shape itself is just an extruded shape so should in theory should perform very well.

Any tips on how I can improve the performance of my scene? It’s pretty much unusable on mobile devices.

Avoid to call requestAnimationFrame in render(). This is definitely not correct. Instead, comment in the call of animate() at the top of your code so you have an ongoing animation loop. You should also avoid to call render() from onWindowResize() and plaque().

These two lines look incorrect, too:

controls.addEventListener( 'change', render );
//
window.addEventListener( 'change', render );

Since you have an active animation loop, you don’t need this stuff.

1 Like

If you don’t want an animation loop for some reasons (e.g. for static scenes), remove at least the call of requestAnimationFrame() from render()

1 Like

Oh wow - I think that did it!! thanks so much :sweat_smile:

The irony is that I’m sure I used an animation function to show the fps using gui.dat / stats - so it was a leftover remnant of my attempt at problem solving!

I’m kind of glad it’s not something more complicated :wink:

1 Like