diff --git a/examples/files.js b/examples/files.js
index 15fff9e71ddecf..dca4335e6f29e2 100644
--- a/examples/files.js
+++ b/examples/files.js
@@ -1,5 +1,7 @@
var files = {
"webgl": [
+
+ "webgl_instanced_mesh",
"webgl_animation_cloth",
"webgl_animation_keyframes_json",
"webgl_animation_scene",
diff --git a/examples/webgl_instanced_mesh.html b/examples/webgl_instanced_mesh.html
new file mode 100644
index 00000000000000..cb64dbc52a10b5
--- /dev/null
+++ b/examples/webgl_instanced_mesh.html
@@ -0,0 +1,463 @@
+
+
+
+ three.js webgl - InstancedMesh example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Three.js b/src/Three.js
index 86c86576d9b359..2a0b598da327ad 100644
--- a/src/Three.js
+++ b/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';
diff --git a/src/objects/InstancedMesh.js b/src/objects/InstancedMesh.js
new file mode 100644
index 00000000000000..99266dbdaf9ecc
--- /dev/null
+++ b/src/objects/InstancedMesh.js
@@ -0,0 +1,374 @@
+/**
+ * @author pailhead / www.dusanbosnjak.com
+ */
+
+import { Mesh } from './Mesh';
+import { Object3D } from '../core/Object3D';
+import { InstancedBufferGeometry } from '../core/InstancedBufferGeometry';
+import { InstancedBufferAttribute } from '../core/InstancedBufferAttribute';
+import { Matrix3 } from '../math/Matrix3';
+import { Matrix4 } from '../math/Matrix4';
+import { Vector3 } from '../math/Vector3';
+import { Quaternion } from '../math/Quaternion';
+
+import { MeshDepthMaterial } from '../materials/MeshDepthMaterial';
+import { RGBADepthPacking } from '../constants';
+import { ShaderMaterial } from '../materials/ShaderMaterial';
+import { UniformsUtils } from '../renderers/shaders/UniformsUtils';
+import { ShaderLib } from '../renderers/shaders/ShaderLib';
+
+
+//custom depth and distance material to be attached to meshes
+
+var depthMaterialTemplate = new MeshDepthMaterial();
+
+depthMaterialTemplate.depthPacking = RGBADepthPacking;
+
+depthMaterialTemplate.clipping = true;
+
+depthMaterialTemplate.defines = {
+
+ INSTANCE_TRANSFORM: ''
+
+};
+
+var
+
+ distanceShader = ShaderLib[ "distanceRGBA" ],
+ distanceUniforms = UniformsUtils.clone( distanceShader.uniforms ),
+ distanceMaterialTemplate = new ShaderMaterial( {
+ defines: {
+ 'USE_SHADOWMAP': '',
+ 'INSTANCE_TRANSFORM': ''
+ },
+ uniforms: distanceUniforms,
+ vertexShader: distanceShader.vertexShader,
+ fragmentShader: distanceShader.fragmentShader,
+ clipping: true
+ })
+;
+
+//main class
+function InstancedMesh ( bufferGeometry , material , numInstances , dynamic , colors , uniformScale ) {
+
+ Mesh.call( this , (new InstancedBufferGeometry()).copy( bufferGeometry ) ); //hacky for now
+
+ this._dynamic = !!dynamic; //TODO: set a bit mask for different attributes?
+
+ this._uniformScale = !!uniformScale;
+
+ this._colors = !!colors;
+
+ this.numInstances = numInstances;
+
+ this._setAttributes();
+
+ /**
+ * use the setter to decorate this material
+ * this is in lieu of changing the renderer
+ * WebGLRenderer injects stuff like this
+ */
+ this.material = material.clone();
+
+ this.frustumCulled = false; //you can uncheck this if you generate your own bounding info
+
+ //make it work with depth effects
+ this.customDepthMaterial = depthMaterialTemplate;
+
+ this.customDistanceMaterial = distanceMaterialTemplate;
+
+}
+
+InstancedMesh.prototype = Object.create( Mesh.prototype );
+
+InstancedMesh.constructor = InstancedMesh;
+
+//this is kinda gnarly, done in order to avoid setting these defines in the WebGLRenderer (it manages most if not all of the define flags)
+Object.defineProperties( InstancedMesh.prototype , {
+
+ 'material': {
+
+ set: function( m ){
+
+ /**
+ * whenever a material is set, decorate it,
+ * if a material used with regular geometry is passed,
+ * it will mutate it which is bad mkay
+ *
+ * either flag Material with these instance properties:
+ *
+ * "i want to create a RED PLASTIC material that will
+ * be INSTANCED and i know it will be used on clones
+ * that are known to be UNIFORMly scaled"
+ * (also figure out where dynamic fits here)
+ *
+ * or check here if the material has INSTANCE_TRANSFORM
+ * define set, if not, clone, document that it breaks reference
+ * or do a shallow copy or something
+ *
+ * or something else?
+ */
+ m = m.clone();
+
+ if ( m.defines ) {
+
+ m.defines.INSTANCE_TRANSFORM = '';
+
+ if ( this._uniformScale ) m.defines.INSTANCE_UNIFORM = ''; //an optimization, should avoid doing an expensive matrix inverse in the shader
+ else delete m.defines['INSTANCE_UNIFORM'];
+
+ if ( this._colors ) m.defines.INSTANCE_COLOR = '';
+ else delete m.defines['INSTANCE_COLOR'];
+ }
+
+ else{
+
+ m.defines = { INSTANCE_TRANSFORM: '' };
+
+ if ( this._uniformScale ) m.defines.INSTANCE_UNIFORM = '';
+ if ( this._colors ) m.defines.INSTANCE_COLOR = '';
+ }
+
+ this._material = m;
+
+ },
+
+ get: function(){ return this._material; }
+
+ },
+
+ //force new attributes to be created when set?
+ 'numInstances': {
+
+ set: function( v ){
+
+ this._numInstances = v;
+
+ //reset buffers
+
+ this._setAttributes();
+
+ },
+
+ get: function(){ return this._numInstances; }
+
+ },
+
+ //do some auto-magic when BufferGeometry is set
+ //TODO: account for Geometry, or change this approach completely
+ 'geometry':{
+
+ set: function( g ){
+
+ //if its not already instanced attach buffers
+ if ( !!g.attributes.instancePosition ) {
+
+ this._geometry = new InstancedBufferGeometry();
+
+ this._setAttributes();
+
+ }
+
+ else
+
+ this._geometry = g;
+
+ },
+
+ get: function(){ return this._geometry; }
+
+ }
+
+});
+
+InstancedMesh.prototype.setPositionAt = function( index , position ){
+
+ this.geometry.attributes.instancePosition.setXYZ( index , position.x , position.y , position.z );
+
+};
+
+InstancedMesh.prototype.setQuaternionAt = function ( index , quat ) {
+
+ this.geometry.attributes.instanceQuaternion.setXYZW( index , quat.x , quat.y , quat.z , quat.w );
+
+};
+
+InstancedMesh.prototype.setScaleAt = function ( index , scale ) {
+
+ this.geometry.attributes.instanceScale.setXYZ( index , scale.x , scale.y , scale.z );
+
+};
+
+InstancedMesh.prototype.setColorAt = function ( index , color ) {
+
+ if( !this._colors ) {
+
+ console.warn( 'THREE.InstancedMesh: color not enabled');
+
+ return;
+
+ }
+
+ this.geometry.attributes.instanceColor.setXYZ(
+ index ,
+ Math.floor( color.r * 255 ),
+ Math.floor( color.g * 255 ),
+ Math.floor( color.b * 255 )
+ );
+
+};
+
+InstancedMesh.prototype.setColorAt = function ( index , color ) {
+
+ if( !this._colors ) {
+
+ console.warn( 'THREE.InstancedMesh: color not enabled');
+
+ return;
+
+ }
+
+ this.geometry.attributes.instanceColor.setXYZ(
+ index ,
+ Math.floor( color.r * 255 ),
+ Math.floor( color.g * 255 ),
+ Math.floor( color.b * 255 )
+ );
+
+};
+
+InstancedMesh.prototype.getPositionAt = function( index , position ){
+
+ var arr = this.geometry.attributes.instancePosition.array;
+
+ index *= 3;
+
+ return position ?
+
+ position.set( arr[index++], arr[index++], arr[index] ) :
+
+ new Vector3( arr[index++], arr[index++], arr[index] )
+ ;
+
+};
+
+InstancedMesh.prototype.getQuaternionAt = function ( index , quat ) {
+
+ var arr = this.geometry.attributes.instanceQuaternion.array;
+
+ index = index << 2;
+
+ return quat ?
+
+ quat.set( arr[index++], arr[index++], arr[index++], arr[index] ) :
+
+ new Quaternion( arr[index++], arr[index++], arr[index++], arr[index] )
+ ;
+
+};
+
+InstancedMesh.prototype.getScaleAt = function ( index , scale ) {
+
+ var arr = this.geometry.attributes.instanceScale.array;
+
+ index *= 3;
+
+ return scale ?
+
+ scale.set( arr[index++], arr[index++], arr[index] ) :
+
+ new Vector3( arr[index++], arr[index++], arr[index] )
+ ;
+
+};
+
+InstancedMesh.prototype.getColorAt = (function(){
+
+ var inv255 = 1/255;
+
+ return function ( index , color ) {
+
+ if( !this._colors ) {
+
+ console.warn( 'THREE.InstancedMesh: color not enabled');
+
+ return false;
+
+ }
+
+ var arr = this.geometry.attributes.instanceColor.array;
+
+ index *= 3;
+
+ return color ?
+
+ color.setRGB( arr[index++] * inv255, arr[index++] * inv255, arr[index] * inv255 ) :
+
+ new Vector3( arr[index++], arr[index++], arr[index] ).multiplyScalar( inv255 )
+ ;
+
+ };
+
+})()
+
+InstancedMesh.prototype.needsUpdate = function( attribute ){
+
+ switch ( attribute ){
+
+ case 'position' :
+
+ this.geometry.attributes.instancePosition.needsUpdate = true;
+
+ break;
+
+ case 'quaternion' :
+
+ this.geometry.attributes.instanceQuaternion.needsUpdate = true;
+
+ break;
+
+ case 'scale' :
+
+ this.geometry.attributes.instanceScale.needsUpdate = true;
+
+ break;
+
+ case 'colors' :
+
+ this.geometry.attributes.instanceColor.needsUpdate = true;
+
+ default:
+
+ this.geometry.attributes.instancePosition.needsUpdate = true;
+ this.geometry.attributes.instanceQuaternion.needsUpdate = true;
+ this.geometry.attributes.instanceScale.needsUpdate = true;
+ this.geometry.attributes.instanceColor.needsUpdate = true;
+
+ break;
+
+ }
+
+};
+
+InstancedMesh.prototype._setAttributes = function(){
+
+ this.geometry.addAttribute( 'instancePosition' , new InstancedBufferAttribute( new Float32Array( this.numInstances * 3 ) , 3 , 1 ) );
+ this.geometry.addAttribute( 'instanceQuaternion' , new InstancedBufferAttribute( new Float32Array( this.numInstances * 4 ) , 4 , 1 ) );
+ this.geometry.addAttribute( 'instanceScale' , new InstancedBufferAttribute( new Float32Array( this.numInstances * 3 ) , 3 , 1 ) );
+
+ //TODO: allow different combinations
+ this.geometry.attributes.instancePosition.dynamic = this._dynamic;
+ this.geometry.attributes.instanceQuaternion.dynamic = this._dynamic;
+ this.geometry.attributes.instanceScale.dynamic = this._dynamic;
+
+ if ( this._colors ){
+
+ this.geometry.addAttribute( 'instanceColor' , new InstancedBufferAttribute( new Uint8Array( this.numInstances * 3 ) , 3 , 1 ) );
+ this.geometry.attributes.instanceColor.normalized = true;
+ this.geometry.attributes.instanceColor.dynamic = this._dynamic;
+
+ }
+
+};
+
+export { InstancedMesh };
\ No newline at end of file
diff --git a/src/renderers/shaders/ShaderChunk/begin_vertex.glsl b/src/renderers/shaders/ShaderChunk/begin_vertex.glsl
index 9f681c6600faa8..29c32b1830afc1 100644
--- a/src/renderers/shaders/ShaderChunk/begin_vertex.glsl
+++ b/src/renderers/shaders/ShaderChunk/begin_vertex.glsl
@@ -1,2 +1,19 @@
+#ifndef INSTANCE_TRANSFORM
+
vec3 transformed = vec3( position );
+
+#else
+
+#ifndef INSTANCE_MATRIX
+
+ mat4 _instanceMatrix = getInstanceMatrix();
+
+ #define INSTANCE_MATRIX
+
+#endif
+
+vec3 transformed = ( _instanceMatrix * vec4( position , 1. )).xyz;
+
+#endif
+
diff --git a/src/renderers/shaders/ShaderChunk/color_fragment.glsl b/src/renderers/shaders/ShaderChunk/color_fragment.glsl
index 693fdf061c226f..31ff04baedcc52 100644
--- a/src/renderers/shaders/ShaderChunk/color_fragment.glsl
+++ b/src/renderers/shaders/ShaderChunk/color_fragment.glsl
@@ -2,4 +2,10 @@
diffuseColor.rgb *= vColor;
+#endif
+
+#ifdef INSTANCE_COLOR
+
+ diffuseColor.rgb *= vInstanceColor;
+
#endif
\ No newline at end of file
diff --git a/src/renderers/shaders/ShaderChunk/color_pars_fragment.glsl b/src/renderers/shaders/ShaderChunk/color_pars_fragment.glsl
index 4f59898b6ef7fd..6dfe8262779e70 100644
--- a/src/renderers/shaders/ShaderChunk/color_pars_fragment.glsl
+++ b/src/renderers/shaders/ShaderChunk/color_pars_fragment.glsl
@@ -3,3 +3,9 @@
varying vec3 vColor;
#endif
+
+#if defined( INSTANCE_COLOR )
+
+ varying vec3 vInstanceColor;
+
+#endif
diff --git a/src/renderers/shaders/ShaderChunk/color_vertex.glsl b/src/renderers/shaders/ShaderChunk/color_vertex.glsl
index 7bd534d9cf170f..802e9df44bd5d9 100644
--- a/src/renderers/shaders/ShaderChunk/color_vertex.glsl
+++ b/src/renderers/shaders/ShaderChunk/color_vertex.glsl
@@ -2,4 +2,10 @@
vColor.xyz = color.xyz;
+#endif
+
+#if defined( INSTANCE_COLOR ) && defined( INSTANCE_TRANSFORM )
+
+ vInstanceColor = instanceColor;
+
#endif
\ No newline at end of file
diff --git a/src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl b/src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl
index d9fb04ee0a892a..f0150d478a10a8 100644
--- a/src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl
+++ b/src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl
@@ -4,4 +4,30 @@
#endif
+#ifndef INSTANCE_TRANSFORM
+
vec3 transformedNormal = normalMatrix * objectNormal;
+
+#else
+
+
+
+#ifndef INSTANCE_MATRIX
+
+ mat4 _instanceMatrix = getInstanceMatrix();
+
+ #define INSTANCE_MATRIX
+
+#endif
+
+#ifndef INSTANCE_UNIFORM
+
+vec3 transformedNormal = transpose( inverse( mat3( modelViewMatrix * _instanceMatrix ) ) ) * objectNormal ;
+
+#else
+
+vec3 transformedNormal = ( modelViewMatrix * _instanceMatrix * vec4( objectNormal , 0.0 ) ).xyz;
+
+#endif
+
+#endif
\ No newline at end of file
diff --git a/src/renderers/shaders/ShaderChunk/uv_pars_vertex.glsl b/src/renderers/shaders/ShaderChunk/uv_pars_vertex.glsl
index d2e81dd526c1cc..808c970ff27cde 100644
--- a/src/renderers/shaders/ShaderChunk/uv_pars_vertex.glsl
+++ b/src/renderers/shaders/ShaderChunk/uv_pars_vertex.glsl
@@ -4,3 +4,61 @@
uniform vec4 offsetRepeat;
#endif
+
+//@author pailhead
+//for now the most convenient place to attach vert transformation logic in global scope ( before main() )
+#if defined( INSTANCE_TRANSFORM )
+
+mat3 inverse(mat3 m) {
+ float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2];
+ float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2];
+ float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2];
+
+ float b01 = a22 * a11 - a12 * a21;
+ float b11 = -a22 * a10 + a12 * a20;
+ float b21 = a21 * a10 - a11 * a20;
+
+ float det = a00 * b01 + a01 * b11 + a02 * b21;
+
+ return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11),
+ b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10),
+ b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det;
+}
+
+//for dynamic, avoid computing the matrices on the cpu
+attribute vec3 instancePosition;
+attribute vec4 instanceQuaternion;
+attribute vec3 instanceScale;
+
+#if defined( INSTANCE_COLOR )
+ attribute vec3 instanceColor;
+ varying vec3 vInstanceColor;
+#endif
+
+mat4 getInstanceMatrix(){
+
+ vec4 q = instanceQuaternion;
+ vec3 s = instanceScale;
+ vec3 v = instancePosition;
+
+ vec3 q2 = q.xyz + q.xyz;
+ vec3 a = q.xxx * q2.xyz;
+ vec3 b = q.yyz * q2.yzz;
+ vec3 c = q.www * q2.xyz;
+
+ vec3 r0 = vec3( 1.0 - (b.x + b.z) , a.y + c.z , a.z - c.y ) * s.xxx;
+ vec3 r1 = vec3( a.y - c.z , 1.0 - (a.x + b.z) , b.y + c.x ) * s.yyy;
+ vec3 r2 = vec3( a.z + c.y , b.y - c.x , 1.0 - (a.x + b.x) ) * s.zzz;
+
+ return mat4(
+
+ r0 , 0.,
+ r1 , 0.,
+ r2 , 0.,
+ v , 1.0
+
+ );
+
+}
+
+#endif