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.