Samuel Fournier
Samuel Fournier

Reputation: 19

How to do face culling in a voxel game using Java and LWJGL

For the past few weeks I've been using a tutorial to guide myself through LWJGL (https://www.youtube.com/playlist?list=PLaWuTOi9sDeomi2umQ7N8Lqs-GtE1H4-b here's the link). I stopped following once I was able to generate a cube, since that's all I really need for a Minecraft clone (voxel game). I'm able to generate multiple cubes, however, all 6 faces of the cube get rendered and I've been trying (with no succes) to make it so I only render the faces that aren't connected to other faces. I have a Mesh class containing the array of vertices and indices used for generation and here's the code tied to the vertices and indices in the create function:

        // Creation of the VAO
        vertexArrayObject = GL30.glGenVertexArrays();
        GL30.glBindVertexArray(vertexArrayObject);
        /*
        All the buffers we add will be binded to the vertexArrayObject.
         */

        // Creation of the PBO
        FloatBuffer positionBuffer = MemoryUtil.memAllocFloat(vertices.length * 3);
        float[] positionData = new float[vertices.length * 3];

        /*
        Stores all the (x,y,z) coordinates in an array so we can send it to the buffer so that GPU can do stuff.
         */
        for(int i = 0; i < vertices.length; i++) {
            positionData[i * 3] = vertices[i].getPosition().getX();
            positionData[i * 3 + 1] = vertices[i].getPosition().getY();
            positionData[i * 3 + 2] = vertices[i].getPosition().getZ();
        }

        positionBuffer.put(positionData).flip(); // For some reason OpenGl like the data flipped.

        positionBufferObject = storeData(positionBuffer, 0, 3);

        // Code tied to the creation of the IBO

        IntBuffer indicesBuffer = MemoryUtil.memAllocInt(indices.length);
        indicesBuffer.put(indices).flip();

        indicesBufferObject = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBufferObject);
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW);
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0); // Unbinds the buffer.

Here is my renderMesh function in my Renderer class:

public void renderMesh(GameObject object, Camera camera) {
        GL30.glBindVertexArray(object.getMesh().getVAO());
        GL30.glEnableVertexAttribArray(0);
        GL30.glEnableVertexAttribArray(1);
        GL30.glEnableVertexAttribArray(2);
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER,object.getMesh().getIBO());
        // Binds the shader to draw
        GL13.glActiveTexture(GL13.GL_TEXTURE0);
        GL13.glBindTexture(GL11.GL_TEXTURE_2D, object.getMesh().getMaterial().getTextureID());
        shader.bind();
        shader.setUniform("model", Matrix4f.transform(object.getPosition(), object.getRotation(), object.getScale()));
        shader.setUniform("projection", window.getProjectionMatrix());
        shader.setUniform("view", Matrix4f.view(camera.getPosition(), camera.getRotation()));
        GL11.glDrawElements(GL11.GL_TRIANGLES, object.getMesh().getIndices().length, GL11.GL_UNSIGNED_INT, 0);
        // unbinds the shader to get ready for the next shader
        shader.unbind();
        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER,0);
        GL30.glDisableVertexAttribArray(0);
        GL30.glDisableVertexAttribArray(1);
        GL30.glDisableVertexAttribArray(2);
        GL30.glBindVertexArray(0);
    }

In my Main class, I have a general Mesh object that describes the shape of a cube and I have a GameObject class which contains a position and rotation vector as well as the mesh defined in Main. I initially thought that I could modify the GameObject's mesh by simply modifying the list of vertices or indices, but neither worked. I then realized that the reason why nothing changed was because I call the create() function of Mesh only once (at the initialization) which meant that the IBO, VBO, VAO, etc... never get updated to consider the modified meshes. So I tried updating them by simply copy/pasting the code associated to their creation in an update method that would always get called to make sure that the renderer would always get an up-to-date VAO and IBO, however doing that TANKED my performance (like I went from 100 fps to less than 10). However, I did "somewhat" achieve the result I wanted since some faces weren't being rendered (just not the right ones). This is my first time doing this kind of project so I'm really not familiar with a lot of these concepts. Thank you in advance for the help!!! If you're wondering here's the mesh:

public Mesh mesh = new Mesh(new Vertex[] {
            // Vertices
            //Back face
            new Vertex(new Vector3f(-0.5f,  0.5f, -0.5f), new Vector2f(0.0f, 0.0f)),
            new Vertex(new Vector3f(-0.5f, -0.5f, -0.5f), new Vector2f(0.0f, 1.0f)),
            new Vertex(new Vector3f( 0.5f, -0.5f, -0.5f), new Vector2f(1.0f, 1.0f)),
            new Vertex(new Vector3f( 0.5f,  0.5f, -0.5f), new Vector2f(1.0f, 0.0f)),


            //Front face
            new Vertex(new Vector3f(-0.5f,  0.5f,  0.5f), new Vector2f(0.0f, 0.0f)),
            new Vertex(new Vector3f(-0.5f, -0.5f,  0.5f), new Vector2f(0.0f, 1.0f)),
            new Vertex(new Vector3f( 0.5f, -0.5f,  0.5f), new Vector2f(1.0f, 1.0f)),
            new Vertex(new Vector3f( 0.5f,  0.5f,  0.5f), new Vector2f(1.0f, 0.0f)),

            //Right face
            new Vertex(new Vector3f( 0.5f,  0.5f, -0.5f), new Vector2f(0.0f, 0.0f)),
            new Vertex(new Vector3f( 0.5f, -0.5f, -0.5f), new Vector2f(0.0f, 1.0f)),
            new Vertex(new Vector3f( 0.5f, -0.5f,  0.5f), new Vector2f(1.0f, 1.0f)),
            new Vertex(new Vector3f( 0.5f,  0.5f,  0.5f), new Vector2f(1.0f, 0.0f)),

            //Left face
            new Vertex(new Vector3f(-0.5f,  0.5f, -0.5f), new Vector2f(0.0f, 0.0f)),
            new Vertex(new Vector3f(-0.5f, -0.5f, -0.5f), new Vector2f(0.0f, 1.0f)),
            new Vertex(new Vector3f(-0.5f, -0.5f,  0.5f), new Vector2f(1.0f, 1.0f)),
            new Vertex(new Vector3f(-0.5f,  0.5f,  0.5f), new Vector2f(1.0f, 0.0f)),

            //Top face
            new Vertex(new Vector3f(-0.5f,  0.5f,  0.5f), new Vector2f(0.0f, 0.0f)),
            new Vertex(new Vector3f(-0.5f,  0.5f, -0.5f), new Vector2f(0.0f, 1.0f)),
            new Vertex(new Vector3f( 0.5f,  0.5f, -0.5f), new Vector2f(1.0f, 1.0f)),
            new Vertex(new Vector3f( 0.5f,  0.5f,  0.5f), new Vector2f(1.0f, 0.0f)),

            //Bottom face
            new Vertex(new Vector3f(-0.5f, -0.5f,  0.5f), new Vector2f(0.0f, 0.0f)),
            new Vertex(new Vector3f(-0.5f, -0.5f, -0.5f), new Vector2f(0.0f, 1.0f)),
            new Vertex(new Vector3f( 0.5f, -0.5f, -0.5f), new Vector2f(1.0f, 1.0f)),
            new Vertex(new Vector3f( 0.5f, -0.5f,  0.5f), new Vector2f(1.0f, 0.0f)),
    }, new int[] {
            // Indices
            //Back face
            0, 1, 3,
            3, 1, 2,

            //Front face
            4, 5, 7,
            7, 5, 6,

            //Right face
            8, 9, 11,
            11, 9, 10,

            //Left face
            12, 13, 15,
            15, 13, 14,

            //Top face
            16, 17, 19,
            19, 17, 18,

            //Bottom face
            20, 21, 23,
            23, 21, 22
    }, new Material("/textures/acacia_planks.png")); // The texture (it works fine, ignore it)

(What I tried doing): I tried updating the IBO and VAO but that tanked my performance by A LOT.

Upvotes: 0

Views: 320

Answers (1)

Luke100000
Luke100000

Reputation: 1539

Don't create one object/mesh/whatever per block, this generate massive amount of overhead. Always batch many blocks together, at very least a 16^3 chunk.

Don't modify anything, generate the culled mesh and regenerate on block change.

While rendering you check the neighboring block on whether it obstructs the view (e.g. in its simplest case if its a solid block), and if yes, you simply don't generate that face.

Regenerating a chunk is faster than doing any other hacks and can be offloaded to a thread.

Upvotes: 1

Related Questions