Optimizing 3D data with Draco Geometry Compression

3D graphics are a fundamental part of many applications, including gaming, design and data visualization. As graphics processors and creation tools continue to improve, larger and more complex 3D models will become commonplace and help fuel new applications in immersive virtual reality (VR) and augmented reality (AR). Because of this increased model complexity, storage and bandwidth requirements are forced to keep pace with the explosion of 3D data.

With Draco, applications using 3D graphics can be significantly smaller without compromising visual fidelity. For users this means apps can now be downloaded faster, 3D graphics in the browser can load quicker, and VR and AR scenes can now be transmitted with a fraction of the bandwidth, rendered quickly and look fantastic.

What is Draco?

Draco is a library for compressing and decompressing 3D geometric meshes and point clouds. It is intended to improve the storage and transmission of 3D graphics.

Draco was designed and built for compression efficiency and speed. The code supports compressing points, connectivity information, texture coordinates, color information, normals, and any other generic attributes associated with geometry. Draco is released as C++ source code that can be used to compress 3D graphics as well as C++ and Javascript decoders for the encoded data.

What you will learn

  • How to use Draco to compress a 3D model
  • How to use different compression models and how they impact model quality and size
  • How to view a 3D model on the web

What you'll need

Clone the Github repository using this command line:

git clone https://github.com/google/draco

Navigate to the Draco root directory.

cd draco

To start with Draco encoding and decoding, let's start first by building the apps.

Build Encoder

  • Run cmake from a directory where you would like to generate build files, and pass it the path to your Draco repository.
mkdir build

cd build

cmake ../

make

draco_encoder will read OBJ or PLY files as input, and output Draco-encoded files. We have included Stanford's Bunny mesh for testing. The basic command line looks like this:

./draco_encoder -i ../testdata/bun_zipper.ply -o bunny.drc

You can now look at the size of the output file and compare to the original .ply file. The compressed file should be much smaller than the original file size.

Note: The compressed size can vary based on compression options.

At this point we will start with a basic web page to decode Draco files. We'll start by copying and pasting the following code sections into the text editor.

  1. Start with basic HTML file.
<!DOCTYPE html>
<html>
<head>
  <title>Codelab - Draco Decoder</title>
  1. The following code snippet will load the Draco WASM decoder.
  <script src="https://www.gstatic.com/draco/v1/decoders/draco_wasm_wrapper.js">
    // It is recommended to always pull your Draco JavaScript and WASM decoders
    // from the above URL. Users will benefit from having the Draco decoder in
    // cache as more sites start using the static URL.
  </script>
  1. Next add this function, that will create a Draco decoder module. The creation of the decoder module is asynchronous, so you need to wait until the callback is called before you can use the module.
  <script>
    'use strict';

    // The global Draco decoder module.
    let decoderModule = null;

    // Creates the Draco decoder module.
    function createDracoDecoderModule() {
      let dracoDecoderType = {};

      // Callback when the Draco decoder module is fully instantiated. The
      // module parameter is the created Draco decoder module.
      dracoDecoderType['onModuleLoaded'] = function(module) {
        decoderModule = module;

        // Download the Draco encoded file and decode.
        downloadEncodedMesh('bunny.drc');
      };
      DracoDecoderModule(dracoDecoderType);
    }
  1. Add the function to decode a Draco encoded mesh.
    // Decode an encoded Draco mesh. byteArray is the encoded mesh as
    // an Uint8Array.
    function decodeMesh(byteArray) {
      // Create the Draco decoder.
      const decoder = new decoderModule.Decoder();

      // Create a buffer to hold the encoded data.
      const buffer = new decoderModule.DecoderBuffer();
      buffer.Init(byteArray, byteArray.length);

      // Decode the encoded geometry.
      let outputGeometry = new decoderModule.Mesh();
      let decodingStatus = decoder.DecodeBufferToMesh(buffer, outputGeometry);

      alert('Num points = ' + outputGeometry.num_points());

      // You must explicitly delete objects created from the DracoModule
      // or Decoder.
      decoderModule.destroy(outputGeometry);
      decoderModule.destroy(decoder);
      decoderModule.destroy(buffer);
    }
  1. Now that we have the Draco decode function in place, add a function to download a Draco encoded mesh. The function ‘downloadEncodedMesh' accepts a parameter to the Draco file to be loaded. In this case it will be ‘bunny.drc' from the previous stage.
    // Download and decode the Draco encoded geometry.
    function downloadEncodedMesh(filename) {
      // Download the encoded file.
      const xhr = new XMLHttpRequest();
      xhr.open("GET", filename, true);
      xhr.responseType = "arraybuffer";
      xhr.onload = function(event) {
        const arrayBuffer = xhr.response;
        if (arrayBuffer) {
          const byteArray = new Uint8Array(arrayBuffer);
          decodeMesh(byteArray);
        }
      };
      xhr.send(null);
    }
  1. Call the ‘createDracoDecoderModule' function to create the Draco decoder module, which will call ‘downloadEncodedMesh' function to download the encoded Draco file, which will call the ‘decodeMesh' function to decode the encoded Draco mesh.
    // Create the Draco decoder module.
    createDracoDecoderModule();
  </script>
</head>
<body>
</body>
</html>
  1. Save this file as "DracoDecode.html"
  2. Start the python webserver. In the terminal type:
python -m SimpleHTTPServer

  1. Open localhost:8000/DracoDecode.html in Chrome. This should display an alert message box with the number of points (Num points = 34834) that have been decoded from the model.

Now that we know how to decode a Draco file using WASM, we'll use a popular web 3D viewer - three.js. As in the previous example we'll start by copying and pasting the following code sections into the text editor.

  1. Start with basic HTML file
<!DOCTYPE html>
<html>
<head>
  <title>Codelab - Draco three.js Render</title>
  1. Add code to load three.js and Draco three.js loader.
  <script src="https://cdn.rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script>
  <script src="https://rawgit.com/google/draco/master/javascript/example/DRACOLoader.js"></script>
  1. Setup the Draco decoder path.
  <script>
    // three.js globals.
    var camera, scene, renderer;

    // Create the Draco loader.
    var dracoLoader = new THREE.DRACOLoader();

    // Specify path to a folder containing WASM/JS decoding libraries.
    // It is recommended to always pull your Draco JavaScript and WASM decoders
    // from the below URL. Users will benefit from having the Draco decoder in
    // cache as more sites start using the static URL.
    dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
  1. Add three.js rendering code.
    function initThreejs() {
      camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 0.1, 15 );
      camera.position.set( 3, 0.25, 3 );

      scene = new THREE.Scene();
      scene.background = new THREE.Color( 0x443333 );
      scene.fog = new THREE.Fog( 0x443333, 1, 4 );

      // Ground
      var plane = new THREE.Mesh(
        new THREE.PlaneBufferGeometry( 8, 8 ),
        new THREE.MeshPhongMaterial( { color: 0x999999, specular: 0x101010 } )
      );
      plane.rotation.x = - Math.PI / 2;
      plane.position.y = 0.03;
      plane.receiveShadow = true;
      scene.add(plane);

      // Lights
      var light = new THREE.HemisphereLight( 0x443333, 0x111122 );
      scene.add( light );

      var light = new THREE.SpotLight();
      light.angle = Math.PI / 16;
      light.penumbra = 0.5;
      light.castShadow = true;
      light.position.set( - 1, 1, 1 );
      scene.add( light );

      // renderer
      renderer = new THREE.WebGLRenderer( { antialias: true } );
      renderer.setPixelRatio( window.devicePixelRatio );
      renderer.setSize( window.innerWidth, window.innerHeight );
      renderer.shadowMap.enabled = true;

      const container = document.getElementById('container');
      container.appendChild( renderer.domElement );

      window.addEventListener( 'resize', onWindowResize, false );
    }

    function onWindowResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();

      renderer.setSize( window.innerWidth, window.innerHeight );
    }

    function animate() {
      render();
      requestAnimationFrame( animate );
    }

    function render() {
      var timer = Date.now() * 0.0003;

      camera.position.x = Math.sin( timer ) * 0.5;
      camera.position.z = Math.cos( timer ) * 0.5;
      camera.lookAt( new THREE.Vector3( 0, 0.1, 0 ) );

      renderer.render( scene, camera );
    }
  1. Add Draco loading and decode code.
    function loadDracoMesh(dracoFile) {
      dracoLoader.load(dracoFile, function ( geometry ) {
        geometry.computeVertexNormals();

        var material = new THREE.MeshStandardMaterial( { vertexColors: THREE.VertexColors } );
        var mesh = new THREE.Mesh( geometry, material );
        mesh.castShadow = true;
        mesh.receiveShadow = true;
        scene.add( mesh );
      } );
    }

  1. Add code to load the file.
    window.onload = function() {
      initThreejs();
      animate();
      loadDracoMesh('bunny.drc');
    }
  </script>
</head>
<body>
  <div id="container"></div>
</body>
</html>
  1. Save this file as "DracoRender.html"
  2. If needed, restart the webserver.
python -m SimpleHTTPServer

  1. Open localhost:8000/DracoRender.html in Chrome. You should now see your Draco file rendered in the browser.

The Draco encoder allows for many different parameters that impact the size of the compressed file and visual quality of the code. Try running the next few commands in the command line and seeing the results.

  1. The following command quantizes the model's positions using 12 (default is 11) bits.
./draco_encoder -i ../testdata/bun_zipper.ply -o out12.drc -qp 12

  1. Note the size of the out12.drc compared to the bunny.drc file in the previous section. Using more quantization bits can increase the size of the compressed file.

3.The following command quantizes the model's positions using 6 bits.

./draco_encoder -i ../testdata/bun_zipper.ply -o out6.drc -qp 6

  1. Note the size of the out6.drc compared to the bunny.drc file in the previous section. Using fewer quantization bits can reduce the size of the compressed file.
  1. The following parameters impact the compression levels of the model. Using the cl flag, you can tune your compression from 1 (lowest compression ratio) to 10 (highest).
./draco_encoder -i ../testdata/bun_zipper.ply -o outLow.drc -cl 1

./draco_encoder -i ../testdata/bun_zipper.ply -o outHigh.drc -cl 10

Note the output from the Draco encoder. You can see that there is a tradeoff in the time required to compress at the highest compression levels compared to the savings in bits. The correct parameter for your application will depend on timing and size requirements at encode time.

You've finished the Draco mesh compression code lab and successfully explored many key features of Draco!

Hopefully it's clear to you how Draco can help make your 3D assets smaller and more efficient to transmit over the web. You can learn more about Draco and get the latest library from github.