Browse Source

Merge pull request #18254 from donmccurdy/examples-instancing-performance

Examples: Refactor 'instancing / interactive' → 'instancing / performance'
Mr.doob 5 years ago
parent
commit
ffd0a8ad4e
3 changed files with 359 additions and 1084 deletions
  1. 1 1
      examples/files.js
  2. 0 1083
      examples/webgl_instancing_interactive.html
  3. 358 0
      examples/webgl_instancing_performance.html

+ 1 - 1
examples/files.js

@@ -48,7 +48,7 @@ var files = {
 		"webgl_geometry_text_stroke",
 		"webgl_helpers",
 		"webgl_instancing_dynamic",
-		"webgl_instancing_modified",
+		"webgl_instancing_performance",
 		"webgl_instancing_interactive",
 		"webgl_instancing_raycast",
 		"webgl_instancing_scatter",

+ 0 - 1083
examples/webgl_instancing_interactive.html

@@ -1,1083 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-	<title>three.js webgl - instancing - interactive</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">
-	<style>
-		#info {
-			background-color: rgba(0,0,0,0.75);
-		}
-		#notSupported {
-			width: 50%;
-			margin: auto;
-			border: 2px red solid;
-			margin-top: 20px;
-			padding: 10px;
-		}
-	</style>
-</head>
-<body>
-
-	<div id="info">
-
-		<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - gpu picking of geometry instances
-
-		<div id="notSupported" style="display:none">Sorry your graphics card + browser does not support hardware instancing</div>
-
-		<br/><br/>
-
-		<div>
-
-			<div>
-				number of instances:
-				<select id="instanceCount">
-					<option>100</option>
-					<option>500</option>
-					<option selected>1000</option>
-					<option>2000</option>
-					<option>3000</option>
-					<option>5000</option>
-					<option>10000</option>
-					<option>20000</option>
-					<option>30000</option>
-					<option>50000</option>
-					<option>100000</option>
-				</select>
-			</div>
-
-			<div>
-				method:
-				<select id="method">
-					<option>instanced</option>
-					<option>merged</option>
-					<option selected>singleMaterial</option>
-					<option>multiMaterial</option>
-				</select>
-			</div>
-
-			<div>
-				render continuously:
-				<input id="animate" type="checkbox" />
-			</div>
-
-			<div>
-				override material (only affects singleMaterial):
-				<input id="override" type="checkbox" checked/>
-			</div>
-
-			<div>
-				construct anew(to get additional timings):
-				<button id="construct" type="button">do it</button>
-			</div>
-
-		</div>
-
-		<br/>
-
-		<div>
-
-			<span>Materials: #<span id="materialCount"></span></span>
-
-			&nbsp;&nbsp;&nbsp;
-
-			<span>Objects: #<span id="objectCount"></span></span>
-
-			&nbsp;&nbsp;&nbsp;
-
-			<span>Drawcalls: #<span id="drawcalls"></span></span>
-
-			&nbsp;&nbsp;&nbsp;
-
-			<span>Construction time: <span id="initTime"></span>&nbsp;ms</span>
-
-			&nbsp;&nbsp;&nbsp;
-
-		</div>
-
-	</div>
-
-	<div id="container"></div>
-
-	<script id="vertMerged" type="x-shader/x-vertex">
-		#define SHADER_NAME vertMerged
-
-		precision highp float;
-
-		uniform mat4 modelViewMatrix;
-		uniform mat4 projectionMatrix;
-
-		attribute vec3 position;
-		#ifdef PICKING
-			attribute vec3 pickingColor;
-		#else
-			attribute vec3 color;
-			varying vec3 vPosition;
-		#endif
-
-		varying vec3 vColor;
-
-		void main()	{
-
-			vec3 positionEye = ( modelViewMatrix * vec4( position, 1.0 ) ).xyz;
-
-			#ifdef PICKING
-				vColor = pickingColor;
-			#else
-				vColor = color;
-				vPosition = positionEye;
-			#endif
-
-			gl_Position = projectionMatrix * vec4( positionEye, 1.0 );
-
-		}
-
-	</script>
-
-	<script id="fragMerged" type="x-shader/x-fragment">
-		#define SHADER_NAME fragMerged
-
-		#extension GL_OES_standard_derivatives : enable
-
-		precision highp float;
-
-		varying vec3 vColor;
-
-		#ifndef PICKING
-			varying vec3 vPosition;
-		#endif
-
-		void main()	{
-
-			#ifdef PICKING
-				gl_FragColor = vec4( vColor, 1.0 );
-			#else
-				vec3 fdx = dFdx( vPosition );
-				vec3 fdy = dFdy( vPosition );
-				vec3 normal = normalize( cross( fdx, fdy ) );
-				float diffuse = dot( normal, vec3( 0.0, 0.0, 1.0 ) );
-
-				gl_FragColor = vec4( diffuse * vColor, 1.0 );
-			#endif
-
-		}
-
-	</script>
-
-	<script id="vertInstanced" type="x-shader/x-vertex">
-		#define SHADER_NAME vertInstanced
-
-		precision highp float;
-
-		uniform mat4 modelViewMatrix;
-		uniform mat4 projectionMatrix;
-
-		attribute vec3 position;
-		attribute vec3 mcol0;
-		attribute vec3 mcol1;
-		attribute vec3 mcol2;
-		attribute vec3 mcol3;
-
-		#ifdef PICKING
-			attribute vec3 pickingColor;
-		#else
-			attribute vec3 color;
-			varying vec3 vPosition;
-		#endif
-
-		varying vec3 vColor;
-
-		void main()	{
-
-			mat4 matrix = mat4(
-				vec4( mcol0, 0 ),
-				vec4( mcol1, 0 ),
-				vec4( mcol2, 0 ),
-				vec4( mcol3, 1 )
-			);
-
-			vec3 positionEye = ( modelViewMatrix * matrix * vec4( position, 1.0 ) ).xyz;
-
-			#ifdef PICKING
-				vColor = pickingColor;
-			#else
-				vColor = color;
-				vPosition = positionEye;
-			#endif
-
-			gl_Position = projectionMatrix * vec4( positionEye, 1.0 );
-
-		}
-
-	</script>
-
-	<script id="fragInstanced" type="x-shader/x-fragment">
-		#define SHADER_NAME fragInstanced
-
-		#extension GL_OES_standard_derivatives : enable
-
-		precision highp float;
-
-		varying vec3 vColor;
-
-		#ifndef PICKING
-			varying vec3 vPosition;
-		#endif
-
-		void main()	{
-
-			#ifdef PICKING
-				gl_FragColor = vec4( vColor, 1.0 );
-			#else
-				vec3 fdx = dFdx( vPosition );
-				vec3 fdy = dFdy( vPosition );
-				vec3 normal = normalize( cross( fdx, fdy ) );
-				float diffuse = dot( normal, vec3( 0.0, 0.0, 1.0 ) );
-
-				gl_FragColor = vec4( diffuse * vColor, 1.0 );
-			#endif
-
-		}
-
-	</script>
-
-	<script id="vertMaterial" type="x-shader/x-vertex">
-		#define SHADER_NAME vertMaterial
-
-		precision highp float;
-
-		uniform mat4 modelViewMatrix;
-		uniform mat4 projectionMatrix;
-
-		attribute vec3 position;
-
-		#ifndef PICKING
-			varying vec3 vPosition;
-		#endif
-
-		void main()	{
-
-			vec3 positionEye = ( modelViewMatrix * vec4( position, 1.0 ) ).xyz;
-
-			#ifndef PICKING
-				vPosition = positionEye;
-			#endif
-
-			gl_Position = projectionMatrix * vec4( positionEye, 1.0 );
-
-		}
-
-	</script>
-
-	<script id="fragMaterial" type="x-shader/x-fragment">
-		#define SHADER_NAME fragMaterial
-
-		#extension GL_OES_standard_derivatives : enable
-
-		precision highp float;
-
-		#ifdef PICKING
-			uniform vec3 pickingColor;
-		#else
-			uniform vec3 color;
-			varying vec3 vPosition;
-		#endif
-
-		void main()	{
-
-			#ifdef PICKING
-				gl_FragColor = vec4( pickingColor, 1.0 );
-			#else
-				vec3 fdx = dFdx( vPosition );
-				vec3 fdy = dFdy( vPosition );
-				vec3 normal = normalize( cross( fdx, fdy ) );
-				float diffuse = dot( normal, vec3( 0.0, 0.0, 1.0 ) );
-
-				gl_FragColor = vec4( diffuse * color, 1.0 );
-			#endif
-
-		}
-
-	</script>
-
-	<script type="module">
-		import * as THREE from '../build/three.module.js';
-
-		import Stats from './jsm/libs/stats.module.js';
-
-		import { TrackballControls } from './jsm/controls/TrackballControls.js';
-
-		var container, stats;
-		var camera, controls, scene, renderer;
-		var pickingData, pickingRenderTarget, pickingScene;
-		var useOverrideMaterial = true;
-		var singleMaterial, singlePickingMaterial;
-		var highlightBox;
-		var materialList = [];
-		var geometryList = [];
-		var objectCount = 0;
-		var geometrySize = new THREE.Vector3();
-		var mouse = new THREE.Vector2();
-		var scale = 1.03;
-
-		var loader = new THREE.BufferGeometryLoader();
-
-		//create buffer for reading a single pixel
-		var pixelBuffer = new Uint8Array( 4 );
-
-		// gui
-		var instanceCount, method, doAnimate;
-
-		//
-
-		gui();
-		init();
-		initMesh();
-		if ( doAnimate ) animate();
-
-		//
-
-		function gui() {
-
-			var instanceCountElm = document.getElementById( 'instanceCount' );
-
-			instanceCount = parseInt( instanceCountElm.value );
-
-			instanceCountElm.addEventListener( "change", function () {
-
-				instanceCount = parseInt( instanceCountElm.value );
-				initMesh();
-
-			} );
-
-			//
-
-			var methodElm = document.getElementById( 'method' );
-
-			method = methodElm.value;
-
-			methodElm.addEventListener( "change", function () {
-
-				method = methodElm.value;
-				initMesh();
-
-			} );
-
-			//
-
-			var animateElm = document.getElementById( 'animate' );
-
-			doAnimate = animateElm.checked;
-
-			animateElm.addEventListener( "click", function () {
-
-				doAnimate = animateElm.checked;
-				animate();
-
-			} );
-
-			//
-
-			var overrideElm = document.getElementById( 'override' );
-
-			useOverrideMaterial = overrideElm.checked;
-
-			overrideElm.addEventListener( "click", function () {
-
-				useOverrideMaterial = overrideElm.checked;
-				initMesh();
-
-			} );
-
-			//
-
-			var constructElm = document.getElementById( 'construct' );
-
-			constructElm.addEventListener( "click", function () {
-
-				initMesh();
-
-			} );
-
-		}
-
-		function clean() {
-
-			THREE.Cache.clear();
-
-			materialList.forEach( function ( m ) {
-
-				m.dispose();
-
-			} );
-
-			geometryList.forEach( function ( g ) {
-
-				g.dispose();
-
-			} );
-
-			scene = new THREE.Scene();
-			scene.background = new THREE.Color( 0xffffff );
-
-			scene.add( camera );
-			scene.add( highlightBox );
-
-			pickingScene = new THREE.Scene();
-			pickingData = {};
-			materialList = [];
-			geometryList = [];
-			objectCount = 0;
-
-			singleMaterial = undefined;
-			singlePickingMaterial = undefined;
-
-		}
-
-		var randomizeMatrix = function () {
-
-			var position = new THREE.Vector3();
-			var rotation = new THREE.Euler();
-			var quaternion = new THREE.Quaternion();
-			var scale = new THREE.Vector3();
-
-			return function ( matrix ) {
-
-				position.x = Math.random() * 40 - 20;
-				position.y = Math.random() * 40 - 20;
-				position.z = Math.random() * 40 - 20;
-
-				rotation.x = Math.random() * 2 * Math.PI;
-				rotation.y = Math.random() * 2 * Math.PI;
-				rotation.z = Math.random() * 2 * Math.PI;
-
-				quaternion.setFromEuler( rotation );
-
-				scale.x = scale.y = scale.z = Math.random() * 1;
-
-				matrix.compose( position, quaternion, scale );
-
-			};
-
-		}();
-
-		function initMesh() {
-
-			clean();
-
-			// make instances
-			loader.load( 'models/json/suzanne_buffergeometry.json', function ( geo ) {
-
-				geo = geo.toNonIndexed();
-				geo.computeBoundingBox();
-				geo.boundingBox.getSize( geometrySize );
-				geometryList.push( geo );
-
-				var start = window.performance.now();
-
-				switch ( method ) {
-
-					case "merged":
-						makeMerged( geo );
-						break;
-
-					case "instanced":
-						makeInstanced( geo );
-						break;
-
-					case "singleMaterial":
-						makeSingleMaterial( geo );
-						break;
-
-					case "multiMaterial":
-						makeMultiMaterial( geo );
-						break;
-
-				}
-
-				render();
-
-				var end = window.performance.now();
-
-				document.getElementById( 'materialCount' ).innerText = materialList.length;
-				document.getElementById( 'objectCount' ).innerText = objectCount;
-				document.getElementById( 'drawcalls' ).innerText = renderer.info.render.calls;
-				document.getElementById( 'initTime' ).innerText = ( end - start ).toFixed( 2 );
-
-			} );
-
-		}
-
-		function makeMultiMaterial( geo ) {
-
-			// material
-
-			var vert = document.getElementById( 'vertMaterial' ).textContent;
-			var frag = document.getElementById( 'fragMaterial' ).textContent;
-
-			var material = new THREE.RawShaderMaterial( {
-				vertexShader: vert,
-				fragmentShader: frag,
-				uniforms: {
-					color: {
-						value: new THREE.Color()
-					}
-				}
-			} );
-
-			var pickingMaterial = new THREE.RawShaderMaterial( {
-				vertexShader: "#define PICKING\n" + vert,
-				fragmentShader: "#define PICKING\n" + frag,
-				uniforms: {
-					pickingColor: {
-						value: new THREE.Color()
-					}
-				}
-			} );
-
-			// geometry / mesh
-
-			var matrix = new THREE.Matrix4();
-
-			for ( var i = 0; i < instanceCount; i ++ ) {
-
-				var object = new THREE.Mesh( geo, material );
-				objectCount ++;
-				randomizeMatrix( matrix );
-				object.applyMatrix( matrix );
-				var pickingObject = object.clone();
-				objectCount ++;
-
-				object.material = material.clone();
-				object.material.uniforms[ "color" ].value.setHex( Math.random() * 0xffffff );
-				materialList.push( object.material );
-
-				pickingObject.material = pickingMaterial.clone();
-				pickingObject.material.uniforms[ "pickingColor" ].value.setHex( i + 1 );
-				materialList.push( pickingObject.material );
-
-				pickingData[ i + 1 ] = object;
-
-				scene.add( object );
-				pickingScene.add( pickingObject );
-
-			}
-
-			material.dispose();
-			pickingMaterial.dispose();
-
-		}
-
-		function makeSingleMaterial( geo ) {
-
-			// material
-
-			var vert = document.getElementById( 'vertMaterial' ).textContent;
-			var frag = document.getElementById( 'fragMaterial' ).textContent;
-
-			var material = new THREE.RawShaderMaterial( {
-				vertexShader: vert,
-				fragmentShader: frag,
-				uniforms: {
-					"color": {
-						value: new THREE.Color()
-					}
-				}
-			} );
-			materialList.push( material );
-
-			var pickingMaterial = new THREE.RawShaderMaterial( {
-				vertexShader: "#define PICKING\n" + vert,
-				fragmentShader: "#define PICKING\n" + frag,
-				uniforms: {
-					"pickingColor": {
-						value: new THREE.Color()
-					}
-				}
-			} );
-			materialList.push( pickingMaterial );
-
-			if ( useOverrideMaterial ) {
-
-				// make globally available
-				singleMaterial = material;
-				singlePickingMaterial = pickingMaterial;
-
-			}
-
-			// geometry / mesh
-
-			var matrix = new THREE.Matrix4();
-
-			function onBeforeRender( renderer, scene, camera, geometry, material ) {
-
-				var updateList = [];
-				var u = material.uniforms;
-				var d = this.userData;
-
-				if ( u.pickingColor ) {
-
-					u.pickingColor.value.setHex( d.pickingColor );
-					updateList.push( "pickingColor" );
-
-				}
-
-				if ( u.color ) {
-
-					u.color.value.setHex( d.color );
-					updateList.push( "color" );
-
-				}
-
-				if ( updateList.length ) {
-
-					var materialProperties = renderer.properties.get( material );
-
-					if ( materialProperties.program ) {
-
-						var gl = renderer.getContext();
-						var p = materialProperties.program;
-						gl.useProgram( p.program );
-						var pu = p.getUniforms();
-
-						updateList.forEach( function ( name ) {
-
-							pu.setValue( gl, name, u[ name ].value );
-
-						} );
-
-					}
-
-				}
-
-			}
-
-			for ( var i = 0; i < instanceCount; i ++ ) {
-
-				var object = new THREE.Mesh( geo, material );
-				objectCount ++;
-				randomizeMatrix( matrix );
-				object.applyMatrix( matrix );
-
-				var pickingObject;
-				if ( ! useOverrideMaterial ) {
-
-					pickingObject = object.clone();
-					objectCount ++;
-
-				}
-
-				object.material = material;
-				object.userData[ "color" ] = Math.random() * 0xffffff;
-
-				if ( useOverrideMaterial ) {
-
-					object.userData[ "pickingColor" ] = i + 1;
-					object.onBeforeRender = onBeforeRender;
-
-				} else {
-
-					pickingObject.material = pickingMaterial;
-					pickingObject.userData[ "pickingColor" ] = i + 1;
-					pickingObject.onBeforeRender = onBeforeRender;
-
-				}
-
-				pickingData[ i + 1 ] = object;
-
-				scene.add( object );
-				if ( ! useOverrideMaterial ) pickingScene.add( pickingObject );
-
-			}
-
-		}
-
-		function makeMerged( geo ) {
-
-			// material
-
-			var vert = document.getElementById( 'vertMerged' ).textContent;
-			var frag = document.getElementById( 'fragMerged' ).textContent;
-
-			var material = new THREE.RawShaderMaterial( {
-				vertexShader: vert,
-				fragmentShader: frag
-			} );
-			materialList.push( material );
-
-			var pickingMaterial = new THREE.RawShaderMaterial( {
-				vertexShader: "#define PICKING\n" + vert,
-				fragmentShader: "#define PICKING\n" + frag
-			} );
-			materialList.push( pickingMaterial );
-
-			// geometry
-
-			var mgeo = new THREE.BufferGeometry();
-			geometryList.push( mgeo );
-
-			var pos = geo.attributes.position;
-			var posLen = geo.attributes.position.count * 3;
-			var vertices = new THREE.BufferAttribute(
-				new Float32Array( instanceCount * posLen ), 3
-			);
-
-			var vertex = new THREE.Vector3();
-			var matrix = new THREE.Matrix4();
-
-			for ( var i = 0, ul = instanceCount; i < ul; i ++ ) {
-
-				var offset = i * posLen;
-
-				randomizeMatrix( matrix );
-				var object = new THREE.Object3D();
-				objectCount ++;
-				object.applyMatrix( matrix );
-				pickingData[ i + 1 ] = object;
-				vertices.set( pos.array, offset );
-
-				for ( var k = 0, l = offset; k < posLen; k += 3, l += 3 ) {
-
-					vertex.fromArray( vertices.array, l );
-					vertex.applyMatrix4( matrix );
-					vertex.toArray( vertices.array, l );
-
-				}
-
-			}
-
-			mgeo.setAttribute( 'position', vertices );
-
-			var colCount = posLen / 3;
-			var colors = new THREE.BufferAttribute(
-				new Float32Array( instanceCount * colCount * 3 ), 3
-			);
-			var randCol = function () {
-
-				return Math.random();
-
-			};
-			for ( var i = 0, ul = instanceCount; i < ul; i ++ ) {
-
-				var r = randCol(), g = randCol(), b = randCol();
-				for ( var j = i * colCount, jl = ( i + 1 ) * colCount; j < jl; j ++ ) {
-
-					colors.setXYZ( j, r, g, b );
-
-				}
-
-			}
-			mgeo.setAttribute( 'color', colors );
-
-			var col = new THREE.Color();
-			var pickingColors = new THREE.BufferAttribute(
-				new Float32Array( instanceCount * colCount * 3 ), 3
-			);
-			for ( var i = 0, ul = instanceCount; i < ul; i ++ ) {
-
-				col.setHex( i + 1 );
-				for ( var j = i * colCount, jl = ( i + 1 ) * colCount; j < jl; j ++ ) {
-
-					pickingColors.setXYZ( j, col.r, col.g, col.b );
-
-				}
-
-			}
-			mgeo.setAttribute( 'pickingColor', pickingColors );
-
-			// mesh
-
-			var mesh = new THREE.Mesh( mgeo, material );
-			scene.add( mesh );
-
-			var pickingMesh = new THREE.Mesh( mgeo, pickingMaterial );
-			pickingScene.add( pickingMesh );
-
-		}
-
-		function makeInstanced( geo ) {
-
-			// material
-
-			var vert = document.getElementById( 'vertInstanced' ).textContent;
-			var frag = document.getElementById( 'fragInstanced' ).textContent;
-
-			var material = new THREE.RawShaderMaterial( {
-				vertexShader: vert,
-				fragmentShader: frag,
-			} );
-			materialList.push( material );
-
-			var pickingMaterial = new THREE.RawShaderMaterial( {
-				vertexShader: "#define PICKING\n" + vert,
-				fragmentShader: "#define PICKING\n" + frag
-			} );
-			materialList.push( pickingMaterial );
-
-			// geometry
-
-			var igeo = new THREE.InstancedBufferGeometry();
-			geometryList.push( igeo );
-
-			var vertices = geo.attributes.position.clone();
-			igeo.setAttribute( 'position', vertices );
-
-			// var matrices = new THREE.InstancedBufferAttribute(
-			// 	new Float32Array( instanceCount * 16 ), 16
-			// );
-			var mcol0 = new THREE.InstancedBufferAttribute(
-				new Float32Array( instanceCount * 3 ), 3
-			);
-			var mcol1 = new THREE.InstancedBufferAttribute(
-				new Float32Array( instanceCount * 3 ), 3
-			);
-			var mcol2 = new THREE.InstancedBufferAttribute(
-				new Float32Array( instanceCount * 3 ), 3
-			);
-			var mcol3 = new THREE.InstancedBufferAttribute(
-				new Float32Array( instanceCount * 3 ), 3
-			);
-			var matrix = new THREE.Matrix4();
-			var me = matrix.elements;
-			for ( var i = 0, ul = mcol0.count; i < ul; i ++ ) {
-
-				randomizeMatrix( matrix );
-				var object = new THREE.Object3D();
-				objectCount ++;
-				object.applyMatrix( matrix );
-				pickingData[ i + 1 ] = object;
-				// matrices.set( matrix.elements, i * 16 );
-				mcol0.setXYZ( i, me[ 0 ], me[ 1 ], me[ 2 ] );
-				mcol1.setXYZ( i, me[ 4 ], me[ 5 ], me[ 6 ] );
-				mcol2.setXYZ( i, me[ 8 ], me[ 9 ], me[ 10 ] );
-				mcol3.setXYZ( i, me[ 12 ], me[ 13 ], me[ 14 ] );
-
-			}
-			// igeo.setAttribute( 'matrix', matrices );
-			igeo.setAttribute( 'mcol0', mcol0 );
-			igeo.setAttribute( 'mcol1', mcol1 );
-			igeo.setAttribute( 'mcol2', mcol2 );
-			igeo.setAttribute( 'mcol3', mcol3 );
-
-			var randCol = function () {
-
-				return Math.random();
-
-			};
-			var colors = new THREE.InstancedBufferAttribute(
-				new Float32Array( instanceCount * 3 ), 3
-			);
-			for ( var i = 0, ul = colors.count; i < ul; i ++ ) {
-
-				colors.setXYZ( i, randCol(), randCol(), randCol() );
-
-			}
-			igeo.setAttribute( 'color', colors );
-
-			var col = new THREE.Color();
-			var pickingColors = new THREE.InstancedBufferAttribute(
-				new Float32Array( instanceCount * 3 ), 3
-			);
-			for ( var i = 0, ul = pickingColors.count; i < ul; i ++ ) {
-
-				col.setHex( i + 1 );
-				pickingColors.setXYZ( i, col.r, col.g, col.b );
-
-			}
-			igeo.setAttribute( 'pickingColor', pickingColors );
-
-			// mesh
-
-			var mesh = new THREE.Mesh( igeo, material );
-			scene.add( mesh );
-
-			var pickingMesh = new THREE.Mesh( igeo, pickingMaterial );
-			pickingScene.add( pickingMesh );
-
-		}
-
-		function init() {
-
-			// camera
-
-			camera = new THREE.PerspectiveCamera(
-				70, window.innerWidth / window.innerHeight, 1, 100
-			);
-			camera.position.z = 40;
-
-			// picking render target
-
-			pickingRenderTarget = new THREE.WebGLRenderTarget(
-				window.innerWidth, window.innerHeight
-			);
-			pickingRenderTarget.texture.generateMipmaps = false;
-			pickingRenderTarget.texture.minFilter = THREE.NearestFilter;
-
-			// highlight box
-
-			highlightBox = new THREE.Mesh(
-				new THREE.BoxBufferGeometry( 1, 1, 1 ),
-				new THREE.MeshLambertMaterial( {
-					emissive: 0xffff00,
-					transparent: true,
-					opacity: 0.5
-				} )
-			);
-
-			// renderer
-
-			container = document.getElementById( "container" );
-			renderer = new THREE.WebGLRenderer( {
-				antialias: true,
-				alpha: true
-			} );
-			if ( renderer.extensions.get( 'ANGLE_instanced_arrays' ) === null ) {
-
-				document.getElementById( "notSupported" ).style.display = "";
-				return;
-
-			}
-			renderer.setPixelRatio( window.devicePixelRatio );
-			renderer.setSize( window.innerWidth, window.innerHeight );
-			//renderer.sortObjects = false;
-			container.appendChild( renderer.domElement );
-
-			if ( renderer.extensions.get( 'ANGLE_instanced_arrays' ) === null ) {
-
-				throw 'ANGLE_instanced_arrays not supported';
-
-			}
-
-			// controls
-
-			controls = new TrackballControls( camera, renderer.domElement );
-			controls.staticMoving = true;
-
-			// stats
-
-			stats = new Stats();
-			container.appendChild( stats.dom );
-
-			// listeners
-
-			renderer.domElement.addEventListener( 'mousemove', onMouseMove );
-
-			window.addEventListener( 'resize', onWindowResize, false );
-
-		}
-
-		//
-
-		function onMouseMove( e ) {
-
-			mouse.x = e.clientX;
-			mouse.y = e.clientY;
-
-			controls.update();
-			requestAnimationFrame( render );
-
-		}
-
-		function onWindowResize() {
-
-			camera.aspect = window.innerWidth / window.innerHeight;
-			camera.updateProjectionMatrix();
-
-			renderer.setSize( window.innerWidth, window.innerHeight );
-			pickingRenderTarget.setSize( window.innerWidth, window.innerHeight );
-
-		}
-
-		function animate() {
-
-			if ( doAnimate ) {
-
-				requestAnimationFrame( animate );
-
-			}
-
-			controls.update();
-			stats.update();
-
-			render();
-
-		}
-
-		function pick() {
-
-			// render the picking scene off-screen
-
-			highlightBox.visible = false;
-
-			renderer.setRenderTarget( pickingRenderTarget );
-
-			if ( singlePickingMaterial ) {
-
-				scene.overrideMaterial = singlePickingMaterial;
-				renderer.render( scene, camera );
-				scene.overrideMaterial = null;
-
-			} else {
-
-				renderer.render( pickingScene, camera );
-
-			}
-
-			// read the pixel under the mouse from the texture
-
-			renderer.readRenderTargetPixels(
-				pickingRenderTarget,
-				mouse.x,
-				pickingRenderTarget.height - mouse.y,
-				1,
-				1,
-				pixelBuffer
-			);
-
-			// interpret the pixel as an ID
-
-			var id =
-				( pixelBuffer[ 0 ] << 16 ) |
-				( pixelBuffer[ 1 ] << 8 ) |
-				( pixelBuffer[ 2 ] );
-
-			var object = pickingData[ id ];
-
-			if ( object ) {
-
-				// move the highlightBox so that it surrounds the picked object
-
-				if ( object.position && object.rotation && object.scale ) {
-
-					highlightBox.position.copy( object.position );
-					highlightBox.rotation.copy( object.rotation );
-
-					highlightBox.scale.copy( object.scale )
-						.multiply( geometrySize )
-						.multiplyScalar( scale );
-
-					highlightBox.visible = true;
-
-				}
-
-			} else {
-
-				highlightBox.visible = false;
-
-			}
-
-		}
-
-		function render() {
-
-			pick();
-			renderer.setRenderTarget( null );
-			renderer.render( scene, camera );
-
-		}
-
-	</script>
-
-</body>
-</html>

+ 358 - 0
examples/webgl_instancing_performance.html

@@ -0,0 +1,358 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<title>three.js webgl - instancing - performance</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">
+	<style>
+		#info {
+			background-color: rgba(0,0,0,0.75);
+		}
+
+		.dg .folder .gui-stats {
+			height: auto;
+		}
+	</style>
+</head>
+<body>
+
+	<div id="info">
+
+		<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - instancing - performance
+
+	</div>
+
+	<div id="container"></div>
+
+	<script type="module">
+		import * as THREE from '../build/three.module.js';
+
+		import Stats from './jsm/libs/stats.module.js';
+		import { GUI } from './jsm/libs/dat.gui.module.js';
+
+		import { OrbitControls } from './jsm/controls/OrbitControls.js';
+		import { BufferGeometryUtils } from './jsm/utils/BufferGeometryUtils.js';
+
+		var container, stats, gui, guiStatsEl;
+		var camera, controls, scene, renderer, material;
+
+		// gui
+
+		var Method = {
+			INSTANCED: 'INSTANCED',
+			MERGED: 'MERGED',
+			NAIVE: 'NAIVE'
+		};
+
+		var api = {
+			method: Method.INSTANCED,
+			count: 1000
+		};
+
+		//
+
+		init();
+		initMesh();
+		animate();
+
+		//
+
+		function clean() {
+
+			var meshes = [];
+
+			scene.traverse( function ( object ) {
+
+				if ( object.isMesh ) meshes.push( object );
+
+			} );
+
+			for ( var i = 0; i < meshes.length; i ++ ) {
+
+				var mesh = meshes[ i ];
+				mesh.material.dispose();
+				mesh.geometry.dispose();
+
+				scene.remove( mesh );
+
+			}
+
+		}
+
+		var randomizeMatrix = function () {
+
+			var position = new THREE.Vector3();
+			var rotation = new THREE.Euler();
+			var quaternion = new THREE.Quaternion();
+			var scale = new THREE.Vector3();
+
+			return function ( matrix ) {
+
+				position.x = Math.random() * 40 - 20;
+				position.y = Math.random() * 40 - 20;
+				position.z = Math.random() * 40 - 20;
+
+				rotation.x = Math.random() * 2 * Math.PI;
+				rotation.y = Math.random() * 2 * Math.PI;
+				rotation.z = Math.random() * 2 * Math.PI;
+
+				quaternion.setFromEuler( rotation );
+
+				scale.x = scale.y = scale.z = Math.random() * 1;
+
+				matrix.compose( position, quaternion, scale );
+
+			};
+
+		}();
+
+		function initMesh() {
+
+			clean();
+
+			// make instances
+			new THREE.BufferGeometryLoader()
+				.setPath( 'models/json/' )
+				.load( 'suzanne_buffergeometry.json', function ( geometry ) {
+
+					material = new THREE.MeshNormalMaterial();
+
+					geometry.computeVertexNormals();
+
+					console.time( api.method + ' (build)'  );
+
+					switch ( api.method ) {
+
+						case Method.INSTANCED:
+							makeInstanced( geometry );
+							break;
+
+						case Method.MERGED:
+							makeMerged( geometry );
+							break;
+
+						case Method.NAIVE:
+							makeNaive( geometry );
+							break;
+
+					}
+
+					console.timeEnd( api.method + ' (build)' );
+
+			} );
+
+		}
+
+		function makeInstanced( geometry ) {
+
+			var matrix = new THREE.Matrix4();
+			var mesh = new THREE.InstancedMesh( geometry, material, api.count );
+
+			for ( var i = 0; i < api.count; i ++ ) {
+
+				randomizeMatrix( matrix );
+				mesh.setMatrixAt( i, matrix );
+
+			}
+
+			scene.add( mesh );
+
+			//
+
+			var geometryByteLength = getGeometryByteLength( geometry );
+
+			guiStatsEl.innerHTML = [
+
+				'<i>GPU draw calls</i>: 1',
+				'<i>GPU memory</i>: ' + formatBytes( api.count * 16 + geometryByteLength, 2 )
+
+			].join( '<br/>' );
+
+		}
+
+		function makeMerged( geometry ) {
+
+			var instanceGeometry;
+			var geometries = [];
+			var matrix = new THREE.Matrix4();
+
+			for ( var i = 0; i < api.count; i ++ ) {
+
+				randomizeMatrix( matrix );
+
+				var instanceGeometry = geometry.clone();
+				instanceGeometry.applyMatrix( matrix );
+
+				geometries.push( instanceGeometry );
+
+			}
+
+			var mergedGeometry = BufferGeometryUtils.mergeBufferGeometries( geometries );
+
+			scene.add( new THREE.Mesh( mergedGeometry, material ) );
+
+			//
+
+			guiStatsEl.innerHTML = [
+
+				'<i>GPU draw calls</i>: 1',
+				'<i>GPU memory</i>: ' + formatBytes( getGeometryByteLength( mergedGeometry ), 2 )
+
+			].join( '<br/>' );
+
+		}
+
+		function makeNaive( geometry ) {
+
+			var matrix = new THREE.Matrix4();
+
+			for ( var i = 0; i < api.count; i ++ ) {
+
+				randomizeMatrix( matrix );
+
+				var mesh = new THREE.Mesh( geometry, material );
+				mesh.applyMatrix( matrix );
+
+				scene.add( mesh );
+
+			}
+
+			//
+
+			var geometryByteLength = getGeometryByteLength( geometry );
+
+			guiStatsEl.innerHTML = [
+
+				'<i>GPU draw calls</i>: ' + api.count,
+				'<i>GPU memory</i>: ' + formatBytes( api.count * 16 + geometryByteLength, 2 )
+
+			].join( '<br/>' );
+
+		}
+
+		function init() {
+
+			var width = window.innerWidth;
+			var height = window.innerHeight;
+
+			// camera
+
+			camera = new THREE.PerspectiveCamera( 70, width / height, 1, 100 );
+			camera.position.z = 30;
+
+			// renderer
+
+			renderer = new THREE.WebGLRenderer( { antialias: true } );
+			renderer.setPixelRatio( window.devicePixelRatio );
+			renderer.setSize( width, height );
+			renderer.outputEncoding = THREE.sRGBEncoding;
+
+			container = document.getElementById( 'container' );
+			container.appendChild( renderer.domElement );
+
+			// scene
+
+			scene = new THREE.Scene();
+			scene.background = new THREE.Color( 0xffffff );
+
+			// controls
+
+			controls = new OrbitControls( camera, renderer.domElement );
+			controls.autoRotate = true;
+
+			// stats
+
+			stats = new Stats();
+			container.appendChild( stats.dom );
+
+			// gui
+
+			gui = new GUI();
+			gui.add( api, 'method', Method ).onChange( initMesh );
+			gui.add( api, 'count', 1, 10000 ).step( 1 ).onChange( initMesh );
+
+			var perfFolder = gui.addFolder( 'Performance' );
+
+			guiStatsEl = document.createElement( 'li' );
+			guiStatsEl.classList.add( 'gui-stats' );
+
+			perfFolder.__ul.appendChild( guiStatsEl );
+			perfFolder.open();
+
+			// listeners
+
+			window.addEventListener( 'resize', onWindowResize, false );
+
+			Object.assign(window, {scene});
+
+		}
+
+		//
+
+		function onWindowResize() {
+
+			var width = window.innerWidth;
+			var height = window.innerHeight;
+
+			camera.aspect = width / height;
+			camera.updateProjectionMatrix();
+
+			renderer.setSize( width, height );
+
+		}
+
+		function animate() {
+
+			requestAnimationFrame( animate );
+
+			controls.update();
+			stats.update();
+
+			render();
+
+		}
+
+		function render() {
+
+			renderer.render( scene, camera );
+
+		}
+
+		//
+
+		function getGeometryByteLength( geometry ) {
+
+			var total = 0;
+
+			if ( geometry.index ) total += geometry.index.array.byteLength;
+
+			for ( var name in geometry.attributes ) {
+
+				total += geometry.attributes[ name ].array.byteLength;
+
+			}
+
+			return total;
+
+		}
+
+		// Source: https://stackoverflow.com/a/18650828/1314762
+		function formatBytes( bytes, decimals ) {
+
+			if ( bytes === 0 ) return '0 bytes';
+
+			var k = 1024;
+			var dm = decimals < 0 ? 0 : decimals;
+			var sizes = [ 'bytes', 'KB', 'MB' ];
+
+			var i = Math.floor( Math.log( bytes ) / Math.log( k ) );
+
+			return parseFloat( ( bytes / Math.pow( k, i ) ).toFixed( dm ) ) + ' ' + sizes[ i ];
+
+		}
+
+	</script>
+
+</body>
+</html>