So for now I will go back to focus my efforts on making a prototype using XNA only and then when it's in a playable state, start porting for the other platforms.
Voxel World - First Attempt
Meanwhile, I decided I want to try my hand making a voxel-based world because I realized that, for a while, I have been interested in the idea in creating a huge world that would be at least interesting to look at and walk around on. Also, I want to find out the challenges that come in rendering such a world.
Eventually, I will want to make some sort of action/adventure type of game with the voxel/cube world engine once it is fleshed out well enough. So far it has been a mostly smooth experience to see what it goes into the code. To get a head start I picked out some pre-existing code to make 2D and 3D simplex noise. I quickly learned how the noise functions readily make a continuous texture without being repetitive, as it's of huge importance when making a procedurally generated world.
I started working on this on Saturday, and made some decent progress by Sunday night, making a cube world with 3D Simplex noise and some mesh optimization to avoid rendering hidden cubes. The custom shader is very simple and low bandwidth, taking only a Vector3 for local position and a Byte4 for color. The mesh-building code also adds a shade of green to cubes that are visible from above, and the rest are brownish in color. This creates a somewhat convincing grass-and-dirt look. Finally I implemented some basic brute-force culling to avoid rendering invisible chunks. Quick and dirty procedural world!
Some problems I found with this voxel-cube generator were, I frequently ran into out-of-memory exceptions when I decided to go any higher than 192^3 cubes. I was splitting the world into 32^3 sized chunks but it didn't really help out the memory problem. My guess is that the triple-nested loops used to calculate the noise values are wounded too tight, and it might benefit from single-dimensional arrays. Also, I was storing the chunks in a resizable list, but it makes more sense to have a fixed sized array to be able to keep track of them. Also, while interesting to look at, the shapes produced by 3D noise weren't very desirable so I switched to 2D to make a more believable height map. From there, I will then experiment with some 3D noise to get some interesting rock formations and caves going on.
Streaming the Voxel World
The next steps are to stream new chunks of the world into view as you move the camera around. I've already made some progress with this, only for now by showing a 2D noise texture that "scrolls" when the camera moves past a certain distance from the center chunk. Currently, when the world requests new chunks to be added, the cube meshes for all chunks are re-built. This of course is not efficient enough, but I have come across bugs in how chunks are ordered when I try to just re-build some of the chunks.
The world is drawn with a grid of n x n chunks, where n is an odd number so there is an actual "center" chunk for the camera to sit in. Each chunk has a 2D coordinate called the offset, which tells its world position, as offset by its local position. For a chunk that is 32 units long and wide, its local coordinates are 0 to 32, and the offset vector is added to give the chunk's cubes their world positions.
To update chunks I have planned the following series of steps to do on every frame.
For each chunk in the grid:
- Get the X and Z distances between its offset position and the camera's position
- If X or Z are too far away from the camera:
- Set a new offset for the chunk according to its distance from the camera
- Chunk automatically tagged as 'dirty' in updating its offset
- Set a new offset for the chunk according to its distance from the camera
Set a "chunks rebuilt" variable to 0
(2nd loop) for each chunk in the grid:
- If chunk is 'dirty':
- Re-build the chunk's mesh with the new offset location
- Increment "chunks rebuilt" by 1
- Re-build the chunk's mesh with the new offset location
Hopefully this plan will make it possible to update only part of the world, and with the proper offset coordinates being fed to them. I split the chunk updates into two separate loops so there it is clear which ones need to re-build their mesh and avoid discontinuities in updating. I actually think that recalculating the correct offset coordinates for "faraway" chunks would actually be the trickiest to get right, since it depends on the X and Z values of the camera.
As far as the the "exit the loop" business goes in the second loop, that is meant to minimize any CPU stalling when re-building chunks. Calculating the noise values and determining what cubes need to be added to the mesh is going to be tasking for the CPU, so I decided it is better to just update a few chunks every frame. I'd rather have some chunks be visibly "in progress" rather than try to re-build all dirty chunks and abruptly stall or lag the game when the player enters another section of the world.
So far I haven't seen any visible dips in framerate when 81 32x32 chunks are updated in one frame, but it's still good to have the feature of splitting updates across several frames when needed. Then I will go back and optimize the mesh building so that only the faces facing the outside are drawn, rather than the entire cube.