androidopengl-es-2.0vbovertex-buffer-objects

Android OpenGL ES 2.0 : VBA and VBO with different positioned objects


So I am working on a project which is a bit like Minecraft in that there are thousands of cubes.

I hit a huge performance hit quite early on and so started looking at ways to improve the FPS

I first looked at this and followed the recommended stuff Android OpenGL 2.0 Low FPS

I am now trying out this tutorial on VBO's and its is extremely fast.

http://www.learnopengles.com/android-lesson-seven-an-introduction-to-vertex-buffer-objects-vbos/

I am guessing you have to build the cubePositionData every time I add a cube?

Are VBO's the way to go for something like Minecraft?

public class CubeVBOVBA extends Cube {

    /**
     * Size of the position data in elements.
     */
    static final int POSITION_DATA_SIZE = 3;
    /**
     * Size of the normal data in elements.
     */
    static final int NORMAL_DATA_SIZE = 3;
    /**
     * Size of the texture coordinate data in elements.
     */
    static final int TEXTURE_COORDINATE_DATA_SIZE = 2;
    /**
     * How many bytes per float.
     */
    static final int BYTES_PER_FLOAT = 4;
    final int mCubePositionsBufferIdx;
    final int mCubeNormalsBufferIdx;
    final int mCubeTexCoordsBufferIdx;
    private int mActualCubeFactor = 40;

    public CubeVBOVBA( int generatedCubeFactor)
    {
        final float[] cubePositionData = new float[108 * generatedCubeFactor * generatedCubeFactor * generatedCubeFactor];
        int cubePositionDataOffset = 0;

        final int segments = generatedCubeFactor + (generatedCubeFactor - 1);
        final float minPosition = -1.0f;
        final float maxPosition = 1.0f;
        final float positionRange = maxPosition - minPosition;

        for (int x = 0; x < generatedCubeFactor; x++) {
            for (int y = 0; y < generatedCubeFactor; y++) {
                for (int z = 0; z < generatedCubeFactor; z++) {
                    final float x1 = minPosition + ((positionRange / segments) * (x * 2));
                    final float x2 = minPosition + ((positionRange / segments) * ((x * 2) + 1));

                    final float y1 = minPosition + ((positionRange / segments) * (y * 2));
                    final float y2 = minPosition + ((positionRange / segments) * ((y * 2) + 1));

                    final float z1 = minPosition + ((positionRange / segments) * (z * 2));
                    final float z2 = minPosition + ((positionRange / segments) * ((z * 2) + 1));

                    // Define points for a cube.
                    // X, Y, Z
                    final float[] p1p = { x1, y2, z2 };
                    final float[] p2p = { x2, y2, z2 };
                    final float[] p3p = { x1, y1, z2 };
                    final float[] p4p = { x2, y1, z2 };
                    final float[] p5p = { x1, y2, z1 };
                    final float[] p6p = { x2, y2, z1 };
                    final float[] p7p = { x1, y1, z1 };
                    final float[] p8p = { x2, y1, z1 };

                    final float[] thisCubePositionData = ShapeBuilder.generateCubeData(p1p, p2p, p3p, p4p, p5p, p6p, p7p, p8p,
                            p1p.length);

                    System.arraycopy(thisCubePositionData, 0, cubePositionData, cubePositionDataOffset, thisCubePositionData.length);
                    cubePositionDataOffset += thisCubePositionData.length;
                }
            }
        }

        FloatBuffer[] floatBuffers = getBuffers(cubePositionData, cubeNormalData, textureCoordinateData, generatedCubeFactor);

        FloatBuffer cubePositionsBuffer = floatBuffers[0];
        FloatBuffer cubeNormalsBuffer = floatBuffers[1];
        FloatBuffer cubeTextureCoordinatesBuffer = floatBuffers[2];

        // Second, copy these buffers into OpenGL's memory. After, we don't need to keep the client-side buffers around.
        final int buffers[] = new int[3];
        GLES20.glGenBuffers(3, buffers, 0);

        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[0]);
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, cubePositionsBuffer.capacity() * BYTES_PER_FLOAT, cubePositionsBuffer, GLES20.GL_STATIC_DRAW);

        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[1]);
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, cubeNormalsBuffer.capacity() * BYTES_PER_FLOAT, cubeNormalsBuffer, GLES20.GL_STATIC_DRAW);

        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[2]);
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, cubeTextureCoordinatesBuffer.capacity() * BYTES_PER_FLOAT, cubeTextureCoordinatesBuffer,
                GLES20.GL_STATIC_DRAW);

        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        mCubePositionsBufferIdx = buffers[0];
        mCubeNormalsBufferIdx = buffers[1];
        mCubeTexCoordsBufferIdx = buffers[2];

        cubePositionsBuffer.limit(0);
        cubePositionsBuffer = null;
        cubeNormalsBuffer.limit(0);
        cubeNormalsBuffer = null;
        cubeTextureCoordinatesBuffer.limit(0);
        cubeTextureCoordinatesBuffer = null;
    }

    FloatBuffer[] getBuffers(float[] cubePositions, float[] cubeNormals, float[] cubeTextureCoordinates, int generatedCubeFactor) {
        // First, copy cube information into client-side floating point buffers.
        final FloatBuffer cubePositionsBuffer;
        final FloatBuffer cubeNormalsBuffer;
        final FloatBuffer cubeTextureCoordinatesBuffer;

        cubePositionsBuffer = ByteBuffer.allocateDirect(cubePositions.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        cubePositionsBuffer.put(cubePositions).position(0);

        cubeNormalsBuffer = ByteBuffer.allocateDirect(cubeNormals.length * BYTES_PER_FLOAT * generatedCubeFactor * generatedCubeFactor * generatedCubeFactor)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();

        for (int i = 0; i < (generatedCubeFactor * generatedCubeFactor * generatedCubeFactor); i++) {
            cubeNormalsBuffer.put(cubeNormals);
        }

        cubeNormalsBuffer.position(0);

        cubeTextureCoordinatesBuffer = ByteBuffer.allocateDirect(cubeTextureCoordinates.length * BYTES_PER_FLOAT * generatedCubeFactor * generatedCubeFactor * generatedCubeFactor)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();

        for (int i = 0; i < (generatedCubeFactor * generatedCubeFactor * generatedCubeFactor); i++) {
            cubeTextureCoordinatesBuffer.put(cubeTextureCoordinates);
        }

        cubeTextureCoordinatesBuffer.position(0);

        return new FloatBuffer[]{cubePositionsBuffer, cubeNormalsBuffer, cubeTextureCoordinatesBuffer};
    }

    public void render(float[] mVMatrix, float[] mProjMatrix) {

        GLES20.glUseProgram(mProgramHandle);

        // Pass in the position information
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mCubePositionsBufferIdx);
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        GLES20.glVertexAttribPointer(mPositionHandle, POSITION_DATA_SIZE, GLES20.GL_FLOAT, false, 0, 0);

        // Pass in the normal information
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mCubeNormalsBufferIdx);
        GLES20.glEnableVertexAttribArray(mNormalHandle);
        GLES20.glVertexAttribPointer(mNormalHandle, NORMAL_DATA_SIZE, GLES20.GL_FLOAT, false, 0, 0);

        // Pass in the texture information
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mCubeTexCoordsBufferIdx);
        GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
        GLES20.glVertexAttribPointer(mTextureCoordinateHandle, TEXTURE_COORDINATE_DATA_SIZE, GLES20.GL_FLOAT, false,
                0, 0);

        // Clear the currently bound buffer (so future OpenGL calls do not use this buffer).
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        Matrix.setIdentityM(mRenderer.mModelMatrix, 0);
        Matrix.translateM(mRenderer.mModelMatrix, 0, position.x, position.y, position.z);
        Matrix.scaleM(mRenderer.mModelMatrix, 0, scale.x, scale.y, scale.z);

        Matrix.rotateM(mRenderer.mModelMatrix, 0, rotation.x, 1, 0, 0);
        Matrix.rotateM(mRenderer.mModelMatrix, 0, rotation.y, 0, 1, 0);
        Matrix.rotateM(mRenderer.mModelMatrix, 0, rotation.z, 0, 0, 1);

        Matrix.multiplyMM(mRenderer.mMVPMatrix, 0, mVMatrix, 0, mRenderer.mModelMatrix, 0);

        // Pass in the modelview matrix.
        GLES20.glUniformMatrix4fv(mRenderer.mMVMatrixHandle, 1, false, mRenderer.mMVPMatrix, 0);

        // This multiplies the modelview matrix by the projection matrix, and stores the result in the MVP matrix
        // (which now contains model * view * projection).
        Matrix.multiplyMM(mTemporaryMatrix, 0, mProjMatrix, 0, mRenderer.mMVPMatrix, 0);
        System.arraycopy(mTemporaryMatrix, 0, mRenderer.mMVPMatrix, 0, 16);

        // Pass in the combined matrix.
        GLES20.glUniformMatrix4fv(mRenderer.mMVPMatrixHandle, 1, false, mRenderer.mMVPMatrix, 0);

        // Draw the cubes.
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mActualCubeFactor * mActualCubeFactor * mActualCubeFactor * 36);
    }

    public void release() {
        // Delete buffers from OpenGL's memory
        final int[] buffersToDelete = new int[]{mCubePositionsBufferIdx, mCubeNormalsBufferIdx,
                mCubeTexCoordsBufferIdx};
        GLES20.glDeleteBuffers(buffersToDelete.length, buffersToDelete, 0);
    }
}

Solution

  • I went with implementing VBO / VBA and had a massive performance increase, from 20 objects at 40fps to 50,000 at 60fps! Well worth using VBO / VBA with static items