Voxel Loader

Click and drag to rotate, refresh page for a different model

Description

This project reads binary .vox files generated by MagicaVoxel and outputs the voxel model using Raylib. The main learning goal for the project was to familiarize myself with binary file input in C++, as well as parsing the resulting data. After that initial goal, A secondary learning goal came about in learning to compile C++ and Raylib projects to WebAssembly using Emscripten and configuring it to work with my existing build pipeline.

In the course of building this project, I was able to familiarize myself with pointer arithmatic and memory manipulation operations in the C++ standard library. I had to find a way to iterate over an array of raw byte data with irregularly sized hunks of data. Luckily, the .vox files provide headers for each chunk of data that specify what type of chunk it is and how many bytes it occupies. I was able to do this by creating a pseudo 'for' loop where a while loop checks the offset of a byte pointer against the total size of the file being processed. The byte pointer is then used as the starting point for each chunk of data, and is incremented by the size of the chunk afterwards. I really enjoyed implementing this, as it felt very satisfying to successfully read and interpret raw byte data.

In the initial version of the project, I used a naive approach to rendering the models, because I needed it to be done in a relatively short timeframe. Once I had the voxel data parsed from the binary file, I simply looped through the array containing coordinates where a voxel existed, and drew a cube with raylib. Doing this every frame was obviously not ideal, and the performance implications were very apparent when trying to render large models like the voxelized version of the stanford dragon provided by the .vox file format github (this model is included as an example in this build). Not only is drawing a cube for every voxel causing a lot of overdraw, the bigger issue is the large number of draw calls happening. To get around this, the obvious solution is to generate a proper mesh from the provided voxel data. Not only does this allow me to cull the faces in between voxels, but crucially it allows the entire model to be drawn in very few calls to the GPU—ideally even just a single one. At first I tried turning the models into a single mesh regardless of size, and managed to get it working pretty quickly. Unfortunately when I tried to load the aforementioned dragon model, it only rendered a small portion of the tail. I spent quite a while trying to figure out what was happenning, before ultimately discovering the culprit was integer overflow. Raylib(and I'm led to believe OpenGL) uses a uint16 array for triangle indices. This means that if a mesh has more than 65535 vertices, only a portion will be able to be accessed and converted to triangles. I knew from a brief venture into bare OpenGl that it was possible to draw mesh without using the index array, and to my mild surprise all it took to get the model to draw correctly was adding some duplicate vertices to the mesh and removing the code to add indices.

Sadly for me, that was not actually the end of the story. When I tried doing a WASM build of the project, nothing rendered. It wasn't just the large stanford dragon, but even the smaller models that hadn't had any issues bfore were now gone, leaving an empty square of cornflower blue and a very unhelpful webGL error message. As it turns out, webGL and a few other implementations require index arrays to draw anything. This means I have to take a few steps back and add another step of processing to the models before they can be rendered. Fortunately that step is pretty simple. Instead of storing a single raylib mesh struct, we can store a vector of meshes and create a new mesh as soon as the current one gets too close to the limit imposed by uint16. To absolutely nobody's surprise, I ran into yet another issue trying to implement this. As it turns out, initializing a local mesh struct with Mesh mesh{}; does not create a unique object every time, and in fact causes some weird situations where even code behind an if statement that isn't ever reached, can cause the mesh to think it's been uploaded to the GPU more than it actually has. The way I fixed this was to instead initialize the struct with auto* mesh = new Mesh();, and then push it to the vector with meshes.push_back(*mesh). With this final problem solved, I was able to successfully split up large meshes and draw them with very acceptable performance, even in web builds.

Source Code

The following is most of the relevant code. Full source code can be found here.

C++

cmakelists