0
\$\begingroup\$

I've been coding a minecraft-like game in lwjgl using cubes. I've reached a point where I can render 6 chunks of blocks before it starts lagging. I've searched online for optimizations and one of the most common ones was to remove the faces of the cube that cannot be seen by the player. I've tried implementing this but even after only leaving one face of each cube enabled, the performance is still the same. This is my current (and probably terrible) implementation:

Block.java

package com.yellow.engine.prefabs.base;

import com.yellow.engine.enums.Face;
import com.yellow.engine.handlers.TextureHandler;
import com.yellow.engine.rendering.Mesh;
import com.yellow.engine.rendering.Texture;
import com.yellow.engine.world.GameObject;

public class Block extends GameObject{

float[] vertices = new float[] {
    // Front Face
    -0.5f,  0.5f,  0.5f, // V0
    -0.5f, -0.5f,  0.5f, // V1
     0.5f, -0.5f,  0.5f, // V2
     0.5f,  0.5f,  0.5f, // V3

    // Top Face
    -0.5f, 0.5f, -0.5f, // V4
     0.5f, 0.5f, -0.5f, // V5
    -0.5f, 0.5f,  0.5f, // V6
     0.5f, 0.5f,  0.5f, // V7
    
    // Right Face
     0.5f,  0.5f,  0.5f, // V8
     0.5f, -0.5f,  0.5f, // V9
     0.5f, -0.5f, -0.5f, // V10
     0.5f,  0.5f, -0.5f, // V11

    // Left Face
    -0.5f,  0.5f,  0.5f, // V12
    -0.5f,  0.5f, -0.5f, // V13
    -0.5f, -0.5f, -0.5f, // V14
    -0.5f, -0.5f,  0.5f, // V15
    
    // Bottom Face
    -0.5f, -0.5f, -0.5f, // V16
    -0.5f, -0.5f,  0.5f, // V17
     0.5f, -0.5f, -0.5f, // V18
     0.5f, -0.5f,  0.5f, // V19

    // Back Face
    -0.5f,  0.5f, -0.5f, // V20
     0.5f,  0.5f, -0.5f, // V21
    -0.5f, -0.5f, -0.5f, // V22
     0.5f, -0.5f, -0.5f, // V23
};

float[] texturePos = new float[]{
    // Front Face
    0.0f, 0.0f,
    0.0f, 0.5f,
    0.5f, 0.5f,
    0.5f, 0.0f,

    // Top Face
    0.0f, 0.5f,
    0.5f, 0.5f,
    0.0f, 1.0f,
    0.5f, 1.0f,

    // Right Face
    0.0f, 0.0f,
    0.0f, 0.5f,
    0.5f, 0.5f,
    0.5f, 0.0f,

    // Left Face
    0.5f, 0.0f,
    0.0f, 0.0f,
    0.0f, 0.5f,
    0.5f, 0.5f,

    // Bottom Face
    0.5f, 0.5f,
    0.5f, 0.0f,
    1.0f, 0.5f,
    1.0f, 0.0f,

    // Back Face
    0.5f, 0.0f,
    0.0f, 0.0f,
    0.5f, 0.5f,
    0.0f, 0.5f,
};

int[] indices = new int[]{
    0,  1,  3,  3,  1,  2,  // Front Face
    7,  5,  6,  6,  5,  4,  // Top Face
    8,  9,  11, 11, 9,  10, // Right Face
    12, 13, 15, 15, 13, 14, // Left Face
    19, 17, 18, 18, 17, 16, // Bottom Face
    20, 21, 22, 22, 21, 23  // Back Face
};

// Lo mettiamo uguale ad indices così anche se non togliamo una faccia ha i valori di base
int[] activeIndices = indices;
float[] activeVertices = vertices;
float[] activeTexturePos = texturePos;

private Texture texture;

public Block() {
    super();

    Face[] faces = new Face[]{ Face.Top, Face.Bottom, Face.Right, Face.Left };
    this.removeFaces(faces);

    this.texture = TextureHandler.getTexture("grassblock.png");
    //this.mesh = new Mesh(vertices, indices, texturePos, texture);
    this.mesh = new Mesh(activeVertices, activeIndices, activeTexturePos, texture);
    this.mesh.generateBuffers();
}

// TODO: Finisci di implementare questo.
// I vertici e le coordinat edella texture non devono essere passate se le loro facce sono disabilitate.
protected void removeFace(Face face) {
    int faceIndex = Face.valueOf(face.toString()).ordinal();

    for(int i = 0; i < indices.length; i++) {
        // 6 è il numero di indici per faccia
        if((faceIndex * 6) <= i && i < (faceIndex * 6 + 6)) {
            // TODO: Implementa un check per questo -1 nel renderer: se vede -1, lo ignora direttamente
            // forse c'è un modo migliore di implementarlo.
            activeIndices[i] = -1;
            continue;
        }

        activeIndices[i] = indices[i];
    }

    for(int i = 0; i < vertices.length; i++) {
        if((faceIndex * 12) <= i & i < (faceIndex * 12 + 12)) {
            activeVertices[i] = -1;
            continue;
        }
    }

    for(int i = 0; i < texturePos.length; i++) {
        if((faceIndex * 8) <= i & i < (faceIndex * 8 + 8)) {
            activeTexturePos[i] = -1;
            continue;
        }
    }
}

protected void removeFaces(Face[] faces) {
    for(Face face : faces) {
        this.removeFace(face);
    }
}

}

Mesh.java

package com.yellow.engine.rendering;

import org.lwjgl.system.MemoryStack;

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

import static org.lwjgl.opengl.GL33.*;
import static org.lwjgl.system.MemoryStack.stackPush;

public class Mesh {

private int VAO;
private int vertxVbo, idVbo, textVbo;
protected List<Integer> VBOs;

private int vertexCount;

private float[] vertices, texturePos;
private int[] indices;

private Texture texture;

// TODO: Rinomina texturePos che è brutto
public Mesh(float[] vertices, int[] indices, float[] texturePos, Texture texture){
    this.vertices = vertices;
    this.texturePos = texturePos;
    this.indices = indices;

    this.texture = texture;
}

// Questo metodo è fondamentale per le draw call
// Però deve essere chiamato dopo Renderer#init altrimenti le shader non sono linkate
public void generateBuffers() {
    vertexCount = indices.length;
    VBOs = new ArrayList<>();

    try(MemoryStack stack = stackPush()){
        FloatBuffer verticesBuffer = stack.callocFloat(vertices.length);
        verticesBuffer.put(vertices).flip();

        FloatBuffer textureBuffer = stack.callocFloat(texturePos.length);
        textureBuffer.put(texturePos).flip();

        IntBuffer indicesBuffer = stack.callocInt(indices.length);
        indicesBuffer.put(indices).flip();
        
        // Crea il VAO e bindalo
        VAO = glGenVertexArrays();
        glBindVertexArray(VAO);

        // Crea il VBO (posizioni) e bindalo
        vertxVbo = glGenBuffers();
        VBOs.add(vertxVbo);
        glBindBuffer(GL_ARRAY_BUFFER, vertxVbo);
        glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);

        // Crea il VBO (colori) e bindalo
        textVbo = glGenBuffers();
        VBOs.add(textVbo);
        glBindBuffer(GL_ARRAY_BUFFER, textVbo);
        glBufferData(GL_ARRAY_BUFFER, textureBuffer, GL_STATIC_DRAW);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);

        // Crea il VBO (indici) e bindalo
        idVbo = glGenBuffers();
        VBOs.add(idVbo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idVbo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);


        // Unbinda VBO e VAO
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
    } catch(Exception e) {
        // Questo catch serve per Texture#loadImage, altrimenti ogni volta che
        // chiamiamo generateBuffers bisogna aggiungere un throws Exception ai prefab, per esempio.
        e.printStackTrace();
    }
}

public void draw() {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE0, texture.getTextureId());

    glBindVertexArray(this.getVAO());

    glDrawElements(GL_TRIANGLES, this.getVertexCount(), GL_UNSIGNED_INT, 0);

    glBindVertexArray(0);
}

// Questo metodo di norma va chiamato con GameObject#dispose() direttamente se assegnato ad un gameobject
// così si elimina anche dalla lista dei gameobject.
public void dispose(){
    // Disattiva gli attributi del VAO
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

    // Elimina VBOs
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    for(int vboId : VBOs) {
        glDeleteBuffers(vboId);
    }

    // Elimina la texture
    texture.dispose();

    // Elimina VAO
    glBindVertexArray(0);
    glDeleteVertexArrays(VAO);
}

public int getVAO(){
    return VAO;
}

public int getVertexCount(){
    return vertexCount;
}

}

I think the problem is the way I'm telling OpenGL not to draw a quad of a cube, which is by setting the indices of that face to -1. That doesn't point to any vertex so it's not rendered but it's still sent to the GPU (?).

Thanks in advance.

\$\endgroup\$
1
  • \$\begingroup\$ Usually the way this is done — you can find this discussed in past Q&A about voxel games — is to combine the visible outer faces of a set of cubes into a single "chunk" mesh, where the invisible faces are not present whatsoever — not -1 indices, just not in the vertex or index buffers at all. That way you pay for only what you might possibly draw. \$\endgroup\$
    – DMGregory
    Aug 28, 2021 at 13:13

1 Answer 1

0
\$\begingroup\$

It appears you are manually removing faces, rather than using:

glEnable(GL_CULL_FACE)

Although you are using Java, perhaps having a look at this?

\$\endgroup\$
3
  • \$\begingroup\$ I'm already using face culling but I think that using a manual method is better since while using face culling opengl will still draw three faces of the cube, my method would only draw the faces that are actually visible, which could also be just one instead of three. \$\endgroup\$
    – xGolden
    Jul 29, 2021 at 11:10
  • \$\begingroup\$ Oh alright, my apologies . Perhaps research into batch rendering, considering you are using the same object, just in different locations? \$\endgroup\$ Jul 29, 2021 at 11:22
  • \$\begingroup\$ @xGolden - this is an optimization that could go either way. Manually removing faces has a CPU cost, letting the GPU cull them has a GPU cost, and depending the relative costs, depending the other performance characteristics of your program, and depending where you're bottlenecked elsewhere, either could be faster than the other. I'd suggest that you at least code up both and benchmark both so that you're making an informed choice, rather than one based on guesswork. \$\endgroup\$ Jul 29, 2021 at 12:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .