0
\$\begingroup\$

There are a couple of questions like this already. This one is different because it is specifically about rendering, as opposed to navigation and generation. I've implemented suggestions from here already.

The current optimisations are:

  • Only render block faces that aren't adjacent to another face
  • Render blocks in chunks, each chunk as one mesh (VAO)

According to the NetBeans profiler, 96% of time is spent on the glfwSwapBuffers(long window) method, which is when OpenGL renders the scene. This implies that my other operations are efficient enough, compared to rendering.

What else can I do to improve rendering performance?

The chunk size is 16x16x16 voxels. Sample renders and FPS:

  • 64 chunks : 500 FPS
  • 125 chunks : 300 FPS
  • 1000 chunks : 45 FPS
  • 4096 chunks : 12 FPS
\$\endgroup\$
  • 1
    \$\begingroup\$ Sounds weird. How large is a chunk? How many chunks are loaded? Your CPU & GPU? \$\endgroup\$ – KaareZ Feb 28 '16 at 10:03
  • \$\begingroup\$ @KaareZ, I can do any number up to ~64 and stay at >500 FPS but when I do more (like 125) the FPS starts to drop. 1000 chunks (Minecraft's render distance of 5) = 49 FPS but that's the block face adjacency optimisation at its very best. CPU: 2.5 GHz Intel Core i7, GPU: AMD Radeon HD 6770M 1024 MB \$\endgroup\$ – Lolums Feb 28 '16 at 10:11
  • \$\begingroup\$ Have you tried profilling with 4096 chunks? \$\endgroup\$ – KaareZ Feb 28 '16 at 10:34
  • \$\begingroup\$ @KaareZ, just did. For 4096: glDrawArrays() for each mesh took 77% and glfwSwapBuffers() took 20%. For 512: glfwSwapBuffers() took 92% and glDrawArrays() only took 4.5%. For 1024: glDrawArrays() took most time too. \$\endgroup\$ – Lolums Feb 28 '16 at 10:40
  • \$\begingroup\$ Why are you using drawarrays? You could save memory with indices. \$\endgroup\$ – Bálint Feb 28 '16 at 11:51
2
\$\begingroup\$

For my voxel game, i have (only) implemented:

  • Rendering block faces only adjacent to another face.
  • Using one VBO per chunk (And another for liquids)
  • Frustrum culling at chunk level
  • Backface culling

Altough I haven't done it myself, you can also perform some greedy meshing algorithm, "mixing" same type cubes into same triangles. Imagine you have 4 stone blocks, collindant to each other. When drawing the front face of each block you could draw 2 triangles per block, being 8 triangles in total. However, you can take advantage of the fact that all those blocks are of the same type, and you can draw 2 big triangles that cover all those 4 blocks instead. However this method can become tricky to use when each block haves different brightness levels, but anyways, considering that in a voxel game most of the blocks often have the same brightness (No artificial light and full sunlight or no artificial light and no sunlight), this can greatly improve your perfomance.

Nonetheless, i don't think your problem lies there. With almost the same optimizations than you, I can run smoothly more than 14440 chunks, while performing gpu-heavy algorithms (Cascaded shadow mapping, water shading), in a fairly old PC. For all those optimizations, the most important one is "Only rendering block faces that aren't adjacent to another face". Before implementing it, I was only able to draw ~20 chunks at most without lagging. Almost all the perfomance improvements i had been able to achieve relied on making this optimization work on all cases it should (For example, in the chunk limits, being for example x=0 and x=15, some of the collindant cubes are outside the chunk. Are you considering this special case? If not, you will be drawing the "outline" of each chunk, killing perfomance). If you disable collisions and go "into" the terrain, you should ideally not see anything drawed, only the terrain outline. It is possible that your problem lies there.

There is a simple test that can help you. Count the number of triangles you are generating, using a static var. Create a flat world, being only the y=0 cube coordinate of the y=0 chunk full, and the rest of the cubes empty. Check how much triangles the program had generated. Now, do a full world, with all cubes full excepting the last cube layer of the last chunk, being air. If the "Only rendering block faces that aren't adjacent to another face" optimization works right, the number of triangles generated should be equal than the number of triangles in the "empty world" case, altough the number of cubes is way higher.

Also, you should check that you are only binding textures at the beggining of the program (Or at least only a few times when rendering), that you are using the same shader program without switching it when drawing each chunk, etc.

Hope it helps.

\$\endgroup\$
  • \$\begingroup\$ I did a lot of reading up on greedy meshing but as you mentioned, we can't do as much with our attribute arrays when greedy meshing is in place. The "Rendering block faces only adjacent to another face" optimisation sounds really promising but I do not understand exactly what you mean by it. Mind explaining? \$\endgroup\$ – Lolums Feb 29 '16 at 17:04
  • \$\begingroup\$ Oh, i tougth you had already implemented it, since you put it in the question. Its kinda simple: You have a solid block. Before drawing, say, its top face, you look if the cube above this one is solid. If it is, there is no need to draw the face, since it was going to be occluded by this above block anyways, Repeating this for all the faces, you will draw only the faces of the cube that can be seen. Considering voxel terrains are usually very homogeneous, you will end only drawing like 1/1000 of the triangles you were drawing before, and your perfomance will increase dramatically. \$\endgroup\$ – Ivelate Feb 29 '16 at 17:15
  • \$\begingroup\$ Oh, I see! Yeah, I've already implemented that. Sorry for making you write all that out! :p \$\endgroup\$ – Lolums Feb 29 '16 at 17:18
  • \$\begingroup\$ No problem, maybe another person will make use of it :) As I said, check if this optimization is working as it should, I had perfomance problems and all of them were because of it, specially in the trancision between chunks. Good luck! \$\endgroup\$ – Ivelate Feb 29 '16 at 17:21
  • 1
    \$\begingroup\$ Ah, I do not have it in the transition between chunks. I'll go ahead and do it and get back to you. \$\endgroup\$ – Lolums Feb 29 '16 at 17:26
2
\$\begingroup\$

There are quite a lot of way to optimize voxel based rendering, some of them are:

Octrees

Octree is a way to store voxels, you have a root, wich sometimes is the chunk itself, then you have 8 smaller chunk in it, then those also have 8-8 even smaller chunks, etc...

In a chunk with the size of 64, you have the main chunk as root, then 8 smaller chunks, each containing 8 blocks.

After you grouped together all the blocks, then you can do some optimizations, for example, if an octree doesn't have any data in it, delete it or don't even try to render it.

Frustum culling

You get the faces wich are inside the camera's frustum, so they are visible for the user, and only render those.

\$\endgroup\$
  • \$\begingroup\$ Octree will only help on memory consumption and the time it takes to generate a mesh. He already have frustum culling. \$\endgroup\$ – KaareZ Feb 28 '16 at 11:53
  • \$\begingroup\$ @KaareZ No, he doesn't, he only renders the faces, wich aren't covered by other blocks,but he renders the faces behid the camera \$\endgroup\$ – Bálint Feb 28 '16 at 11:57
  • \$\begingroup\$ @KaareZ Also, octrees does help in speed, because ypu can check, if a tree contains any blocks, rather than checking visibility for each block separately \$\endgroup\$ – Bálint Feb 28 '16 at 11:59
  • \$\begingroup\$ anyways he still have other problems. I have made a voxel render myself, and haven't implemented frustum culling. I didn't have the problem. \$\endgroup\$ – KaareZ Feb 28 '16 at 11:59
  • \$\begingroup\$ I stated they help in generation time, but they won't help on the FPS \$\endgroup\$ – KaareZ Feb 28 '16 at 12:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.