Instancing Discussion

Mhh ok… well maybe we can work on that though…

Eh I don’t think there’s a point.

Even as gpus get better and nearly every pc in existence supports 3.1 and onwards we’ll always have macOS stuck at 2.0 because reasons.

So the options will be either have a compatibility mode for macs or not support them at all. Or just not use instancing.

Its not a good idea to allocate FloatBuffers every frame.
To update a vertex buffer:

VertexBuffer vb = mesh.getBuffer(VertexBuffer.Type.InstanceData);
FloatBuffer fb = (FloatBuffer)vb.getData();
//do any changes to fb, but remember what buffers are
//buffers have position, mark, limit, capacity
//   -capacity is maximum length, this does not change
//   if you need bigger capacity, either change the buffer or create new instanced geom
//   -limit is the current length, make sure the limit 
//	  corresponds with your (instance count)x(data per instance)
			
//eg lets clear all and add new
//clear
fb.clear();
//just make sure object.size can fit into the buffer
for (int i = 0; i < objects.size(); i++) {
    Matrix4f mat4 = objects.get(i).transform.toTransformMatrix();
    mat4.fillFloatBuffer(data, true);
}
//flip will set the proper limit
fb.flip();
			
//finally update the buffer
vb.updateData(data);
//update number of instances(instanceCount) in the mesh
mesh.updateCounts();

You start by creating a buffer that can hold enough data for you. Eg. you create one that can hold 1k shots.
It’s the same principle how for example array list manages its array. Once the capacity is not enough, you either enlarge the buffer or create a separate one with new geom.

1 Like

I should mark this thread and read it later.

I played around with the code above. I tried to add light and shadow to this code.

First I need to change the last row of transformMatrix, so the light works correctlly.

    for (int i = 0; i < objects.size(); i++) {
        Shot shot = objects.get(i);
        Matrix4f mat4 = shot.transform.toTransformMatrix();
        
        Quaternion rot = shot.transform.getRotation();
        mat4.m30 = rot.getX();
        mat4.m31 = rot.getY();
        mat4.m32 = rot.getZ();
        mat4.m33 = rot.getW();
        
        mat4.fillFloatBuffer(fb, true);
    }

But I duno how to make shadow work.

This is the code.

import java.nio.FloatBuffer;
import java.util.ArrayList;

import com.jme3.app.SimpleApplication;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.FXAAFilter;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Quad;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.util.BufferUtils;

public class MCVE extends SimpleApplication {

    public final static float COOL_DOWN = 0.02f;
    public final static float LIFE = 10f;
    public final static int MAX_BUFFER_LENGTH = (int) (LIFE / COOL_DOWN * 16 * 2);
    
    private float cd = 0;
    private boolean instance = true;
    
    private boolean shoot = false;

    private Mesh mesh;
    private Geometry model;
    private ArrayList<Shot> objects = new ArrayList<Shot>();

    public static void main(String[] args) {
        MCVE app = new MCVE();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        setupCamera();
        setupLights();
        setupScene();
        setupInput();
    }
    
    /**
     * setup camera
     */
    private void setupCamera() {
        flyCam.setMoveSpeed(10);
        cam.setFrustumPerspective(70f, (float) cam.getWidth() / cam.getHeight(), 1f, 1000f);
        cam.setLocation(Vector3f.UNIT_Z.mult(10f));
    }
    
    /**
     * setup lights
     */
    private void setupLights() {
        // add lights
        ColorRGBA lightColor = new ColorRGBA(1, 1, 1, 1);
        
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-3, -4, -5).normalizeLocal());
        sun.setColor(lightColor.mult(0.7f));
        
        AmbientLight ambient = new AmbientLight();
        ambient.setColor(lightColor.mult(0.3f));
        
        rootNode.addLight(sun);
        rootNode.addLight(ambient);
        
        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
        viewPort.addProcessor(fpp);
        
        // add shadow filter
        DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 1024, 4);
        dlsf.setLight(sun);
        dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
        dlsf.setRenderBackFacesShadows(false);
        fpp.addFilter(dlsf);
        
        // add fxaa filter
        FXAAFilter fxaa = new FXAAFilter();
        fpp.addFilter(fxaa);
    }
    
    /**
     * setup scene
     */
    private void setupScene() {
        // setup material
        Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        material.setColor("Diffuse", new ColorRGBA(0.7f, 1f, 1f, 1f));
        material.setColor("Ambient", new ColorRGBA(0.7f, 1f, 1f, 1f));
        material.setColor("Specular", ColorRGBA.White);
        material.setBoolean("UseMaterialColors", true);
        material.setBoolean("UseInstancing", instance);

        // create a cube mesh
        mesh = new Box(1f, 1f, 1f);
        
        // setup instance data
        VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.InstanceData);
        vb.setInstanced(true);
        FloatBuffer data = BufferUtils.createFloatBuffer(MAX_BUFFER_LENGTH);
        vb.setupData(Usage.Static, 16, VertexBuffer.Format.Float, data);
        
        mesh.setBuffer(vb);
        
        // setup model
        model = new Geometry("cube", mesh);
        model.setCullHint(CullHint.Never);
        model.setLocalTranslation(0, 0, 0);
        model.setMaterial(material);
        rootNode.attachChild(model);

        rootNode.setCullHint(CullHint.Never);
        
        // make a floor to receive shadow
        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        mat.setColor("Diffuse", new ColorRGBA(1f, 0.8f, 0.6f, 1f));
        mat.setColor("Ambient", new ColorRGBA(1f, 0.8f, 0.6f, 1f));
        mat.setColor("Specular", new ColorRGBA(1, 1, 1, 1));
        mat.setBoolean("UseMaterialColors", true);
        
        Geometry floor = new Geometry("floor", new Quad(2000, 2000));
        floor.setMaterial(mat);
        floor.rotate(-0.5f * FastMath.PI, 0, 0);
        floor.center();
        
        rootNode.attachChild(floor);
        
        // setup shadow mode
        floor.setShadowMode(ShadowMode.Receive);
        model.setShadowMode(ShadowMode.CastAndReceive);
    }
    
    /**
     * setup input
     */
    private void setupInput() {
        inputManager.addListener(new ActionListener() {
            public void onAction(String name, boolean isPressed, float tpf) {
                if (name.equals("shoot"))
                    shoot = isPressed;
            }
        }, "shoot");
        inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    }
    
    @Override
    public void simpleUpdate(float tpf) {
        cd += tpf;
        if (shoot) {
            if (cd >= COOL_DOWN) {
                cd = 0f;
                
                Vector3f side = cam.getRotation().getRotationColumn(0).mult(3);
                objects.add(new Shot(cam.getLocation().add(side), cam.getRotation()));
                objects.add(new Shot(cam.getLocation().add(side.negate()), cam.getRotation()));
            }
        }

        for (int i = 0; i < objects.size(); i++) {
            Shot b = objects.get(i);
            b.ttl -= tpf;

            if (b.ttl > 0)
                b.update(tpf);
            else {
                objects.remove(b);
                i--;
            }
        }

        recalculate();
    }

    private void recalculate() {
        if (objects.size() == 0) {
            return;
        }

        VertexBuffer vb = mesh.getBuffer(VertexBuffer.Type.InstanceData);
        FloatBuffer fb = (FloatBuffer)vb.getData();
        
        fb.clear();
        for (int i = 0; i < objects.size(); i++) {
            Shot shot = objects.get(i);
            Matrix4f mat4 = shot.transform.toTransformMatrix();
            
            Quaternion rot = shot.transform.getRotation();
            mat4.m30 = rot.getX();
            mat4.m31 = rot.getY();
            mat4.m32 = rot.getZ();
            mat4.m33 = rot.getW();
            
            mat4.fillFloatBuffer(fb, true);
        }
        fb.flip();

        vb.updateData(fb);
        mesh.updateCounts();
        mesh.updateBound();
    }
}

class Shot {

    float ttl = MCVE.LIFE;
    Transform transform;

    public Shot(Vector3f loc, Quaternion dir) {
        transform = new Transform();
        transform.setTranslation(loc);
        transform.setRotation(dir);
    }

    public void update(float tpf) {
        Vector3f dir = transform.getRotation().getRotationColumn(2).mult(60 * tpf);
        transform.setTranslation(transform.getTranslation().add(dir));
    }

}

For what it’s worth, the instancing I use in the IsoSurfaceDemo is compatible with JME’s shaders, supports lighting, shadows, etc… I create my own meshes but they work fine with JME materials.

As you can see from your screenshots, there are shadows, but they are “cut off”.
Furthermore, don’t forget that I have used CullHint.Never just for testing.

In other words, mesh.updateBounds() will update the bounding volume as if it were normal non instanced mesh.

Thus, it is up to you to calculate the correct bounding volume.
Eg. if you use

mesh.setBound(new BoundingBox(Vector3f.ZERO, 1000,1000,1000));

the shadows with work within 1000 units around origin.

2 Likes

Cool