Difference between revisions of "Instance Batch Rendering"

From JPCT
Jump to: navigation, search
(The Code)
Line 35: Line 35:
  
  
'''XXX Class:'''
+
'''InstanceManager Class:'''
 
<pre>
 
<pre>
 +
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;
 +
        }
 +
    }
 +
}
 
</pre>
 
</pre>
  
 +
 +
'''InstanceObjectRenderer Class:'''
 +
<pre>
 +
package yourpackagename.instances;
 +
 +
import com.threed.jpct.GLSLShader;
 +
import com.threed.jpct.IRenderHook;
 +
import com.threed.jpct.Matrix;
 +
import com.threed.jpct.Object3D;
 +
import com.threed.jpct.SimpleVector;
 +
 +
import raft.jpct.bones.Animated3D;
 +
 +
/**
 +
* 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);
 +
            if(renderObj3D instanceof Animated3D) {
 +
                ;
 +
            }
 +
        }
 +
        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);
 +
                }
 +
            }
 +
        }
 +
    }
 +
}
 +
</pre>
 +
 +
 +
'''InstanceObject3D Class:'''
 +
<pre>
 +
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) {
 +
        rotationMatrix.rotateX(w);
 +
    }
 +
    public void rotateY(float w) {
 +
        rotationMatrix.rotateY(w);
 +
    }
 +
    public void rotateZ(float w) {
 +
        rotationMatrix.rotateZ(w);
 +
    }
 +
    public void rotateAxis(SimpleVector axis, float angle) {
 +
        rotationMatrix.rotateAxis(axis, angle);
 +
    }
 +
 +
    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);
 +
    }
 +
}
 +
</pre>
 +
 +
 +
'''InstanceShader Class:'''
 +
<pre>
 +
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);
 +
    }
 +
}
 +
</pre>
 +
 +
 +
'''IInstanceManagerHelper Interface:'''
 +
<pre>
 +
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();
 +
}
 +
</pre>
 +
 +
 +
'''InstanceAnimatedRenderer Class:'''
 +
Bones Animated Class.  Requires BonesNamespaceUtils: [[Hybrid_GPU_Shader_Animations_for_Bones]]
 +
<pre>
 +
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 );
 +
        }
 +
    }
 +
}
 +
</pre>
 +
 +
 +
'''InstanceAnimated3D Class:'''
 +
Bones Animated Class.  Requires BonesNamespaceUtils: [[Hybrid_GPU_Shader_Animations_for_Bones]]
 +
<pre>
 +
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();
 +
        }
 +
    }
 +
}
 +
</pre>
 +
 +
 +
'''InstanceAnimated3D Class:'''
 +
Bones Animated Class.  Provided by: [[Hybrid_GPU_Shader_Animations_for_Bones]].  '''CHANGE EXTENDED CLASS TO InstanceShader'''
 +
<pre>
 +
package yourpackagename;
 +
...
 +
 +
public class GPUAnimated3DShader extends InstanceShader {
 +
    ...
 +
}
 +
</pre>
  
 
== How to Use the Code ==
 
== How to Use the Code ==

Revision as of 04:25, 15 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.


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;
import com.threed.jpct.SimpleVector;

import raft.jpct.bones.Animated3D;

/**
 * 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);
            if(renderObj3D instanceof Animated3D) {
                ;
            }
        }
        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) {
        rotationMatrix.rotateX(w);
    }
    public void rotateY(float w) {
        rotationMatrix.rotateY(w);
    }
    public void rotateZ(float w) {
        rotationMatrix.rotateZ(w);
    }
    public void rotateAxis(SimpleVector axis, float angle) {
        rotationMatrix.rotateAxis(axis, angle);
    }

    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 {
    ...
}

How to Use the Code

  • XXX


FAQ

To be written