利用 Draco 几何图形压缩功能优化 3D 数据

1. 概览

3D 图形是许多应用(包括游戏、设计和数据可视化)的基本组成部分。随着图形处理器和制作工具的不断改进,更大、更复杂的 3D 模型将会普及,并且有助于为沉浸式虚拟现实 (VR) 和增强现实 (AR) 中的新应用提供支持。由于模型复杂性的增加,存储和带宽需求被迫与 3D 数据激增的需求同步。

借助 Draco,您可以在不牺牲视觉保真度的情况下,大幅缩小使用 3D 图形的应用。对于用户而言,这意味着现在可以更快地下载应用、更快速地加载浏览器中的 3D 图形,并且只需少许带宽即可传输 VR 和 AR 场景、快速渲染和呈现出色效果。

Draco 是什么?

Draco 是一个用于压缩和解压缩 3D 几何网格点云的库。它旨在改进 3D 图形的存储和传输。

Draco 的设计宗旨是压缩效率和速度,该代码支持压缩点、连接信息、纹理坐标、颜色信息、法线以及与几何图形关联的任何其他通用属性。Draco 作为 C++ 源代码发布,可用于压缩 3D 图形以及用于编码数据的 C++ 和 JavaScript 解码器。

学习内容

  • 如何使用 Draco 压缩 3D 模型
  • 如何使用不同的压缩模型以及它们对模型质量和大小的影响
  • 如何在网上查看 3D 模型

所需条件

2. 准备工作

使用以下命令行克隆 GitHub 代码库:

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

进入 Draco 根目录。

cd draco

3. 构建编码器

我们先从 Draco 编码和解码开始,先构建应用。

Build Encoder

  • 从要生成构建文件的目录中运行 cmake,并将其传递至您的 Draco 代码库的路径。
mkdir build

cd build

cmake ../

make

4. 对首个 3D 作品进行编码

draco_encoder 会读取 OBJ 或 PLY 文件作为输入,并输出 Draco 编码的文件。我们已纳入斯坦福大学的 Bunny 网状网用于测试。基本命令行如下所示:

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

您现在可以查看输出文件的大小,并与原始 .ply 文件进行比较。压缩文件应比原始文件小得多。

注意:压缩后的大小可能会因压缩选项而异。

5. 在浏览器中解码 Draco 文件

此时,我们将从基本网页开始,用于解码 Draco 文件。首先,复制以下代码部分并将其粘贴到文本编辑器中。

  1. 从基本 HTML 文件开始。
<!DOCTYPE html>
<html>
<head>
  <title>Codelab - Draco Decoder</title>
  1. 以下代码段将加载 Draco WASM 解码器。
  <script src="https://www.gstatic.com/draco/versioned/decoders/1.4.1/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. 接下来添加此函数,将创建一个 Draco 解码器模块。解码器模块的创建是异步进行的,因此您需要等到回调调用之后才能使用该模块。
  <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. 添加函数以解码 Draco 编码网格。
    // 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. 现在,我们已经有了 Draco 解码函数,接下来添加一个函数来下载 Draco 编码网格。函数“downloadEncodedMesh”接受要加载的 Draco 文件的参数。在此示例中为“bunny.drc”从上一阶段开始。
    // 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. 先调用“createDracoDecoderModule”函数来创建 Draco 解码器模块,该模块将调用“downloadEncodedMesh”函数下载已编码的 Draco 文件,该文件将调用“decodeMesh”函数解码已编码的 Draco 网格。
    // Create the Draco decoder module.
    createDracoDecoderModule();
  </script>
</head>
<body>
</body>
</html>
  1. 将此文件另存为“DracoDecode.html”
  2. 启动 Python Web 服务器。在终端类型中:
python -m SimpleHTTPServer

  1. 在 Chrome 中打开 localhost:8000/DracoDecode.html。这应该会显示一个警告消息框,其中包含从模型中解码的点数(点数 = 34834)。

6. 使用 three.js 渲染 Draco 文件

我们已经知道如何使用 WASM 解码 Draco 文件,接下来我们将使用热门 Web 3D 查看器 - three.js。与上例一样,我们首先将以下代码部分复制并粘贴到文本编辑器中。

  1. 从基本 HTML 文件开始
<!DOCTYPE html>
<html>
<head>
  <title>Codelab - Draco three.js Render</title>
  1. 添加代码以加载 three.js 和 Draco three.js 加载器。
  <script type="importmap">
          {
            "imports": {
              "three": "https://unpkg.com/three@v0.162.0/build/three.module.js",
              "three/addons/": "https://unpkg.com/three@v0.162.0/examples/jsm/"
            }
          }
  </script>
  1. 设置 Draco 解码器路径。
  <script type="module">
    // import three.js and DRACOLoader.
    import * as THREE from 'three';
    import {DRACOLoader} from 'three/addons/loaders/DRACOLoader.js'

    // three.js globals.
    var camera, scene, renderer;

    // Create the Draco loader.
    var dracoLoader = new 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/versioned/decoders/1.4.1/');
  1. 添加 three.js 渲染代码。
    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.PlaneGeometry( 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. 添加了 Draco 加载和解码代码。
    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. 添加代码以加载文件。
    window.onload = function() {
      initThreejs();
      animate();
      loadDracoMesh('bunny.drc');
    }
  </script>
</head>
<body>
  <div id="container"></div>
</body>
</html>
  1. 将此文件另存为“DracoRender.html”
  2. 如果需要,请重启 Web 服务器。
python -m SimpleHTTPServer

  1. 在 Chrome 中打开 localhost:8000/DracoRender.html。现在,您应该会看到您的 Draco 文件已呈现在浏览器中。

7. 请尝试不同的编码参数

Draco 编码器允许使用许多不同的参数,这些参数会影响压缩文件的大小和代码的视觉质量。尝试在命令行中运行下面几个命令并查看结果。

  1. 以下命令使用 12 位(默认为 11)位量化模型的位置。
./draco_encoder -i ../testdata/bun_zipper.ply -o out12.drc -qp 12

  1. 注意与上一部分中的 bunny.drc 文件相比,out12.drc 的大小。使用更多量化位会增加压缩文件的大小。

3. 以下命令使用 6 位量化模型的位置。

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

  1. 请注意与上一部分中的 bunny.drc 文件相比,out6.drc 的大小。使用较少的量化位可以减小压缩文件的大小。
  1. 以下参数会影响模型的压缩级别。使用 cl 标记,您可以将压缩率调整为从 1(最低)到 10(最高)。
./draco_encoder -i ../testdata/bun_zipper.ply -o outLow.drc -cl 1

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

请注意 Draco 编码器的输出结果。您可以看到,与节省的位数相比,在最高压缩级别进行压缩所需的时间是有利的。您的应用的正确参数取决于编码时的时间和大小要求。

8. 恭喜

您已完成 Draco 网格压缩 Codelab,并成功探索了 Draco 的许多主要功能!

希望您清楚 Draco 如何帮助您缩小 3D 资源,并更高效地通过网络传输。您可以从 github 详细了解 Draco 并获取最新的库。