Compressing Massive Point Clouds with 3D Tiles and Draco
by Sean Lilley
Point clouds now load faster than ever in Cesium thanks to Google Draco compression, which we’ve added to the Cesium ion 3D tiling pipeline.
Our 3D tiling pipeline efficiently tiles full-resolution point clouds into 3D Tiles, an OGC community standard, so they can be streamed into CesiumJS for visualization and analysis in the browser.
We had previously collaborated with Google to bring Draco compression optimizations to our tilers for massive mesh-based 3D tilesets, such as massive photogrammetry models and millions of 3D buildings. We saw dramatic results: the compression resulted in 5x smaller file sizes and faster streaming.
So we brought the same technique to massive point clouds, which can be billions of points or more, with each point having multiple attributes. With the open Draco format, we’re producing 3D tilesets with:
- 7.0x compression for 1mm precision (compared to source LAS)
- 4.4x compression for 1mm precision (compared to uncompressed 3D Tiles)
- 2.0x faster streaming in CesiumJS
Not only does Draco encoding lead to smaller file sizes; it decodes very quickly, so these tilesets stream twice as fast with no loss in visual quality.
This means users can efficiently stream full-resolution point clouds into CesiumJS, even on mobile. The compression preserves all original data, so per-point attributes are all retained and can still be styled for analysis. And it makes it easier than ever to fuse point clouds with other 3D data, such as photogrammetry models or terrain.
Read on for more details about how Draco encoding has led to dramatically smaller file sizes and faster streaming, as well as how we’ve taken best advantage of the available options and optimizations.
High-resolution point cloud of the historic Pharsalia cabins in Virginia, with millimeter precision. Collected by Trimble and shown in CesiumJS.
Draco encoding for point clouds
The point cloud 3D Tiles format already includes several options for compressing point data:
- 16-bit quantization for each x, y, z position component
- 16-bit oct-encoding for each normal
- 16-bit rgb565 packing for each color
Draco, a quantization-based compression method, is an open format that is similar in spirit but offers some key advantages:
- Order-optimized encoding: the Draco encoder rearranges points for optimal compression using a KD-tree based encoder.
- Generic attributes: Draco can compress generic attributes such as intensity and classification.
- Configurable compression: a point cloud can use anywhere between 1 and 31 quantization bits depending on the tolerated lossiness. The compression can be tuned for millimeter or better precision.
Dramatically smaller files with Draco encoding
What does this mean for overall tileset size? We ran the numbers on a section of the Pharsalia cabin point cloud collected by Trimble. The 3D point cloud has 185 million points with color, intensity, and classification attributes.
|3D Tiles + Gzip||3D Tiles Draco + Gzip|
|1.83 GB||0.64 GB|
The full breakdown:
|Total tileset size|
|Source LAS (Point Format 2)||4.48 GB|
|3D Tiles Uncompressed||2.93 GB|
|3D Tiles + Gzip||1.83 GB|
|3D Tiles 16-bit Quantization||1.90 GB|
|3D Tiles 16-bit Quantization + Gzip||1.58 GB|
|3D Tiles Draco (1 mm precision)||0.67 GB|
|3D Tiles Draco (1 mm precision) + Gzip||0.64 GB|
To summarize, we saw:
- 7.0x improvement compared to the source LAS
- 4.4x improvement compared to the uncompressed tileset
- 2.7x improvement compared to the gzipped tileset
- 2.4x improvement compared to the 16-bit quantized and gzipped tileset
With Draco compression we were able to produce a tileset that is 7.0x smaller than the source LAS and 2.4x smaller than our previous best case. This is a nice win with no noticeable difference in visual quality. We also confirmed our suspicion that gzip would not help out significantly when applied to an already Draco compressed tileset.
Next we experimented with some of the Draco specific options. First we looked at how different precisions would compare visually. It was pretty obvious once we loaded them side by side in CesiumJS:
|Lossless (1.83 GB)||Millimeter (0.64 GB)|
|Centimeter (0.47 GB)||Decimeter (0.37 GB)|
While this particular point cloud requires millimeter precision, other point clouds such as those collected from aerial LiDAR may look acceptable with centimeter-scale precision.
At runtime CesiumJS continually streams in new point cloud tiles to refine the current view. Since Draco-compressed tiles are smaller they are faster to download. Once downloaded, they are decoded and sent to the GPU. To improve overall loading speed, CesiumJS decodes point cloud tiles in parallel with Web Workers.
|3D Tiles + Gzip||3D Tiles Draco + Gzip|
For this test we hosted each tileset on Cesium ion with the browser cache disabled:
|3D Tiles||9.79 seconds|
|3D Tiles Draco (1 mm precision)||4.45 seconds|
Next we ran the same benchmark but with the tilesets hosted on the local disk. Here the CPU decode became the bottleneck and we saw opposite results:
|3D Tiles||1.75 seconds|
|3D Tiles Draco (1 mm precision)||3.36 seconds|
Overall there is some variability based on network and decoding speed, but in the common case where content is streamed over the web, the Draco tileset is the clear winner. The Draco decoder is fast enough that network speed is often the bottleneck.
Optimizations for best performance
For a more in-depth overview of the optimizations we use to load Draco content, check out our previous post Draco for glTF and 3D Tiles. Point clouds use the same two techniques - parallel decoding and GPU dequantization - to get the best performance out of the hardware. We decode on the CPU using Web Workers and the Draco Web Assembly module. The decoder returns array buffers in 16-bit quantized form that we then upload to the GPU. The final decode from 16-bit quantized integers to 32-bit floats occurs on the GPU, which effectively saves us half the GPU memory usage.
Parallel Decode Architecture
We also explored some optimizations on the encoding side. Draco allows us to apply any number of quantization bits to each attribute. In general we found that 8–16 bits works well for positions, and 8 bits is generally sufficient for colors, normals, and generic attributes. Attributes can also be encoded losslessy, so it is possible to write a tileset that contains, for example, lossless positions and lossy intensity values.
The number of quantization bits we use for positions is largely dependent on tile size and our desired precision. For instance, if we are targeting a resolution of one point per centimeter, a one meter cubed tile requires only 7 quantization bits (6.644 to be exact, but rounded up). If instead we want millimeter-scale precision the same tile requires 10 quantization bits. In either case this is a huge improvement over the original 16-bit quantization option.
Because of this flexibility, most tilesets will contain a mix of compression amounts. The tilesets we tested contained tiles that used anywhere from 7 to 16 quantization bits for positions. Considering that most tilesets consist of some large tiles and many more small tiles, the average was close to 8 bits and only the root tile required 16 bits of precision.
You can take advantage of these optimizations today. Any point clouds uploaded to Cesium ion are now Draco encoded, providing a performance boost when streaming them into your Cesium-based app.