瀏覽代碼

Merge pull request #17505 from mrdoob/instancing

Implemented THREE.InstancedMesh
Mr.doob 5 年之前
父節點
當前提交
99970ca6ef

+ 1 - 0
examples/files.js

@@ -47,6 +47,7 @@ var files = {
 		"webgl_geometry_text_shapes",
 		"webgl_geometry_text_stroke",
 		"webgl_helpers",
+		"webgl_instancing",
 		"webgl_interactive_buffergeometry",
 		"webgl_interactive_cubes",
 		"webgl_interactive_cubes_gpu",

+ 132 - 0
examples/webgl_instancing.html

@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - instancing</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>
+
+		<script type="module">
+
+			import * as THREE from '../build/three.module.js';
+
+			import Stats from './jsm/libs/stats.module.js';
+
+			var camera, scene, renderer, stats;
+
+			var mesh;
+			var amount = 12;
+			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, 1000 );
+				camera.position.set( 10, 10, 10 );
+				camera.lookAt( 0, 0, 0 );
+
+				scene = new THREE.Scene();
+
+				var material = new THREE.MeshNormalMaterial();
+				// check overdraw
+				// var material = new THREE.MeshBasicMaterial( { color: 0xff0000, opacity: 0.1, transparent: true } );
+
+
+				var loader = new THREE.BufferGeometryLoader();
+				loader.load( 'models/json/suzanne_buffergeometry.json', function ( geometry ) {
+
+					geometry.computeVertexNormals();
+					geometry.scale( 0.5, 0.5, 0.5 );
+
+					mesh = new THREE.InstancedMesh( geometry, material, count );
+					scene.add( mesh );
+
+				} );
+
+				//
+
+				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;
+
+					for ( var x = 0; x < amount; x ++ ) {
+
+						for ( var y = 0; y < amount; y ++ ) {
+
+							for ( var z = 0; z < amount; z ++ ) {
+
+								dummy.position.set( 6 - x, 6 - y, 6 - 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 );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 1 - 0
src/Three.js

@@ -18,6 +18,7 @@ export { SkinnedMesh } from './objects/SkinnedMesh.js';
 export { Skeleton } from './objects/Skeleton.js';
 export { Bone } from './objects/Bone.js';
 export { Mesh } from './objects/Mesh.js';
+export { InstancedMesh } from './objects/InstancedMesh.js';
 export { LineSegments } from './objects/LineSegments.js';
 export { LineLoop } from './objects/LineLoop.js';
 export { Line } from './objects/Line.js';

+ 34 - 0
src/objects/InstancedMesh.js

@@ -0,0 +1,34 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+import { BufferAttribute } from '../core/BufferAttribute.js';
+import { Mesh } from './Mesh.js';
+
+function InstancedMesh( geometry, material, count ) {
+
+	Mesh.call( this, geometry, material );
+
+	this.instanceMatrix = new BufferAttribute( new Float32Array( count * 16 ), 16 );
+
+}
+
+InstancedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
+
+	constructor: InstancedMesh,
+
+	isInstancedMesh: true,
+
+	raycast: function () {},
+
+	setMatrixAt: function ( index, matrix ) {
+
+		matrix.toArray( this.instanceMatrix.array, index * 16 );
+
+	},
+
+	updateMorphTargets: function () {}
+
+} );
+
+export { InstancedMesh };

+ 33 - 16
src/renderers/WebGLRenderer.js

@@ -258,7 +258,7 @@ function WebGLRenderer( parameters ) {
 
 		capabilities = new WebGLCapabilities( _gl, extensions, parameters );
 
-		if ( ! capabilities.isWebGL2 ) {
+		if ( capabilities.isWebGL2 === false ) {
 
 			extensions.get( 'WEBGL_depth_texture' );
 			extensions.get( 'OES_texture_float' );
@@ -283,7 +283,7 @@ function WebGLRenderer( parameters ) {
 		textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info );
 		attributes = new WebGLAttributes( _gl );
 		geometries = new WebGLGeometries( _gl, attributes, info );
-		objects = new WebGLObjects( geometries, info );
+		objects = new WebGLObjects( _gl, geometries, attributes, info );
 		morphtargets = new WebGLMorphtargets( _gl );
 		programCache = new WebGLPrograms( _this, extensions, capabilities );
 		renderLists = new WebGLRenderLists();
@@ -765,7 +765,7 @@ function WebGLRenderer( parameters ) {
 
 		if ( updateBuffers ) {
 
-			setupVertexAttributes( material, program, geometry );
+			setupVertexAttributes( object, geometry, material, program );
 
 			if ( index !== null ) {
 
@@ -831,7 +831,6 @@ function WebGLRenderer( parameters ) {
 
 			}
 
-
 		} else if ( object.isLine ) {
 
 			var lineWidth = material.linewidth;
@@ -864,13 +863,13 @@ function WebGLRenderer( parameters ) {
 
 		}
 
-		if ( geometry && geometry.isInstancedBufferGeometry ) {
+		if ( object.isInstancedMesh ) {
 
-			if ( geometry.maxInstancedCount > 0 ) {
+			renderer.renderInstances( geometry, drawStart, drawCount, object.instanceMatrix.count );
 
-				renderer.renderInstances( geometry, drawStart, drawCount );
+		} else if ( geometry.isInstancedBufferGeometry ) {
 
-			}
+			renderer.renderInstances( geometry, drawStart, drawCount, geometry.maxInstancedCount );
 
 		} else {
 
@@ -880,16 +879,11 @@ function WebGLRenderer( parameters ) {
 
 	};
 
-	function setupVertexAttributes( material, program, geometry ) {
-
-		if ( geometry && geometry.isInstancedBufferGeometry && ! capabilities.isWebGL2 ) {
+	function setupVertexAttributes( object, geometry, material, program ) {
 
-			if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) {
+		if ( capabilities.isWebGL2 === false && ( object.isInstancedMesh || geometry.isInstancedBufferGeometry ) ) {
 
-				console.error( 'THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );
-				return;
-
-			}
+			if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) return;
 
 		}
 
@@ -972,6 +966,29 @@ function WebGLRenderer( parameters ) {
 
 					}
 
+				} else if ( name === 'instanceMatrix' ) {
+
+					var attribute = attributes.get( object.instanceMatrix );
+
+					// TODO Attribute may not be available on context restore
+
+					if ( attribute === undefined ) continue;
+
+					var buffer = attribute.buffer;
+					var type = attribute.type;
+
+					state.enableAttributeAndDivisor( programAttribute + 0, 1 );
+					state.enableAttributeAndDivisor( programAttribute + 1, 1 );
+					state.enableAttributeAndDivisor( programAttribute + 2, 1 );
+					state.enableAttributeAndDivisor( programAttribute + 3, 1 );
+
+					_gl.bindBuffer( _gl.ARRAY_BUFFER, buffer );
+
+					_gl.vertexAttribPointer( programAttribute + 0, 4, type, false, 64, 0 );
+					_gl.vertexAttribPointer( programAttribute + 1, 4, type, false, 64, 16 );
+					_gl.vertexAttribPointer( programAttribute + 2, 4, type, false, 64, 32 );
+					_gl.vertexAttribPointer( programAttribute + 3, 4, type, false, 64, 48 );
+
 				} else if ( materialDefaultAttributeValues !== undefined ) {
 
 					var value = materialDefaultAttributeValues[ name ];

+ 9 - 1
src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl.js

@@ -1,5 +1,13 @@
 export default /* glsl */`
-vec3 transformedNormal = normalMatrix * objectNormal;
+vec3 transformedNormal = objectNormal;
+
+#ifdef USE_INSTANCING
+
+	transformedNormal = mat3( instanceMatrix ) * transformedNormal;
+
+#endif
+
+transformedNormal = normalMatrix * transformedNormal;
 
 #ifdef FLIP_SIDED
 

+ 9 - 1
src/renderers/shaders/ShaderChunk/project_vertex.glsl.js

@@ -1,5 +1,13 @@
 export default /* glsl */`
-vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );
+vec4 mvPosition = vec4( transformed, 1.0 );
+
+#ifdef USE_INSTANCING
+
+	mvPosition = instanceMatrix * mvPosition;
+
+#endif
+
+mvPosition = modelViewMatrix * mvPosition;
 
 gl_Position = projectionMatrix * mvPosition;
 `;

+ 9 - 1
src/renderers/shaders/ShaderChunk/worldpos_vertex.glsl.js

@@ -1,7 +1,15 @@
 export default /* glsl */`
 #if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )
 
-	vec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );
+	vec4 worldPosition = vec4( transformed, 1.0 );
+
+	#ifdef USE_INSTANCING
+
+		worldPosition = instanceMatrix * worldPosition;
+
+	#endif
+
+	worldPosition = modelMatrix * worldPosition;
 
 #endif
 `;

+ 5 - 3
src/renderers/webgl/WebGLBufferRenderer.js

@@ -20,7 +20,9 @@ function WebGLBufferRenderer( gl, extensions, info, capabilities ) {
 
 	}
 
-	function renderInstances( geometry, start, count ) {
+	function renderInstances( geometry, start, count, primcount ) {
+
+		if ( primcount === 0 ) return;
 
 		var extension, methodName;
 
@@ -43,9 +45,9 @@ function WebGLBufferRenderer( gl, extensions, info, capabilities ) {
 
 		}
 
-		extension[ methodName ]( mode, start, count, geometry.maxInstancedCount );
+		extension[ methodName ]( mode, start, count, primcount );
 
-		info.update( count, mode, geometry.maxInstancedCount );
+		info.update( count, mode, primcount );
 
 	}
 

+ 5 - 3
src/renderers/webgl/WebGLIndexedBufferRenderer.js

@@ -29,7 +29,9 @@ function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) {
 
 	}
 
-	function renderInstances( geometry, start, count ) {
+	function renderInstances( geometry, start, count, primcount ) {
+
+		if ( primcount === 0 ) return;
 
 		var extension, methodName;
 
@@ -52,9 +54,9 @@ function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) {
 
 		}
 
-		extension[ methodName ]( mode, count, type, start * bytesPerElement, geometry.maxInstancedCount );
+		extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount );
 
-		info.update( count, mode, geometry.maxInstancedCount );
+		info.update( count, mode, primcount );
 
 	}
 

+ 7 - 1
src/renderers/webgl/WebGLObjects.js

@@ -2,7 +2,7 @@
  * @author mrdoob / http://mrdoob.com/
  */
 
-function WebGLObjects( geometries, info ) {
+function WebGLObjects( gl, geometries, attributes, info ) {
 
 	var updateList = {};
 
@@ -29,6 +29,12 @@ function WebGLObjects( geometries, info ) {
 
 		}
 
+		if ( object.isInstancedMesh ) {
+
+			attributes.update( object.instanceMatrix, gl.ARRAY_BUFFER );
+
+		}
+
 		return buffergeometry;
 
 	}

+ 7 - 0
src/renderers/webgl/WebGLProgram.js

@@ -441,6 +441,7 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters
 
 			customDefines,
 
+			parameters.instancing ? '#define USE_INSTANCING' : '',
 			parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '',
 
 			'#define GAMMA_FACTOR ' + gammaFactorDefine,
@@ -511,6 +512,12 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters
 
 			].join( '\n' ),
 
+			'#ifdef USE_INSTANCING',
+
+			' attribute mat4 instanceMatrix;',
+
+			'#endif',
+
 			'attribute vec3 position;',
 			'attribute vec3 normal;',
 			'attribute vec2 uv;',

+ 7 - 2
src/renderers/webgl/WebGLPrograms.js

@@ -28,7 +28,8 @@ function WebGLPrograms( renderer, extensions, capabilities ) {
 	};
 
 	var parameterNames = [
-		"precision", "supportsVertexTextures", "map", "mapEncoding", "matcap", "matcapEncoding", "envMap", "envMapMode", "envMapEncoding",
+		"precision", "supportsVertexTextures", "instancing",
+		"map", "mapEncoding", "matcap", "matcapEncoding", "envMap", "envMapMode", "envMapEncoding",
 		"lightMap", "aoMap", "emissiveMap", "emissiveMapEncoding", "bumpMap", "normalMap", "objectSpaceNormalMap", "tangentSpaceNormalMap", "clearcoatNormalMap", "displacementMap", "specularMap",
 		"roughnessMap", "metalnessMap", "gradientMap",
 		"alphaMap", "combine", "vertexColors", "vertexTangents", "fog", "useFog", "fogExp2",
@@ -134,10 +135,14 @@ function WebGLPrograms( renderer, extensions, capabilities ) {
 
 		var parameters = {
 
+			isWebGL2: capabilities.isWebGL2,
+
 			shaderID: shaderID,
 
 			precision: precision,
-			isWebGL2: capabilities.isWebGL2,
+
+			instancing: object.isInstancedMesh === true,
+
 			supportsVertexTextures: capabilities.vertexTextures,
 			outputEncoding: getTextureEncodingFromMap( ( ! currentRenderTarget ) ? null : currentRenderTarget.texture, renderer.gammaOutput ),
 			map: !! material.map,