RTS Voxel Engine using LWJGL - Textures glitching
- by Dieter Hubau
I'm currently working on an RTS game engine using voxels.
I have implemented a basic chunk manager using an Octree of Octrees which contains my voxels (simple square blocks, as in Minecraft). I'm using a Voronoi-based terrain generation to get a simplistic yet relatively realistic heightmap.
I have no problem showing a 256*256*256 grid of voxels with a decent framerate (250), because of frustum culling, face culling and only rendering visible blocks. For example, in a random voxel grid of 256*256*256 I generally only render 100k-120k faces, not counting frustum culling. Frustum culling is only called every 100ms, since calling it every frame seemed a bit overkill.
Now I have reached the stage of texturing and I'm experiencing some problems:
Some experienced people might already see the problem, but if we zoom in, you can see the glitches more clearly:
All the seams between my blocks are glitching and kind of 'overlapping' or something. It's much more visible when you're moving around.
I'm using a single, simple texture map to draw on my cubes, where each texture is 16*16 pixels big:
I have added black edges around the textures to get a kind of cellshaded look, I think it's cool. The texture map has 256 textures of each 16*16 pixels, meaning the total size of my texture map is 256*256 pixels.
The code to update the ChunkManager:
public void update(ChunkManager chunkManager) {
for (Octree<Cube> chunk : chunks) {
if (chunk.getId() < 0) {
// generate an id for the chunk to be able to call it later
chunk.setId(glGenLists(1));
}
glNewList(chunk.getId(), GL_COMPILE);
glBegin(GL_QUADS);
faces += renderChunk(chunk);
glEnd();
glEndList();
}
}
Where my renderChunk method is:
private int renderChunk(Octree<Cube> node) {
// keep track of the number of visible faces in this chunk
int faces = 0;
if (!node.isEmpty()) {
if (node.isLeaf()) {
faces += renderItem(node);
}
List<Octree<Cube>> children = node.getChildren();
if (children != null && !children.isEmpty()) {
for (Octree<Cube> child : children) {
faces += renderChunk(child);
}
}
return faces;
}
Where my renderItem method is the following:
private int renderItem(Octree<Cube> node) {
Cube cube = node.getItem(-1, -1, -1);
int faces = 0;
float x = node.getPosition().x;
float y = node.getPosition().y;
float z = node.getPosition().z;
float size = cube.getSize();
Vector3f point1 = new Vector3f(-size + x, -size + y, size + z);
Vector3f point2 = new Vector3f(-size + x, size + y, size + z);
Vector3f point3 = new Vector3f(size + x, size + y, size + z);
Vector3f point4 = new Vector3f(size + x, -size + y, size + z);
Vector3f point5 = new Vector3f(-size + x, -size + y, -size + z);
Vector3f point6 = new Vector3f(-size + x, size + y, -size + z);
Vector3f point7 = new Vector3f(size + x, size + y, -size + z);
Vector3f point8 = new Vector3f(size + x, -size + y, -size + z);
TextureCoordinates tc = textureManager.getTextureCoordinates(cube.getCubeType());
// front face
if (cube.isVisible(CubeSide.FRONT)) {
faces++;
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u], TEXTURE_V_COORDINATES[tc.v]);
glVertex3f(point1.x, point1.y, point1.z);
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u + 1], TEXTURE_V_COORDINATES[tc.v]);
glVertex3f(point4.x, point4.y, point4.z);
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u + 1], TEXTURE_V_COORDINATES[tc.v + 1]);
glVertex3f(point3.x, point3.y, point3.z);
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u], TEXTURE_V_COORDINATES[tc.v + 1]);
glVertex3f(point2.x, point2.y, point2.z);
}
// back face
if (cube.isVisible(CubeSide.BACK)) {
faces++;
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u + 1], TEXTURE_V_COORDINATES[tc.v]);
glVertex3f(point5.x, point5.y, point5.z);
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u + 1], TEXTURE_V_COORDINATES[tc.v + 1]);
glVertex3f(point6.x, point6.y, point6.z);
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u], TEXTURE_V_COORDINATES[tc.v + 1]);
glVertex3f(point7.x, point7.y, point7.z);
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u], TEXTURE_V_COORDINATES[tc.v]);
glVertex3f(point8.x, point8.y, point8.z);
}
// left face
if (cube.isVisible(CubeSide.SIDE_LEFT)) {
faces++;
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u], TEXTURE_V_COORDINATES[tc.v]);
glVertex3f(point5.x, point5.y, point5.z);
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u + 1], TEXTURE_V_COORDINATES[tc.v]);
glVertex3f(point1.x, point1.y, point1.z);
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u + 1], TEXTURE_V_COORDINATES[tc.v + 1]);
glVertex3f(point2.x, point2.y, point2.z);
glTexCoord2f(TEXTURE_U_COORDINATES[tc.u], TEXTURE_V_COORDINATES[tc.v + 1]);
glVertex3f(point6.x, point6.y, point6.z);
}
// ETC ETC
return faces;
}
When all this is done, I simply render my lists every frame, like this:
public void render(ChunkManager chunkManager) {
glBindTexture(GL_TEXTURE_2D, textureManager.getCubeTextureId());
// load all chunks from the tree
List<Octree<Cube>> chunks = chunkManager.getTree().getAllItems();
for (Octree<Cube> chunk : chunks) {
if (frustum.cubeInFrustum(chunk.getPosition(), chunk.getSize() / 2)) {
glCallList(chunk.getId());
}
}
}
I don't know if anyone is willing to go through all of this code or maybe you can spot the problem right away, but that is basically the problem, and I can't find a solution :-)
Thanks for reading and any help is appreciated!