OpenGL Programming/Glescraft 2
Introduction
editIn the previous tutorial, you have seen how to render a chunk of voxels. However, all the voxels were drawn, even the ones that weren't visible. Also, when neighbouring voxels share the same color or texture, it is possible to merge some triangles. In this part, we will try to reduce the amount of vertices necessary by removing those that are invisible.
Removing invisible voxel faces
editIn general, it is very hard to determine if a triangle is invisible for all possible camera positions. However, in our voxel world, we can be sure that one side of a voxel is invisible if there is another voxel right next to it on that side. If that is the case, we can omit drawing the two triangles that make up that voxel:
for(int x = 0; x < CX; x++) {
for(int y = 0; y < CY; y++) {
for(int z = 0; z < CZ; z++) {
// Empty block?
if(!blk[x][y][z])
continue;
// View from negative x, only draw if there is no block in front of it
if(x > 0 && !blk[x - 1][y][z]) {
vertex[i++] = byte4(x, y, z, blk[x][y][z]);
vertex[i++] = byte4(x, y, z + 1, blk[x][y][z]);
vertex[i++] = byte4(x, y + 1, z, blk[x][y][z]);
vertex[i++] = byte4(x, y + 1, z, blk[x][y][z]);
vertex[i++] = byte4(x, y, z + 1, blk[x][y][z]);
vertex[i++] = byte4(x, y + 1, z + 1, blk[x][y][z]);
}
...
Note that this implementation only checks for visibility inside one chunk. If we have more that one chunk, then if x == 0, one should check if there is a voxel in the neighbouring chunk that could block visibility.
Exercises:
- Implement this check for the other 5 directions as well.
- Given a chunk that is completely filled (all blk[x][y][z] are non-zero), how many vertices are saved by removing the invisible ones?
- Which configuration of voxels in a chunk results in the maximum amount of vertices?
- Try rendering the chunk with GL_LINES instead of GL_TRIANGLES.
Merging adjacent faces
editAlthough there are various algorithms one can think of to merge adjacent triangles, a quick way is to check if we have two visible voxels in a row of the same type. If that is true, then instead of adding two new triangles, we change the two previous triangles to cover the current voxel as well. This is how it can be done:
for(int x = 0; x < CX; x++) {
for(int y = 0; y < CY; y++) {
bool visible = false;
for(int z = 0; z < CZ; z++) {
// Empty block?
if(!blk[x][y][z]) {
visible = false;
continue;
}
// Check if we are the same type as the previous block, if so merge the triangles.
if(visible && blk[x][y][z] == blk[x][y][z - 1]) {
vertex[i - 5] = byte4(x, y, z + 1, blk[x][y][z]);
vertex[i - 2] = byte4(x, y, z + 1, blk[x][y][z]);
vertex[i - 1] = byte4(x, y + 1, z + 1, blk[x][y][z]);
}
else
// View from negative x, only draw if there is no block in front of it
if(x > 0 && !blk[x - 1][y][z]) {
vertex[i++] = byte4(x, y, z, blk[x][y][z]);
vertex[i++] = byte4(x, y, z + 1, blk[x][y][z]);
vertex[i++] = byte4(x, y + 1, z, blk[x][y][z]);
vertex[i++] = byte4(x, y + 1, z, blk[x][y][z]);
vertex[i++] = byte4(x, y, z + 1, blk[x][y][z]);
vertex[i++] = byte4(x, y + 1, z + 1, blk[x][y][z]);
visible = true;
} else {
visible = false;
}
...
This implementation merges faces across the inner most loop (in this case z). We keep track of the visibility of the previous voxel along the z axis and merge accordingly. It's important to note that we must now do full scans for each face in order to keep our loop variable "i" accurate and simple to use for vertex updates. As the overhead for rendering is almost entirely gpu bound, the extra loops will not hinder us too much.
The flag visible tracks whether the previous voxel was visible. If so, we can be sure that the previous two triangles in our vertex buffer do indeed belong to that voxel. Now, since our inner most for loop variable is z, we are looking at two voxels that only differ in the z coordinate. So, we should extend the three vertices of the previous voxel, that border on the current voxel, to cover the current voxel as well. Those three are the ones that have "z + 1" in them.
Exercises
- Implement this check for the other 5 directions as well.
- How many vertices can potentially be saved using this particular algorithm?
- This algorithm only merges voxel faces along one direction. Can you think of an easy way to merge them in the other direction as well?
- Can we merge with triangles from neightbouring chunks as well?
- Try rendering the chunk with GL_LINES instead of GL_TRIANGLES to see which faces are merged.