46 | >,fontname="monospace"]
47 |
48 | c [shape=square,label="b",fontname="monospace"]
49 |
50 | a -> b [style=dotted,minlen=2,fontname="monospace"]
51 | b -> c [style=dotted,minlen=2,fontname="monospace"]
52 | }
53 |
--------------------------------------------------------------------------------
/src/graphviz/octree_serial.dot:
--------------------------------------------------------------------------------
1 | digraph g {
2 | bgcolor=transparent
3 | splines=spline
4 | rankdir=LR
5 | node [shape = record]
6 |
7 | root [label="{1 | 00 | 1 | 000 | 1}"]
8 |
9 | b0 [label="{ 1 | 00 | 1 | 0000}"]
10 | b1 [label="00000000"]
11 | b2 [label="{000 | 1| 0000}"]
12 |
13 | c00 [shape=none, label=<#d13232>]
14 | c01 [shape=none, label=<#6c6e3b>]
15 | c20 [shape=none, label=<#1d6bd1>]
16 |
17 | root -> b0 -> c00 -> c01 -> b1 -> b2 -> c20 [weight=100, style=invis]
18 |
19 | root:f0:n -> b0:nw
20 | root:f1:s -> b1:s
21 | root:f2:s -> b2:s
22 | b0:f0:n -> c00:n
23 | b0:f1:n -> c01:n
24 | b2:f0:n -> c20:n
25 | }
26 |
--------------------------------------------------------------------------------
/src/graphviz/octree_squashed.dot:
--------------------------------------------------------------------------------
1 | digraph g {
2 | bgcolor=transparent
3 | node [shape = record]
4 | root [label=" 1 | 00 | 1 | 00000000...00000000 | 1| 0000"]
5 |
6 | c00 [shape=none, label=<#d13232>]
7 | c01 [shape=none, label=<#6c6e3b>]
8 | c20 [shape=none, label=<#1d6bd1>]
9 |
10 | root:f0 -> c00
11 | root:f1 -> c01
12 | root:f2 -> c20
13 | }
14 |
--------------------------------------------------------------------------------
/src/graphviz/render-all.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | for f in *.dot; do
4 | out="../img/graph/${f%.dot}.svg"
5 | dot "$f" -o "$out" -Tsvg
6 | done
7 |
--------------------------------------------------------------------------------
/src/graphviz/simple_growth.dot:
--------------------------------------------------------------------------------
1 | digraph g {
2 | bgcolor=transparent
3 | rankdir=LR
4 |
5 | source [shape=record,label="{1|3}|{0|2}",fontname="monospace"]
6 | target [shape=record,label="{|||}|{|||}|{1|3||}|{0|2||}",fontname="monospace"]
7 |
8 | source -> target [minlen=3,style=dotted]
9 | }
10 |
--------------------------------------------------------------------------------
/src/graphviz/test_model.dot:
--------------------------------------------------------------------------------
1 | digraph g {
2 | rankdir=LR
3 | bgcolor=transparent
4 | node [shape = record]
5 | root [label="1 |00 | 1 |00 | 1 | 1"]
6 |
7 | b_red [label=" 1 |0 | 1 | 1 |0 | 1 | 1 | 1"]
8 | b_gra [label=" 1| 1| 1| 1| 1| 1| 1| 1"]
9 | b_wht [label="0 | 1 |000000"]
10 | b_aqu [label=" 1 |000000 | 1"]
11 |
12 | red [shape=rectangle, label=<ffff000a>]
13 | org [shape=rectangle, label=<ffff8b33>]
14 | gra [shape=rectangle, label=<ff646464>]
15 | wht [shape=rectangle, label=<ffffffff>]
16 | grn [shape=rectangle, label=<ff33ff4c>]
17 | blu [shape=rectangle, label=<ff3373ff>]
18 |
19 | root:f0 -> b_red
20 | root:f3 -> b_gra
21 | root:f6 -> b_wht
22 | root:f7 -> b_aqu
23 |
24 | b_red:f0 -> org
25 | b_red:f2 -> red
26 | b_red:f3 -> red
27 | b_red:f5 -> org
28 | b_red:f6 -> red
29 | b_red:f7 -> red
30 |
31 | b_aqu:f0 -> grn
32 | b_aqu:f7 -> blu
33 |
34 | b_wht:f1 -> wht
35 |
36 | b_gra:f0 -> gra
37 | b_gra:f1 -> gra
38 | b_gra:f2 -> gra
39 | b_gra:f3 -> gra
40 | b_gra:f4 -> gra
41 | b_gra:f5 -> gra
42 | b_gra:f6 -> gra
43 | b_gra:f7 -> gra
44 | }
45 |
--------------------------------------------------------------------------------
/src/graphviz/test_model_delta.dot:
--------------------------------------------------------------------------------
1 | digraph g {
2 | rankdir=LR
3 | bgcolor=transparent
4 | node [shape = record]
5 | root [label="1 |00 | 1 |00 | 1 | 1| ff33000a"]
6 |
7 | b_red [label=" 1 |0 | 1 | 1 |0 | 1 | 1 | 1| 00cc0000"]
8 | b_gra [label=" 1| 1| 1| 1| 1| 1| 1| 1| 0031645a"]
9 | b_wht [label="0 | 1 |000000 | 00ccfff5"]
10 | b_aqu [label=" 1 |000000 | 1 | 00007342"]
11 |
12 | red [shape=rectangle, label=<00000000>]
13 | org [shape=rectangle, label=<00008b29>]
14 | gra [shape=rectangle, label=<00000000>]
15 | wht [shape=rectangle, label=<00000000>]
16 | grn [shape=rectangle, label=<00008c00>]
17 | blu [shape=rectangle, label=<000000b3>]
18 |
19 | root:f0 -> b_red
20 | root:f3 -> b_gra
21 | root:f6 -> b_wht
22 | root:f7 -> b_aqu
23 |
24 | b_red:f0 -> org
25 | b_red:f2 -> red
26 | b_red:f3 -> red
27 | b_red:f5 -> org
28 | b_red:f6 -> red
29 | b_red:f7 -> red
30 |
31 | b_aqu:f0 -> grn
32 | b_aqu:f7 -> blu
33 |
34 | b_wht:f1 -> wht
35 |
36 | b_gra:f0 -> gra
37 | b_gra:f1 -> gra
38 | b_gra:f2 -> gra
39 | b_gra:f3 -> gra
40 | b_gra:f4 -> gra
41 | b_gra:f5 -> gra
42 | b_gra:f6 -> gra
43 | b_gra:f7 -> gra
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/src/graphviz/test_model_min.dot:
--------------------------------------------------------------------------------
1 | digraph g {
2 | rankdir=LR
3 | bgcolor=transparent
4 | node [shape = record]
5 | root [label="1 |00 | 1 |00 | 1 | 1| ff33000a"]
6 |
7 | b_red [label=" 1 |0 | 1 | 1 |0 | 1 | 1 | 1| ffff000a"]
8 | b_gra [label=" 1| 1| 1| 1| 1| 1| 1| 1| ff646464"]
9 | b_wht [label="0 | 1 |000000 | ffffffff"]
10 | b_aqu [label=" 1 |000000 | 1 | ff33734c"]
11 |
12 | red [shape=rectangle, label=<ffff000a>]
13 | org [shape=rectangle, label=<ffff8b33>]
14 | gra [shape=rectangle, label=<ff646464>]
15 | wht [shape=rectangle, label=<ffffffff>]
16 | grn [shape=rectangle, label=<ff33ff4c>]
17 | blu [shape=rectangle, label=<ff3373ff>]
18 |
19 | root:f0 -> b_red
20 | root:f3 -> b_gra
21 | root:f6 -> b_wht
22 | root:f7 -> b_aqu
23 |
24 | b_red:f0 -> org
25 | b_red:f2 -> red
26 | b_red:f3 -> red
27 | b_red:f5 -> org
28 | b_red:f6 -> red
29 | b_red:f7 -> red
30 |
31 | b_aqu:f0 -> grn
32 | b_aqu:f7 -> blu
33 |
34 | b_wht:f1 -> wht
35 |
36 | b_gra:f0 -> gra
37 | b_gra:f1 -> gra
38 | b_gra:f2 -> gra
39 | b_gra:f3 -> gra
40 | b_gra:f4 -> gra
41 | b_gra:f5 -> gra
42 | b_gra:f6 -> gra
43 | b_gra:f7 -> gra
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/src/graphviz/unilateral_growth.dot:
--------------------------------------------------------------------------------
1 | digraph g {
2 | bgcolor=transparent
3 | rankdir=LR
4 |
5 | source [shape=record,label="{1|3}|{0|2}",fontname="monospace"]
6 | target [shape=record,label="{|||}|{|1|3|}|{|0|2|}|{|||}",fontname="monospace"]
7 |
8 | source -> target [minlen=3,style=dotted]
9 | }
10 |
--------------------------------------------------------------------------------
/src/img/cube.svg:
--------------------------------------------------------------------------------
1 |
2 |
36 |
--------------------------------------------------------------------------------
/src/img/graph/README.txt:
--------------------------------------------------------------------------------
1 | The files in this directory are auto-generated.
2 | Do not put user files in here!
3 |
--------------------------------------------------------------------------------
/src/img/graph/acc_depth_first.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
115 |
--------------------------------------------------------------------------------
/src/img/graph/breadth_first.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
115 |
--------------------------------------------------------------------------------
/src/img/graph/depth_first.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
115 |
--------------------------------------------------------------------------------
/src/img/graph/huffman-start.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
32 |
--------------------------------------------------------------------------------
/src/img/graph/octree_complete_branches_optimization.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
96 |
--------------------------------------------------------------------------------
/src/img/graph/octree_growth.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
68 |
--------------------------------------------------------------------------------
/src/img/graph/octree_node_index.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
91 |
--------------------------------------------------------------------------------
/src/img/graph/octree_squashed.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
62 |
--------------------------------------------------------------------------------
/src/img/graph/simple_growth.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
67 |
--------------------------------------------------------------------------------
/src/img/graph/unilateral_growth.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
67 |
--------------------------------------------------------------------------------
/src/img/hilbert.svg:
--------------------------------------------------------------------------------
1 |
2 |
64 |
--------------------------------------------------------------------------------
/src/img/hilbert3d.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/hilbert3d.gif
--------------------------------------------------------------------------------
/src/img/model/chessmaster3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/model/chessmaster3.png
--------------------------------------------------------------------------------
/src/img/model/hilbert0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/model/hilbert0.png
--------------------------------------------------------------------------------
/src/img/model/hilbert1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/model/hilbert1.png
--------------------------------------------------------------------------------
/src/img/model/mars_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/model/mars_map.png
--------------------------------------------------------------------------------
/src/img/model/ragged_cluster_magica.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/model/ragged_cluster_magica.png
--------------------------------------------------------------------------------
/src/img/model/scrapyard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/model/scrapyard.png
--------------------------------------------------------------------------------
/src/img/model/spacestation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/model/spacestation.png
--------------------------------------------------------------------------------
/src/img/model/test_model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/model/test_model.png
--------------------------------------------------------------------------------
/src/img/nested_iter.svg:
--------------------------------------------------------------------------------
1 |
2 |
51 |
--------------------------------------------------------------------------------
/src/img/perlin_noise_mve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/perlin_noise_mve.png
--------------------------------------------------------------------------------
/src/img/pipeline.svg:
--------------------------------------------------------------------------------
1 |
2 |
39 |
--------------------------------------------------------------------------------
/src/img/render-svg.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [[ "$#" -lt 2 ]]; then
4 | printf "Usage: render-svg [height]\n"
5 | exit 1
6 | fi
7 |
8 | file_in="$1"
9 | file_out="$(basename "$file_in" .svg).png"
10 |
11 | width="$2"
12 |
13 | if [[ "$#" -ge 3 ]]; then
14 | height="$3"
15 | rsvg-convert -w "$width" -h "$height" "$file_in" > "$file_out"
16 | else
17 | rsvg-convert -w "$width" "$file_in" > "$file_out"
18 | fi
19 |
20 |
--------------------------------------------------------------------------------
/src/img/voxel_noise_mve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/voxel_noise_mve.png
--------------------------------------------------------------------------------
/src/img/z_order.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Eisenwave/voxel-compression-docs/8817b0b47d53c4a3316204544d967443e55605c1/src/img/z_order.png
--------------------------------------------------------------------------------
/src/img/z_order.svg:
--------------------------------------------------------------------------------
1 |
2 |
60 |
--------------------------------------------------------------------------------
/src/index.md:
--------------------------------------------------------------------------------
1 | # Compression of Voxel Models
2 |
3 | ## Abstract
4 |
5 | This website contains the documentation of my research project at TU Dresden.
6 | The goal of the project is to develop an efficient algorithm for compressing voxel models.
7 | As an input format, an unsorted list of 3D integer coordinates and attribute data is used.
8 | Multiple methods for encoding geometry data including
9 | Cuboid Extraction (CE),
10 | Sparse Voxel Octrees (SVOs) with Space-Filling Curves, and
11 | Run-Length Encoding (RLE)
12 | are explained and then compared in terms of complexity, compression ratio, and real life performance.
13 | CE fails based on its high complexity, SVOs and RLE perform almost identically in terms of compression ratio and
14 | complexity.
15 | SVOs are the clear winner because of their lower real life memory requirements when reading the input data, resulting
16 | in better performance.
17 |
18 | The Free Lossless Voxel Compression (FLVC) codec is developed based on these findings.
19 | Its advantages include arbitrary attribute information per voxel, high performance, streamability, and very low memory
20 | requirements for decoding.
21 | The codec is made available through a FOSS command-line utility which can convert between various common voxel model
22 | formats and the new FLVC format.
23 |
24 |
25 | ##### Relevant Links
26 |
27 | - [FLVC Command-Line Interface](https://github.com/Eisenwave/flvc)
28 | - [This documentation on GitHub](https://eisenwave.github.io/voxel-compression-docs/)
29 |
30 | ##### Notes
31 |
32 | This documentation was written using [MkDocs](https://www.mkdocs.org/).
33 | ^^`
34 | !!! warning
35 | Many functions such as searching or syntax highlighting won't work when opening this project with a `file://`
36 | scheme.
37 | You can browse this site offline by hosting it as a static web page on a local server.
38 |
39 | To name an example, use `python3 -m http.server 8000` in the root directory to host this page locally on port 8000.
40 | Then connect to
41 |
--------------------------------------------------------------------------------
/src/introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Volumetric Pixels or voxels are an increasingly popular method of representing 3-dimensional data.
4 | Their uses are plentiful such as representing quantized point cloud data, medical scans, acceleration data structures
5 | for VFX, high resolution sculptures, or 3D models with an intentional voxel art style.
6 | Accessing voxels randomly can even be achieved with constant time complexity, all while using relatively simple data
7 | structures.
8 |
9 | However, perhaps because of this simplicity, efficient permanent storage of voxel models is rarely a concern.
10 | Out of more than 20 common voxel file formats, only one
11 | ([Qubicle Binary Tree](related/voxel_formats.md#qubicle-binary-tree)) makes a serious attempt at compression
12 | of voxel models by using [zlib](https://zlib.net/).
13 | Some solutions combining general-purpose technologies such as storing a stack of PNG layers can also be found in formats
14 | like [SVX](related/voxel_formats.md#simple-voxels).
15 | The sophistication of widely used voxel software is overall at a pre-1996 level compared to image compression.
16 | This fact promises enormous improvements in compression ratio for a new file format that was designed specifically
17 | for the purpose of efficiently storing voxel models on disk.
18 |
19 | The goal of this project was to develop an efficient codec for the purpose of on-disk storage of voxel models.
20 | Compression ratio and performance are equally important.
21 | In addition to the overwhelmingly popular approach of using sparse voxel octrees (SVOs) for compact storage, we also
22 | examine alternative solutions such as a variety of run-length encoding schemes (RLE) using different space-filling
23 | curves, including Nested Iteration, Morton Curves, and Hilbert Curves.
24 |
25 | In the following chapters we first examine geometry-only encoding of voxel models.
26 | RLE and SVO techniques are examined thoroughly and compared against each other in compression and performance.
27 | The reference point for compression efficiency is a list of points with quantized coordinates.
28 | This was the size of the input data is directly proportional to the number of voxels.
29 | Based on the findings, we discuss a suitable attribute encoding scheme that can be well combined with our geometry
30 | encoding scheme of choice.
31 | Finally, the FLVC codec which incorporates all our findings is presented.
32 |
33 | ## Related Work
34 |
35 | [Schnabel et al.](related/literature.md#octree-based-point-cloud-compression) first presented in 2006 how sparse voxel
36 | octrees can be used to efficiently store point-cloud data.
37 | A technique is suggested where a single byte is used to encode the existence of a child node, which represents
38 | individual voxels at the bottommost level of the SVO.
39 | While this technique was not its main subject, the suggested approach can be further optimized by encoding recursively
40 | occupied nodes of the octree specially.
41 | The research also shows that the number of child nodes is not uniformly distributed and may show significant
42 | redundancy based on the model, which can be exploited by an arithmetic coder.
43 |
44 | [Byrne et al.](related/literature.md#vola) designed an efficient file format named VOLA for storing quantized point
45 | cloud data with no attributes other than geometry.
46 | VOLA stores voxels not as an array -as is often done at runtime- but as an octree where to layers are
47 | combined into one, creating a tree with a branching factor of 64.
48 | The performance of this approach is outdone by our codec, but VOLA was an inspiration.
49 |
50 | Most other contemporary research is focused on the use of voxels at runtime.
51 | In part, this is because voxels are being used primarily as VFX accelerators where polygonal models are unsuited to
52 | achieve certain effects in real time, such as global illumination.
53 | This task inherently comes with strict performance requirements.
54 |
55 | [Laine et al.](related/literature.md#efficient-svos) presents a sparse voxel octree-based technique for representing
56 | geometry and shading attributes.
57 | Some techniques found in our implementation are similar to the presented memory layout.
58 | Namely, the use of an 8-bit child mask to encode which children of a parent node exist was inspired by this paper.
59 |
60 | [Kämpe et al.](related/literature.md#high-resolution-sparse-voxel-dags) show that SVOs can be further compactified
61 | by merging identical trees and generalizing to sparse voxel directed acyclic graphs (SVDAGs).
62 | This approach was developed for realtime rendering with 32-bit pointers encoding links between nodes.
63 | While a significant reduction in space can be observed, this technique does not provide significant benefits to a
64 | pointerless structure with 8-bit child masks, which is why it was not used in our project.
65 |
--------------------------------------------------------------------------------
/src/js/extra.js:
--------------------------------------------------------------------------------
1 | // disable language-auto-detection
2 | // necessary to prevent no-code markdown preformatted blocks from being highlighted
3 | hljs.configure({languages: []});
4 | hljs.initHighlightingOnLoad();
5 |
--------------------------------------------------------------------------------
/src/js/mathjax_config.js:
--------------------------------------------------------------------------------
1 | MathJax.Hub.Config({
2 | config: ["MMLorHTML.js"],
3 | jax: ["input/TeX", "output/HTML-CSS", "output/NativeMML"],
4 | extensions: [],
5 | showMathMenu: true
6 | });
7 |
--------------------------------------------------------------------------------
/src/properties.md:
--------------------------------------------------------------------------------
1 | # Properties of Voxel Models
2 |
3 | The cornerstone of compression is discovering and eliminating redundancies in data.
4 | We must first recognize these redundancies by observing the nature of voxel models.
5 | There are plenty of properties which we can exploit, as described in the following sections.
6 |
7 | ## Geometric/Spatial Locality
8 |
9 | 
10 | *Normalized 3D Perlin Noise with a threshold of 0.4*
11 |
12 | Voxels rarely come alone.
13 | All 3D models will typically take some distinguished shape.
14 | There will be large areas with filled geometry (voxels) and large empty areas, *air*, or *void*.
15 | In artistic circles these are often described as [negative space](https://en.wikipedia.org/wiki/Negative_space).
16 |
17 | 
18 | *Uniform voxel noise with a probability of 0.1 that a voxel is set*
19 |
20 | The only kind of model which has no spatial locality whatsoever would be uniformly distributed noise. (see above)
21 | These kinds of models are the absolute exception and are rarely if ever seen.
22 |
23 | ### Conclusion
24 |
25 | Compression schemes have to be able to describe large volumes of positive and negative space efficiently.
26 |
27 | ## Color/Attribute Locality
28 |
29 | 
30 | *"Ragged Cluster" model visualized in Magica Voxel*
31 |
32 | Color and other attributes such as normals correlate with geometry.
33 | This means that the color distance between neighboring voxels is significantly lower than the distance of random points
34 | in RGB space.
35 |
36 | In fact, this correlation is dramatically high.
37 | For the above model, the average euclidean distance of any unique pair of neighboring voxel colors
38 | $(0, 0, 0) \le (r, g, b) \le (255, 255, 255)$
39 | is $8.56$.
40 | Normalized for positions in a unit cube instead this is $0.03357$.
41 |
42 | For uniform random colors, this distance would be equal to
43 | [Robbin's Constant](https://mathworld.wolfram.com/RobbinsConstant.html) which is roughly equal to $0.66170$, almost
44 | 20 times higher.
45 |
46 | ### Conclusion
47 |
48 | Compression schemes should make use of geometric encoding and attach attributes on top of that.
49 | Alternatively voxel compression schemes should encode attributes in a fashion very similar to geometry.
50 |
51 | ## Color Sparsity
52 |
53 | While in principle, plenty of software supports 24-bit True Color RGB or even wider standards, only a small amount of
54 | colors are actually used.
55 | Especially in the field of *Voxel Art*, the 3D-equivalent to [Pixel Art](https://en.wikipedia.org/wiki/Pixel_art),
56 | small palettes are used.
57 | To name an example, [Magica Voxel](https://ephtracy.github.io/) supports 255 unique colors but each individual color
58 | is a 24-bit True Color.
59 |
60 | ### Conclusion
61 |
62 | Compression schemes should be aware of potentially very low color counts and exploit these by building a palette.
63 | A palette will not always be beneficial since in principle, any amount of colors should be present.
64 | However, in many cases this will be beneficial.
65 |
--------------------------------------------------------------------------------
/src/statistical_tests.md:
--------------------------------------------------------------------------------
1 | # Statistical Tests to Determine Voxel Model Properties
2 |
3 | The following list should give an overview over possible properties which voxel models are suspected to have.
4 | A description of an algorithm and/or pseudocude is used to provide a potential test.
5 |
6 |
7 | ## Spatial Locality of Different Methods of Iteration Over Voxel Models
8 |
9 | There are various different ways of iterating over a voxel model.
10 | We will consider only two or three, which are relevant to us.
11 |
12 | #### Nested Loop Iteration
13 | This is the traditional method of iterating over multi-dimensional arrays.
14 | ```c
15 | char data[SIZE_X][SIZE_Y][SIZE_Z];
16 | for (size_t x = 0; x < SIZE_X; ++x)
17 | for (size_t y = 0; y < SIZE_Y; ++y)
18 | for (size_t z = 0; z < SIZE_Z; ++z)
19 | data[x][y][z];
20 | ```
21 | It is extremely efficient for traversing memory because it seemlessly iterates over memory locations, as long as the
22 | axes of iteration are in the right order.
23 | The distances between two positions are at best `1` and at worst `SIZE_X + SIZEY`.
24 | However, the spatial locality is much worse when looking at more than one position.
25 | For say, 10 positions the distance between the first and last will be 10, which is comparably far.
26 |
27 | #### Z-Order Iteration
28 | Z-Order iteration traverses space with much higher locality.
29 | It works by traversing all nodes of an octree depth-first.
30 | 3D-Vectors can be converted into "octree positions" by interleaving the bits of the coordinates into a single number.
31 | ```c
32 | char data[SIZE_X][SIZE_Y][SIZE_Z];
33 | for (size_t x = 0; x < SIZE_X; ++x)
34 | for (size_t y = 0; y < SIZE_Y; ++y)
35 | for (size_t z = 0; z < SIZE_Z; ++z)
36 | data[interleave_bits(x, y, z)];
37 | )
38 | ```
39 |
40 | #### Hilbert-Curve Iteration
41 | Hilbert-Curves have even higher locality than Z-Order iterations, but are considerably better in locality.
42 | See https://slideplayer.com/slide/3370293/ for construction.
43 | In short, space is filled in the following order, which is a Gray Code.
44 | ```json
45 | [[0, 0, 0], [0, 0, 1], [0, 1, 1], [0, 1, 0], [1, 1, 0], [1, 1, 1], [1, 0, 1], [1, 0, 0]]
46 | ```
47 | The entry direction for one of these pieces is +Z and the exit-direction is -Y.
48 | This pattern is repeated recursively, but the smaller building blocks have to be mirrored and rotated to fit together
49 | seemlessly into a larger block.
50 | The tremendously useful property here is that the distance between two points in the iteration is at most `1`.
51 |
52 | ### Testing Spatial Locality
53 | Spatial locality can simply be tested by comparing the average distance between each pair of points, triple of points,
54 | etc. within an iteration.
55 | When more than two points are tested, the distances of each unique pair of points can be summed up or averaged.
56 | Hilber-Curve Iteration is expected to deliver the best results on most, if not all scales.
57 |
58 |
59 | ## Correlation of Color and Geometry Deltas
60 |
61 | It is expected that color correlates with positions.
62 | This means that positions which are close to each other should also have similar colors.
63 | To verify this property, each unique pair of points can be iterated over.
64 | The euclidean distance in geometry-space (scaled down to a unit-cube) as well as the euclidean distance in the RGB
65 | color space should be plotted.
66 | The correlation can then be calculated from all data entries.
67 | ```cpp
68 | struct Entry {
69 | double geoDistance;
70 | double colDistance;
71 | };
72 | struct ColoredPoint {
73 | vec3 geo;
74 | vec3 col;
75 | }
76 | std::vector entries;
77 | for (const std::pair &pair : allPoints) {
78 | double geoDistance = pair.first.geo.distanceTo(pair.second.geo);
79 | double colDistance = pair.first.col.distanceTo(pair.second.col);
80 | entries.emplace_back(geoDistance, colDistance);
81 | }
82 | ```
83 |
--------------------------------------------------------------------------------
/src/svo/optimization.md:
--------------------------------------------------------------------------------
1 | # SVO Optimization
2 |
3 | Sparse Voxel Octrees are often sufficiently compact data structures for computer graphics applications.
4 | However, they still contain a large amount of redundancy and have plenty of potential for optimization.
5 |
6 | We can differentiate between two types of optimizations:
7 | **top-down optimizations** optimize nodes starting from the root node and continue downwards.
8 | **bottom-up optimizations** optimize nodes starting from the leafs and continue upwards.
9 |
10 | *Top-down optimizations* alter the geometric boundaries of the octree but have little effect on the amount of nodes.
11 | This is due to the fact that in any tree, there are more nodes towards the bottom.
12 | Their primary purpose is accelerating access to the deeper layers.
13 |
14 | *Bottom-up optimizations* have no impact on the boundaries of the octree as this solely depends on the maximum depth.
15 | Their primary purpose is decreasing the amount of nodes or reducing nodes in size.
16 |
17 |
18 | ## Single-Octant Optimization
19 |
20 | 
21 | *Figure 1: Octree Optimization, where b is a sub-branch (visualized using a Quadtree)*
22 |
23 | Once an octree has been fully constructed, unused octants can be optimized or "cut away" recursively.
24 | This can be especially helpful for octrees where all voxels reside very far from the origin.
25 | For instance, we could be encoding voxels with coordinates ranging from 100,000 to 100,050.
26 | Many almost completely empty octree layers would need to be traversed to get to where such locations are stored.
27 | Trimming away such single-octant layers accelerates encoding and decoding.
28 |
29 | This is our first and only *top-down optimization*.
30 |
31 | ### Algorithm
32 |
33 | 1. $s \gets (0,0,0)$
34 | 2. If the root node $r$ has exactly one branch $b$:
35 | - $s \gets s + 2^{d-2}$
36 | - $r \gets b$
37 | - repeat 2.
38 |
39 | $2^d$ is the negated minimum point of our current octree, as described in
40 | [Position Normalization](#position-normalization).
41 |
42 | After this process has been completed, we simply store $s$ alongside the octree.
43 | When decoding, $s$ is added back onto all found positions.
44 |
45 |
46 | ## Complete Branch Optimization
47 |
48 | 
49 | *Figure 2: A Complete (Sub-)Tree*
50 |
51 | Observe the *Figure 2* above.
52 | All nodes are completely filled with `1`, representing that their subtree or color is present.
53 | This demonstrates one of the significant weak points of octrees:
54 | They are very good at trimming away air recursively, but have a very high cost when encoding a completely filled
55 | container.
56 |
57 | In fact, we would be much better off encoding such containers as arrays than using octrees, as octrees only add
58 | redundancy and no benefit in this case.
59 | To optimize such cases, we must eliminate nodes which are recursively filled in, or *complete*.
60 | A voxel model that demonstrates this well is *chessmaster3*:
61 |
62 | 
63 | *Figure 3: chessmaster3 Model, visualized in Magica Voxel*
64 |
65 | A very large portion of this model is completely filled in.
66 | In fact, the cuboid base of this model alone is 1088x1088 large and many voxels tall.
67 | When encoding this model, we see the following results:
68 |
69 | | Method | Geometry Bytes |
70 | | ----- | ----- |
71 | SVX | 1031 KiB
72 | Octree | 25044 KiB (~24x)
73 | Tetrahexacontree | 22704 KiB (~22x)
74 | Octree Optimized | 1340 KiB (~1.3x)
75 | Thct. Optimized | 4619 KiB (~4.5x)
76 |
77 | So how could we accomplish a seemingly miraculous drop from 24x worse to nearly identical for octrees?
78 | By simply using the *degenerate case* of a zero-node to encode a complete subtree.
79 |
80 | 
81 | *Figure 4: The Tree from Figure 2, after optimization.*
82 |
83 | Compare the above figure to *Figure 2*. We are eliminating one layer of nodes and going from the root directly to
84 | the color information.
85 | Of course, the above data only includes geometry bytes, not color bytes.
86 | By eliminating such layers, we can save ourselves 8 nodes on the second level from the bottom, 64 nodes on the third,
87 | and so on.
88 |
89 | Geometrically, it does not even require a lot of space to be completely filled.
90 | We merely need a 4x4x4 volume of voxels to be complete in order to eliminate 8 nodes.
91 | Tetrahexacontrees do not perform nearly as well though.
92 | They require a 16x16x16 volume to be complete and only then is an optimization possible.
93 | The more squashed an octree becomes, the fewer opportunities there are to optimize it.
94 |
--------------------------------------------------------------------------------
/src/uncompressed.md:
--------------------------------------------------------------------------------
1 | # Uncompressed Format
2 |
3 | The uncompressed format used for reference in this project is a 32-bit list of voxels.
4 | In this case a voxel is a triple of coordinates and an ARGB integer, meaning that voxels can be partially transparent.
5 |
6 | ## Example Implementation
7 |
8 | Here is a simple example implementation of a 32-bit voxel list in C++.
9 |
10 | ```cpp
11 | struct voxel {
12 | int32_t x;
13 | int32_t y;
14 | int32_t z;
15 | uint8_t a;
16 | uint8_t r;
17 | uint8_t g;
18 | uint8_t b;
19 | }
20 |
21 | std::vector voxel_list;
22 | ```
23 |
24 | ## Justification - Voxel Arrays vs. Voxel Lists
25 |
26 | There are at least two popular methods of representing voxels in an uncompressed way:
27 |
28 | - 3D-array of colors, aka. voxel array
29 | - array of coordinate/color pairs, aka. voxel list
30 |
31 | The first method is often used in software on a small scale.
32 | Arrays allow for random access in $O(1)$.
33 | They are also used in other research, such as [High Resolution Sparse Voxel DAGs](
34 | related/literature.md#high-resolution-sparse-voxel-dags).
35 | Despite its popularity, arrays are unsuitable for measuring compression ratios because the entropy of the voxel data
36 | poorly correlates with the size of this representation.
37 | For instance, two voxels at $(1, 1, 1)$ and $(1000, 1000, 1000)$ require 1 gigavoxel of space due to all the empty space
38 | between the two voxels.
39 | However, in the best case where all voxels are present, this method has only constant overhead: that which is necessary
40 | to store the dimensions of the array.
41 |
42 | The second representation -which is the one used here- will require space that linearly scales with the amount of
43 | non-empty voxels.
44 | Note that in the worst case, this could require four times the space of the first method due to the coordinates
45 | being stored explicitly.
46 | This kind of overhead is still preferable to the potential (near) complete waste of space of the first method.
47 |
48 | To summarize, here is a quick overview:
49 |
50 | | | Voxel Array | Voxel List |
51 | | ----- | ----- | ----- |
52 | worst case overhead (space) | $O(\infty)$[^1] | $O(4n)$[^2]
53 | best case overhead (space) | $\Omega(1)$ | $\Omega(4n)$[^2]
54 | random access (time) | $O(1)$ | $O(n)$
55 |
56 | [^1]: Unbounded because two voxels can be infinitely far apart, creating infinite wasted space / overhead
57 | [^2]: The constant factor of 4 is irrelevant in this notation, but illustrates the extent of the overhead
58 |
59 | ## Serialization
60 |
61 | Serialization of the [VL32](file_formats/vl32.md) representation is trivial because the data structure in memory is
62 | similar, if not identical to its serialized counterpart.
63 | To serialize it, we must simply write each element of the array from first to last to disk.
64 |
--------------------------------------------------------------------------------