소스 검색

* Merge revision 11058 from experimental branch
- Add instanced geometry support. This is performed by uploading 4 vertex buffers each containing 4 floats. The top 3 rows are the world matrix and the bottom row is a quaternion representing the normal matrix. Hence, both unshaded and lit geometries can be rendered through instancing.
See Instancing.glsllib for more information as well as the comment in LwjglRenderer.

shadowislord 11 년 전
부모
커밋
35cfae5ef0

+ 1 - 1
jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java

@@ -2362,7 +2362,7 @@ public class OGLESShaderRenderer implements Renderer {
         clearTextureUnits();
     }
 
-    public void renderMesh(Mesh mesh, int lod, int count) {
+    public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
         if (mesh.getVertexCount() == 0) {
             return;
         }

+ 16 - 3
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -47,6 +47,8 @@ import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.Renderer;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.instancing.InstancedGeometry;
 import com.jme3.shader.Shader;
 import com.jme3.shader.Uniform;
 import com.jme3.shader.UniformBindingManager;
@@ -684,6 +686,17 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         return ambientLightColor;
     }
 
+    private static void renderMeshFromGeometry(Renderer renderer, Geometry geom) {
+        Mesh mesh = geom.getMesh();
+        int lodLevel = geom.getLodLevel();
+        if (geom instanceof InstancedGeometry) {
+            InstancedGeometry instGeom = (InstancedGeometry) geom;
+            renderer.renderMesh(mesh, lodLevel, instGeom.getCurrentNumInstances(), instGeom.getAllInstanceData());
+        } else {
+            renderer.renderMesh(mesh, lodLevel, 1, null);
+        }
+    }
+    
     /**
      * Uploads the lights in the light list as two uniform arrays.<br/><br/> *
      * <p>
@@ -858,7 +871,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
             }
             vars.release();
             r.setShader(shader);
-            r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
+            renderMeshFromGeometry(r, g);
         }
 
         if (isFirstLight && lightList.size() > 0) {
@@ -868,7 +881,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
             lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
             lightPos.setValue(VarType.Vector4, nullDirLight);
             r.setShader(shader);
-            r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
+            renderMeshFromGeometry(r, g);
         }
     }
 
@@ -1139,7 +1152,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
             r.setShader(shader);
         }
 
-        r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1);
+        renderMeshFromGeometry(r, geom);
     }
 
     public void write(JmeExporter ex) throws IOException {

+ 8 - 3
jme3-core/src/main/java/com/jme3/renderer/Renderer.java

@@ -267,18 +267,23 @@ public interface Renderer {
     public void deleteBuffer(VertexBuffer vb);
 
     /**
-     * Renders <code>count</code> meshes, with the geometry data supplied.
+     * Renders <code>count</code> meshes, with the geometry data supplied and
+     * per-instance data supplied.
      * The shader which is currently set with <code>setShader</code> is
      * responsible for transforming the input vertices into clip space
      * and shading it based on the given vertex attributes.
-     * The int variable gl_InstanceID can be used to access the current
+     * The integer variable gl_InstanceID can be used to access the current
      * instance of the mesh being rendered inside the vertex shader.
+     * If the instance data is non-null, then it is submitted as a
+     * per-instance vertex attribute to the shader.
      *
      * @param mesh The mesh to render
      * @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
      * @param count Number of mesh instances to render
+     * @param instanceData When count is greater than 1, these buffers provide
+     *                     the per-instance attributes.
      */
-    public void renderMesh(Mesh mesh, int lod, int count);
+    public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData);
 
     /**
      * Resets all previously used {@link NativeObject Native Objects} on this Renderer.

+ 40 - 6
jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java

@@ -206,6 +206,14 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
          * either an int or float buffer due to shader attribute types restrictions.
          */
         HWBoneIndex,
+        
+        /**
+	 * Information about this instance.
+         * 
+	 * Format should be {@link Format#Float} and number of components
+	 * should be 16.
+	 */
+	InstanceData
     }
 
     /**
@@ -325,6 +333,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
     protected Type bufType;
     protected Format format;
     protected boolean normalized = false;
+    protected transient boolean instanced = false;
     protected transient boolean dataSizeChanged = false;
 
     /**
@@ -365,10 +374,14 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
             throw new AssertionError();
         }
         // Are components between 1 and 4?
-        if (components < 1 || components > 4) {
-            throw new AssertionError();
-        }
         
+        // Are components between 1 and 4 and not InstanceData?
+        if (bufType != Type.InstanceData) {
+            if (components < 1 || components > 4) {
+                throw new AssertionError();
+            }
+        }
+
         // Does usage comply with buffer directness?
         //if (usage == Usage.CpuOnly && data.isDirect()) {
         //    throw new AssertionError();
@@ -531,6 +544,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
         return normalized;
     }
 
+    /**
+     * TODO:
+     */
+    public void setInstanced(boolean instanced) {
+        this.instanced = instanced;
+    }
+
+    public boolean isInstanced() {
+        return instanced;
+    }
+    
     /**
      * @return The type of information that this buffer has.
      */
@@ -585,9 +609,12 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
         if (data.isReadOnly()) 
             throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." );
 
-        if (components < 1 || components > 4)
-            throw new IllegalArgumentException("components must be between 1 and 4");
-
+        if (bufType != Type.InstanceData) {
+            if (components < 1 || components > 4) {
+                throw new IllegalArgumentException("components must be between 1 and 4");
+            }
+        }
+        
         this.data = data;
         this.components = components;
         this.usage = usage;
@@ -982,6 +1009,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
         vb.handleRef = new Object();
         vb.id = -1;
         vb.normalized = normalized;
+        vb.instanced = instanced;
         vb.offset = offset;
         vb.stride = stride;
         vb.updateNeeded = true;
@@ -1030,7 +1058,12 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
         return ((long)OBJTYPE_VERTEXBUFFER << 32) | ((long)id);
     }
     
+    @Override
     public void write(JmeExporter ex) throws IOException {
+        if (instanced) {
+            throw new IOException("Serialization of instanced data not allowed");
+        }
+        
         OutputCapsule oc = ex.getCapsule(this);
         oc.write(components, "components", 0);
         oc.write(usage, "usage", Usage.Dynamic);
@@ -1064,6 +1097,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
         }
     }
 
+    @Override
     public void read(JmeImporter im) throws IOException {
         InputCapsule ic = im.getCapsule(this);
         components = ic.readInt("components", 0);

+ 502 - 0
jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java

@@ -0,0 +1,502 @@
+package com.jme3.scene.instancing;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <code>InstancedGeometry</code> allows rendering many similar 
+ * geometries efficiently through a feature called geometry 
+ * instancing. 
+ * 
+ * <p>
+ * All rendered geometries share material, mesh, and lod level 
+ * but have different world transforms or possibly other parameters. 
+ * The settings for all instances are inherited from this geometry's 
+ * {@link #setMesh(com.jme3.scene.Mesh) mesh},
+ * {@link #setMaterial(com.jme3.material.Material) material} and 
+ * {@link #setLodLevel(int) lod level} and cannot be changed per-instance.
+ * </p>
+ * 
+ * <p>
+ * In order to receive any per-instance parameters, the material's shader
+ * must be changed to retrieve per-instance data via 
+ * {@link VertexBuffer#setInstanced(boolean) instanced vertex attributes} 
+ * or uniform arrays indexed with the GLSL built-in uniform 
+ * <code>gl_InstanceID</code>. At the very least, they should use the 
+ * functions specified in <code>Instancing.glsllib</code> shader library
+ * to transform vertex positions and normals instead of multiplying by the 
+ * built-in matrix uniforms.
+ * </p>
+ * 
+ * <p>
+ * This class can operate in two modes, {@link InstancedGeometry.Mode#Auto}
+ * and {@link InstancedGeometry.Mode#Manual}. See the respective enums
+ * for more information</p>
+ * 
+ * <p>
+ * Prior to usage, the maximum number of instances must be set via 
+ * {@link #setMaxNumInstances(int) } and the current number of instances set
+ * via {@link #setCurrentNumInstances(int) }. The user is then
+ * expected to provide transforms for all instances up to the number
+ * of current instances.
+ * </p>
+ * 
+ * @author Kirill Vainer
+ */
+public class InstancedGeometry extends Geometry {
+    
+    /**
+     * Indicates how the per-instance data is to be specified.
+     */
+    public static enum Mode {
+        
+        /**
+         * The user must specify all per-instance transforms and 
+         * parameters manually via
+         * {@link InstancedGeometry#setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }
+         * or 
+         * {@link InstancedGeometry#setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }.
+         */
+        Manual,
+        
+        /**
+         * The user 
+         * {@link InstancedGeometry#setInstanceTransform(int, com.jme3.math.Transform) provides world transforms}
+         * and then uses the <code>Instancing.glsllib</code> transform functions in the 
+         * shader to transform vertex attributes to the respective spaces.
+         * Additional per-instance data can be specified via
+         * {@link InstancedGeometry#setManualGlobalInstanceData(com.jme3.scene.VertexBuffer[]) }.
+         * {@link #setManualCameraInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }
+         * cannot be used at this mode since it is computed automatically.
+         */
+        Auto
+    }
+    
+    private static class InstancedGeometryControl extends AbstractControl {
+
+        private InstancedGeometry geom;
+        
+        public InstancedGeometryControl() {
+        }
+        
+        public InstancedGeometryControl(InstancedGeometry geom) {
+            this.geom = geom;
+        }
+        
+        @Override
+        protected void controlUpdate(float tpf) {
+        }
+
+        @Override
+        protected void controlRender(RenderManager rm, ViewPort vp) {
+            geom.renderFromControl(vp.getCamera());
+        }
+    }
+    
+    private static final int INSTANCE_SIZE = 16;
+    
+    private InstancedGeometry.Mode mode;
+    private InstancedGeometryControl control;
+    private int currentNumInstances = 1;
+    private Camera lastCamera = null;
+    private Matrix4f[] worldMatrices = new Matrix4f[1];
+    private VertexBuffer[] globalInstanceData;
+    
+    private final HashMap<Camera, VertexBuffer> instanceDataPerCam 
+            = new HashMap<Camera, VertexBuffer>();
+    
+    // TODO: determine if perhaps its better to use TempVars here.
+    
+    private final Matrix4f tempMat4 = new Matrix4f();
+    private final Matrix4f tempMat4_2 = new Matrix4f();
+    private final Matrix3f tempMat3 = new Matrix3f();
+    private final Quaternion tempQuat = new Quaternion();
+    private final float[] tempFloatArray = new float[16];
+    
+    /**
+     * Serialization only. Do not use.
+     */
+    public InstancedGeometry() {
+        super();
+        setIgnoreTransform(true);
+    }
+    
+    /**
+     * Creates instanced geometry with the specified mode and name.
+     * 
+     * @param mode The {@link Mode} at which the instanced geometry operates at.
+     * @param name The name of the spatial. 
+     * 
+     * @see Mode
+     * @see Spatial#Spatial(java.lang.String)
+     */
+    public InstancedGeometry(InstancedGeometry.Mode mode, String name) {
+        super(name);
+        this.mode = mode;
+        setIgnoreTransform(true);
+        if (mode == InstancedGeometry.Mode.Auto) {
+            control = new InstancedGeometryControl(this);
+            addControl(control);
+        }
+    }
+    
+    /**
+     * The mode with which this instanced geometry was initialized
+     * with. Cannot be changed after initialization.
+     * 
+     * @return instanced geometry mode.
+     */
+    public InstancedGeometry.Mode getMode() {
+        return mode;
+    }
+    
+    /**
+     * Global user specified per-instance data. 
+     * 
+     * By default set to <code>null</code>, specify an array of VertexBuffers
+     * via {@link #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }.
+     * 
+     * @return global user specified per-instance data. 
+     * @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) 
+     */
+    public VertexBuffer[] getGlobalUserInstanceData() {
+        return globalInstanceData;
+    }
+    
+    /**
+     * Specify global user per-instance data.
+     * 
+     * By default set to <code>null</code>, specify an array of VertexBuffers
+     * that contain per-instance vertex attributes.
+     * 
+     * @param globalInstanceData global user per-instance data.
+     * 
+     * @throws IllegalArgumentException If one of the VertexBuffers is not 
+     * {@link VertexBuffer#setInstanced(boolean) instanced}.
+     */
+    public void setGlobalUserInstanceData(VertexBuffer[] globalInstanceData) {
+        this.globalInstanceData = globalInstanceData;
+    }
+    
+    /**
+     * Specify camera specific user per-instance data.
+     * 
+     * Only applies when operating in {@link Mode#Manual}. 
+     * When operating in {@link Mode#Auto}, this data is computed automatically,
+     * and using this method is not allowed.
+     * 
+     * @param camera The camera for which per-instance data is to be set.
+     * @param cameraInstanceData The camera's per-instance data.
+     * 
+     * @throws IllegalArgumentException If camera is null.
+     * @throws IllegalStateException If {@link #getMode() mode} is set to 
+     * {@link Mode#Auto}.
+     * 
+     * @see Mode
+     * @see #getCameraUserInstanceData(com.jme3.renderer.Camera)
+     */
+    public void setCameraUserInstanceData(Camera camera, VertexBuffer cameraInstanceData) {
+        if (mode == Mode.Auto) {
+            throw new IllegalStateException("Not allowed in auto mode");
+        }
+        if (camera == null) {
+            throw new IllegalArgumentException("camera cannot be null");
+        }
+        instanceDataPerCam.put(camera, cameraInstanceData);
+    }
+    
+    /**
+     * Return camera specific user per-instance data.
+     * 
+     * Only applies when operating in {@link Mode#Manual}. 
+     * When operating in {@link Mode#Auto}, this data is computed automatically,
+     * and using this method is not allowed.
+     * 
+     * @param camera The camera to look up the per-instance data for.
+     * @return The per-instance data, or <code>null</code> if none was specified
+     * for the given camera.
+     * 
+     * @throws IllegalArgumentException If camera is null.
+     * @throws IllegalStateException If {@link #getMode() mode} is set to 
+     * {@link Mode#Auto}.
+     * 
+     * @see Mode
+     * @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) 
+     */
+    public VertexBuffer getCameraUserInstanceData(Camera camera) {
+        if (mode == Mode.Auto) {
+            throw new IllegalStateException("Not allowed in auto mode");
+        }
+        if (camera == null) {
+            throw new IllegalArgumentException("camera cannot be null");
+        }
+        return instanceDataPerCam.get(camera);
+    }
+    
+    /**
+     * Return a read only map with the mappings between cameras and camera 
+     * specific per-instance data. 
+     * 
+     * Only applies when operating in {@link Mode#Manual}. 
+     * When operating in {@link Mode#Auto}, this data is computed automatically,
+     * and using this method is not allowed.
+     * 
+     * @return read only map with the mappings between cameras and camera 
+     * specific per-instance data. 
+     * 
+     * @throws IllegalStateException If {@link #getMode() mode} is set to 
+     * {@link Mode#Auto}.
+     * 
+     * @see Mode
+     * @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) 
+     */
+    public Map<Camera, VertexBuffer> getAllCameraUserInstanceData() {
+        if (mode == Mode.Auto) {
+            throw new IllegalStateException("Not allowed in auto mode");
+        }
+        return Collections.unmodifiableMap(instanceDataPerCam);
+    }
+    
+    private void updateInstance(Matrix4f viewMatrix, Matrix4f worldMatrix, float[] store, int offset) {
+        viewMatrix.mult(worldMatrix, tempMat4);
+        tempMat4.toRotationMatrix(tempMat3);
+        tempMat3.invertLocal();
+        
+        // NOTE: No need to take the transpose in order to encode
+        // into quaternion, the multiplication in the shader is vec * quat
+        // apparently...
+        tempQuat.fromRotationMatrix(tempMat3);
+        
+        // Column-major encoding. The "W" field in each of the encoded
+        // vectors represents the quaternion.
+        store[offset + 0] = tempMat4.m00;
+        store[offset + 1] = tempMat4.m10;
+        store[offset + 2] = tempMat4.m20;
+        store[offset + 3] = tempQuat.getX();
+        store[offset + 4] = tempMat4.m01;
+        store[offset + 5] = tempMat4.m11;
+        store[offset + 6] = tempMat4.m21;
+        store[offset + 7] = tempQuat.getY();
+        store[offset + 8] = tempMat4.m02;
+        store[offset + 9] = tempMat4.m12;
+        store[offset + 10] = tempMat4.m22;
+        store[offset + 11] = tempQuat.getZ();
+        store[offset + 12] = tempMat4.m03;
+        store[offset + 13] = tempMat4.m13;
+        store[offset + 14] = tempMat4.m23;
+        store[offset + 15] = tempQuat.getW();
+    }
+    
+    private void renderFromControl(Camera cam) {
+        if (mode != Mode.Auto) {
+            return;
+        }
+        
+        // Get the instance data VBO for this camera.
+        VertexBuffer instanceDataVB = instanceDataPerCam.get(cam);
+        FloatBuffer instanceData;
+        
+        if (instanceDataVB == null) {
+            // This is a new camera, create instance data VBO for it.
+            instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE);
+            instanceDataVB = new VertexBuffer(Type.InstanceData);
+            instanceDataVB.setInstanced(true);
+            instanceDataVB.setupData(Usage.Stream, INSTANCE_SIZE, Format.Float, instanceData);
+            instanceDataPerCam.put(cam, instanceDataVB);
+        } else {
+            // Retrieve the current instance data buffer.
+            instanceData = (FloatBuffer) instanceDataVB.getData();
+        }
+        
+        Matrix4f viewMatrix = cam.getViewMatrix();
+        
+        instanceData.limit(instanceData.capacity());
+        instanceData.position(0);
+        
+        assert currentNumInstances <= worldMatrices.length;
+        
+        for (int i = 0; i < currentNumInstances; i++) {
+            Matrix4f worldMatrix = worldMatrices[i];
+            if (worldMatrix == null) {
+                worldMatrix = Matrix4f.IDENTITY;
+            }
+            updateInstance(viewMatrix, worldMatrix, tempFloatArray, 0);
+            instanceData.put(tempFloatArray);
+        }
+        
+        instanceData.flip();
+        
+        this.lastCamera = cam;
+        instanceDataVB.updateData(instanceDataVB.getData());
+    }
+    
+    /**
+     * Set the current number of instances to be rendered.
+     * 
+     * @param currentNumInstances the current number of instances to be rendered.
+     * 
+     * @throws IllegalArgumentException If current number of instances is 
+     * greater than the maximum number of instances.
+     */
+    public void setCurrentNumInstances(int currentNumInstances) {
+        if (currentNumInstances > worldMatrices.length) {
+            throw new IllegalArgumentException("currentNumInstances cannot be larger than maxNumInstances");
+        }
+        this.currentNumInstances = currentNumInstances;
+    }
+    
+    /**
+     * Set the maximum amount of instances that can be rendered by this
+     * instanced geometry when mode is set to auto.
+     * 
+     * This re-allocates internal structures and therefore should be called
+     * only when necessary. 
+     * 
+     * @param maxNumInstances The maximum number of instances that can be
+     * rendered.
+     * 
+     * @throws IllegalStateException If mode is set to manual.
+     * @throws IllegalArgumentException If maxNumInstances is zero or negative
+     */
+    public void setMaxNumInstances(int maxNumInstances) {
+        if (mode == Mode.Manual) {
+            throw new IllegalStateException("Not allowed in manual mode");
+        }
+        if (maxNumInstances < 1) {
+            throw new IllegalArgumentException("maxNumInstances must be 1 or higher");
+        }
+        
+        this.worldMatrices = new Matrix4f[maxNumInstances];
+        
+        if (currentNumInstances > maxNumInstances) {
+            currentNumInstances = maxNumInstances;
+        }
+        
+        // Resize instance data for each of the cameras.
+        for (VertexBuffer instanceDataVB : instanceDataPerCam.values()) {
+            FloatBuffer instanceData = (FloatBuffer) instanceDataVB.getData();
+            if (instanceData.capacity() / INSTANCE_SIZE != worldMatrices.length) {
+                // Delete old data.
+                BufferUtils.destroyDirectBuffer(instanceData);
+
+                // Resize instance data for this camera.
+                // Create new data with new length.
+                instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE);
+                instanceDataVB.updateData(instanceData);
+            }
+        }
+    }
+    
+    public int getMaxNumInstances() {
+        return worldMatrices.length;
+    }
+
+    public int getCurrentNumInstances() {
+        return currentNumInstances;
+    }
+    
+    public void setInstanceTransform(int instanceIndex, Matrix4f worldTransform) {
+        if (mode == Mode.Manual) {
+            throw new IllegalStateException("Not allowed in manual mode");
+        }
+        if (worldTransform == null) {
+            throw new IllegalArgumentException("worldTransform cannot be null");
+        }
+        if (instanceIndex < 0) {
+            throw new IllegalArgumentException("instanceIndex cannot be smaller than zero");
+        }
+        if (instanceIndex >= currentNumInstances) {
+            throw new IllegalArgumentException("instanceIndex cannot be larger than currentNumInstances");
+        }
+        // TODO: Determine if need to make a copy of matrix or just doing this
+        // is fine.
+        worldMatrices[instanceIndex] = worldTransform;
+    }
+    
+    public void setInstanceTransform(int instanceIndex, Transform worldTransform) {
+        if (worldTransform == null) {
+            throw new IllegalArgumentException("worldTransform cannot be null");
+        }
+        
+        // Compute the world transform matrix.
+        tempMat4.loadIdentity();
+        tempMat4.setRotationQuaternion(worldTransform.getRotation());
+        tempMat4.setTranslation(worldTransform.getTranslation());
+        tempMat4_2.loadIdentity();
+        tempMat4_2.scale(worldTransform.getScale());
+        tempMat4.multLocal(tempMat4_2);
+        
+        setInstanceTransform(instanceIndex, tempMat4.clone());
+    }
+    
+    public VertexBuffer[] getAllInstanceData() {
+        VertexBuffer instanceDataForCam = instanceDataPerCam.get(lastCamera);
+        ArrayList<VertexBuffer> allData = new ArrayList();
+
+        if (instanceDataForCam != null) {
+            allData.add(instanceDataForCam);
+        }
+        if (globalInstanceData != null) {
+            allData.addAll(Arrays.asList(globalInstanceData));
+        }
+
+        return allData.toArray(new VertexBuffer[allData.size()]);
+    }
+
+    @Override
+    public void write(JmeExporter exporter) throws IOException {
+        super.write(exporter);
+        OutputCapsule capsule = exporter.getCapsule(this);
+        capsule.write(currentNumInstances, "cur_num_instances", 1);
+        capsule.write(mode, "instancing_mode", InstancedGeometry.Mode.Auto);
+        if (mode == Mode.Auto) {
+            capsule.write(worldMatrices, "world_matrices", null);
+        }
+    }
+    
+    @Override
+    public void read(JmeImporter importer) throws IOException {
+        super.read(importer);
+        InputCapsule capsule = importer.getCapsule(this);
+        currentNumInstances = capsule.readInt("cur_num_instances", 1);
+        mode = capsule.readEnum("instancing_mode", InstancedGeometry.Mode.class, 
+                                InstancedGeometry.Mode.Auto);
+        
+        if (mode == Mode.Auto) {
+            Savable[] matrixSavables = capsule.readSavableArray("world_matrices", null);
+            worldMatrices = new Matrix4f[matrixSavables.length];
+            for (int i = 0; i < worldMatrices.length; i++) {
+                worldMatrices[i] = (Matrix4f) matrixSavables[i];
+            }
+
+            control = getControl(InstancedGeometryControl.class);
+            control.geom = this;
+        }
+    }
+}

+ 1 - 1
jme3-core/src/main/java/com/jme3/system/NullRenderer.java

@@ -137,7 +137,7 @@ public class NullRenderer implements Renderer {
     public void deleteBuffer(VertexBuffer vb) {
     }
 
-    public void renderMesh(Mesh mesh, int lod, int count) {
+    public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
     }
 
     public void resetGLObjects() {

+ 10 - 0
jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md

@@ -1,10 +1,20 @@
 MaterialDef Debug Normals {
+    MaterialParameters {
+        // For instancing
+        Boolean UseInstancing
+    }
+
     Technique {
         VertexShader GLSL100:   Common/MatDefs/Misc/ShowNormals.vert
         FragmentShader GLSL100: Common/MatDefs/Misc/ShowNormals.frag
 
         WorldParameters {
             WorldViewProjectionMatrix
+            ProjectionMatrix
         }
+
+        Defines {
+            INSTANCING : UseInstancing
+	}
     }
 }

+ 2 - 2
jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.vert

@@ -1,4 +1,4 @@
-uniform mat4 g_WorldViewProjectionMatrix;
+#import "Common/ShaderLib/Instancing.glsllib"
 
 attribute vec3 inPosition;
 attribute vec3 inNormal;
@@ -6,6 +6,6 @@ attribute vec3 inNormal;
 varying vec3 normal;
 
 void main(){
-    gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0);
+    gl_Position = TransformWorldViewProjection(vec4(inPosition,1.0));
     normal = inNormal;
 }

+ 5 - 0
jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md

@@ -12,6 +12,9 @@ MaterialDef Unshaded {
         // The glow color of the object
         Color GlowColor
 
+        // For instancing
+	Boolean UseInstancing
+
         // For hardware skinning
         Int NumberOfBones
         Matrix4Array BoneMatrices
@@ -56,9 +59,11 @@ MaterialDef Unshaded {
 
         WorldParameters {
             WorldViewProjectionMatrix
+            ProjectionMatrix
         }
 
         Defines {
+            INSTANCING : UseInstancing
             SEPARATE_TEXCOORD : SeparateTexCoord
             HAS_COLORMAP : ColorMap
             HAS_LIGHTMAP : LightMap

+ 3 - 2
jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert

@@ -1,6 +1,6 @@
 #import "Common/ShaderLib/Skinning.glsllib"
+#import "Common/ShaderLib/Instancing.glsllib"
 
-uniform mat4 g_WorldViewProjectionMatrix;
 attribute vec3 inPosition;
 
 #if defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD))
@@ -33,5 +33,6 @@ void main(){
     #ifdef NUM_BONES
         Skinning_Compute(modelSpacePos);
     #endif
-    gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
+
+    gl_Position = TransformWorldViewProjection(modelSpacePos);
 }

+ 87 - 0
jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib

@@ -0,0 +1,87 @@
+// Instancing GLSL library.
+// 
+// When the INSTANCING define is set in the shader, 
+// all global matrices are replaced with "instanced" versions.
+// One exception is g_NormalMatrix which becomes unusable,
+// instead the function ApplyNormalTransform is used to transform
+// the normal and tangent vectors into world view space.
+
+// The world matrix and normal transform quaternion need to be passed
+// as vertex attributes "inWorldMatrix" and "inNormalRotationQuaternion"
+// respectively. 
+// The VertexBuffers for those two attributes 
+// need to be configured into instanced mode (VertexBuffer.setInstanced(true)). 
+//  - inWorldMatrix should have 12 * numInstances floats.
+//  - inNormalRotationQuaternion should have 4 * numInstances.
+// Thus, instancing data occupies 4 vertex attributes (16 / 4 = 4).
+// 
+// The GL_ARB_draw_instanced and GL_ARB_instanced_arrays extensions 
+// are required (OGL 3.3).
+
+uniform mat4 g_WorldMatrix;
+uniform mat4 g_ViewMatrix;
+uniform mat4 g_ProjectionMatrix;
+uniform mat4 g_WorldViewMatrix;
+uniform mat4 g_WorldViewProjectionMatrix;
+uniform mat3 g_NormalMatrix;
+
+#if defined INSTANCING
+
+// World Matrix + Normal Rotation Quaternion. 
+// The World Matrix is the top 3 rows - 
+//     since the bottom row is always 0,0,0,1 for this transform.
+// The bottom row is the transpose of the inverse of WorldView Transform 
+//     as a quaternion. i.e. g_NormalMatrix converted to a quaternion.
+//
+// Using a quaternion instead of a matrix here allows saving approximately
+// 2 vertex attributes which now can be used for additional per-vertex data.
+attribute mat4 inInstanceData;
+
+// Extract the world view matrix out of the instance data, leaving out the 
+// quaternion at the end.
+mat4 worldViewMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0), 
+                            vec4(inInstanceData[1].xyz, 0.0), 
+                            vec4(inInstanceData[2].xyz, 0.0), 
+                            vec4(inInstanceData[3].xyz, 1.0));
+
+
+vec4 TransformWorldView(vec4 position)
+{
+    return worldViewMatrix * position;
+}
+
+vec4 TransformWorldViewProjection(vec4 position)
+{
+    return g_ProjectionMatrix * TransformWorldView(position);
+}
+
+vec3 TransformNormal(vec3 vec)
+{
+    vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w,
+                     inInstanceData[2].w, inInstanceData[3].w);
+    return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
+}
+
+// Prevent user from using g_** matrices which will have invalid data in this case.
+#define g_WorldMatrix               Use_the_instancing_functions_for_this
+#define g_WorldViewMatrix           Use_the_instancing_functions_for_this
+#define g_WorldViewProjectionMatrix Use_the_instancing_functions_for_this
+#define g_NormalMatrix              Use_the_instancing_functions_for_this
+
+#else
+
+vec4 TransformWorldView(vec4 position)
+{
+    return g_WorldViewMatrix * position;
+}
+
+vec4 TransformWorldViewProjection(vec4 position)
+{
+    return g_WorldViewProjectionMatrix * position;
+}
+
+vec3 TransformNormal(vec3 normal) {
+	return g_NormalMatrix * normal;
+}
+
+#endif

+ 111 - 0
jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java

@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package jme3test.scene.instancing;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.scene.instancing.InstancedGeometry;
+import com.jme3.scene.shape.Sphere;
+
+public class TestInstancing extends SimpleApplication  {
+
+    private InstancedGeometry instancedGeometry;
+    
+    public static void main(String[] args){
+        TestInstancing app = new TestInstancing();
+        //app.setShowSettings(false);
+        //app.setDisplayFps(false);
+        //app.setDisplayStatView(false);
+        app.start();
+    }
+
+    private Geometry createInstance(float x, float z) {
+        // Note: it doesn't matter what mesh or material we set here.
+        Geometry geometry = new Geometry("randomGeom", instancedGeometry.getMesh());
+        geometry.setMaterial(instancedGeometry.getMaterial());
+        geometry.setLocalTranslation(x, 0, z);
+        return geometry;
+    }
+    
+    @Override
+    public void simpleInitApp() {
+        Sphere sphere = new Sphere(10, 10, 0.5f, true, false);
+        Material material = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
+        material.setBoolean("UseInstancing", true);
+        
+        instancedGeometry = new InstancedGeometry(InstancedGeometry.Mode.Auto, "instanced_geom");
+        instancedGeometry.setMaxNumInstances(60 * 60);
+        instancedGeometry.setCurrentNumInstances(60 * 60);
+        instancedGeometry.setCullHint(CullHint.Never);
+        instancedGeometry.setMesh(sphere);
+        instancedGeometry.setMaterial(material);
+        rootNode.attachChild(instancedGeometry);
+        
+        Node instancedGeoms = new Node("instances_node");
+        
+        // Important: Do not render these geometries, only
+        // use their world transforms to instance them via
+        // InstancedGeometry.
+        instancedGeoms.setCullHint(CullHint.Always);
+        
+        for (int y = -30; y < 30; y++) {
+            for (int x = -30; x < 30; x++) {
+                Geometry instance = createInstance(x, y);
+                instancedGeoms.attachChild(instance);
+            }
+        }
+        
+        rootNode.attachChild(instancedGeoms);
+        rootNode.setCullHint(CullHint.Never);
+
+        int instanceIndex = 0;
+        for (Spatial child : instancedGeoms.getChildren()) {
+            if (instanceIndex < instancedGeometry.getMaxNumInstances()) {
+                instancedGeometry.setInstanceTransform(instanceIndex++, child.getWorldTransform());
+            }
+        }
+        
+        instancedGeometry.setCurrentNumInstances(instanceIndex);
+
+        cam.setLocation(new Vector3f(38.373516f, 6.689055f, 38.482082f));
+        cam.setRotation(new Quaternion(-0.04004206f, 0.918326f, -0.096310444f, -0.38183528f));
+        flyCam.setMoveSpeed(15);
+    }
+
+}

+ 1 - 1
jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java

@@ -936,7 +936,7 @@ public class IGLESShaderRenderer implements Renderer {
      * @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
      * @param count Number of mesh instances to render
      */
-    public void renderMesh(Mesh mesh, int lod, int count) {
+    public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
         logger.log(Level.FINE, "IGLESShaderRenderer renderMesh");
         if (mesh.getVertexCount() == 0) {
         	return;

+ 1 - 1
jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL1Renderer.java

@@ -1181,7 +1181,7 @@ public class JoglGL1Renderer implements GL1Renderer {
         resetFixedFuncBindings();
     }
 
-    public void renderMesh(Mesh mesh, int lod, int count) {
+    public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
         if (mesh.getVertexCount() == 0) {
             return;
         }

+ 1 - 1
jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java

@@ -2554,7 +2554,7 @@ public class JoglRenderer implements Renderer {
         clearTextureUnits();
     }
 
-    public void renderMesh(Mesh mesh, int lod, int count) {
+    public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
         if (mesh.getVertexCount() == 0) {
             return;
         }

+ 1 - 1
jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java

@@ -1121,7 +1121,7 @@ public class LwjglGL1Renderer implements GL1Renderer {
         resetFixedFuncBindings();
     }
 
-    public void renderMesh(Mesh mesh, int lod, int count) {
+    public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
         if (mesh.getVertexCount() == 0) {
             return;
         }

+ 85 - 30
jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java

@@ -281,7 +281,7 @@ public class LwjglRenderer implements Renderer {
             caps.add(Caps.PackedDepthStencilBuffer);
         }
 
-        if (ctxCaps.GL_ARB_draw_instanced) {
+        if (ctxCaps.GL_ARB_draw_instanced && ctxCaps.GL_ARB_instanced_arrays) {
             caps.add(Caps.MeshInstancing);
         }
 
@@ -316,7 +316,8 @@ public class LwjglRenderer implements Renderer {
             caps.add(Caps.TextureCompressionLATC);
         }
 
-        if (ctxCaps.GL_EXT_packed_float) {
+        if (ctxCaps.GL_EXT_packed_float || ctxCaps.OpenGL30) {
+            // This format is part of the OGL3 specification
             caps.add(Caps.PackedFloatColorBuffer);
             if (ctxCaps.GL_ARB_half_float_pixel) {
                 // because textures are usually uploaded as RGB16F
@@ -329,7 +330,7 @@ public class LwjglRenderer implements Renderer {
             caps.add(Caps.TextureArray);
         }
 
-        if (ctxCaps.GL_EXT_texture_shared_exponent) {
+        if (ctxCaps.GL_EXT_texture_shared_exponent || ctxCaps.OpenGL30) {
             caps.add(Caps.SharedExponentTexture);
         }
 
@@ -392,8 +393,8 @@ public class LwjglRenderer implements Renderer {
             caps.add(Caps.Multisample);
         }
         
-        //supports sRGB pipeline
-        if (ctxCaps.GL_ARB_framebuffer_sRGB && ctxCaps.GL_EXT_texture_sRGB){
+        // Supports sRGB pipeline.
+        if ( (ctxCaps.GL_ARB_framebuffer_sRGB && ctxCaps.GL_EXT_texture_sRGB ) || ctxCaps.OpenGL30 ) {
             caps.add(Caps.Srgb);
         }
 
@@ -1529,7 +1530,7 @@ public class LwjglRenderer implements Renderer {
             }
         }
 
-        if (fb == null) {
+    if (fb == null) {
             // unbind any fbos
             if (context.boundFBO != 0) {
                 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
@@ -2168,6 +2169,9 @@ public class LwjglRenderer implements Renderer {
         for (int i = 0; i < attribList.oldLen; i++) {
             int idx = attribList.oldList[i];
             glDisableVertexAttribArray(idx);
+            if (context.boundAttribs[idx].isInstanced()) {
+                ARBInstancedArrays.glVertexAttribDivisorARB(idx, 0);
+            }
             context.boundAttribs[idx] = null;
         }
         context.attribIndexList.copyNewToOld();
@@ -2200,15 +2204,32 @@ public class LwjglRenderer implements Renderer {
                     attrib.setLocation(loc);
                 }
             }
+            
+            int slotsRequired = 1;
+            if (vb.isInstanced()) {
+                if (!GLContext.getCapabilities().GL_ARB_instanced_arrays
+                 || !GLContext.getCapabilities().GL_ARB_draw_instanced) {
+                    throw new RendererException("Instancing is required, "
+                            + "but not supported by the "
+                            + "graphics hardware");
+                }
+                if (vb.getNumComponents() > 4 && vb.getNumComponents() % 4 != 0) {
+                    throw new RendererException("Number of components in multi-slot "
+                            + "buffers must be divisible by 4");
+                }
+                slotsRequired = vb.getNumComponents() / 4;
+            }
 
             if (vb.isUpdateNeeded() && idb == null) {
                 updateBufferData(vb);
             }
 
             VertexBuffer[] attribs = context.boundAttribs;
-            if (!context.attribIndexList.moveToNew(loc)) {
-                glEnableVertexAttribArray(loc);
-                //System.out.println("Enabled ATTRIB IDX: "+loc);
+            for (int i = 0; i < slotsRequired; i++) {
+                if (!context.attribIndexList.moveToNew(loc + i)) {
+                    glEnableVertexAttribArray(loc + i);
+                    //System.out.println("Enabled ATTRIB IDX: "+loc);
+                }
             }
             if (attribs[loc] != vb) {
                 // NOTE: Use id from interleaved buffer if specified
@@ -2222,14 +2243,43 @@ public class LwjglRenderer implements Renderer {
                     //statistics.onVertexBufferUse(vb, false);
                 }
 
-                glVertexAttribPointer(loc,
-                        vb.getNumComponents(),
-                        convertFormat(vb.getFormat()),
-                        vb.isNormalized(),
-                        vb.getStride(),
-                        vb.getOffset());
+                if (slotsRequired == 1) {
+                    glVertexAttribPointer(loc,
+                            vb.getNumComponents(),
+                            convertFormat(vb.getFormat()),
+                            vb.isNormalized(),
+                            vb.getStride(),
+                            vb.getOffset());
+                } else {
+                    for (int i = 0; i < slotsRequired; i++) {
+	 	 	// The pointer maps the next 4 floats in the slot.
+                        // E.g.
+                        // P1: XXXX____________XXXX____________
+                        // P2: ____XXXX____________XXXX________
+                        // P3: ________XXXX____________XXXX____
+                        // P4: ____________XXXX____________XXXX
+                        // stride = 4 bytes in float * 4 floats in slot * num slots
+                        // offset = 4 bytes in float * 4 floats in slot * slot index
+                        glVertexAttribPointer(loc + i,
+                                4,
+                                convertFormat(vb.getFormat()),
+                                vb.isNormalized(),
+                                4 * 4 * slotsRequired,
+                                4 * 4 * i);
+                    }
+                }
 
-                attribs[loc] = vb;
+                for (int i = 0; i < slotsRequired; i++) {
+                    int slot = loc + i;
+                    if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) {
+                        // non-instanced -> instanced
+                        ARBInstancedArrays.glVertexAttribDivisorARB(slot, 1);
+                    } else if (!vb.isInstanced() && attribs[slot] != null && attribs[slot].isInstanced()) {
+                        // instanced -> non-instanced
+                        ARBInstancedArrays.glVertexAttribDivisorARB(slot, 0);
+                    }
+                    attribs[slot] = vb;
+                }
             }
         } else {
             throw new IllegalStateException("Cannot render mesh without shader bound");
@@ -2241,7 +2291,8 @@ public class LwjglRenderer implements Renderer {
     }
 
     public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {
-        if (count > 1) {
+        boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
+        if (useInstancing) {
             ARBDrawInstanced.glDrawArraysInstancedARB(convertElementMode(mode), 0,
                     vertCount, count);
         } else {
@@ -2350,7 +2401,7 @@ public class LwjglRenderer implements Renderer {
         }
     }
 
-    public void updateVertexArray(Mesh mesh) {
+    public void updateVertexArray(Mesh mesh, VertexBuffer instanceData) {
         int id = mesh.getId();
         if (id == -1) {
             IntBuffer temp = intBuf1;
@@ -2368,6 +2419,10 @@ public class LwjglRenderer implements Renderer {
         if (interleavedData != null && interleavedData.isUpdateNeeded()) {
             updateBufferData(interleavedData);
         }
+        
+        if (instanceData != null) {
+            setVertexAttrib(instanceData, null);
+        }
 
         for (VertexBuffer vb : mesh.getBufferList().getArray()) {
             if (vb.getBufferType() == Type.InterleavedData
@@ -2386,9 +2441,9 @@ public class LwjglRenderer implements Renderer {
         }
     }
 
-    private void renderMeshVertexArray(Mesh mesh, int lod, int count) {
+    private void renderMeshVertexArray(Mesh mesh, int lod, int count, VertexBuffer instanceData) {
         if (mesh.getId() == -1) {
-            updateVertexArray(mesh);
+            updateVertexArray(mesh, instanceData);
         } else {
             // TODO: Check if it was updated
         }
@@ -2414,25 +2469,25 @@ public class LwjglRenderer implements Renderer {
         clearTextureUnits();
     }
 
-    private void renderMeshDefault(Mesh mesh, int lod, int count) {
-        VertexBuffer indices;
-
+    private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
         VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
         if (interleavedData != null && interleavedData.isUpdateNeeded()) {
             updateBufferData(interleavedData);
         }
 
-//        IntMap<VertexBuffer> buffers = mesh.getBuffers();
-//        SafeArrayList<VertexBuffer> buffersList = mesh.getBufferList();
-
+        VertexBuffer indices;
         if (mesh.getNumLodLevels() > 0) {
             indices = mesh.getLodLevel(lod);
         } else {
             indices = mesh.getBuffer(Type.Index);
         }
 
-//        for (Entry<VertexBuffer> entry : buffers) {
-//             VertexBuffer vb = entry.getValue();
+        if (instanceData != null) {
+            for (VertexBuffer vb : instanceData) {
+                setVertexAttrib(vb, null);
+            }
+        }
+        
         for (VertexBuffer vb : mesh.getBufferList().getArray()) {
             if (vb.getBufferType() == Type.InterleavedData
                     || vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
@@ -2458,7 +2513,7 @@ public class LwjglRenderer implements Renderer {
         clearTextureUnits();
     }
 
-    public void renderMesh(Mesh mesh, int lod, int count) {
+    public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
         if (mesh.getVertexCount() == 0) {
             return;
         }
@@ -2489,7 +2544,7 @@ public class LwjglRenderer implements Renderer {
 //        if (GLContext.getCapabilities().GL_ARB_vertex_array_object){
 //            renderMeshVertexArray(mesh, lod, count);
 //        }else{
-        renderMeshDefault(mesh, lod, count);
+        renderMeshDefault(mesh, lod, count, instanceData);
 //        }
     }