Difference between revisions of "Instance Batch Rendering"

From JPCT
Jump to: navigation, search
(FAQ)
(FAQ)
Line 762: Line 762:
 
== FAQ ==
 
== FAQ ==
  
'''How can one detect when an InstanceObject3D is touched'''
+
'''How can one detect when an InstanceObject3D is touched?'''
  
 
By default the mesh of an InstanceObject3D is not touched by IBR.  If you do alter the mesh data, you already have access to the Mesh / Object3D as you've altered the vertices / uvs.  If you do alter the mesh, note that it will affect all instances as it uses a shared mesh.  If you need to get the Object3D of an InstanceObject3D type, use the InstanceManager.getObject3DOfInstanceType().
 
By default the mesh of an InstanceObject3D is not touched by IBR.  If you do alter the mesh data, you already have access to the Mesh / Object3D as you've altered the vertices / uvs.  If you do alter the mesh, note that it will affect all instances as it uses a shared mesh.  If you need to get the Object3D of an InstanceObject3D type, use the InstanceManager.getObject3DOfInstanceType().
Line 769: Line 769:
  
  
'''Does the order in which the InstanceObject3D's are drawn matter'''
+
'''Does the order in which the InstanceObject3D's are drawn matter?'''
  
 
It can, and Egon may be able to better explain some of this.  It should only matter if your Object3D uses transparency or you set the OpenGL to disable the depth test, in which case it will render in synchronous order.  To put it simply, I don't believe transparency InstanceObject3D's are going to play nicely with other transparent Object3D's, other transparent InstanceObject3D types, or overlaps of the multiple IntanceObject3D of that type.  I believe jPCT has software-side ordering (based off the Object3D's origin distance from camera?) when it comes to transparent objects, and the transparent objects are rendered last as transparency requires the solid pixels behind it for the different pixel write modes (add, modulate, blend, etc...).  Because all Object3D's for this method are stacked a certain distance from the camera, they will be picked up by jPCT render pipeline in regards to that distance from the camera.  All InstanceObject3D's that are rendered would be synchronously rendered on top of one another according to the order when added.  I could add simple sorting, but it won't solve all problems, as transparency is a complex subject that requires work arounds and tweaks for speed.  But it will never work great with IBR as the distance from the camera will mess up jPCT's sorting order.
 
It can, and Egon may be able to better explain some of this.  It should only matter if your Object3D uses transparency or you set the OpenGL to disable the depth test, in which case it will render in synchronous order.  To put it simply, I don't believe transparency InstanceObject3D's are going to play nicely with other transparent Object3D's, other transparent InstanceObject3D types, or overlaps of the multiple IntanceObject3D of that type.  I believe jPCT has software-side ordering (based off the Object3D's origin distance from camera?) when it comes to transparent objects, and the transparent objects are rendered last as transparency requires the solid pixels behind it for the different pixel write modes (add, modulate, blend, etc...).  Because all Object3D's for this method are stacked a certain distance from the camera, they will be picked up by jPCT render pipeline in regards to that distance from the camera.  All InstanceObject3D's that are rendered would be synchronously rendered on top of one another according to the order when added.  I could add simple sorting, but it won't solve all problems, as transparency is a complex subject that requires work arounds and tweaks for speed.  But it will never work great with IBR as the distance from the camera will mess up jPCT's sorting order.

Revision as of 17:50, 21 July 2016

Overview

Instance Batch Rendering (IBR) is a name I (alias Redman) dubbed for a lighter-weight method of handling and rendering multiple Object3D's of the same Mesh. Android applications need to be optimized for limited memory and speed. This method will help increase the speed of the rendering process with a lighter footprint, but requires work on your-end as well. IBR relies on GLSL, and does support bones using a hybrid GPU method. Features of jPCT may not be available for IBR.

Example uses of IBR might be:

  • an RTS with multiples of the same model;
  • an FPS with multiple enemies of the same model;
  • rendering trees on terrain;
  • etc...

I have added notes and personal recommendations. This architecture will most likely not be perfect for your project, so please alter and use to fit your needs.

Comments and suggestions are welcome as I will be adding a FAQ to the bottom of the page.


Requirements

  • jPCT (requires GL ES 2+ as it uses GLSL Shaders)


How It Works

IBR works by only creating and adding one Object3D to the world for a given object that can be rendered any number of times. This Object3D is always positioned in-front of camera so it is always picked up in the render pipeline (handled by the InstanceManager on update). A lighter-weight InstanceObject3D class is used instead of an Object3D for the instance's position, rotation, scale, and object type (reference to the Object3D's mesh). An IRenderHook is attached to the Object3D, which ties into jPCT's render pipeline. When the Object3D is rendered, it will loop X number of times through the render call for each instance. Each time calculating the ModelViewMatrix of your Instance3D and passing it into the GLSL Shader as a uniform.

Gains: Speed: IBR taps into the jPCT's render pipeline to batch render all instances of a model synchronously. Memory Usage: lighter footprint per multiple instances.

Losses: Many niceties that the Object3D offers: different texture data per model, octrees, etc... (These features will not come stock)

Using IBR, you will be responsible for determining if each instance is visible by the camera. If there are no visible instances, the InstanceManager will automatically set the Object3D visibility to false, so it does not get added to the render visibility list.


The Code

This code is free, open-source code and you are welcome to use it as you see fit.

This code can be modified to remove Bones, but I will leave that up to you.


InstanceManager Class:

package yourpackagename.instances;

import com.threed.jpct.Camera;
import com.threed.jpct.Matrix;
import com.threed.jpct.Object3D;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.World;

import java.util.ArrayList;
import java.util.HashMap;

import raft.jpct.bones.Animated3D;

/**
 * Created by Dougie on 7/6/16.
 */
public class InstanceManager {
    private static InstanceManager instance = null;
    private InstanceList instanceFullList = new InstanceList();
    private InstanceObject3D[] instances = null;
    private HashMap<Short, Short> instanceIdMap = new HashMap<Short,Short>();
    private ArrayList<InstanceTypeList> instanceLists = new ArrayList<InstanceTypeList>();
    private InstanceShader shader = null;
    private IInstanceManagerHelper helper = null;
    private SimpleVector objectHolderPosition = new SimpleVector();
    private Object3D objectHolder = Object3D.createDummyObj();
    public Matrix cameraMatrix = new Matrix();
    public SimpleVector cameraBack = new SimpleVector();
    public float objectHolderOffsetDistance = 10f; //Change this to something within range of the camera near/far plane if necessary
    //Temp variable for memory saving purposes
    private InstanceTypeList tmpIL;
    private InstanceObject3D tmpIO3D;
    private Short tmpIndex;
    private short i;
    private Camera tmpCamera;

    protected InstanceManager() { ; }
    public static InstanceManager getInstance() {
        if(instance==null) {
            instance = new InstanceManager();
        }
        return instance;
    }

    /*** GETTERS & SETTERS ***/
    public InstanceShader getShader() {
        return shader;
    }
    public void setShader(InstanceShader pShader) {
        shader = pShader;
    }
    public IInstanceManagerHelper getHelper() {
        return helper;
    }
    public void setHelper(IInstanceManagerHelper pHelper) {
        helper = pHelper;
    }

    public Object3D getObject3DOfInstanceType(short pType) {
        tmpIndex = instanceIdMap.get(pType);
        if(tmpIndex!=null) {
            tmpIL = instanceLists.get(tmpIndex);
            if(tmpIL!=null && tmpIL.renderer!=null) {
                return tmpIL.renderer.renderObj3D;
            }
        }
        return null;
    }
    //Instance Renderer must be called for the ID or it will never get renderered (properly at least).  Should happen automatically with spawn or add
    public InstanceObjectRenderer addInstanceRenderer(short pType, World pWorld) {
        if(!instanceIdMap.containsKey(pType)) {
            Object3D obj = helper!=null ? helper.fetchObject3D(pType) : null;

            if(obj!=null) {
                Object3D[] parents = obj.getParents();
                if(parents!=null && parents.length>0) {
                    for(int j=0;j<parents.length;++j) {
                        obj.removeParent(parents[j]);
                    }
                }
                obj.addParent(objectHolder);
                pWorld.addObject(obj);

                if(shader!=null) {
                    obj.setShader(shader);
                }
                tmpIL = new InstanceTypeList();
                tmpIL.type = pType;
                tmpIL.renderer = obj instanceof Animated3D ? new InstanceAnimatedRenderer(obj) : new InstanceObjectRenderer(obj);
                instanceLists.add(tmpIL);
                instanceIdMap.put(tmpIL.type, (short)(instanceLists.size()-1));

                return tmpIL.renderer;
            }
        } else {
            tmpIndex = instanceIdMap.get(pType);
            if(tmpIndex!=null) {
                tmpIL = instanceLists.get(tmpIndex);
                if (tmpIL != null) {
                    return tmpIL.renderer;
                }
            }
        }
        return null;
    }
    public InstanceObjectRenderer getInstanceRenderer(short pType) {
        if(instanceLists.contains(pType)) {
            tmpIL = instanceLists.get(pType);
            if(tmpIL!=null) {
                return tmpIL.renderer;
            }
        }
        return null;
    }
    //To be called on tear down
    public void flush() {
        instanceFullList.clear();
        instanceIdMap.clear();
        instanceLists.clear();
        instances = null;
        tmpIL = null;
        tmpIO3D = null;
        tmpIndex = null;
    }
    public InstanceObject3D spawn(short pType, World pWorld) {
        InstanceObjectRenderer ior = addInstanceRenderer(pType, pWorld);
        boolean animated = ior!=null && ior instanceof InstanceAnimatedRenderer;
        InstanceObject3D inst = (helper!=null ? helper.spawn(pType, ior) : (animated ? new InstanceAnimated3D(pType, ior) : new InstanceObject3D(pType)));
        add(inst, pWorld);
        return inst;
    }
    public void add(InstanceObject3D pIO3D, World pWorld) {
        if(pIO3D!=null) {
            instanceFullList.add(pIO3D);
            addInstanceRenderer(pIO3D.type, pWorld);
        }
    }
    public void remove(InstanceObject3D pIO3D) {
        if(pIO3D!=null) {
            instanceFullList.remove(pIO3D);
        }
    }
    public void onUpdate(World pWorld, long pTimeSinceLastFrame) {
        cameraMatrix.setIdentity();
        if(pWorld!=null) {
            tmpCamera = pWorld.getCamera();
            cameraMatrix.setTo(tmpCamera.getBack());
            cameraMatrix.transformToGL();
            cameraBack.set(tmpCamera.getPosition());

            //Shift the objectHolder (which holds one instance of each Object3D) a distance of objectHolderOffsetDistance infront of the camera
            SimpleVector newObjectHolderPosition = tmpCamera.getDirection().normalize();
            newObjectHolderPosition.scalarMul(objectHolderOffsetDistance);
            newObjectHolderPosition.add(tmpCamera.getPosition());
            objectHolder.translate(newObjectHolderPosition.calcSub(objectHolderPosition));
            objectHolderPosition.set(newObjectHolderPosition);
        }

        //Reset Renderable
        for(i=0;i<instanceLists.size();++i) {
            instanceLists.get(i).visInstances.clear();
        }

        //Loop through all instances
        instances = instanceFullList.getList();
        if(instances!=null) {
            for (i = 0; i < instances.length; ++i) {
                tmpIO3D = instances[i];
                if (tmpIO3D != null) {
                    tmpIO3D.update(pTimeSinceLastFrame);
                    if (helper != null && helper.checkVisibility(tmpIO3D, tmpCamera)) {
                        tmpIndex = instanceIdMap.get(tmpIO3D.type);
                        if (tmpIndex != null) {
                            tmpIL = instanceLists.get(tmpIndex);
                            if (tmpIL != null && !tmpIL.visInstances.contains(tmpIO3D)) {
                                //Add this instance3D to the visList!
                                tmpIL.visInstances.add(tmpIO3D);
                            }
                        }
                    }
                }
            }
        }

        //Finalize
        for(i=0;i<instanceLists.size();++i) {
            tmpIL = instanceLists.get(i);
            tmpIL.renderer.finalizeRenderableInstances(tmpIL.visInstances.getList());
        }
    }


    /*** Private Class ***/
    private class InstanceTypeList {
        public short type;
        public InstanceList visInstances = new InstanceList();
        public InstanceObjectRenderer renderer;
    }
    private class InstanceList {
        private static final short SIZE = 100;
        private InstanceObject3D[] listWithPad = null;
        private InstanceObject3D[] list = null;
        public int count = 0;
        private boolean changed = true;
        private int tmpI;

        public InstanceList() {
            listWithPad = new InstanceObject3D[InstanceList.SIZE];
        }

        /*** PUBLIC ***/
        public InstanceObject3D[] getList() {
            if(changed) {
                list = toArray();
                changed = false;
            }
            return list;
        }
        public InstanceObject3D get(int i) {
            return listWithPad[i];
        }
        public void clear() {
            for(tmpI = 0; tmpI < count; ++tmpI) {
                listWithPad[tmpI] = null;
            }
            if(listWithPad.length > 1000) {
                listWithPad = new InstanceObject3D[InstanceList.SIZE];
            }
            count = 0;
            changed = true;
        }
        public void add(InstanceObject3D obj) {
            if(count >= listWithPad.length) {
                InstanceObject3D[] tmp = new InstanceObject3D[InstanceList.SIZE + listWithPad.length];
                System.arraycopy(listWithPad, 0, tmp, 0, listWithPad.length);
                listWithPad = tmp;
            }

            listWithPad[count] = obj;
            ++count;
            changed = true;
        }
        public void remove(int pIndex) {
            if(pIndex + 1 < count) {
                System.arraycopy(listWithPad, pIndex+1, listWithPad, pIndex, count - pIndex - 1);
            }

            --count;
            listWithPad[count] = null;
            changed = true;
        }
        boolean remove(InstanceObject3D pObj) {
            for(tmpI = 0; tmpI < count; ++tmpI) {
                if(listWithPad[tmpI].equals(pObj)) {
                    remove(tmpI);
                    return true;
                }
            }

            return false;
        }
        boolean contains(InstanceObject3D pObj) {
            for(tmpI = 0; tmpI < count; ++tmpI) {
                if(listWithPad[tmpI].equals(pObj)) {
                    return true;
                }
            }
            return false;
        }

        /*** PRIVATE ***/
        private InstanceObject3D[] toArray() {
            InstanceObject3D[] res = new InstanceObject3D[count];
            System.arraycopy(listWithPad, 0, res, 0, count);
            return res;
        }
    }
}


InstanceObjectRenderer Class:

package yourpackagename.instances;

import com.threed.jpct.GLSLShader;
import com.threed.jpct.IRenderHook;
import com.threed.jpct.Matrix;
import com.threed.jpct.Object3D;

/**
 * Created by Dougie on 7/6/16.
 */
public class InstanceObjectRenderer implements IRenderHook {
    protected short renderIndex=0, renderCount=0, tmpIndex;
    protected InstanceObject3D[] instances;
    protected GLSLShader shader;
    private InstanceObject3D instance;
    private Matrix mo = new Matrix();
    private InstanceManager instanceMgr;
    public Object3D renderObj3D = null;

    public InstanceObjectRenderer(Object3D pRenderObj3D) {
        renderObj3D = pRenderObj3D;
        if(renderObj3D!=null) {
            renderObj3D.setRenderHook(this);
        }
        instanceMgr = InstanceManager.getInstance();
    }

    public void finalizeRenderableInstances(InstanceObject3D[] pInstances) {
        instances = pInstances;
        if(renderObj3D!=null) {
            //Set the object3D visibility based off any visible instances
            renderObj3D.setVisibility(instances != null && instances.length > 0);
        }
    }

    @Override
    public void beforeRendering(int i) {
        renderIndex=0;
        renderCount=(short)(instances!=null ? instances.length : 0);
        setInstanceUniformValues(true);
    }
    @Override
    public void afterRendering(int i) {
        if(renderObj3D!=null) {
            shader = renderObj3D.getShader();
            if(shader != null && shader instanceof InstanceShader) {
                ((InstanceShader) shader).setInstanceCleanupUniforms();
            }
        }
    }
    @Override
    public void setCurrentObject3D(Object3D object3D) { ; }
    @Override
    public void setCurrentShader(GLSLShader glslShader) { ; }
    @Override
    public void setTransparency(float v) { ; }
    @Override
    public void onDispose() { ; }
    @Override
    public boolean repeatRendering() {
        renderIndex++;
        if(renderIndex<renderCount) {
            setInstanceUniformValues(false);
            return true;
        }
        return false;
    }

    /*** Protected methods ***/
    protected void setAnimatedUniforms(InstanceAnimated3D pIA3D) { ; }

    /*** Private methods ***/
    private void setInstanceUniformValues(boolean pSetSingleUniforms) {
        if(renderObj3D!=null && instances!=null && renderIndex>=0 && renderIndex<instances.length) {
            shader = renderObj3D.getShader();
            instance = instances[renderIndex];
            if(shader!=null && shader instanceof InstanceShader && instance!=null) {
                if(pSetSingleUniforms) {
                    ((InstanceShader)shader).setInstanceStartUniforms();
                }

                mo.setTo(instance.getTransformMatrix());
                mo.translate(-instanceMgr.cameraBack.x, instanceMgr.cameraBack.y, instanceMgr.cameraBack.z);
                mo.matMul(instanceMgr.cameraMatrix);
                ((InstanceShader)shader).setInstanceUniforms(mo);

                if(instance instanceof InstanceAnimated3D) {
                    setAnimatedUniforms((InstanceAnimated3D)instance);
                }
            }
        }
    }
}


InstanceObject3D Class:

package yourpackagename.instances;

import com.threed.jpct.Matrix;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.World;

/**
 * Created by Dougie on 7/6/16.
 */
public class InstanceObject3D {
    protected SimpleVector position = new SimpleVector();
    protected float scaleFactor = 1.0f;
    protected Matrix rotationMatrix = new Matrix();
    protected Matrix translationMatrix = new Matrix();
    protected Matrix transformMatrix = new Matrix();
    protected boolean changed = true;
    public short type = -1;
    public boolean visible = true;

    public InstanceObject3D(short pType) {
        type = pType;
        transformMatrix.setIdentity();
        rotationMatrix.setIdentity();
    }

    public void translate(SimpleVector pVector) {
        changed = true;
        position.add(pVector);
        translationMatrix.translate(pVector);
    }
    public void setPosition(SimpleVector pPosition) {
        changed = true;
        position.set(pPosition);
        regenMatrix();
    }
    public SimpleVector getPosition() {
        return new SimpleVector(position);
    }
    public void setPosition(float pX, float pY, float pZ) {
        changed = true;
        position.set(pX, pY, pZ);
        regenMatrix();
    }
    public void setType(short pType, World pWorld) {
        type = pType;
        InstanceManager.getInstance().addInstanceRenderer(type, pWorld);
    }
    public void scale(float scale) {
        if(scale > 0.0F) {
            changed = true;
            scaleFactor *= scale;
            rotationMatrix.scalarMul(scale);
        }
    }
    public void setScale(float absScale) {
        if(scaleFactor != 0.0F && absScale > 0.0F) {
            float scale = absScale / scaleFactor;
            if(scale < 1.0E-4F) {
                scale = 1.0E-4F;
            }

            scale(scale);
        }
    }
    public void rotateX(float w) {
        changed = true;
        rotationMatrix.rotateX(w);
    }
    public void rotateY(float w) {
        changed = true;
        rotationMatrix.rotateY(w);
    }
    public void rotateZ(float w) {
        changed = true;
        rotationMatrix.rotateZ(w);
    }
    public void rotateAxis(SimpleVector axis, float angle) {
        changed = true;
        rotationMatrix.rotateAxis(axis, angle);
    }

    public boolean hasChanged() {
        return changed;
    }

    public Matrix getTransformMatrix() {
        if(changed) {
            changed = false;
            transformMatrix.setIdentity();
            transformMatrix.matMul(rotationMatrix);
            transformMatrix.matMul(translationMatrix);
            transformMatrix.transformToGL();
        }
        return transformMatrix;
    }

    public void update(long pTimeSinceLastFrame) {
        //Either add code here or extend class and override this method to add logic on frame update
    }

    private void regenMatrix() {
        translationMatrix.setIdentity();
        translationMatrix.translate(position);
    }
}


InstanceShader Class:

package yourpackagename.instances;

import com.threed.jpct.GLSLShader;
import com.threed.jpct.Matrix;

/**
 * Created by Dougie on 7/6/16.
 */
public class InstanceShader extends GLSLShader {
    private static final String UNIFORM_ISINSTANCE = "isInstance";
    private static final String UNIFORM_IMVM = "iMVM";
    private InstanceManager instanceMgr;

    public InstanceShader(String vertexShaderSource, String fragmentShaderSource) {
        super(vertexShaderSource, fragmentShaderSource);
        instanceMgr = InstanceManager.getInstance();
    }

    public void setInstanceStartUniforms() {
        setUniform(UNIFORM_ISINSTANCE, 1);
        setUniform("skelPoseSize", 0);
    }

    public void setInstanceUniforms(Matrix pMatrix) {
        setUniform(UNIFORM_IMVM, pMatrix);
    }
    public void setInstanceCleanupUniforms() {
        setUniform(UNIFORM_ISINSTANCE, 0);
    }
}


IInstanceManagerHelper Interface:

package yourpackagename.instances;

import com.threed.jpct.Camera;
import com.threed.jpct.Object3D;

/**
 * Created by Dougie on 7/6/16.
 */
public interface IInstanceManagerHelper {
    //Method is called to create an instance of pType with its respective InstanceObjectRenderer
    public InstanceObject3D spawn(short pType, InstanceObjectRenderer pRenderer);

    //Method is called to create an Object3D / Animated3D of pType.
    public Object3D fetchObject3D(short pType);

    //Method is called during update to see if an InstanceObject3D is visible by the camera
    public boolean checkVisibility(InstanceObject3D pIO3D, Camera pCamera);

    //Method is called on destruction / clearing of the InstanceManager 
    public void onDestroy();
}


InstanceAnimatedRenderer Class:

Bones Animated Class. Requires BonesNamespaceUtils: Hybrid_GPU_Shader_Animations_for_Bones

package yourpackagename.instances;

import yourpackagename.GPUAnimated3DShader;
import com.threed.jpct.Object3D;

import raft.jpct.bones.Animated3D;
import raft.jpct.bones.BonesNamespaceUtils;

/**
 * Created by Dougie on 7/6/16.
 */
public class InstanceAnimatedRenderer extends InstanceObjectRenderer {
    public InstanceAnimatedRenderer(Object3D pRenderObj3D) {
        super(pRenderObj3D);
        if(pRenderObj3D!=null && pRenderObj3D instanceof Animated3D) {
            Animated3D pAnimated3D = (Animated3D)pRenderObj3D;
            pRenderObj3D.getMesh().removeVertexController();
            BonesNamespaceUtils.setSkinAttributes(pAnimated3D);
        }
    }

    @Override
    protected void setAnimatedUniforms(InstanceAnimated3D pIA3D) {
        if(pIA3D!=null && shader!=null && shader instanceof GPUAnimated3DShader) {
            ((GPUAnimated3DShader)shader).updateBeforeRenderingObject( pIA3D );
        }
    }
}


InstanceAnimated3D Class:

Bones Animated Class. Requires BonesNamespaceUtils: Hybrid_GPU_Shader_Animations_for_Bones

package yourpackagename.instances;

import raft.jpct.bones.Animated3D;
import raft.jpct.bones.BonesNamespaceUtils;
import raft.jpct.bones.SkeletonPose;
import raft.jpct.bones.SkinClip;

/**
 * Created by Dougie on 7/6/16.
 */
public class InstanceAnimated3D extends InstanceObject3D {
    protected Animated3D animated3D = null;
    public SkeletonPose pose = null;

    public InstanceAnimated3D(short pType, InstanceObjectRenderer pRenderer) {
        super(pType);
        if(pRenderer!=null && pRenderer.renderObj3D!=null && pRenderer.renderObj3D instanceof Animated3D) {
            animated3D = (Animated3D) pRenderer.renderObj3D;
            pose = new SkeletonPose(animated3D.getSkeleton());
        }
    }

    public void animateSkin(float index, int sequence) {
        if(animated3D!=null && animated3D.getSkinClipSequence()!=null) {
            if(sequence == 0) {
                BonesNamespaceUtils.animate(animated3D, index*animated3D.getSkinClipSequence().getTime(), pose);
            } else {
                SkinClip clip = animated3D.getSkinClipSequence().getClip(sequence - 1);
                clip.applyTo(index * clip.getTime(), pose);
            }
            pose.updateTransforms();
        }
    }
}


InstanceAnimated3D Class:

Bones Animated Class. Provided by: Hybrid_GPU_Shader_Animations_for_Bones. CHANGE EXTENDED CLASS TO InstanceShader

package yourpackagename;
...

public class GPUAnimated3DShader extends InstanceShader {
    ...
}

Vertex Shader Snippet:

This just contains the necessary snippets of code to add to your Vertex Shader to have it work.

 
...
uniform mat4 modelViewMatrix;
uniform mat4 modelViewProjectionMatrix;
uniform mat4 projectionMatrix;
uniform mat4 iMVM;
uniform int isInstance;
...
uniform mat4 skelPose[50];
uniform int skelPoseSize;

attribute vec4 position;
attribute vec3 normal;
...
attribute vec4 skinWeights;
attribute vec4 jointIndices;
...

void main(void)
{
	mat4 mvm = modelViewMatrix;
	mat4 mvpm = modelViewProjectionMatrix;
	if(isInstance>0) {
		mvm = iMVM;
		mvpm = projectionMatrix*mvm;
	}

	vec4 newPosition = vec4(0.0,0.0,0.0, position[3]);
	vec3 newNormal = vec3(0.0,0.0,0.0);
	if(skelPoseSize>0) {
		float weight;
		for(int j=0; j<4; ++j) {
			weight = skinWeights[j];
			if(weight != 0.0) {
				mat4 boneMat = skelPose[int(floor(jointIndices[j]+0.5))];
				newPosition.xyz += vec3(position.x*boneMat[0][0] + position.y*boneMat[1][0] + position.z*boneMat[2][0] + boneMat[3][0],
										position.x*boneMat[0][1] + position.y*boneMat[1][1] + position.z*boneMat[2][1] + boneMat[3][1],
										position.x*boneMat[0][2] + position.y*boneMat[1][2] + position.z*boneMat[2][2] + boneMat[3][2])*weight;
				newNormal += vec3(normal.x*boneMat[0][0] + normal.y*boneMat[1][0] + normal.z*boneMat[2][0],
								  normal.x*boneMat[0][1] + normal.y*boneMat[1][1] + normal.z*boneMat[2][1],
								  normal.x*boneMat[0][2] + normal.y*boneMat[1][2] + normal.z*boneMat[2][2])*weight;
			}
		}
		newPosition.yz *= -1.0;
		newNormal.yz *= -1.0;
	} else {
		newPosition = position;
		newNormal = normal;
	}

        ...

	gl_Position = mvpm * newPosition;
}

How to Use the Code

Create a new class of your own implementing the IInstanceManagerHelper Interface

Example:

    public class YourInstanceManagerHelper implements IInstanceManagerHelper {
       <<Add your logic here>>
    }

Instantiating the InstanceManager:

    InstanceManager instanceManager = InstanceManager.getInstance();
    InstanceShader instShader = <<Create InstanceShader>>
    instanceManager.setShader(instShader);
    instanceManager.setHelper(new YourInstanceManagerHelper(world.getCamera()));

onDrawFrame, call the InstanceManager's onUpdate(...)

    InstanceManager.getInstance().onUpdate(world, milliSecondsSinceLastFrame);

FAQ

How can one detect when an InstanceObject3D is touched?

By default the mesh of an InstanceObject3D is not touched by IBR. If you do alter the mesh data, you already have access to the Mesh / Object3D as you've altered the vertices / uvs. If you do alter the mesh, note that it will affect all instances as it uses a shared mesh. If you need to get the Object3D of an InstanceObject3D type, use the InstanceManager.getObject3DOfInstanceType().

As for the transform matrices of the InstanceObject3D, by default it does a sort of lazy-transformations. Any time you do a scale/translation/rotation, it sets a protected boolean changed to true. On call of getTransformMatrix(), it will re-create the transform matrix if changed==true, otherwise it just returns the previously calculated transform matrix. getTransformMatrix() get called when rendering each instance (every frame). I have added a method to the code called hasChanged(), which will return true if there has been a translate, scale or rotation since last getTransformMatrix(). I hope this answers your question.


Does the order in which the InstanceObject3D's are drawn matter?

It can, and Egon may be able to better explain some of this. It should only matter if your Object3D uses transparency or you set the OpenGL to disable the depth test, in which case it will render in synchronous order. To put it simply, I don't believe transparency InstanceObject3D's are going to play nicely with other transparent Object3D's, other transparent InstanceObject3D types, or overlaps of the multiple IntanceObject3D of that type. I believe jPCT has software-side ordering (based off the Object3D's origin distance from camera?) when it comes to transparent objects, and the transparent objects are rendered last as transparency requires the solid pixels behind it for the different pixel write modes (add, modulate, blend, etc...). Because all Object3D's for this method are stacked a certain distance from the camera, they will be picked up by jPCT render pipeline in regards to that distance from the camera. All InstanceObject3D's that are rendered would be synchronously rendered on top of one another according to the order when added. I could add simple sorting, but it won't solve all problems, as transparency is a complex subject that requires work arounds and tweaks for speed. But it will never work great with IBR as the distance from the camera will mess up jPCT's sorting order.