4

I'm working on a 3D game.

The game requires around 100 cubes to work, all cube have be dynamic.

I don't really know how much perfomance is required for a game like this, but i'm testing with a tablet with Mali-400 MP2 GPU, 1 GB ram, 1.5 GHz dual core. I know about rendering all of the cubes in one mesh, but then i can't move all of them separately.

This setup gives me a very vacillating fps. Jumping between 20 and 50, mostly under 30. (In emulator 10-15)

When the game starts, i build an arraylist of ModelInstances, all of them is using the same model.

model = new ModelBuilder().createBox(1f, 1f, 1f, new Material(ColorAttribute.createDiffuse(Color.GREEN)), Usage.Position | Usage.Normal);

// width,height,length = 5, creating a total of 125 cubes

for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
        for (int z = 0; z < length; z++) {
            if (this.map[x][y][z] > 0) {
                this.modelInstances.add(instance = new ModelInstance(model));
                instance.transform.translate(x, -(y * 1.5f), -z);
            }
        }
    }
}

Rendering:

Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
mb.begin(camera3D);
mb.render(this.modelInstances);
mb.end();

The camera initializing:

camera3D = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
camera3D.position.set(0f, 8f, 5f);
camera3D.lookAt(0, 0, 0);
camera3D.near = 1f;
camera3D.far = 300f;
camera3D.update();
  • What can i do to increase the perfomance?
  • Is the tablet too weak for a game like this? or the problem is the code?

EDIT:

Did some test with webGL too, same tablet, using chrome, rendering 125 cubes: stable 40-50 fps

Dávid Szabó
  • 2,235
  • 2
  • 14
  • 26
  • What does width, height, length try to equal? – Hayden Jun 07 '14 at 16:12
  • They are 5 5 5, 125 cubes rendered totally. – Dávid Szabó Jun 07 '14 at 16:18
  • That seems to be fine. Is that all you are doing in `render()`? – noone Jun 07 '14 at 20:28
  • @noone I have some lines to write the fps to the screen, but that isn't the issue, if i comment the 'mb.render(this.modelInstances)' the fps goes up to stable 55-60 – Dávid Szabó Jun 07 '14 at 20:48
  • I could confirm that this is a problem. Same story as yours: Using ModelBuilder to create boxes, in my case i have around 100-120. Put them in an ModelInstance array and render them. Get 30 fps on galaxy note 2. My cubes are dynamically generated in real time but once they appear they never move (no transformations applied). – Justas Sakalauskas Jun 11 '14 at 11:06
  • @JustasSakalauskas =( Sadly no answer here nor at badlogic forum. – Dávid Szabó Jun 11 '14 at 16:29
  • I noticed that you posted in general development section on libgdx forums. That section isn't very active. You might consider moving that post to libgdx section ( where it actually belongs as it is directly related to libgdx) ;) – Justas Sakalauskas Jun 11 '14 at 18:10
  • @JustasSakalauskas Nah, i can't post it there, it is for 'LibGdx' development, i mean for the library, not for the users. – Dávid Szabó Jun 11 '14 at 19:00
  • "Libgdx Development" is for libgdx's core developers that develop the library . "Libgdx" is the place for users to discus libgdx related problems. And thats where you belong. "General Development" is for not libgdx related development. – Justas Sakalauskas Jun 11 '14 at 19:21
  • I don't have time at the moment to go into a detailed answer, but when working with mobile you need to minimize your draw calls. One way to achieve this would be to merge your models together into fewer meshes. I've been able to scale this up to millions of textured cubes with dynamic lighting (on a desktop obviously). – Jyro117 Jun 11 '14 at 19:22
  • @Jyro117 Thanks for your comment! Yeah i know about 'minimizing draw calls', but i can't use it (am i right?) because i need to manipulate all the cubes differently. – Dávid Szabó Jun 11 '14 at 19:24
  • @JustasSakalauskas I feel so stupid =( i really didn't see that (Edit: Deleted the one in the general development, and created a new one in the libgdx. Thanks!!!) – Dávid Szabó Jun 11 '14 at 19:24
  • @newboyhun incorrect, you are thinking about it in the naive way. You can manipulate the vertices independent of how you store it. For example, you want to translate a cube. You take each vertex in your large mesh you wish to manipulate and then apply your transformation. Write the new values to those vertices back into the backing buffer. – Jyro117 Jun 11 '14 at 19:29
  • @Jyro117 but isn't that what ModelBatch should do? Isn't that how SpriteBatch works. Where it batches all sprites and then sends them in one draw call. Why ModelBatch doesn't work that way? (just curious i don't have much knowledge about this) – Justas Sakalauskas Jun 11 '14 at 20:20
  • That is indeed how SpriteBatch work. ModelBatch doesn't merge your models together to reduce draw calls. Just look at the implementation https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g3d/ModelBatch.java all it does is loop through the supplied renderables and draws them. There are some good reasons for this because merging shaders/meshes/materials/etc is not a trivial task. – Jyro117 Jun 11 '14 at 21:44
  • @newboyhun Hi, try to use view frustum culling. I am facing the same issue, when rendering my 100*100map, which has a maximum of 10000 1*4*1 Blocks, which form a wall. But my FPS only drop to 20, if i am looking from one corner to the opposite corner (x=0,z=0 to x=100, z = 100) beacuse then almost all blocks are inside my frustum. I am also using another technique: If 2 walls are near each other 1 face of each wall is invisible, so i do not render it. By doing this i can highly reduce the number of rendered blocks. – Robert P Jun 12 '14 at 07:49
  • @Springrbua Frustum culling is an another story (i will implement it later), you said you are rendering 100*100 map, i'm rendering only 125 cubes because i have all of my model separate, trying to figure out how to merge them and edit it later without problem. – Dávid Szabó Jun 12 '14 at 11:58
  • Rendering 125 models imho shouldn't be a huge problem, even not on a Tablet... The culling is not verry hard to implement, camera has methods. I used `isSphereInFrustum` and used a sphereradius a bit bigger than my blocks size. by doing this i was able to cull out a huge amount of blocks. Are you using any `Shader`, which could be expensive? – Robert P Jun 12 '14 at 12:28
  • @Springrbua Nothing. How can i merge the 125 boxes into one so i can minimize the draw calls? (And manipulate the boxes separately) I need the 125 cubes rendered, maybe do you have a link to it? – Dávid Szabó Jun 12 '14 at 12:37
  • Sorry with this i can't help. I know that for Voxel-Engines blocks which are near each other and have the same `Textures` are merged to one bigger block, but idk how to do that, if they need to be translated independend... Sorry – Robert P Jun 12 '14 at 12:45

1 Answers1

5

You can batch all your cubes into a single Model and ModelInstance like this:

int width = 5;
int height = 5;
int length = 5;
int numCubes = width*height*length;

ModelBuilder mb = new ModelBuilder();
mb.begin();
MeshPartBuilder mpb = mb.part("cubes", GL20.GL_TRIANGLES, (Usage.Position | Usage.Normal), new Material(ColorAttribute.createDiffuse(Color.GREEN)));
for (int i=0; i<numCubes; i++){
    mpb.box(1, 1, 1); 
}
Model model = mb.end();
mBatchedCubesModelInstance = new ModelInstance(model);

But the tricky part is being able to move each of those cubes to a different location and be able to manipulate them independently.

Here's a Cube class that can manipulate the individual cubes from the above model. I think theoretically it should work with any Mesh you create that uses 24 unique vertices per cube, so you could add texture coordinates for example.

This also relies on position and then normal always being the first two Usage attributes of the mesh, so hopefully that holds true in libGDX's Mesh class.

This works basically by keeping track of it's index number in the base mesh (which cube number it is) so it can pick out the vertices that it needs to update in the vertices array. The vertices need to be copied into the mesh each frame.

public class Cube {

    private int index;
    int vertexFloatSize;
    int posOffset;
    int norOffset;
    boolean hasColor;
    int colOffset;
    private Vector3 position = new Vector3();
    private Matrix4 rotationTransform = new Matrix4().idt();
    private Color color = new Color();
    public float halfWidth, halfHeight, halfDepth;
    private boolean transformDirty = false;
    private boolean colorDirty = false;

    static final Vector3 CORNER000 = new Vector3();
    static final Vector3 CORNER010 = new Vector3();
    static final Vector3 CORNER100 = new Vector3();
    static final Vector3 CORNER110 = new Vector3();
    static final Vector3 CORNER001 = new Vector3();
    static final Vector3 CORNER011 = new Vector3();
    static final Vector3 CORNER101 = new Vector3();
    static final Vector3 CORNER111 = new Vector3();

    static final Vector3[] FACE0 = {CORNER000, CORNER100, CORNER110, CORNER010};
    static final Vector3[] FACE1 = {CORNER101, CORNER001, CORNER011, CORNER111};
    static final Vector3[] FACE2 = {CORNER000, CORNER010, CORNER011, CORNER001};
    static final Vector3[] FACE3 = {CORNER101, CORNER111, CORNER110, CORNER100};
    static final Vector3[] FACE4 = {CORNER101, CORNER100, CORNER000, CORNER001};
    static final Vector3[] FACE5 = {CORNER110, CORNER111, CORNER011, CORNER010};
    static final Vector3[][] FACES = {FACE0, FACE1, FACE2, FACE3, FACE4, FACE5};

    static final Vector3 NORMAL0 = new Vector3();
    static final Vector3 NORMAL1 = new Vector3();
    static final Vector3 NORMAL2 = new Vector3();
    static final Vector3 NORMAL3 = new Vector3();
    static final Vector3 NORMAL4 = new Vector3();
    static final Vector3 NORMAL5 = new Vector3();
    static final Vector3[] NORMALS = {NORMAL0, NORMAL1, NORMAL2, NORMAL3, NORMAL4, NORMAL5};

    public Cube(float x, float y, float z, float width, float height, float depth, int index, 
            VertexAttributes vertexAttributes, float[] meshVertices){
        position.set(x,y,z);
        this.halfWidth = width/2;
        this.halfHeight = height/2;
        this.halfDepth = depth/2;
        this.index = index;


        vertexFloatSize = vertexAttributes.vertexSize/4; //4 bytes per float
        posOffset = getVertexAttribute(Usage.Position, vertexAttributes).offset/4;
        norOffset = getVertexAttribute(Usage.Normal, vertexAttributes).offset/4;

        VertexAttribute colorAttribute = getVertexAttribute(Usage.Color, vertexAttributes);
        hasColor = colorAttribute!=null;
        if (hasColor){
            colOffset = colorAttribute.offset/4;
            this.setColor(Color.WHITE, meshVertices);
        }
        transformDirty = true;
    }

    public void setIndex(int index){
        this.index = index;
        transformDirty = true;
        colorDirty = true;
    }

    /**
     * Call this after moving and/or rotating.
     */
    public void update(float[] meshVertices){
        if (colorDirty && hasColor){
            for (int faceIndex= 0; faceIndex<6; faceIndex++){
                int baseVertexIndex = (index*24 + faceIndex*4)*vertexFloatSize;//24 unique vertices per cube, 4 unique vertices per face
                for (int cornerIndex=0; cornerIndex<4; cornerIndex++){
                    int vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + colOffset;
                    meshVertices[vertexIndex] = color.r;
                    meshVertices[++vertexIndex] = color.g;
                    meshVertices[++vertexIndex] = color.b;
                    meshVertices[++vertexIndex] = color.a;
                }
            }
            colorDirty = false;
        }


        if (!transformDirty){
            return;
        }
        transformDirty = false;

        CORNER000.set(-halfWidth,-halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER010.set(-halfWidth,halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER100.set(halfWidth,-halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER110.set(halfWidth,halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER001.set(-halfWidth,-halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER011.set(-halfWidth,halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER101.set(halfWidth,-halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER111.set(halfWidth,halfHeight,halfDepth).rot(rotationTransform).add(position);

        NORMAL0.set(0,0,-1).rot(rotationTransform);
        NORMAL1.set(0,0,1).rot(rotationTransform);
        NORMAL2.set(-1,0,0).rot(rotationTransform);
        NORMAL3.set(1,0,0).rot(rotationTransform);
        NORMAL4.set(0,-1,0).rot(rotationTransform);
        NORMAL5.set(0,1,0).rot(rotationTransform);

        for (int faceIndex= 0; faceIndex<6; faceIndex++){
            int baseVertexIndex = (index*24 + faceIndex*4)*vertexFloatSize;//24 unique vertices per cube, 4 unique vertices per face
            for (int cornerIndex=0; cornerIndex<4; cornerIndex++){
                int vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + posOffset;
                meshVertices[vertexIndex] = FACES[faceIndex][cornerIndex].x;
                meshVertices[++vertexIndex] = FACES[faceIndex][cornerIndex].y;
                meshVertices[++vertexIndex] = FACES[faceIndex][cornerIndex].z;

                vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + norOffset;
                meshVertices[vertexIndex] = NORMALS[faceIndex].x;
                meshVertices[++vertexIndex] = NORMALS[faceIndex].y;
                meshVertices[++vertexIndex] = NORMALS[faceIndex].z;
            }
        }
    }

    public Cube setColor(Color color){
        if (hasColor){
            this.color.set(color);
            colorDirty = true;
        }
        return this;
    }

    public Cube translate(float x, float y, float z){
        position.add(x,y,z);
        transformDirty = true;
        return this;
    }

    public Cube translateTo(float x, float y, float z){
        position.set(x,y,z);
        transformDirty = true;
        return this;
    }

    public Cube rotate(float axisX, float axisY, float axisZ, float degrees){
        rotationTransform.rotate(axisX, axisY, axisZ, degrees);
        transformDirty = true;
        return this;
    }

    public Cube rotateTo(float axisX, float axisY, float axisZ, float degrees){
        rotationTransform.idt();
        rotationTransform.rotate(axisX, axisY, axisZ, degrees);
        transformDirty = true;
        return this;
    }

    public VertexAttribute getVertexAttribute (int usage, VertexAttributes attributes) {
        int len = attributes.size();
        for (int i = 0; i < len; i++)
            if (attributes.get(i).usage == usage) return attributes.get(i);

        return null;
    }
}

To use this, first get a mesh reference and create the cubes:

    mBatchedCubesMesh = model.meshes.get(0);
    VertexAttributes vertexAttributes = mBatchedCubesMesh.getVertexAttributes();
    int vertexFloatSize = vertexAttributes .vertexSize / 4; //4 bytes per float
    mBatchedCubesVertices = new float[numCubes * 24 * vertexFloatSize]; //24 unique vertices per cube
    mBatchedCubesMesh.getVertices(mBatchedCubesVertices);

    mBatchedCubes = new Array<Cube>(numCubes);
    int cubeNum = 0;
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            for (int z = 0; z < length; z++) {
                mBatchedCubes.add(new Cube((x-(width/2f))*1.5f, -((y-(height/2f)) * 1.5f), -(z-(length/2f))*1.5f, 1,1,1, cubeNum++, vertexAttributes, mBatchedCubesVertices ));
            }
        }
    }

Then in your render method:

mBatchedCubes.get(0).rotate(1, 1, 1, 180*delta); //example manipulation of a single cube

for (Cube cube : mBatchedCubes){ //must update any changed cubes. 
    cube.update(mBatchedCubesVertices);
}
mBatchedCubesMesh.setVertices(mBatchedCubesVertices); //apply changes to mesh

...

modelBatch.begin(camera);
modelBatch.render(mBatchedCubesModelInstance);
modelBatch.end();

Now CPU vertex manipulation is not as efficient as shader vertex manipulation, so this may become CPU bound if you're moving all your cubes around every frame. If you aren't rotating them much, it would probably help to create a separate "dirty" variable for rotation and only rotate if necessary in the update method.


EDIT: Update from this question

If you want to have transparency, then the cubes must be sortable, so they can be ordered from far to near for drawing. Their index values must be updated to the new order since that is how they are ordered into the mesh. Here is a Cube class that supports sorting (the color has to be tracked independently now since the cube might be moved to a different part of the mesh).

public class Cube implements Comparable<Cube>{

    private int index;
    int vertexFloatSize;
    int posOffset;
    int norOffset;
    boolean hasColor;
    int colOffset;
    private Vector3 position = new Vector3();
    private Matrix4 rotationTransform = new Matrix4().idt();
    public float halfWidth, halfHeight, halfDepth;
    private boolean transformDirty = false;
    private boolean colorDirty = false;
    private Color color = new Color();
    float camDistSquared;

    static final Vector3 CORNER000 = new Vector3();
    static final Vector3 CORNER010 = new Vector3();
    static final Vector3 CORNER100 = new Vector3();
    static final Vector3 CORNER110 = new Vector3();
    static final Vector3 CORNER001 = new Vector3();
    static final Vector3 CORNER011 = new Vector3();
    static final Vector3 CORNER101 = new Vector3();
    static final Vector3 CORNER111 = new Vector3();

    static final Vector3[] FACE0 = {CORNER000, CORNER100, CORNER110, CORNER010};
    static final Vector3[] FACE1 = {CORNER101, CORNER001, CORNER011, CORNER111};
    static final Vector3[] FACE2 = {CORNER000, CORNER010, CORNER011, CORNER001};
    static final Vector3[] FACE3 = {CORNER101, CORNER111, CORNER110, CORNER100};
    static final Vector3[] FACE4 = {CORNER101, CORNER100, CORNER000, CORNER001};
    static final Vector3[] FACE5 = {CORNER110, CORNER111, CORNER011, CORNER010};
    static final Vector3[][] FACES = {FACE0, FACE1, FACE2, FACE3, FACE4, FACE5};

    static final Vector3 NORMAL0 = new Vector3();
    static final Vector3 NORMAL1 = new Vector3();
    static final Vector3 NORMAL2 = new Vector3();
    static final Vector3 NORMAL3 = new Vector3();
    static final Vector3 NORMAL4 = new Vector3();
    static final Vector3 NORMAL5 = new Vector3();
    static final Vector3[] NORMALS = {NORMAL0, NORMAL1, NORMAL2, NORMAL3, NORMAL4, NORMAL5};

    public Cube(float x, float y, float z, float width, float height, float depth, int index, 
        VertexAttributes vertexAttributes, float[] meshVertices){
    position.set(x,y,z);
    this.halfWidth = width/2;
    this.halfHeight = height/2;
    this.halfDepth = depth/2;
    this.index = index;


    vertexFloatSize = vertexAttributes.vertexSize/4; //4 bytes per float
    posOffset = getVertexAttribute(Usage.Position, vertexAttributes).offset/4;
    norOffset = getVertexAttribute(Usage.Normal, vertexAttributes).offset/4;

    VertexAttribute colorAttribute = getVertexAttribute(Usage.Color, vertexAttributes);
    hasColor = colorAttribute!=null;
    if (hasColor){
        colOffset = colorAttribute.offset/4;
        this.setColor(Color.WHITE, meshVertices);
    }
    transformDirty = true;
    }

    public void updateCameraDistance(Camera cam){
    camDistSquared = cam.position.dst2(position);
    }

    /**
     * Call this after moving and/or rotating.
     */
    public void update(float[] meshVertices){

    if (transformDirty){
        transformDirty = false;

        CORNER000.set(-halfWidth,-halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER010.set(-halfWidth,halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER100.set(halfWidth,-halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER110.set(halfWidth,halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER001.set(-halfWidth,-halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER011.set(-halfWidth,halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER101.set(halfWidth,-halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER111.set(halfWidth,halfHeight,halfDepth).rot(rotationTransform).add(position);

        NORMAL0.set(0,0,-1).rot(rotationTransform);
        NORMAL1.set(0,0,1).rot(rotationTransform);
        NORMAL2.set(-1,0,0).rot(rotationTransform);
        NORMAL3.set(1,0,0).rot(rotationTransform);
        NORMAL4.set(0,-1,0).rot(rotationTransform);
        NORMAL5.set(0,1,0).rot(rotationTransform);

        for (int faceIndex= 0; faceIndex<6; faceIndex++){
        int baseVertexIndex = (index*24 + faceIndex*4)*vertexFloatSize;//24 unique vertices per cube, 4 unique vertices per face
        for (int cornerIndex=0; cornerIndex<4; cornerIndex++){
            int vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + posOffset;
            meshVertices[vertexIndex] = FACES[faceIndex][cornerIndex].x;
            meshVertices[++vertexIndex] = FACES[faceIndex][cornerIndex].y;
            meshVertices[++vertexIndex] = FACES[faceIndex][cornerIndex].z;

            vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + norOffset;
            meshVertices[vertexIndex] = NORMALS[faceIndex].x;
            meshVertices[++vertexIndex] = NORMALS[faceIndex].y;
            meshVertices[++vertexIndex] = NORMALS[faceIndex].z;
        }
        }
    }

    if (colorDirty){
        colorDirty = false;

        for (int faceIndex= 0; faceIndex<6; faceIndex++){
        int baseVertexIndex = (index*24 + faceIndex*4)*vertexFloatSize;//24 unique vertices per cube, 4 unique vertices per face
        for (int cornerIndex=0; cornerIndex<4; cornerIndex++){
            int vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + colOffset;
            meshVertices[vertexIndex] = color.r;
            meshVertices[++vertexIndex] = color.g;
            meshVertices[++vertexIndex] = color.b;
            meshVertices[++vertexIndex] = color.a;
        }
        }
    }
    }

    public Cube setColor(Color color, float[] meshVertices){
    if (hasColor){
        this.color.set(color);
        colorDirty = true;

    }
    return this;
    }

    public void setIndex(int index){
    if (this.index != index){
        transformDirty = true;
        colorDirty = true;
        this.index = index;
    }
    }

    public Cube translate(float x, float y, float z){
    position.add(x,y,z);
    transformDirty = true;
    return this;
    }

    public Cube translateTo(float x, float y, float z){
    position.set(x,y,z);
    transformDirty = true;
    return this;
    }

    public Cube rotate(float axisX, float axisY, float axisZ, float degrees){
    rotationTransform.rotate(axisX, axisY, axisZ, degrees);
    transformDirty = true;
    return this;
    }

    public Cube rotateTo(float axisX, float axisY, float axisZ, float degrees){
    rotationTransform.idt();
    rotationTransform.rotate(axisX, axisY, axisZ, degrees);
    transformDirty = true;
    return this;
    }

    public VertexAttribute getVertexAttribute (int usage, VertexAttributes attributes) {
    int len = attributes.size();
    for (int i = 0; i < len; i++)
        if (attributes.get(i).usage == usage) return attributes.get(i);

    return null;
    }

    @Override
    public int compareTo(Cube other) {
    //This is a simple sort based on center point distance to camera. A more 
    //sophisticated sorting method might be required if the cubes are not all the same 
    //size (such as calculating which of the 8 vertices is closest to the camera
    //and using that instead of the center point).
    if (camDistSquared>other.camDistSquared)
        return -1;
    return camDistSquared<other.camDistSquared ? 1 : 0;
    }
}

Here is how you would sort them:

for (Cube cube : mBatchedCubes){
    cube.updateCameraDistance(camera);
}
mBatchedCubes.sort();
int index = 0;
for (Cube cube : mBatchedCubes){
    cube.setIndex(index++);
}
Community
  • 1
  • 1
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • Looks great. Could you comment something on how one would also manipulate the color of the cubes? with alpha channel preferably? Thanks. – Justas Sakalauskas Jun 18 '14 at 15:33
  • 1
    I updated it to support setting a color for each cube and to make it more robust against various combinations of vertex attribute usage. However, if you want to support alpha channel, it gets more complicated. You would have to sort all your cubes from far to near whenever they move. By sorting, I mean change their `index` parameters so the farthest one is 0 and go on up from there. When changing an index parameter, `dirty` should also be marked true. – Tenfour04 Jun 19 '14 at 00:45
  • 1
    I added a setter for `index` that marks color and transform dirty because it will always need to be updated if the index changes. Just to be clear, it doesn't matter what order they are in whatever array you're storing them. Only their `index` parameter values are important for sorting them. – Tenfour04 Jun 19 '14 at 00:57
  • Thank you very much for adding the color usage. Now i finally got to trying to implement this in my game. I first tried to test the code that you supplied without any modification. It created 125 green cubes that don't seem to react with libgdx Envirnoment class. So i added Usage.color to the mesh parts and removed .createDifuse in material constructor (left it empty). This got them to react with environment and shadows. But now they seem to be somehow messed up. Some sides of the cubes seem to be missing. Have you tested your code? Do you see the same behavior? Thanks. – Justas Sakalauskas Jun 29 '14 at 12:24
  • 1
    I haven't tested it with Environment--the OP was using solid color cubes so I just copied that. So yes you need to change the Usage to get it to react. Are you using a translucent color? If you are, this gets really complicated, because you have to sort the cubes from back to front each frame. Also make sure that backface culling is enabled or you will also have to sort the faces of the cubes, depending on how they are rotated relative to the camera's view! When you say some sides are missing, it sounds like maybe depth testing is off so back faces of cubes are drawing in front of front faces. – Tenfour04 Jun 29 '14 at 12:31
  • Sorry for noobish questions but how would I enable back face culling and depth testing. Is it a one line of code kind of thing or is that more complicated? – Justas Sakalauskas Jun 30 '14 at 14:27
  • 1
    Just one line each. `Gdx.gl.glEnable(GL20.GL_DEPTH_TEST);` and `Gdx.gl.glCullFace(GL20.GL_BACK);`. Put these in the `begin` method of your Shader subclass. – Tenfour04 Jun 30 '14 at 18:40
  • I haven't subclassed Shader in my game. Is that necessary? I am using standard ModelBatch, and its default shader. I tried to put these lines that you mentioned after ModelBatch.begin() method with no luck. I posted a screenshot to illustrate my problem: http://postimg.org/image/qcrxkf6cz/ also posted my code (which is basically a copy of yours): http://pastebin.com/uGr3XnTZ . Could you take a look maybe something simple is missing? Thank you. – Justas Sakalauskas Jun 30 '14 at 20:30
  • 1
    Try `Gdx.gl.glCullFace(GL20.GL_FRONT);` instead. – Tenfour04 Jun 30 '14 at 21:32
  • Just tried it. No luck, nothing has changed it seems. Anything else I could try? – Justas Sakalauskas Jul 01 '14 at 07:39
  • Correction to my former comments: to disable depth testing and enable proper face culling, add these to the Material constructor: `new DepthTestAttribute(false)` and `IntAttribute.createCullFace(GL20.GL_FRONT)`. – Tenfour04 Jul 23 '14 at 12:32