Tối ưu hoá dữ liệu 3D bằng giải pháp nén Hình học Draco

1. Tổng quan

Đồ hoạ 3D là một phần cơ bản của nhiều ứng dụng, trong đó có trò chơi, thiết kế và trực quan hoá dữ liệu. Khi bộ xử lý đồ hoạ và công cụ sáng tạo tiếp tục cải tiến, các mô hình 3D lớn hơn và phức tạp hơn sẽ trở nên phổ biến và giúp thúc đẩy các ứng dụng mới trong chế độ thực tế ảo (VR) và thực tế tăng cường (AR). Do sự phức tạp của mô hình ngày càng tăng này, các yêu cầu về dung lượng lưu trữ và băng thông buộc phải bắt kịp với sự bùng nổ của dữ liệu 3D.

Với Draco, các ứng dụng sử dụng đồ hoạ 3D có thể nhỏ hơn đáng kể mà không ảnh hưởng đến độ trung thực của hình ảnh. Đối với người dùng, điều này có nghĩa là giờ đây ứng dụng có thể được tải xuống nhanh hơn, đồ hoạ 3D trong trình duyệt có thể tải nhanh hơn, ngoài ra các cảnh VR và AR hiện có thể được truyền chỉ với một phần nhỏ băng thông, được hiển thị nhanh chóng và trông tuyệt đẹp.

Draco là gì?

Draco là một thư viện dùng để nén và giải nén lưới hình học 3D và mây điểm. Trình mô phỏng này nhằm cải thiện việc lưu trữ và truyền đồ hoạ 3D.

Draco được thiết kế và xây dựng nhằm mang lại hiệu quả và tốc độ nén. Mã này hỗ trợ các điểm nén, thông tin kết nối, toạ độ hoạ tiết, thông tin màu, pháp tuyến và bất kỳ thuộc tính chung nào khác liên quan đến hình học. Draco được phát hành dưới dạng mã nguồn C++ có thể được dùng để nén đồ hoạ 3D cũng như bộ giải mã C++ và JavaScript cho dữ liệu được mã hoá.

Kiến thức bạn sẽ học được

  • Cách sử dụng Draco để nén mô hình 3D
  • Cách sử dụng các mô hình nén và tác động của các mô hình đó đến chất lượng và kích thước mô hình
  • Cách xem mô hình 3D trên web

Bạn cần có

2. Thiết lập

Sao chép kho lưu trữ GitHub bằng dòng lệnh sau:

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

Chuyển đến thư mục gốc Draco.

cd draco

3. Tạo bộ mã hoá

Để bắt đầu với phương thức mã hoá và giải mã Draco, trước tiên, hãy tạo ứng dụng.

Bộ mã hoá bản dựng

  • Chạy cmake từ một thư mục mà bạn muốn tạo tệp bản dựng và chuyển đường dẫn đến kho lưu trữ Draco của bạn.
mkdir build

cd build

cmake ../

make

4. Mã hoá thành phần 3D đầu tiên của bạn

draco_encoder sẽ đọc các tệp OBJ hoặc PLY dưới dạng các tệp được mã hoá từ Draco. Chúng tôi đã đưa lưới Bunny của Stanford vào để thử nghiệm. Dòng lệnh cơ bản có dạng như sau:

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

Giờ đây, bạn có thể xem kích thước của tệp đầu ra và so sánh với tệp .ply gốc. Tệp nén phải nhỏ hơn nhiều so với kích thước tệp gốc.

Lưu ý: Kích thước nén có thể thay đổi tuỳ theo lựa chọn nén.

5. Giải mã tệp Draco trong trình duyệt

Đến đây, chúng ta sẽ bắt đầu bằng một trang web cơ bản để giải mã các tệp Draco. Chúng ta sẽ bắt đầu bằng cách sao chép và dán các phần mã sau vào trình chỉnh sửa văn bản.

  1. Bắt đầu với tệp HTML cơ bản.
<!DOCTYPE html>
<html>
<head>
  <title>Codelab - Draco Decoder</title>
  1. Đoạn mã sau đây sẽ tải bộ giải mã 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. Tiếp theo, hãy thêm hàm này để tạo một mô-đun bộ giải mã Draco. Việc tạo mô-đun bộ giải mã là không đồng bộ, vì vậy, bạn cần phải đợi cho đến khi lệnh gọi lại được gọi trước khi có thể sử dụng mô-đun.
  <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. Thêm hàm để giải mã lưới được mã hoá 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. Bây giờ, chúng ta đã có chức năng giải mã Draco, hãy thêm một hàm để tải lưới được mã hoá Draco xuống. Hàm "downloadEncodedMesh" chấp nhận một tham số vào tệp Draco cần tải. Trong trường hợp này, tên miền sẽ là "bunny.drc" từ giai đoạn trước.
    // 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. Gọi "createDracoDecoderModule" để tạo mô-đun bộ giải mã Draco, mô-đun này sẽ gọi "downloadEncodedMesh" để tải tệp Draco đã mã hoá xuống. Tệp này sẽ gọi là "decodeMesh" để giải mã lưới Draco đã mã hoá.
    // Create the Draco decoder module.
    createDracoDecoderModule();
  </script>
</head>
<body>
</body>
</html>
  1. Lưu tệp này dưới dạng "DracoDecode.html"
  2. Khởi động máy chủ web Python. Trong loại thiết bị đầu cuối:
python -m SimpleHTTPServer

  1. Mở localhost:8000/DracoDecode.html trong Chrome. Thao tác này sẽ hiển thị một hộp thông báo cảnh báo với số điểm (Num points = 34834) đã được giải mã từ mô hình.

6. Kết xuất tệp Draco bằng ba.js

Bây giờ, chúng ta đã biết cách giải mã tệp Draco bằng WASM, chúng ta sẽ sử dụng trình xem 3D web phổ biến - ba.js. Như trong ví dụ trước, chúng ta sẽ bắt đầu bằng cách sao chép và dán các phần mã sau vào trình chỉnh sửa văn bản.

  1. Bắt đầu với tệp HTML cơ bản
<!DOCTYPE html>
<html>
<head>
  <title>Codelab - Draco three.js Render</title>
  1. Thêm mã để tải ba.js và trình tải ba.js Draco.
  <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. Thiết lập đường dẫn bộ giải mã 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. Thêm mã hiển thị ba.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. Thêm mã tải và giải mã 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. Thêm mã để tải tệp.
    window.onload = function() {
      initThreejs();
      animate();
      loadDracoMesh('bunny.drc');
    }
  </script>
</head>
<body>
  <div id="container"></div>
</body>
</html>
  1. Lưu tệp này dưới dạng "DracoRender.html"
  2. Nếu cần, hãy khởi động lại máy chủ web.
python -m SimpleHTTPServer

  1. Mở localhost:8000/DracoRender.html trong Chrome. Bây giờ, bạn sẽ thấy tệp Draco của mình hiển thị trong trình duyệt.

7. Thử các tham số mã hoá khác

Bộ mã hoá Draco cho phép nhiều tham số ảnh hưởng đến kích thước của tệp nén và chất lượng hình ảnh của mã. Hãy thử chạy một vài lệnh tiếp theo trong dòng lệnh và xem kết quả.

  1. Lệnh sau đây lượng tử hoá các vị trí của mô hình bằng cách sử dụng 12 (mặc định là 11) bit.
./draco_encoder -i ../testdata/bun_zipper.ply -o out12.drc -qp 12

  1. Lưu ý kích thước của out12.drc so với tệp thỏny.drc trong phần trước. Việc sử dụng nhiều bit lượng tử hoá có thể làm tăng kích thước của tệp nén.

3.Lệnh sau đây lượng tử hoá các vị trí của mô hình bằng cách sử dụng 6 bit.

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

  1. Lưu ý kích thước của tệp out6.drc so với tệp thỏi.drc trong phần trước. Việc sử dụng ít bit lượng tử hoá hơn có thể làm giảm kích thước của tệp nén.
  1. Các tham số sau ảnh hưởng đến mức độ nén của mô hình. Bằng cách sử dụng cờ cl, bạn có thể tinh chỉnh mức nén từ 1 (tỷ lệ nén thấp nhất) đến 10 (tỷ lệ nén cao nhất).
./draco_encoder -i ../testdata/bun_zipper.ply -o outLow.drc -cl 1

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

Ghi lại dữ liệu đầu ra từ bộ mã hoá Draco. Bạn có thể thấy rằng có sự đánh đổi về thời gian cần thiết để nén ở mức nén cao nhất so với mức tiết kiệm tính bằng bit. Tham số chính xác cho ứng dụng của bạn sẽ phụ thuộc vào các yêu cầu về thời gian và kích thước tại thời điểm mã hoá.

8. Xin chúc mừng

Bạn đã hoàn tất phòng thí nghiệm mã nén lưới Draco và khám phá thành công nhiều tính năng chính của Draco!

Hy vọng bạn đã hiểu rõ cách Draco có thể giúp giảm kích thước và truyền tải các thành phần 3D trên web một cách hiệu quả hơn. Bạn có thể tìm hiểu thêm về Draco và tải thư viện mới nhất trên github.