4
\$\begingroup\$

I'm developing a voxel engine in C# using Unity and I'm currently trying to implement infinite terrain, so I took a look at other Unity voxel engines that have infinite terrain. I've seen three different ways of doing it:

  1. Using a dictionary to store the chunks and store the block data in another dictionary
  2. Using a dictionary to store the chunks and store the block data on every chunk
  3. Using a fixed size array and shifting it every time the player moves

The engines using approach 1 and 2 were pretty slow at chunk loading. The engines using approach 3 were faster but produced lag every time the player entered a new chunk.

I've already tried to implement it using approach 1, but my chunk loading speed got slower by about 10 times, so it was really unplayable.

How is it done in efficient or popular voxel engines like Minecraft or VoxelFarm? Do you have experience with voxel rendering and know a better approach?

Thanks in advance!

\$\endgroup\$
5
\$\begingroup\$

Non sequitur, your question does not entirely make sense. You cannot store infinite chunks. You can only ever store the subset that is currently of interest, i.e. those around the player(s).

So how does the infinite part work? You generate chunks according to some global function. For example, Minecraft's use of Perlin noise (a global function) means that any given [x,y,z] location can be generated deterministically; changes which were applied at that voxel location are then applied following each deterministic regeneration of that location (more typically, of its parent chunk).

Now we get to the meat of your question -- the Area of Interest (AoI) around the player. Storing these can be done in many different ways, some of which you touch on. Let's look at those.

What is the difference between shifting a 2D array (approach 3) and using a dictionary? A 2D array is used when each of a bunch of closely-packed [x,z] locations are expected to be filled with valid chunks at all times, representing the current AoI; this is a dense collection. Think about that; it may well be all you need (I believe this is how MC works). OTOH, a dictionary is used when you need a sparse collection, i.e. data in memory is contiguous, but your chunks in world space are not, that is they are at non-adjacent chunk coordinates.

As for using chunk values (structs) vs references (pointers) to chunks that are stored elsewhere, this is typically a matter of flexibility. Using an array of values is faster for reading than using an array of pointers, because in the second case you have to not only index into the array, but you (or rather the Mono runtime) then still have to JMP to another location in order to get the chunk data; cost thereof may or may not be negligible depending on how well or poorly you have packed your data. Using an array / dictionary of pointers can be better for writing, because all you have to do then is change the pointer to another location, bearing in mind that you then pay a read-performance penalty. The other aspect to using references (for C#, nullable types) is that you can have null chunk references if you require them.

Your approach 1 is probably not very efficient because besides having to do lookups in two dictionaries (why?) you also have to do multiple jumps to get to the chunk data.

\$\endgroup\$
  • \$\begingroup\$ Thank you very much! That explained many things. So since I'm only storing chunks around the player which are adjacend to each other an fixed array might be good? I've already tried to implement it. I've done a workaround to avoiding to shift the entire chunk array by accessing block values like this: public static byte getData(int x, int y, int z) { return data[ (x < 0) ? size.x + (x % size.x)-1 : x % size.x, (y < 0) ? size.y + (y % size.y)-1 : y % size.y, (z < 0) ? size.z + (z % size.z)-1 : z % size.z ]; } It's still buggy, but runs at much better performance \$\endgroup\$ – yxyx136 May 26 '15 at 9:21
  • \$\begingroup\$ @yxyx136 Yes, I suggest going with a fixed array to start with as this is the simplest solution. The shifting that will be costly doing a memberwise copy; in C, I would just memcpy / memmove the whole contiguous block representing each chunk, to a different array location. Google "memcpy C#", there are quite a few options for fast block-wise copies. Best of luck :) \$\endgroup\$ – Engineer May 26 '15 at 14:43
  • \$\begingroup\$ Thanks again! But finally after trying to implement it many times..., I've decided to change the array which stores my chunks to a dictionary. As you said it costs read speed, but it's much easier to get the chunk position (without all the modulo stuff) \$\endgroup\$ – yxyx136 May 31 '15 at 11:14
  • \$\begingroup\$ @yxyx136 If you mean the actual offset of the chunk in the world... you could just store it in the chunk instance. Thus the array hold chunk by offset, but the chunk itself also holds info on its own offset. This makes things like picking easier. But whatever works for you. \$\endgroup\$ – Engineer May 31 '15 at 11:24
  • \$\begingroup\$ That wasn't the problem, my chunk loader just got very long and inefficient, but thanks again for your effort to help me :) \$\endgroup\$ – yxyx136 May 31 '15 at 11:30
3
\$\begingroup\$

A great explanation of a well known voxel chunk system can be found in the minecraft wiki

Regardless of how you want to store the data programmatically, the ability to generate smooth flowing infinite voxel terrain comes down to a smartly optimized neighbouring chunk loading system.

"The exact number of generated chunks varies in single player mode, depending on view distance and movement. In multiplayer mode, a grid with a default inradius of 10 (for a total of 21x21 or 441) chunks is loaded around each player and sent to the player by default, although this can be configured to be between 3 and 15, usually only lowered with a poor connection home server. These chunks may have activity (mobs spawning, trees growing, water flowing, dropped items disappearing etc.), while the other world chunks are inactive, stored on your hard drive. Chunks will not save again if they were saved in the last 30 seconds."

So effectively in a player driven game like minecraft you're only ever creating/loading a small number of chunks around your player at any time.

It's quite tough to troubleshoot your slow chunk loading issue without more information or code examples etc.

\$\endgroup\$
0
\$\begingroup\$

I'm also creating a voxel engine in unity, and I found to be most efficient to store blocktype data in struct (and load them from, for example XML or ini file, so you can make mods), and for chunk info, just store BlockID (I use ushort for that). Then create job system for rendering those if you don't really care about custom meshed blocks, since you'll have to store all meshinfo somewhere and somehow, and ECS is not yet recommended.

This way I can get 16^3 chunks of 16^3 blocks generated and rendered in like three or four frames at 10fps, or delayed ~10sec for whole thing to render @60fps

If you go for infinite world, it gets even better, since a single chunk renders at around 200ms and it's multithreaded, so you almost can't feel that there's heavy math in background since it renders instantly.

Edit: chunks are separated gameobjects, they store array of ushort[chunksize^3], each chunk generates and renders itself in their own job, so just convert ushort[] to NativeArray and back after job's done.

\$\endgroup\$

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.