Search code examples
three.jsaframe

How to render a less costly sphere in aframe/three js?


I have a vr application that renders a lot of spheres. The built-in aframe <a-sphere> element renders spheres using triangles and as a result my scene has a ton of triangles. This is killing the performance, dropping frame rates down into the teens. Is there a more efficient sphere like object I could use? These spheres really dont need to do anything special, Im willing to sacrifice their appearance in order to be able to render more of them without killing performance.


Solution

  • You could resort to InstancedMesh, which uses GPU instancing to render duplicate geometries in a single drawcall.

    Check the demo below. Rendering 1000 elements would typically take 1000 individual drawcalls, greatly slowing down performance. But with instancing, you can render all of them in the same pass.

    var camera, scene, renderer, stats;
    
    			var mesh;
    			var amount = parseInt( window.location.search.substr( 1 ) ) || 10;
    			var count = Math.pow( amount, 3 );
    			var dummy = new THREE.Object3D();
    
    			init();
    			animate();
    
    			function init() {
    
    				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
    				camera.position.set( amount * 0.9, amount * 0.9, amount * 0.9 );
    				camera.lookAt( 0, 0, 0 );
    
    				scene = new THREE.Scene();
    
    				var loader = new THREE.BufferGeometryLoader();
    				loader.load( 'https://raw.githubusercontent.com/mrdoob/three.js/master/examples/models/json/suzanne_buffergeometry.json', function ( geometry ) {
    
    					geometry.computeVertexNormals();
    					geometry.scale( 0.5, 0.5, 0.5 );
    
    					var material = new THREE.MeshNormalMaterial();
    					// check overdraw
    					// var material = new THREE.MeshBasicMaterial( { color: 0xff0000, opacity: 0.1, transparent: true } );
    
    					mesh = new THREE.InstancedMesh( geometry, material, count );
    					mesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage ); // will be updated every frame
    					scene.add( mesh );
    
    					//
    
    					var gui = new dat.GUI();
    					gui.add( mesh, 'count', 0, count );
    
    				} );
    
    				//
    
    				renderer = new THREE.WebGLRenderer( { antialias: true } );
    				renderer.setPixelRatio( window.devicePixelRatio );
    				renderer.setSize( window.innerWidth, window.innerHeight );
    				document.body.appendChild( renderer.domElement );
    
    				//
    
    				stats = new Stats();
    				document.body.appendChild( stats.dom );
    
    				//
    
    				window.addEventListener( 'resize', onWindowResize, false );
    
    			}
    
    			function onWindowResize() {
    
    				camera.aspect = window.innerWidth / window.innerHeight;
    				camera.updateProjectionMatrix();
    
    				renderer.setSize( window.innerWidth, window.innerHeight );
    
    			}
    
    			//
    
    			function animate() {
    
    				requestAnimationFrame( animate );
    
    				render();
    
    				stats.update();
    
    			}
    
    			function render() {
    
    				if ( mesh ) {
    
    					var time = Date.now() * 0.001;
    
    					mesh.rotation.x = Math.sin( time / 4 );
    					mesh.rotation.y = Math.sin( time / 2 );
    
    					var i = 0;
    					var offset = ( amount - 1 ) / 2;
    
    					for ( var x = 0; x < amount; x ++ ) {
    
    						for ( var y = 0; y < amount; y ++ ) {
    
    							for ( var z = 0; z < amount; z ++ ) {
    
    								dummy.position.set( offset - x, offset - y, offset - z );
    								dummy.rotation.y = ( Math.sin( x / 4 + time ) + Math.sin( y / 4 + time ) + Math.sin( z / 4 + time ) );
    								dummy.rotation.z = dummy.rotation.y * 2;
    
    								dummy.updateMatrix();
    
    								mesh.setMatrixAt( i ++, dummy.matrix );
    
    							}
    
    						}
    
    					}
    
    					mesh.instanceMatrix.needsUpdate = true;
    
    				}
    
    				renderer.render( scene, camera );
    
    			}
    html, body {margin: 0; padding: 0;overflow: hidden;}
    <!DOCTYPE html>
    <html lang="en">
    	<head>
    		<title>three.js webgl - instancing - dynamic</title>
    		<meta charset="utf-8">
    		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    		<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
    		<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/stats.min.js"></script>
    		<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/libs/dat.gui.min.js"></script>
    	</head>
    	<body>
    
    	</body>
    </html>

    Click here to see demo source