Jelajahi Sumber

Example: Update GPU picking to use integer Ids, target (#25983)

* Use an integer buffer

* Clean up

* Cleanup

* Remove unnecessary int cast

* Remove need for disabling autoclear
Garrett Johnson 2 tahun lalu
induk
melakukan
b46de21c1b
1 mengubah file dengan 88 tambahan dan 53 penghapusan
  1. 88 53
      examples/webgl_interactive_cubes_gpu.html

+ 88 - 53
examples/webgl_interactive_cubes_gpu.html

@@ -54,6 +54,7 @@
 
 			const pointer = new THREE.Vector2();
 			const offset = new THREE.Vector3( 10, 10, 10 );
+			const clearColor = new THREE.Color();
 
 			init();
 			animate();
@@ -68,17 +69,66 @@
 				scene = new THREE.Scene();
 				scene.background = new THREE.Color( 0xffffff );
 
-				pickingScene = new THREE.Scene();
-				pickingTexture = new THREE.WebGLRenderTarget( 1, 1 );
-
 				scene.add( new THREE.AmbientLight( 0x555555 ) );
 
 				const light = new THREE.SpotLight( 0xffffff, 1.5 );
 				light.position.set( 0, 500, 2000 );
 				scene.add( light );
 
-				const pickingMaterial = new THREE.MeshBasicMaterial( { vertexColors: true } );
-				const defaultMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff, flatShading: true, vertexColors: true, shininess: 0	} );
+				const defaultMaterial = new THREE.MeshPhongMaterial( {
+					color: 0xffffff,
+					flatShading: true,
+					vertexColors: true,
+					shininess: 0
+				} );
+
+				// set up the picking texture to use a 32 bit integer so we can write and read integer ids from it
+				pickingScene = new THREE.Scene();
+				pickingTexture = new THREE.WebGLRenderTarget( 1, 1, {
+
+					type: THREE.IntType,
+					format: THREE.RedIntegerFormat,
+					internalFormat: 'R32I',
+
+				} );
+				const pickingMaterial = new THREE.ShaderMaterial( {
+
+					glslVersion: THREE.GLSL3,
+
+					vertexShader: /* glsl */`
+						attribute int id;
+						flat varying int vid;
+						void main() {
+
+							vid = id;
+							gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+
+						}
+					`,
+
+					fragmentShader: /* glsl */`
+						layout(location = 0) out int out_id;
+						flat varying int vid;
+
+						void main() {
+
+							out_id = vid;
+
+						}
+					`,
+
+				} );
+
+				function applyId( geometry, id ) {
+
+					const position = geometry.attributes.position;
+					const array = new Int32Array( position.count );
+					array.fill( id );
+
+					const bufferAttribute = new THREE.BufferAttribute( array, 1, false );
+					geometry.setAttribute( 'id', bufferAttribute );
+
+				}
 
 				function applyVertexColors( geometry, color ) {
 
@@ -95,16 +145,14 @@
 
 				}
 
-				const geometriesDrawn = [];
-				const geometriesPicking = [];
-
+				const geometries = [];
 				const matrix = new THREE.Matrix4();
 				const quaternion = new THREE.Quaternion();
 				const color = new THREE.Color();
 
 				for ( let i = 0; i < 5000; i ++ ) {
 
-					let geometry = new THREE.BoxGeometry();
+					const geometry = new THREE.BoxGeometry();
 
 					const position = new THREE.Vector3();
 					position.x = Math.random() * 10000 - 5000;
@@ -126,19 +174,12 @@
 
 					geometry.applyMatrix4( matrix );
 
-					// give the geometry's vertices a random color, to be displayed
-
+					// give the geometry's vertices a random color to be displayed and an integer
+					// identifier as a vertex attribute so boxes can be identified after being merged.
 					applyVertexColors( geometry, color.setHex( Math.random() * 0xffffff ) );
+					applyId( geometry, i );
 
-					geometriesDrawn.push( geometry );
-
-					geometry = geometry.clone();
-
-					// give the geometry's vertices a color corresponding to the "id"
-
-					applyVertexColors( geometry, color.setHex( i, THREE.NoColorSpace ) );
-
-					geometriesPicking.push( geometry );
+					geometries.push( geometry );
 
 					pickingData[ i ] = {
 
@@ -150,15 +191,14 @@
 
 				}
 
-				const objects = new THREE.Mesh( BufferGeometryUtils.mergeGeometries( geometriesDrawn ), defaultMaterial );
-				scene.add( objects );
-
-				pickingScene.add( new THREE.Mesh( BufferGeometryUtils.mergeGeometries( geometriesPicking ), pickingMaterial ) );
+				const mergedGeometry = BufferGeometryUtils.mergeGeometries( geometries );
+				scene.add( new THREE.Mesh( mergedGeometry, defaultMaterial ) );
+				pickingScene.add( new THREE.Mesh( mergedGeometry, pickingMaterial ) );
 
 				highlightBox = new THREE.Mesh(
 					new THREE.BoxGeometry(),
-					new THREE.MeshLambertMaterial( { color: 0xffff00 }
-					) );
+					new THREE.MeshLambertMaterial( { color: 0xffff00 } )
+				);
 				scene.add( highlightBox );
 
 				renderer = new THREE.WebGLRenderer( { antialias: true } );
@@ -202,46 +242,41 @@
 
 			function pick() {
 
-				//render the picking scene off-screen
-
+				// render the picking scene off-screen
 				// set the view offset to represent just a single pixel under the mouse
-
-				camera.setViewOffset( renderer.domElement.width, renderer.domElement.height, pointer.x * window.devicePixelRatio | 0, pointer.y * window.devicePixelRatio | 0, 1, 1 );
+				const dpr = window.devicePixelRatio;
+				camera.setViewOffset(
+					renderer.domElement.width, renderer.domElement.height,
+					Math.floor( pointer.x * dpr ), Math.floor( pointer.y * dpr ),
+					1, 1
+				);
 
 				// render the scene
-
 				renderer.setRenderTarget( pickingTexture );
+
+				// clear the background to - 1 meaning no item was hit
+				clearColor.setRGB( - 1, - 1, - 1 );
+				renderer.setClearColor( clearColor );
 				renderer.render( pickingScene, camera );
 
 				// clear the view offset so rendering returns to normal
-
 				camera.clearViewOffset();
 
-				//create buffer for reading single pixel
-
-				const pixelBuffer = new Uint8Array( 4 );
-
-				//read the pixel
+				// create buffer for reading single pixel
+				const pixelBuffer = new Int32Array( 1 );
 
+				// read the pixel
 				renderer.readRenderTargetPixels( pickingTexture, 0, 0, 1, 1, pixelBuffer );
 
-				//interpret the pixel as an ID
-
-				const id = ( pixelBuffer[ 0 ] << 16 ) | ( pixelBuffer[ 1 ] << 8 ) | ( pixelBuffer[ 2 ] );
-				const data = pickingData[ id ];
+				const id = pixelBuffer[ 0 ];
+				if ( id !== - 1 ) {
 
-				if ( data ) {
-
-					//move our highlightBox so that it surrounds the picked object
-
-					if ( data.position && data.rotation && data.scale ) {
-
-						highlightBox.position.copy( data.position );
-						highlightBox.rotation.copy( data.rotation );
-						highlightBox.scale.copy( data.scale ).add( offset );
-						highlightBox.visible = true;
-
-					}
+					// move our highlightBox so that it surrounds the picked object
+					const data = pickingData[ id ];
+					highlightBox.position.copy( data.position );
+					highlightBox.rotation.copy( data.rotation );
+					highlightBox.scale.copy( data.scale ).add( offset );
+					highlightBox.visible = true;
 
 				} else {