Tuesday, 22 April 2014

Voxels and boxels.

My first thoughts are - what if I just build the entire world out of cubes just like in Minecraft?
I understand distortion is inevitable but perhaps I could minimise it?
With no better plans in mind I got to work.

The plan:


The theory was to have six sides to a cube just as before.  Each side is a cube itself, though tapering in towards a point like a pyramid.


These cubes slot together with each other to form one big cube.



Then I continued  as before to normalize and multiply to project onto the sphere.


It took me a while to implement the LOD, I quickly ran into memory problems.

Rendered naively a 64^3 block volume contains 262144 blocks which requires 24 vertices each.
The reason it is 24 and not 8 is because each face of the cube may have a different texture coordinate and normal vector thus each of the 6 faces needs to store 4 vertices.

Suddenly the mesh is composed of 6.3 million vertices.
Each vertex needs a position, a normal and a texture coordinate which is a total of 8 floats.
At 4 bytes per float each just storing the mesh would take up 201mb of ram, and I wanted a planet much bigger than 64 meters across... I had at least 8 kilometers in mind which has a volume approximately 2 million times bigger! Impossible!

Clearly it was time to optimise.

Making the impossible possible:


The first step was to set up the generation of the cubes such that only faces exposed to a transparent block could generate vertices. The worst possible case now where the most faces would be exposed consisted of a 3d checkerboard pattern which held a volume of n^3 / 2 - 50% reduction.
This pattern is highly unnatural and would not be created by a terrain generator, the average case was now simply just the surface layer of the chunk which held 4096 blocks - sometimes slightly more because of overhangs or tunnels which meant an average case reduction of vertices by a factor of 64 times.

Another optimisation involved storing each vertex as a color instead of a vector, a color in xna is composed of 4 unsigned bytes which is exactly 1/3rd the size of a full vector.
A byte can only store 256 unique numbers which was sufficient to store which point in the volume the vertex was describing, a volume of 64 blocks required only 65 points on any axis - much less than 256.
Once in the shader i converted the vectors back to full sized vectors before offsetting them by a single global vector which the chunk being drawn had supplied.

A similar tactic was also used for the normal vector which combined resulted in reduced memory usage per vertex from 32 bytes to 16 bytes.

The final step was employing run length encoding of the volume which resulted in sometimes astonishing reductions in resource usage, a good introduction is outlined here.

Combined these optimisations allowed interactive frame rates rendering a planet 8192 blocks wide.

The results:



Then, with my improving shader programming skills, I decided to implement atmospheric scattering which is to this day one of the the most fiddly, frustrating things I have ever done. Regardless I managed to get it done and here is a video demonstrating my progress.



More complications:

However it became very clear that inevitable distortions were pretty bad where the cube's met on the surface.
"Perhaps I can distort it in reverse to smooth it out" I tell myself.  I tried this and while it "kind of" worked (through a complicated process of projecting the cube to a cylinder, rotating it and then projecting it back to a sphere) in the sense that the cubes were now properly shaped cubes wherever iI stood, it resulted in landscapes bending and twisting awfully as you traveled while they morphed into shape - I would not recommend this approach to anyone without even considering what such an approach would mean to a physics engine.

So back to the drawing board..



No comments:

Post a Comment