Skip to content
1 change: 1 addition & 0 deletions examples/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
129 changes: 129 additions & 0 deletions examples/webgl_instancing.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<!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 amount = 12;
var count = Math.pow( amount, 3 );

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 geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshNormalMaterial();
// var material = new THREE.MeshBasicMaterial( { color: 0xff0000, opacity: 0.1, transparent: true } ); // overdraw

var mesh = new THREE.InstancedMesh( geometry, material, count );
scene.add( mesh );

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.geometry = geometry;

} );

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should have only one geometry pushed to the GPU.

This works.

var material = new THREE.MeshNormalMaterial();

new THREE.BufferGeometryLoader().load( 'models/json/suzanne_buffergeometry.json', function ( geometry ) {

	geometry.computeVertexNormals();
	geometry.scale( 0.5, 0.5, 0.5 );

	var mesh = new THREE.InstancedMesh( geometry, material, count );
	scene.add( mesh );

} );

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example is still work in progress but... it actually shows that you can easily change geometries at any point, which is something that other PRs didn't make it easy.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then, I guess you should dispose of the old geometry...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, users copy these examples, so I still would suggest using the conventional pattern I posted above, and then add a GUI that allows the user to switch the geometry and/or material (if you feel it is important to demonstrate that capability). JMO.

//

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() {

var time = Date.now() * 0.001;

var mesh = scene.children[ 0 ];
mesh.rotation.x = Math.sin( time / 4 );
mesh.rotation.y = Math.sin( time / 2 );

var dummy = new THREE.Object3D();

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 change: 1 addition & 0 deletions src/Three.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
34 changes: 34 additions & 0 deletions src/objects/InstancedMesh.js
Original file line number Diff line number Diff line change
@@ -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 };
49 changes: 33 additions & 16 deletions src/renderers/WebGLRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' );
Expand All @@ -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();
Expand Down Expand Up @@ -765,7 +765,7 @@ function WebGLRenderer( parameters ) {

if ( updateBuffers ) {

setupVertexAttributes( material, program, geometry );
setupVertexAttributes( object, geometry, material, program );

if ( index !== null ) {

Expand Down Expand Up @@ -831,7 +831,6 @@ function WebGLRenderer( parameters ) {

}


} else if ( object.isLine ) {

var lineWidth = material.linewidth;
Expand Down Expand Up @@ -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 {

Expand All @@ -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;

}

Expand Down Expand Up @@ -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 ];
Expand Down
10 changes: 9 additions & 1 deletion src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl.js
Original file line number Diff line number Diff line change
@@ -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

Expand Down
10 changes: 9 additions & 1 deletion src/renderers/shaders/ShaderChunk/project_vertex.glsl.js
Original file line number Diff line number Diff line change
@@ -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;
`;
10 changes: 9 additions & 1 deletion src/renderers/shaders/ShaderChunk/worldpos_vertex.glsl.js
Original file line number Diff line number Diff line change
@@ -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
`;
8 changes: 5 additions & 3 deletions src/renderers/webgl/WebGLBufferRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 );

}

Expand Down
8 changes: 5 additions & 3 deletions src/renderers/webgl/WebGLIndexedBufferRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 );

}

Expand Down
8 changes: 7 additions & 1 deletion src/renderers/webgl/WebGLObjects.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @author mrdoob / http://mrdoob.com/
*/

function WebGLObjects( geometries, info ) {
function WebGLObjects( gl, geometries, attributes, info ) {

var updateList = {};

Expand All @@ -29,6 +29,12 @@ function WebGLObjects( geometries, info ) {

}

if ( object.isInstancedMesh ) {

attributes.update( object.instanceMatrix, gl.ARRAY_BUFFER );

}

return buffergeometry;

}
Expand Down
Loading