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

What you'll need

Clone the Github repository using this command line:

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

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

Build Encoder

cd path/to/draco
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. This can be done by listing the files with sizes, I.e. "ls -l"

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>

2. The following code snippet will load the Draco javascript decoder.

  <script src="https://rawgit.com/google/draco/master/javascript/draco_decoder.js"></script>

3. Next add this function, that will decode a Draco encoded mesh.

  <script>
    'use strict';


    // Decode an encoded Draco mesh. byteArray is the encoded mesh as
    // an Uint8Array.
    function decodeMesh(byteArray) {
      // Create the Draco decoder.
      let dracoDecoderType = {};
      const dracoModule = DracoModule(dracoDecoderType); 
      const decoder = new dracoModule.WebIDLWrapper();

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

      // Decode the encoded geometry.
      let outputGeometry = decoder.DecodeMeshFromBuffer(buffer);

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

      // You must explicitly delete objects created from the DracoModule
      // or Decoder.
      dracoModule.destroy(outputGeometry);
      dracoModule.destroy(decoder);
      dracoModule.destroy(buffer);
    }

4. Now that we have the Draco decode function in place, we will now load a Draco mesh, and call the function from the previous snippet to decompress our file. 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);
    }

    downloadEncodedMesh('bunny.drc');
  </script>
</head>
<body>
</body>
</html>

5. Save this file as "DracoDecode.html"

6. Start the python webserver. In the terminal type:

 python -m SimpleHTTPServer

7. Open localhost:8000/DracoDecode.html in Chrome. This should display the number of points that have been decoded from the model.

Now that we know how to decode a Draco file in Javascript, 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>
</head>

2. Add code to load three.js, Draco three.js loader, and Draco Javascript decoder.

  <script src="https://cdn.rawgit.com/mrdoob/three.js/r84/build/three.min.js"></script>
  <script src="https://rawgit.com/google/draco/master/javascript/example/DRACOLoader.js"></script>
  <script src="https://rawgit.com/google/draco/master/javascript/draco_decoder.js"></script>

3. Add three.js rendering code.

<script>
      'use strict';
      let camera, cameraTarget, scene, renderer;

      function threejsInit() {
        let container = document.createElement('div');
        document.body.appendChild(container);

        camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 15);
        camera.position.set(3, 0.15, 3);
        cameraTarget = new THREE.Vector3(0, 0, 0);

        scene = new THREE.Scene();
        scene.fog = new THREE.Fog(0x72645b, 2, 15);

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

        // Lights
        scene.add(new THREE.HemisphereLight(0x443333, 0x111122));
        addShadowedLight(1, 1, 1, 0xffffff, 1.35);
        addShadowedLight(0.5, 1, -1, 0xffaa00, 1);

        // renderer
        renderer = new THREE.WebGLRenderer({antialias: true});
        renderer.setClearColor(scene.fog.color);
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        container.appendChild(renderer.domElement);
        window.addEventListener('resize', onWindowResize, false);
      }

      function addShadowedLight(x, y, z, color, intensity) {
        const directionalLight = new THREE.DirectionalLight(color, intensity);
        directionalLight.position.set(x, y, z);
        scene.add(directionalLight);
      }

      function resizeGeometry(bufferGeometry, material) {
        let geometry;
        // Point cloud does not have face indices.
        if (bufferGeometry.index == null) {
          geometry = new THREE.Points(bufferGeometry, material);
        } else {
          bufferGeometry.computeVertexNormals();
          geometry = new THREE.Mesh(bufferGeometry, material);
        }
        // Compute range of the geometry coordinates for proper rendering.
        bufferGeometry.computeBoundingBox();
        const sizeX = bufferGeometry.boundingBox.max.x - bufferGeometry.boundingBox.min.x;
        const sizeY = bufferGeometry.boundingBox.max.y - bufferGeometry.boundingBox.min.y;
        const sizeZ = bufferGeometry.boundingBox.max.z - bufferGeometry.boundingBox.min.z;
        const diagonalSize = Math.sqrt(sizeX * sizeX + sizeY * sizeY + sizeZ * sizeZ);
        const scale = 1.0 / diagonalSize;
        const midX =
          (bufferGeometry.boundingBox.min.x + bufferGeometry.boundingBox.max.x) / 2;
        const midY =
          (bufferGeometry.boundingBox.min.y + bufferGeometry.boundingBox.max.y) / 2;
        const midZ =
          (bufferGeometry.boundingBox.min.z + bufferGeometry.boundingBox.max.z) / 2;

        geometry.scale.multiplyScalar(scale);
        geometry.position.x = -midX * scale;
        geometry.position.y = -midY * scale;
        geometry.position.z = -midZ * scale;
        geometry.castShadow = true;
        geometry.receiveShadow = true;
        return geometry;
      }

      function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      }

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

      function render() {
        const timer = Date.now() * 0.0005;
        camera.position.x = Math.sin(timer) * 2.5;
        camera.position.z = Math.cos(timer) * 2.5;
        camera.lookAt(cameraTarget);
        renderer.render(scene, camera);
      }

4. Add Draco decode code.

      // Global Draco decoder type.
      let dracoDecoderType = {};
      let dracoLoader;
      let currentDecoder;

      function createDracoDecoder() {
        dracoLoader = new THREE.DRACOLoader();
        dracoLoader.setDracoDecoderType(dracoDecoderType);
      }
      
      // bufferGeometry is a geometry decoded by DRACOLoader.js
      function onDecode(bufferGeometry) {
        const material = new THREE.MeshStandardMaterial({vertexColors: THREE.VertexColors});
        
        const geometry = resizeGeometry(bufferGeometry, material);

        const selectedObject = scene.getObjectByName("my_mesh");
        scene.remove(selectedObject);
        geometry.name = "my_mesh";
        scene.add(geometry);
      }

      createDracoDecoder();

5. Add code to load the file.

      window.onload = function() {
        const fileInput = document.getElementById('fileInput');
        fileInput.onclick = function() {
          this.value = '';
        }

        fileInput.addEventListener('change', function(e) {
          const file = fileInput.files[0];

          const reader = new FileReader();
          reader.onload = function(e) {
            // Enable logging to console output.
            dracoLoader.setVerbosity(1);
            dracoLoader.decodeDracoFile(reader.result, onDecode);
          }
          reader.readAsArrayBuffer(file);
        });

        threejsInit();
        threejsAnimate();
      }
    </script>
  </head>
  <body>
  <div id="page-wrapper">
    <h1>Open a draco compressed file (.drc):</h1>
      <div>
         <input type="file" id="fileInput">
      </div>
  </div>
  </body>
</html>

6. Save this file as "DracoRender.html"

7. If needed, restart the webserver.

 python -m SimpleHTTPServer

8. Open localhost:8000/DracoRender.html in Chrome. Now in Chrome, click the open file button and select the bunny.drc file to load. 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 bits.

./draco_encoder -i testdata/bun_zipper.ply -o out12.drc -qp 12

2. Note the size of the out12.drc compared to the bunny.drc file in the previous section. Using fewer quantization bits can reduce the size of the compressed file. Open localhost:8000/DracoRender.html in Chrome, and open the out12.drc file. You should notice that the visual quality of the model is almost identical to the previously loaded model.

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

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

4. 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. Open localhost:8000/DracoRender.html in Chrome, and open the out6.drc file. You should notice that the visual quality is vastly different than the original. Quantizing with too few bits, while resulting in impressive compression ratios, can seriously degrade the quality of the model.

5. 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.

Download the Code

Click the following link to download the 3D assets for this section. Unzip the contents in the root webserver. If a folder is created during the unzip, take the contents out of the folder and put them at the root of the project.

Download Models and textures

1. Copy DracoRender.html to DracoRenderTexture.html.

2. Replace onDecode function with this code:

function onDecode(bufferGeometry) {
        const textureLoader = new THREE.TextureLoader();
        textureLoader.load('wooden_dragon.png', function(dragonTexture) {
          const material = new THREE.MeshBasicMaterial({map : dragonTexture});

          const geometry = resizeGeometry(bufferGeometry, material);
          
          const selectedObject = scene.getObjectByName("my_mesh");
          scene.remove(selectedObject);
          geometry.name = "my_mesh";
          scene.add(geometry);
       });
      }

3. Save this file

4. Make sure webserver is running:

 python -m SimpleHTTPServer

5. Open localhost:8000/DracoRenderTexture.html in Chrome. Now in Chrome, click the open file button and select the wooden_dragon.drc file to load. You should now see your Draco file rendered in the browser.

Model licensed under Creative Commons Library https://creativecommons.org/licenses/by/4.0/

Model from jschmidtcreaform

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.