- Home /
Could anyone tell me what's wrong with my voxel rendering?
Hey there! I'm making a 2.5D platformer game that uses 3D tiles (basically voxels) and quickly ran into performance issues. With around 30k draw calls and 500k triangles (95% percent of which are unnecessary) I really need to change something :p So I read up quite a bit on voxel engines, made chunks (10x10x10) and let each chunk deal with its voxels like this:
The voxels are placed by hand, so instead of being generated, they are scanned, aligned and then indexed on game start. The result is the same, though: the world consists of a 3d array of chunks, each containing a 3d array of voxels.
Voxels that are entirely surrounded by other voxels are then disabled altogether.
All the remaining voxels' meshes are cleared and only the faces that are next to empty space are re-added to the mesh.
This all works, but I still have to combine all the meshes that are the same. Of course, i've been googling around and almost everyone advised to use the CombineChildren script, disabling it by default and then enabling it after the chunks have done their thing, but when I enable the script at the end of the chunk's scripts, the game crashes. Is there another way to combine an object's children's meshes?
My second question is whether I should already notice a major improvement in the performance, even without combining the meshes. unfortunately, im actually getting a longer rendering time per frame at the moment (about 30% longer). This makes sense because i'm also getting an increase in draw calls, but... should i be getting an increase in draw calls, or am i doing something horribly wrong? I do get about a 90% reduction in tris and verts, though, so i guess at least that part of my code works xD
Here is the function i made that deals with generating the mesh of a single voxel:
function GenerateMesh(block: GameObject, position: Vector3) {
// clear old mesh
var meshFilter: MeshFilter = block.GetComponent(MeshFilter);
meshFilter.mesh.Clear();
// make vertices
var size = 0.5;
var vertices = [
Vector3(-size, -size, -size),
Vector3(-size, size, -size),
Vector3( size, size, -size),
Vector3( size, -size, -size),
Vector3( size, -size, size),
Vector3( size, size, size),
Vector3(-size, size, size),
Vector3(-size, -size, size)
];
// make triangles
var top_triangles = [
5, 2, 1,
5, 1, 6
];
var back_triangles = [
0, 1, 3,
1, 2, 3
];
var left_triangles = [
0, 7, 6,
0, 6, 1
];
var front_triangles = [
4, 5, 6,
4, 6, 7
];
var right_triangles = [
3, 2, 5,
3, 5, 4
];
var bottom_triangles = [
3, 4, 7,
3, 7, 0
];
// generate uvs
var uv: Vector2[] = new Vector2[vertices.Length];
for (var j = 0; j < uv.Length; j++) {
uv[j] = Vector2(vertices[j].x * 0.5, vertices[j].z * 0.5);
}
// set materials (these are defined globally per chunk)
var side_material = (CheckBlock(position + Vector3.up)) ? ground_side : ground_side_top;
block.renderer.materials = [
ground_top,
side_material,
side_material,
side_material,
side_material,
ground_bottom
];
// build new mesh
var mesh = new Mesh();
mesh.subMeshCount = 6;
mesh.vertices = vertices;
if (!CheckBlock(position + Vector3.up)) mesh.SetTriangles(top_triangles, 0);
if (!CheckBlock(position + Vector3.back)) mesh.SetTriangles(back_triangles, 1);
if (!CheckBlock(position + Vector3.left)) mesh.SetTriangles(left_triangles, 2);
if (!CheckBlock(position + Vector3.forward)) mesh.SetTriangles(front_triangles, 3);
if (!CheckBlock(position + Vector3.right)) mesh.SetTriangles(right_triangles, 4);
if (!CheckBlock(position + Vector3.down)) mesh.SetTriangles(bottom_triangles, 5);
mesh.uv = uv;
mesh.RecalculateNormals();
// set mesh
meshFilter.mesh = mesh;
}
The last question i have is about the materials. I think I did something wrong generating the UVs of the voxels, because the textures are rendered as a solid color. I've tried messing around with the UVs (again after reading up on them), the textures and the materials' tilings, which changed some stuff, but didn't make it render correctly. Does this make sense with the function above?
I would love to hear your input, cause i have no ideas left anymore. will continue browsing for answers in the meantime ;) thanks a bunch guys!!
I thought I should say this, but as it is more of a suggestion than an answer at the moment, I thought it would be better to use a comment-
Your chunk sizes should really be powers of two. 10s and 5s are easy for humans to understand, but computers will be able to do calculations much faster with 16s and 32s. Besides, you dont really need to do many calculations by hand if you are program$$anonymous$$g a computer to do these calculations.
Answer by Zarenityx · Jun 05, 2013 at 12:09 PM
Hi. I am also working on a voxel engine, only mine is 3d, not 2.5. It is the same basic concept, though.
First off, about your draw calls/combining problem- you, like many others, have made the most common mistake in the world of voxels: you are trying to make the voxels physical objects. What you really want to do to make it fast is to use 'hypothetical voxels'. These are either classes or structs with sub-functions (which works a lot faster, if you know how to use them.) and have variables like type, solidity, and transparency (this allows for glass-like voxels and stuff.). These will extend System.Object, NOT monobehavior. Then, instead of adding them through instantiation to your chunks, you iterate through and set them to the desired voxel. (something like: voxels[x,y,z] = Voxel.Air(); ). All the voxel data is still there, but there is no time wasted in the instantiation of individual objects. This also means that there is no need for using CombineChildren, because there are no children to combine. Instead, a simple modification can be done to your triangle reduction code. Instead of creating all the possible verts first, then making the triangles that you need, try creating a vertexindex integer, a list (not an array) of verts, and a list of triangles. Then have a bunch of if statements:
if the voxel is solid
if the voxel on this side is transparent or null
add the needed verts to the verts list, then add the triangles to the tris list, then the uvs, etc. When adding the tris, however, do it based on the vertex index. always treat the vertex index as if it is zero (even though it is not, and if you set it to zero it won't work) and add select the vertices by adding the correct number to the vertex index.
(when for loops are done)- add the verts, tris, uvs, etc. to the mesh, and set the mesh to the chunk's mesh filter and collider.
That way, there is only one mesh per chunk, no child objects, and no instantiation (which is a fairly slow process.).
Next, your uvs problem- I am not sure what you are doing with all the materials and stuff, but here is how I did it: I added a few arrays in my hypotheticals, for the top,bottom, and sides. (although I am thinking about revising this, given that the memory usage of my game will be very high). Then I made a texture atlas and used it to make a single material, which is applied to every chunk individually and by default. Then I got the uv coords for my chunk mesh from the uv coords of the voxels. Also remember that UV coords are expressed in Vector2s comprised of decimals between 0 and 1, where 0,0 is the bottom left of the texture, and 1,1 is the top right. Essentially, uvs are the percentage of the images pixels from the bottom left expressed in decimal form.
I hope this helps.
Your answer is amazing, thank you so much!! This will definitely get me back on the right track! I will do lots of revising and if I find out something interesting a I'll post it here in case it's useful to someone else later. Thanks again for your help!!