An OpenGL-based tool, and a library, that converts a 3D model to a 3D volume, represented as a Sparse Voxel Octree.
This code was originally part of mdmc
, another project of mine meant to convert a 3D model to a Minecraft schematic. I've decided to modularize this part
as it could be useful for other tasks (and hopefully be useful to other people).
You can generate the octree out of the 3d model using the following command:
./voxelizer <model-file> <volume-height> <output-file>
You can visualize the output octree by running the following command:
./viewer <octree-file> [model-file]
Where model-file
is optional and can be used to compare the original model with the result.
The output file consists of an array of little endian uint32_t
, in binary format, representing the following data:
version
of the format (should be 0x01)volume_size.x
volume_size.y
volume_size.z
octree_resolution
(could be derived from volume_size
)octree_bytesize
: the number of bytes reserved for the octree structureoctree
: the actual octree structureThe octree
structure consists of a set of levels one allocated after the other.
The first 8 uint32_t
correspond to the first octree level. Every value can be either:
Once you have integrated the code in your project (e.g. either by using git submodules, CMake's FetchContent...), you can add it as a dependency to your project:
add_subdirectory(voxelizer)
Then you have to include its directories and link the library to your target:
target_include_directories(<your-target> PUBLIC "voxelizer/voxelizer")
target_link_libraries(<your-target> PUBLIC voxelizer)
As a first step, you have to create a representation of the 3d model that the voxelizer pipeline is compatible with (i.e. you have to initialize the voxelizer::scene
object). You can either load it from a file or initialize it from already loaded data (this is the hard part, I'll cover it, probably).
To load it from a model file:
#include <voxelizer/ai_scene_loader.hpp>
#include <voxelizer/scene.hpp>
voxelizer::assimp_scene_loader scene_loader{};
voxelizer::scene scene{};
char const* my_model_file = /* ... */;
scene_loader.load(scene, my_model_file);
Then you can run the voxelization process:
#include <voxelizer/voxelize.hpp>
voxelizer::voxelize voxelize{};
voxelizer::voxel_list voxel_list{};
uint32_t volume_height = /* ... */;
voxelize(voxel_list, scene, volume_height, scene.m_transformed_min, scene.get_transformed_size());
Finally build the octree:
#include <voxelizer/voxelize.hpp>
#include <voxelizer/octree_builder.hpp>
voxelizer::octree_builder octree_builder{};
glm::uvec3 volume_size = voxelizer::voxelize::calc_proportional_grid(scene.get_transformed_size(), volume_height);
uint32_t max_volume_side = glm::max(glm::max(volume_size.x, volume_size.y), volume_size.z);
uint32_t octree_resolution = (uint32_t) glm::ceil(glm::log2((float) max_volume_side));
size_t octree_bytesize = voxelizer::octree::get_octree_bytesize(octree_resolution);
GLuint octree_buffer{};
glGenBuffers(1, &octree_buffer);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, octree_buffer);
glBufferStorage(GL_SHADER_STORAGE_BUFFER, octree_bytesize, nullptr, NULL);
voxelizer::octree octree{};
octree_builder.build(voxel_list, octree_resolution, octree_buffer, 0, octree);
The built data structure is encapsulated in the octree
object.