|
@@ -0,0 +1,905 @@
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
+<html lang="en">
|
|
|
|
+<head>
|
|
|
|
+ <title>three.js webgl - interactive instances (gpu)</title>
|
|
|
|
+ <meta charset="utf-8">
|
|
|
|
+ <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
|
|
|
+ <style>
|
|
|
|
+ body {
|
|
|
|
+ font-family: Monospace;
|
|
|
|
+ background-color: #f0f0f0;
|
|
|
|
+ margin: 0px;
|
|
|
|
+ overflow: hidden;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .info {
|
|
|
|
+ position: absolute;
|
|
|
|
+ background-color: black;
|
|
|
|
+ opacity: 0.8;
|
|
|
|
+ color: white;
|
|
|
|
+ text-align: center;
|
|
|
|
+ top: 0px;
|
|
|
|
+ width: 100%;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .info a {
|
|
|
|
+ color: #00ffff;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #notSupported {
|
|
|
|
+ width: 50%;
|
|
|
|
+ margin: auto;
|
|
|
|
+ border: 2px red solid;
|
|
|
|
+ margin-top: 20px;
|
|
|
|
+ padding: 10px;
|
|
|
|
+ }
|
|
|
|
+ </style>
|
|
|
|
+</head>
|
|
|
|
+<body>
|
|
|
|
+
|
|
|
|
+ <div class="info">
|
|
|
|
+
|
|
|
|
+ <a href="http://threejs.org" target="_blank">three.js</a> webgl - gpu picking of geometry instances using a single material
|
|
|
|
+
|
|
|
|
+ <div id="notSupported" style="display:none">Sorry your graphics card + browser does not support hardware instancing</div>
|
|
|
|
+
|
|
|
|
+ <br/><br/>
|
|
|
|
+
|
|
|
|
+ <div>
|
|
|
|
+
|
|
|
|
+ <span>number of instances </span>
|
|
|
|
+ <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>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ <span>method</span>
|
|
|
|
+ <select id="method">
|
|
|
|
+ <option>instanced</option>
|
|
|
|
+ <option selected>merged</option>
|
|
|
|
+ <option>singleMaterial</option>
|
|
|
|
+ <option>multiMaterial</option>
|
|
|
|
+ </select>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ <span>render continuously</span>
|
|
|
|
+ <input id="animate" type="checkbox" />
|
|
|
|
+
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div id="container"></div>
|
|
|
|
+
|
|
|
|
+ <script src="../build/three.min.js"></script>
|
|
|
|
+ <script src="../src/renderers/WebGLRenderer.js"></script>
|
|
|
|
+ <script src="js/controls/TrackballControls.js"></script>
|
|
|
|
+ <script src="js/libs/stats.min.js"></script>
|
|
|
|
+
|
|
|
|
+ <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;
|
|
|
|
+ #endif
|
|
|
|
+
|
|
|
|
+ varying vec3 vColor;
|
|
|
|
+ varying vec3 vPosition;
|
|
|
|
+
|
|
|
|
+ void main() {
|
|
|
|
+
|
|
|
|
+ #ifdef PICKING
|
|
|
|
+ vColor = pickingColor;
|
|
|
|
+ #else
|
|
|
|
+ vColor = color;
|
|
|
|
+ #endif
|
|
|
|
+ vPosition = ( modelViewMatrix * vec4( position, 1.0 ) ).xyz;
|
|
|
|
+
|
|
|
|
+ gl_Position = projectionMatrix * vec4( vPosition, 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;
|
|
|
|
+ varying vec3 vPosition;
|
|
|
|
+
|
|
|
|
+ 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 mat4 matrix;
|
|
|
|
+ attribute vec3 mcol0;
|
|
|
|
+ attribute vec3 mcol1;
|
|
|
|
+ attribute vec3 mcol2;
|
|
|
|
+ attribute vec3 mcol3;
|
|
|
|
+ #ifdef PICKING
|
|
|
|
+ attribute vec3 pickingColor;
|
|
|
|
+ #else
|
|
|
|
+ attribute vec3 color;
|
|
|
|
+ #endif
|
|
|
|
+
|
|
|
|
+ varying vec3 vColor;
|
|
|
|
+ varying vec3 vPosition;
|
|
|
|
+
|
|
|
|
+ void main() {
|
|
|
|
+
|
|
|
|
+ mat4 matrix = mat4(
|
|
|
|
+ vec4( mcol0, 0 ),
|
|
|
|
+ vec4( mcol1, 0 ),
|
|
|
|
+ vec4( mcol2, 0 ),
|
|
|
|
+ vec4( mcol3, 1 )
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ #ifdef PICKING
|
|
|
|
+ vColor = pickingColor;
|
|
|
|
+ #else
|
|
|
|
+ vColor = color;
|
|
|
|
+ #endif
|
|
|
|
+ vPosition = ( modelViewMatrix * matrix * vec4( position, 1.0 ) ).xyz;
|
|
|
|
+
|
|
|
|
+ gl_Position = projectionMatrix * vec4( vPosition, 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;
|
|
|
|
+ varying vec3 vPosition;
|
|
|
|
+
|
|
|
|
+ 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;
|
|
|
|
+
|
|
|
|
+ varying vec3 vPosition;
|
|
|
|
+
|
|
|
|
+ void main() {
|
|
|
|
+
|
|
|
|
+ vPosition = ( modelViewMatrix * vec4( position, 1.0 ) ).xyz;
|
|
|
|
+
|
|
|
|
+ gl_Position = projectionMatrix * vec4( vPosition, 1.0 );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ </script>
|
|
|
|
+
|
|
|
|
+ <script id="fragMaterial" type="x-shader/x-fragment">
|
|
|
|
+ #define SHADER_NAME fragMaterial
|
|
|
|
+
|
|
|
|
+ #extension GL_OES_standard_derivatives : enable
|
|
|
|
+
|
|
|
|
+ precision highp float;
|
|
|
|
+
|
|
|
|
+ uniform vec3 color;
|
|
|
|
+
|
|
|
|
+ varying vec3 vPosition;
|
|
|
|
+
|
|
|
|
+ void main() {
|
|
|
|
+
|
|
|
|
+ #ifdef PICKING
|
|
|
|
+ gl_FragColor = vec4( color, 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>
|
|
|
|
+
|
|
|
|
+ var container, stats;
|
|
|
|
+ var camera, controls, scene, renderer;
|
|
|
|
+ var pickingData, pickingTexture, pickingScene;
|
|
|
|
+ var highlightBox;
|
|
|
|
+ var materialList = [];
|
|
|
|
+ var geometryList = [];
|
|
|
|
+ var geometrySize;
|
|
|
|
+ var mouse = new THREE.Vector2();
|
|
|
|
+ var scale = 1.03;
|
|
|
|
+
|
|
|
|
+ var loader = new THREE.JSONLoader();
|
|
|
|
+
|
|
|
|
+ //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();
|
|
|
|
+
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function clean(){
|
|
|
|
+
|
|
|
|
+ THREE.Cache.clear();
|
|
|
|
+
|
|
|
|
+ materialList.forEach( function( m ){
|
|
|
|
+ m.dispose();
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ geometryList.forEach( function( g ){
|
|
|
|
+ g.dispose();
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ scene = new THREE.Scene();
|
|
|
|
+ scene.add( camera );
|
|
|
|
+ scene.add( highlightBox );
|
|
|
|
+
|
|
|
|
+ pickingScene = new THREE.Scene();
|
|
|
|
+ pickingData = {};
|
|
|
|
+ materialList = [];
|
|
|
|
+ geometryList = [];
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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, false );
|
|
|
|
+
|
|
|
|
+ scale.x = scale.y = scale.z = Math.random() * 1;
|
|
|
|
+
|
|
|
|
+ matrix.compose( position, quaternion, scale );
|
|
|
|
+
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ }();
|
|
|
|
+
|
|
|
|
+ function initMesh(){
|
|
|
|
+
|
|
|
|
+ clean();
|
|
|
|
+
|
|
|
|
+ // make instances
|
|
|
|
+ loader.load( 'obj/Suzanne.js', function ( geo ) {
|
|
|
|
+
|
|
|
|
+ geo.computeBoundingBox();
|
|
|
|
+ geometrySize = geo.boundingBox.size();
|
|
|
|
+ geometryList.push( geo );
|
|
|
|
+
|
|
|
|
+ console.log( "method:", method );
|
|
|
|
+ console.log( "instanceCount:", instanceCount );
|
|
|
|
+
|
|
|
|
+ console.time( "init mesh" );
|
|
|
|
+
|
|
|
|
+ switch( method ){
|
|
|
|
+
|
|
|
|
+ case "merged":
|
|
|
|
+ makeMerged( geo );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case "instanced":
|
|
|
|
+ makeInstanced( geo );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case "singleMaterial":
|
|
|
|
+ makeSingleMaterial( geo );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case "multiMaterial":
|
|
|
|
+ makeMultiMaterial( geo );
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ render();
|
|
|
|
+
|
|
|
|
+ console.timeEnd( "init mesh", method );
|
|
|
|
+
|
|
|
|
+ console.log( "material count:", materialList.length );
|
|
|
|
+ console.log( "geometry count:", geometryList.length );
|
|
|
|
+ console.log( renderer.info.memory )
|
|
|
|
+ console.log( renderer.info.render )
|
|
|
|
+
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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: {
|
|
|
|
+ type: "c",
|
|
|
|
+ value: new THREE.Color(),
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ var pickingMaterial = new THREE.RawShaderMaterial( {
|
|
|
|
+ vertexShader: "#define PICKING\n" + vert,
|
|
|
|
+ fragmentShader: "#define PICKING\n" + frag,
|
|
|
|
+ uniforms: {
|
|
|
|
+ color: {
|
|
|
|
+ type: "c",
|
|
|
|
+ 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 );
|
|
|
|
+ randomizeMatrix( matrix );
|
|
|
|
+ object.applyMatrix( matrix );
|
|
|
|
+ var pickingObject = object.clone();
|
|
|
|
+
|
|
|
|
+ object.material = material.clone();
|
|
|
|
+ object.material.uniforms.color.value.setHex( Math.random() * 0xffffff );
|
|
|
|
+ materialList.push( object.material );
|
|
|
|
+
|
|
|
|
+ pickingObject.material = pickingMaterial.clone();
|
|
|
|
+ pickingObject.material.uniforms.color.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: {
|
|
|
|
+ type: "c",
|
|
|
|
+ value: new THREE.Color(),
|
|
|
|
+ updateFunction: function( uniform, camera, object ){
|
|
|
|
+ uniform.value.setHex( object.userData.color );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } );
|
|
|
|
+ material.hasDynamicUniforms = true;
|
|
|
|
+ materialList.push( material );
|
|
|
|
+
|
|
|
|
+ var pickingMaterial = new THREE.RawShaderMaterial( {
|
|
|
|
+ vertexShader: "#define PICKING\n" + vert,
|
|
|
|
+ fragmentShader: "#define PICKING\n" + frag,
|
|
|
|
+ uniforms: {
|
|
|
|
+ color: {
|
|
|
|
+ type: "c",
|
|
|
|
+ value: new THREE.Color(),
|
|
|
|
+ updateFunction: function( uniform, camera, object ){
|
|
|
|
+ uniform.value.setHex( object.userData.color );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } );
|
|
|
|
+ pickingMaterial.hasDynamicUniforms = true;
|
|
|
|
+ materialList.push( pickingMaterial );
|
|
|
|
+
|
|
|
|
+ // geometry / mesh
|
|
|
|
+
|
|
|
|
+ var matrix = new THREE.Matrix4();
|
|
|
|
+
|
|
|
|
+ for ( var i = 0; i < instanceCount; i ++ ) {
|
|
|
|
+
|
|
|
|
+ var object = new THREE.Mesh( geo, material );
|
|
|
|
+ randomizeMatrix( matrix );
|
|
|
|
+ object.applyMatrix( matrix );
|
|
|
|
+ var pickingObject = object.clone();
|
|
|
|
+
|
|
|
|
+ object.material = material;
|
|
|
|
+ object.userData[ "color" ] = Math.random() * 0xffffff;
|
|
|
|
+
|
|
|
|
+ pickingObject.material = pickingMaterial;
|
|
|
|
+ pickingObject.userData[ "color" ] = i + 1;
|
|
|
|
+
|
|
|
|
+ pickingData[ i + 1 ] = object;
|
|
|
|
+
|
|
|
|
+ scene.add( object );
|
|
|
|
+ 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 bgeo = new THREE.BufferGeometry().fromGeometry( geo );
|
|
|
|
+ geometryList.push( bgeo );
|
|
|
|
+
|
|
|
|
+ var mgeo = new THREE.BufferGeometry();
|
|
|
|
+ geometryList.push( mgeo );
|
|
|
|
+
|
|
|
|
+ var pos = bgeo.attributes.position;
|
|
|
|
+ var posLen = bgeo.attributes.position.count * 3;
|
|
|
|
+ var vertices = new THREE.BufferAttribute(
|
|
|
|
+ new Float32Array( instanceCount * posLen ), 3
|
|
|
|
+ );
|
|
|
|
+ var matrix = new THREE.Matrix4();
|
|
|
|
+ for ( var i = 0, ul = instanceCount; i < ul; i++ ) {
|
|
|
|
+ randomizeMatrix( matrix );
|
|
|
|
+ var object = new THREE.Object3D();
|
|
|
|
+ object.applyMatrix( matrix );
|
|
|
|
+ pickingData[ i + 1 ] = object;
|
|
|
|
+ vertices.set( pos.array, i * posLen );
|
|
|
|
+ matrix.applyToVector3Array( vertices.array, i * posLen, posLen )
|
|
|
|
+ }
|
|
|
|
+ mgeo.addAttribute( '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.addAttribute( '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.addAttribute( '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 bgeo = new THREE.BufferGeometry().fromGeometry( geo );
|
|
|
|
+ geometryList.push( bgeo );
|
|
|
|
+
|
|
|
|
+ var igeo = new THREE.InstancedBufferGeometry();
|
|
|
|
+ geometryList.push( igeo );
|
|
|
|
+
|
|
|
|
+ var vertices = bgeo.attributes.position.clone();
|
|
|
|
+ igeo.addAttribute( 'position', vertices );
|
|
|
|
+
|
|
|
|
+ // var matrices = new THREE.InstancedBufferAttribute(
|
|
|
|
+ // new Float32Array( instanceCount * 16 ), 16, 1
|
|
|
|
+ // );
|
|
|
|
+ var mcol0 = new THREE.InstancedBufferAttribute(
|
|
|
|
+ new Float32Array( instanceCount * 3 ), 3, 1
|
|
|
|
+ );
|
|
|
|
+ var mcol1 = new THREE.InstancedBufferAttribute(
|
|
|
|
+ new Float32Array( instanceCount * 3 ), 3, 1
|
|
|
|
+ );
|
|
|
|
+ var mcol2 = new THREE.InstancedBufferAttribute(
|
|
|
|
+ new Float32Array( instanceCount * 3 ), 3, 1
|
|
|
|
+ );
|
|
|
|
+ var mcol3 = new THREE.InstancedBufferAttribute(
|
|
|
|
+ new Float32Array( instanceCount * 3 ), 3, 1
|
|
|
|
+ );
|
|
|
|
+ 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();
|
|
|
|
+ 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.addAttribute( 'matrix', matrices );
|
|
|
|
+ igeo.addAttribute( 'mcol0', mcol0 );
|
|
|
|
+ igeo.addAttribute( 'mcol1', mcol1 );
|
|
|
|
+ igeo.addAttribute( 'mcol2', mcol2 );
|
|
|
|
+ igeo.addAttribute( 'mcol3', mcol3 );
|
|
|
|
+
|
|
|
|
+ var randCol = function(){ return Math.random(); };
|
|
|
|
+ var colors = new THREE.InstancedBufferAttribute(
|
|
|
|
+ new Float32Array( instanceCount * 3 ), 3, 1
|
|
|
|
+ );
|
|
|
|
+ for ( var i = 0, ul = colors.count; i < ul; i++ ) {
|
|
|
|
+ colors.setXYZ( i, randCol(), randCol(), randCol() );
|
|
|
|
+ }
|
|
|
|
+ igeo.addAttribute( 'color', colors );
|
|
|
|
+
|
|
|
|
+ var col = new THREE.Color();
|
|
|
|
+ var pickingColors = new THREE.InstancedBufferAttribute(
|
|
|
|
+ new Float32Array( instanceCount * 3 ), 3, 1
|
|
|
|
+ );
|
|
|
|
+ for ( var i = 0, ul = pickingColors.count; i < ul; i++ ) {
|
|
|
|
+ col.setHex( i + 1 );
|
|
|
|
+ pickingColors.setXYZ( i, col.r, col.g, col.b );
|
|
|
|
+ }
|
|
|
|
+ igeo.addAttribute( '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 texture
|
|
|
|
+
|
|
|
|
+ pickingTexture = new THREE.WebGLRenderTarget(
|
|
|
|
+ window.innerWidth, window.innerHeight
|
|
|
|
+ );
|
|
|
|
+ pickingTexture.generateMipmaps = false;
|
|
|
|
+ pickingTexture.minFilter = THREE.NearestFilter;
|
|
|
|
+
|
|
|
|
+ // highlight box
|
|
|
|
+
|
|
|
|
+ highlightBox = new THREE.Mesh(
|
|
|
|
+ new THREE.BoxGeometry( 1, 1, 1 ),
|
|
|
|
+ new THREE.MeshLambertMaterial( {
|
|
|
|
+ emissive: 0xffff00,
|
|
|
|
+ transparent: true,
|
|
|
|
+ opacity: 0.5,
|
|
|
|
+ side: THREE.FrontSide
|
|
|
|
+ } )
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // renderer
|
|
|
|
+
|
|
|
|
+ container = document.getElementById( "container" );
|
|
|
|
+ renderer = new THREE.WebGLRenderer( {
|
|
|
|
+ antialias: true,
|
|
|
|
+ alpha: true
|
|
|
|
+ } );
|
|
|
|
+ if ( renderer.extensions.get( 'ANGLE_instanced_arrays' ) === false ) {
|
|
|
|
+ document.getElementById( "notSupported" ).style.display = "";
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ renderer.setClearColor( 0xffffff );
|
|
|
|
+ renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
|
+ renderer.sortObjects = false;
|
|
|
|
+ container.appendChild( renderer.domElement );
|
|
|
|
+
|
|
|
|
+ if ( renderer.extensions.get( 'ANGLE_instanced_arrays' ) === false ) {
|
|
|
|
+ throw 'ANGLE_instanced_arrays not supported';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // controls
|
|
|
|
+
|
|
|
|
+ controls = new THREE.TrackballControls(
|
|
|
|
+ camera, renderer.domElement
|
|
|
|
+ );
|
|
|
|
+ controls.staticMoving = true;
|
|
|
|
+
|
|
|
|
+ // stats
|
|
|
|
+
|
|
|
|
+ stats = new Stats();
|
|
|
|
+ stats.domElement.style.position = 'absolute';
|
|
|
|
+ stats.domElement.style.top = '0px';
|
|
|
|
+ container.appendChild( stats.domElement );
|
|
|
|
+
|
|
|
|
+ // 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( event ) {
|
|
|
|
+
|
|
|
|
+ camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
|
+
|
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function animate() {
|
|
|
|
+
|
|
|
|
+ if( doAnimate ){
|
|
|
|
+ requestAnimationFrame( animate );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ controls.update();
|
|
|
|
+ stats.update();
|
|
|
|
+
|
|
|
|
+ render();
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function pick() {
|
|
|
|
+
|
|
|
|
+ // render the picking scene off-screen
|
|
|
|
+
|
|
|
|
+ renderer.render( pickingScene, camera, pickingTexture );
|
|
|
|
+
|
|
|
|
+ // read the pixel under the mouse from the texture
|
|
|
|
+
|
|
|
|
+ renderer.readRenderTargetPixels(
|
|
|
|
+ pickingTexture,
|
|
|
|
+ mouse.x,
|
|
|
|
+ pickingTexture.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.render( scene, camera );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ </script>
|
|
|
|
+
|
|
|
|
+</body>
|
|
|
|
+</html>
|