2
\$\begingroup\$

Improving rendering performance of 3D Voxel Game

Technical features added:

  • Perlin Noise Generator
  • 3D Explorable World (You can't break or place blocks)
  • Free camera, you can fly and go through blocks

Quick overview of implemented optimizations:

  • Asynchronous (Multi-threaded) chunks system.
  • Render only visible faces and cubes.
  • Textures cache and texture atlas
  • Instead of rendering block by block, render the whole chunk at once.
  • Use VBO and Indices
  • Frustum culling.
  • Use Riven's Algorithm for Mathematics Functions (This is what BetterFPS and Optifine does to optimize Minecraft)

My problems:

  • The framerate is sort of correct, but I think it can still be optimized, because when i'm comparing my game performance to Minecraft for exemple, Minecraft is much much more optimized.

  • The camera takes few seconds to move (I think this is due to Mouse Input, I am not sure, too)

  • There is a REAL memory leak! The Chunks System uses too much RAM and CPU for a tiny world! (I used a Profiler, YourKit, and RAM just goes up and up)

Code:

Note: What makes the game lagging is the CHUNKS System!

Main class implementation of Chunks System:

new Thread(new Runnable() {

            @Override
            public void run() {

                while (!GLFW.glfwWindowShouldClose(window)) {

                    if (isFocused) {

                        try {

                            for (int x = (int) (((camera.getX() - WORLD_SIZE) / CHUNK_SIZE)
                                    / BLOCK_SCALE); x < ((camera.getX() + WORLD_SIZE) / CHUNK_SIZE)
                                            / BLOCK_SCALE; x++) {

                                for (int z = (int) (((camera.getZ() - WORLD_SIZE) / CHUNK_SIZE)
                                        / BLOCK_SCALE); z < ((camera.getZ() + WORLD_SIZE) / CHUNK_SIZE)
                                                / BLOCK_SCALE; z++) {

                                    if (!usedPositions.contains(new Vector2f(x * CHUNK_SIZE, z * CHUNK_SIZE))) {
                                        Set<Block> blocks = new HashSet<Block>();

                                        for (int i = 0; i < CHUNK_SIZE; i++) {

                                            for (int j = 0; j < CHUNK_SIZE; j++) {
                                                int generatedY = (int) generator.generateHeight(i + (x * CHUNK_SIZE),
                                                        j + (z * CHUNK_SIZE)) + 25;
                                                blocks.add(new Block(i, generatedY, j, Block.Type.GRASS));
                                            }

                                        }

                                        ChunkMesh chunkMesh = new ChunkMesh(
                                                new Chunk(blocks, x * CHUNK_SIZE, 0 * CHUNK_SIZE, z * CHUNK_SIZE));
                                        chunks.add(chunkMesh);
                                        usedPositions.add(new Vector2f(x * CHUNK_SIZE, z * CHUNK_SIZE));
                                    }

                                }

                            }

                            try {
                                Thread.sleep(5);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }

                        } catch (OutOfMemoryError error) {
                            debug(1);
                            isFocused = false;
                            chunks.clear();
                            usedPositions.clear();
                            entities.clear();
                            Callbacks.glfwFreeCallbacks(window);
                            GLFW.glfwDestroyWindow(window);
                            GLFW.glfwTerminate();
                            GLFW.glfwSetErrorCallback(null).free();
                            loader = null;
                            masterRenderer = null;
                            System.exit(0);
                            break;
                        }

                    }

                }

            }

        }).start();

Chunk Mesh class:

package me.talvorgames.pixeli.world;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import me.talvorgames.pixeli.maths.Vertex;
import me.talvorgames.pixeli.rendering.models.CubeModel;

public class ChunkMesh {

    private List<Vertex> vertices;
    public float[] positions, uvs, normals;

    public Chunk chunk;

    public ChunkMesh(Chunk chunk) {
        this.chunk = chunk;
        vertices = new ArrayList<Vertex>();
        buildMesh();
        populateLists();
    }

    public void update(Chunk chunk) {
        this.chunk = chunk;
        buildMesh();
        populateLists();
    }

    public void buildMesh() {
        Iterator<Block> iterator = chunk.getBlocks().iterator();

        while (iterator.hasNext()) {
            Block blockI = iterator.next();
            boolean px = false, nx = false, py = false, ny = false, pz = false, nz = false;
            Iterator<Block> iterator2 = chunk.getBlocks().iterator();

            while (iterator2.hasNext()) {
                Block blockJ = iterator2.next();

                if (((blockI.x + 1) == (blockJ.x)) && ((blockI.y) == (blockJ.y)) && ((blockI.z) == (blockJ.z))) {
                    px = true;
                }

                if (((blockI.x - 1) == (blockJ.x)) && ((blockI.y) == (blockJ.y)) && ((blockI.z) == (blockJ.z))) {
                    nx = true;
                }

                if (((blockI.x) == (blockJ.x)) && ((blockI.y + 1) == (blockJ.y)) && ((blockI.z) == (blockJ.z))) {
                    py = true;
                }

                if (((blockI.x) == (blockJ.x)) && ((blockI.y - 1) == (blockJ.y)) && ((blockI.z) == (blockJ.z))) {
                    ny = true;
                }

                if (((blockI.x) == (blockJ.x)) && ((blockI.y) == (blockJ.y)) && ((blockI.z + 1) == (blockJ.z))) {
                    pz = true;
                }

                if (((blockI.x) == (blockJ.x)) && ((blockI.y) == (blockJ.y)) && ((blockI.z - 1) == (blockJ.z))) {
                    nz = true;
                }

            }

            if (!px) {

                for (int k = 0; k < 6; k++) {
                    vertices.add(new Vertex(CubeModel.PX_POS[k].x + blockI.x, CubeModel.PX_POS[k].y + blockI.y,
                            CubeModel.PX_POS[k].z + blockI.z, CubeModel.UV_PX[(blockI.type.getId() * 6) + k],
                            CubeModel.NORMALS[k]));
                }

            }

            if (!px) {

                for (int k = 0; k < 6; k++) {
                    vertices.add(new Vertex(CubeModel.PX_POS[k].x + blockI.x, CubeModel.PX_POS[k].y + blockI.y,
                            CubeModel.PX_POS[k].z + blockI.z, CubeModel.UV_PX[(blockI.type.getId() * 6) + k],
                            CubeModel.NORMALS[k]));
                }

            }

            if (!nx) {

                for (int k = 0; k < 6; k++) {
                    vertices.add(new Vertex(CubeModel.NX_POS[k].x + blockI.x, CubeModel.NX_POS[k].y + blockI.y,
                            CubeModel.NX_POS[k].z + blockI.z, CubeModel.UV_NX[(blockI.type.getId() * 6) + k],
                            CubeModel.NORMALS[k]));
                }

            }

            if (!py) {

                for (int k = 0; k < 6; k++) {
                    vertices.add(new Vertex(CubeModel.PY_POS[k].x + blockI.x, CubeModel.PY_POS[k].y + blockI.y,
                            CubeModel.PY_POS[k].z + blockI.z, CubeModel.UV_PY[(blockI.type.getId() * 6) + k],
                            CubeModel.NORMALS[k]));
                }

            }

            if (!ny) {

                for (int k = 0; k < 6; k++) {
                    vertices.add(new Vertex(CubeModel.NY_POS[k].x + blockI.x, CubeModel.NY_POS[k].y + blockI.y,
                            CubeModel.NY_POS[k].z + blockI.z, CubeModel.UV_NY[(blockI.type.getId() * 6) + k],
                            CubeModel.NORMALS[k]));
                }

            }

            if (!pz) {

                for (int k = 0; k < 6; k++) {
                    vertices.add(new Vertex(CubeModel.PZ_POS[k].x + blockI.x, CubeModel.PZ_POS[k].y + blockI.y,
                            CubeModel.PZ_POS[k].z + blockI.z, CubeModel.UV_PZ[(blockI.type.getId() * 6) + k],
                            CubeModel.NORMALS[k]));
                }

            }

            if (!nz) {

                for (int k = 0; k < 6; k++) {
                    vertices.add(new Vertex(CubeModel.NZ_POS[k].x + blockI.x, CubeModel.NZ_POS[k].y + blockI.y,
                            CubeModel.NZ_POS[k].z + blockI.z, CubeModel.UV_NZ[(blockI.type.getId() * 6) + k],
                            CubeModel.NORMALS[k]));
                }

            }

        }

    }

    public void populateLists() {
        positions = new float[vertices.size() * 3];
        normals = new float[vertices.size() * 3];
        uvs = new float[vertices.size() * 2];
        int a = 0;
        int b = 0;

        for (int i = 0; i < vertices.size(); i++) {
            Vertex vertex = vertices.get(i);
            positions[a] = vertex.positionsX;
            positions[a + 1] = vertex.positionsY;
            positions[a + 2] = vertex.positionsZ;

            normals[a] = vertex.normalsX;
            normals[a + 1] = vertex.normalsY;
            normals[a + 2] = vertex.normalsZ;

            uvs[b] = vertex.uvsX;
            uvs[b + 1] = vertex.uvsY;
            a += 3;
            b += 2;
        }

        cleanUp();
    }

    public void cleanUp() {
        vertices.clear();
    }

}
\$\endgroup\$
  • \$\begingroup\$ When you initialize your variables in only one place, it's better to mark them final to avoid surprises. For example, private final List<Integer> vaos = new ArrayList<>(); or private final List<Vertex> vertices = new ArrayList<Vertex>(); \$\endgroup\$ – eric.m Jul 15 at 13:43
  • \$\begingroup\$ eric.m that won't work, the class is not static at all. Thanks anyways! \$\endgroup\$ – Minkizz Jul 15 at 14:27
  • \$\begingroup\$ With final you can still add items to it, what you won't be able to do is to assign a new list. I don't see you doing it anywhere in the posted code, so if you use final it should still compile. \$\endgroup\$ – eric.m Jul 15 at 14:38
  • \$\begingroup\$ Ok, so I figured out that CHUNKS lagged out the game, so I will clarify my post. I also implemented Frustum Culling and I optimized a lot of things, "Flyweight Pattern" and using Optimized Sine and Cosine functions (Riven's Algorithm) \$\endgroup\$ – Minkizz Jul 20 at 17:09
  • \$\begingroup\$ mod reminder: Please do not put reviews into the comments. If you have suggestions or criticisms about the code, the correct place to put them is the answer box. Thanks! @eric.m \$\endgroup\$ – Vogel612 Jul 20 at 19:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.