Mesh Voxelization

Voxel, voxelizer, voxelize, voxelized, voxelization…

Yesterday I stumbled upon a tweet about a header only mesh voxelizer library in C. This library creates a voxelized mesh from a triangular mesh. It’s really simple to use and I decided to add it to GeeXLab (in v0.12.0.0).

This voxelization library does not perform deep voxelization (voxels inside of the mesh), only shell voxelization. But for voxel art, shell voxelization is enough.

Digital Orca statue - VancouverDigital Orca, Vancouver (source)

 
Here is a code snippet that shows how to use this lib:

vx_mesh_t* input_mesh = vx_mesh_alloc(nvertices, nindices);

// Add vertices and indices from the original mesh you want to voxelize
// [...]

// Precision factor to reduce "holes" artifact
float precision = 0.01f;
float voxel_size = 0.025f;
// Run voxelization
vx_mesh_t* output_mesh = vx_voxelize(input_mesh, 
                                     voxel_size, 
                                     voxel_size, 
                                     voxel_size, 
                                     precision);

// Convert the output in your mesh format.
// [...]

vx_mesh_free(output_mesh);
vx_mesh_free(input_mesh);

 
In GeeXLab, the following code will voxelize a mesh torus:

mesh_torus = gh_mesh.create_torus(20, 6, 20) 
voxel_size = 0.75
precision = 0.01
gh_mesh.voxelize(mesh_torus, 
                 voxel_size, 
                 voxel_size, 
                 voxel_size, 
                 precision)

 
The voxelized mesh has lot of vertices / faces compared to the original mesh. The number of vertices / faces depend on the number of vertices / faces of the input mesh as well as the voxel size. Here are some values for an input mesh with 441 vertices and 800 faces (the torus mesh has a radius of 20 unities):
– voxel size: 0.75 unity – vertices=61312, faces=91968
– voxel size: 0.5 unity – vertices=144968, faces=217452


GeeXLab - mesh voxelization - voxel
GeeXLab - mesh voxelization - voxel

 
With the bunny mesh (2503 vertices and 4968 faces), the voxelized mesh has 53720 vertices and 80580 faces for a voxel size of 0.2.

voxel size: 0.2
GeeXLab - mesh voxelization - voxel

 
voxel size: 0.1
GeeXLab - mesh voxelization - voxel
voxel size: 0.1

 
Because I was playing with voxelization, I took some time to code and test a very simple geometry shader-based voxelizer: each triangle is converted into a point and from this point, the geometry shader generates a cube made up of 6 faces (4 vertices per face).

That works but the previous voxelizer library gives better results.


GeeXLab - mesh voxelization - geometry shader, GLSL

 
All GeeXLab demos related to this article are available in the code sample pack in the gl-32/voxelize/ folder. GeeXLab 0.12.0+ is required.


 
For those interested, here is the complete GLSL code for the geometry shader based voxelizer as you can find it in GeeXLab demos (demo03.xml and demo04.xml):

Vertex shader:

#version 150
in vec4 gxl3d_Position;
in vec4 gxl3d_Normal;
in vec4 gxl3d_Color;
uniform mat4 gxl3d_ModelViewProjectionMatrix;

out Vertex
{
  vec4 normal;
  vec4 color;
} vertex;

void main()
{
  gl_Position = gxl3d_Position;
  vertex.normal = gxl3d_Normal;
  vertex.color =  gxl3d_Color;
}

 
Geometry shader:

#version 150
layout(triangles) in;
layout(triangle_strip, max_vertices=24) out;

uniform mat4 gxl3d_ModelViewProjectionMatrix;
uniform mat4 gxl3d_ModelViewMatrix;
uniform mat4 gxl3d_ViewMatrix;
uniform vec4 light_position;
uniform float voxel_size;

in Vertex
{
  vec4 normal;
  vec4 color;
} vertex[];

out vec4 vertex_color;
out vec4 vertex_normal;
out vec4 vertex_eyevec;
out vec4 vertex_lightdir;

void EmitQuad(vec4 p0, vec4 p1, vec4 p2, vec4 p3, vec4 light_pos_view_space)
{
  vec4 V0 = p0 - p1;
  vec4 V1 = p2 - p1;
  
  vec3 N = cross(V1.xyz, V0.xyz);
  N = normalize(N);
  
  vec4 v_normal = gxl3d_ModelViewMatrix  * vec4(N, 0.0);
  
  gl_Position = gxl3d_ModelViewProjectionMatrix * p0;
  vertex_color = vec4(1.0, 1.0, 0.0, 1.0);
  vertex_normal = v_normal;
  vec4 view_vertex = gxl3d_ModelViewMatrix * p0;
  vertex_lightdir = light_pos_view_space - view_vertex;
  vertex_eyevec = -view_vertex;
  EmitVertex();

  gl_Position = gxl3d_ModelViewProjectionMatrix * p1;
  vertex_color = vec4(1.0, 1.0, 0.0, 1.0);
  vertex_normal = v_normal;
  view_vertex = gxl3d_ModelViewMatrix * p1;
  vertex_lightdir = light_pos_view_space - view_vertex;
  vertex_eyevec = -view_vertex;
  EmitVertex();

  gl_Position = gxl3d_ModelViewProjectionMatrix * p2;
  vertex_color = vec4(1.0, 1.0, 0.0, 1.0);
  vertex_normal = v_normal;
  view_vertex = gxl3d_ModelViewMatrix * p2;
  vertex_lightdir = light_pos_view_space - view_vertex;
  vertex_eyevec = -view_vertex;
  EmitVertex();
  
  gl_Position = gxl3d_ModelViewProjectionMatrix * p3;
  vertex_color = vec4(1.0, 1.0, 0.0, 1.0);
  vertex_normal = v_normal;
  view_vertex = gxl3d_ModelViewMatrix * p3;
  vertex_lightdir = light_pos_view_space - view_vertex;
  vertex_eyevec = -view_vertex;
  EmitVertex();

  EndPrimitive();
}  

void main()
{
  int i;
  
  vec3 center = vec3((gl_in[0].gl_Position.xyz + 
                      gl_in[1].gl_Position.xyz + 
                      gl_in[2].gl_Position.xyz) / 3.0);

  vec4 P = vec4(center, 1.0);
  
  vec4 lightPosViewSpace = gxl3d_ViewMatrix * light_position;
  

  //============================================================
  // Front
  //============================================================
  vec4 P0;
  P0.x = P.x - voxel_size/2.0;
  P0.y = P.y - voxel_size/2.0;
  P0.z = P.z + voxel_size/2.0;
  P0.w = 1.0;
  
  vec4 P1;
  P1.x = P.x - voxel_size/2.0;
  P1.y = P.y + voxel_size/2.0;
  P1.z = P.z + voxel_size/2.0;
  P1.w = 1.0;

  vec4 P2;
  P2.x = P.x + voxel_size/2.0;
  P2.y = P.y - voxel_size/2.0;
  P2.z = P.z + voxel_size/2.0;
  P2.w = 1.0;
  
  vec4 P3;
  P3.x = P.x + voxel_size/2.0;
  P3.y = P.y + voxel_size/2.0;
  P3.z = P.z + voxel_size/2.0;
  P3.w = 1.0;
  
  EmitQuad(P0, P1, P2, P3, lightPosViewSpace);
  

  //============================================================
  // Back
  //============================================================
  P0.x = P.x - voxel_size/2.0;
  P0.y = P.y - voxel_size/2.0;
  P0.z = P.z - voxel_size/2.0;
  P0.w = 1.0;
  
  P1.x = P.x - voxel_size/2.0;
  P1.y = P.y + voxel_size/2.0;
  P1.z = P.z - voxel_size/2.0;
  P1.w = 1.0;

  P2.x = P.x + voxel_size/2.0;
  P2.y = P.y - voxel_size/2.0;
  P2.z = P.z - voxel_size/2.0;
  P2.w = 1.0;
  
  P3.x = P.x + voxel_size/2.0;
  P3.y = P.y + voxel_size/2.0;
  P3.z = P.z - voxel_size/2.0;
  P3.w = 1.0;

  EmitQuad(P0, P1, P2, P3, lightPosViewSpace);
   
  
  
  //============================================================
  // Right
  //============================================================
  P0.x = P.x + voxel_size/2.0;
  P0.y = P.y - voxel_size/2.0;
  P0.z = P.z + voxel_size/2.0;
  P0.w = 1.0;
  
  P1.x = P.x + voxel_size/2.0;
  P1.y = P.y + voxel_size/2.0;
  P1.z = P.z + voxel_size/2.0;
  P1.w = 1.0;

  P2.x = P.x + voxel_size/2.0;
  P2.y = P.y - voxel_size/2.0;
  P2.z = P.z - voxel_size/2.0;
  P2.w = 1.0;
  
  P3.x = P.x + voxel_size/2.0;
  P3.y = P.y + voxel_size/2.0;
  P3.z = P.z - voxel_size/2.0;
  P3.w = 1.0;

  EmitQuad(P0, P1, P2, P3, lightPosViewSpace);
  
  
  //============================================================
  // Left
  //============================================================
  P0.x = P.x - voxel_size/2.0;
  P0.y = P.y - voxel_size/2.0;
  P0.z = P.z - voxel_size/2.0;
  P0.w = 1.0;
  
  P1.x = P.x - voxel_size/2.0;
  P1.y = P.y + voxel_size/2.0;
  P1.z = P.z - voxel_size/2.0;
  P1.w = 1.0;

  P2.x = P.x - voxel_size/2.0;
  P2.y = P.y - voxel_size/2.0;
  P2.z = P.z + voxel_size/2.0;
  P2.w = 1.0;
  
  P3.x = P.x - voxel_size/2.0;
  P3.y = P.y + voxel_size/2.0;
  P3.z = P.z + voxel_size/2.0;
  P3.w = 1.0;

  EmitQuad(P0, P1, P2, P3, lightPosViewSpace);
  
  
  //============================================================
  // Top
  //============================================================
  P0.x = P.x - voxel_size/2.0;
  P0.y = P.y + voxel_size/2.0;
  P0.z = P.z + voxel_size/2.0;
  P0.w = 1.0;
  
  P1.x = P.x - voxel_size/2.0;
  P1.y = P.y + voxel_size/2.0;
  P1.z = P.z - voxel_size/2.0;
  P1.w = 1.0;

  P2.x = P.x + voxel_size/2.0;
  P2.y = P.y + voxel_size/2.0;
  P2.z = P.z + voxel_size/2.0;
  P2.w = 1.0;
  
  P3.x = P.x + voxel_size/2.0;
  P3.y = P.y + voxel_size/2.0;
  P3.z = P.z - voxel_size/2.0;
  P3.w = 1.0;

  EmitQuad(P0, P1, P2, P3, lightPosViewSpace);


  //============================================================
  // Bottom
  //============================================================
  P0.x = P.x - voxel_size/2.0;
  P0.y = P.y - voxel_size/2.0;
  P0.z = P.z - voxel_size/2.0;
  P0.w = 1.0;
  
  P1.x = P.x - voxel_size/2.0;
  P1.y = P.y - voxel_size/2.0;
  P1.z = P.z + voxel_size/2.0;
  P1.w = 1.0;

  P2.x = P.x + voxel_size/2.0;
  P2.y = P.y - voxel_size/2.0;
  P2.z = P.z - voxel_size/2.0;
  P2.w = 1.0;
  
  P3.x = P.x + voxel_size/2.0;
  P3.y = P.y - voxel_size/2.0;
  P3.z = P.z + voxel_size/2.0;
  P3.w = 1.0;

  EmitQuad(P0, P1, P2, P3, lightPosViewSpace);
}

 
Pixel shader:

#version 150
uniform vec4 light_diffuse;
uniform vec4 material_diffuse;
uniform vec4 light_specular;
uniform vec4 material_specular;
uniform float material_shininess;
in vec4 vertex_color;
in vec4 vertex_normal;
in vec4 vertex_eyevec;
in vec4 vertex_lightdir;
out vec4 FragColor;
void main()
{
  vec4 final_color = vec4(0.1, 0.1, 0.1, 1.0); 
  vec4 N = normalize(vertex_normal);
  vec4 L = normalize(vertex_lightdir);
  float lambertTerm = dot(N,-L);
  if (lambertTerm > 0.0)
  {
    final_color += light_diffuse * material_diffuse * lambertTerm;	
  }
  FragColor.rgb = final_color.rgb;
  FragColor.a = 1.0;
}

One thought on “Mesh Voxelization”

Comments are closed.