<!DOCTYPE html> <html lang="en"> <head> <title>three.js webgl - indexed instancing (single box), interleaved buffers</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <link type="text/css" rel="stylesheet" href="main.css"> </head> <body> <div id="container"></div> <div id="info"> <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - indexed instancing (single box), interleaved buffers <div id="notSupported" style="display:none">Sorry your graphics card + browser does not support hardware instancing</div> </div> <script type="importmap"> { "imports": { "three": "../build/three.module.js", "three/addons/": "./jsm/" } } </script> <script type="module"> import * as THREE from 'three'; import Stats from 'three/addons/libs/stats.module.js'; let container, stats; let camera, scene, renderer, mesh; const instances = 5000; let lastTime = 0; const moveQ = new THREE.Quaternion( 0.5, 0.5, 0.5, 0.0 ).normalize(); const tmpQ = new THREE.Quaternion(); const tmpM = new THREE.Matrix4(); const currentM = new THREE.Matrix4(); init(); function init() { container = document.getElementById( 'container' ); camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 ); scene = new THREE.Scene(); scene.background = new THREE.Color( 0x101010 ); // geometry const geometry = new THREE.InstancedBufferGeometry(); // per mesh data x,y,z,w,u,v,s,t for 4-element alignment // only use x,y,z and u,v; but x, y, z, nx, ny, nz, u, v would be a good layout const vertexBuffer = new THREE.InterleavedBuffer( new Float32Array( [ // Front - 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, - 1, - 1, 1, 0, 0, 1, 0, 0, 1, - 1, 1, 0, 1, 1, 0, 0, // Back 1, 1, - 1, 0, 1, 0, 0, 0, - 1, 1, - 1, 0, 0, 0, 0, 0, 1, - 1, - 1, 0, 1, 1, 0, 0, - 1, - 1, - 1, 0, 0, 1, 0, 0, // Left - 1, 1, - 1, 0, 1, 1, 0, 0, - 1, 1, 1, 0, 1, 0, 0, 0, - 1, - 1, - 1, 0, 0, 1, 0, 0, - 1, - 1, 1, 0, 0, 0, 0, 0, // Right 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, - 1, 0, 1, 1, 0, 0, 1, - 1, 1, 0, 0, 0, 0, 0, 1, - 1, - 1, 0, 0, 1, 0, 0, // Top - 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, - 1, 1, - 1, 0, 0, 1, 0, 0, 1, 1, - 1, 0, 1, 1, 0, 0, // Bottom 1, - 1, 1, 0, 1, 0, 0, 0, - 1, - 1, 1, 0, 0, 0, 0, 0, 1, - 1, - 1, 0, 1, 1, 0, 0, - 1, - 1, - 1, 0, 0, 1, 0, 0 ] ), 8 ); // Use vertexBuffer, starting at offset 0, 3 items in position attribute const positions = new THREE.InterleavedBufferAttribute( vertexBuffer, 3, 0 ); geometry.setAttribute( 'position', positions ); // Use vertexBuffer, starting at offset 4, 2 items in uv attribute const uvs = new THREE.InterleavedBufferAttribute( vertexBuffer, 2, 4 ); geometry.setAttribute( 'uv', uvs ); const indices = new Uint16Array( [ 0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5, 8, 10, 9, 10, 11, 9, 12, 14, 13, 14, 15, 13, 16, 17, 18, 18, 17, 19, 20, 21, 22, 22, 21, 23 ] ); geometry.setIndex( new THREE.BufferAttribute( indices, 1 ) ); // material const material = new THREE.MeshBasicMaterial(); material.map = new THREE.TextureLoader().load( 'textures/crate.gif' ); material.map.colorSpace = THREE.SRGBColorSpace; material.map.flipY = false; // per instance data const matrix = new THREE.Matrix4(); const offset = new THREE.Vector3(); const orientation = new THREE.Quaternion(); const scale = new THREE.Vector3( 1, 1, 1 ); let x, y, z, w; mesh = new THREE.InstancedMesh( geometry, material, instances ); for ( let i = 0; i < instances; i ++ ) { // offsets x = Math.random() * 100 - 50; y = Math.random() * 100 - 50; z = Math.random() * 100 - 50; offset.set( x, y, z ).normalize(); offset.multiplyScalar( 5 ); // move out at least 5 units from center in current direction offset.set( x + offset.x, y + offset.y, z + offset.z ); // orientations x = Math.random() * 2 - 1; y = Math.random() * 2 - 1; z = Math.random() * 2 - 1; w = Math.random() * 2 - 1; orientation.set( x, y, z, w ).normalize(); matrix.compose( offset, orientation, scale ); mesh.setMatrixAt( i, matrix ); } scene.add( mesh ); renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setAnimationLoop( animate ); container.appendChild( renderer.domElement ); stats = new Stats(); container.appendChild( stats.dom ); window.addEventListener( 'resize', onWindowResize ); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); } // function animate() { const time = performance.now(); mesh.rotation.y = time * 0.00005; const delta = ( time - lastTime ) / 5000; tmpQ.set( moveQ.x * delta, moveQ.y * delta, moveQ.z * delta, 1 ).normalize(); tmpM.makeRotationFromQuaternion( tmpQ ); for ( let i = 0, il = instances; i < il; i ++ ) { mesh.getMatrixAt( i, currentM ); currentM.multiply( tmpM ); mesh.setMatrixAt( i, currentM ); } mesh.instanceMatrix.needsUpdate = true; mesh.computeBoundingSphere(); lastTime = time; renderer.render( scene, camera ); stats.update(); } </script> </body> </html>