אופטימיזציה של נתוני תלת-ממד באמצעות דחיסה של נתוני גיאומטריה של Draco

1. סקירה כללית

גרפיקה תלת-ממדית היא חלק בסיסי בהרבה אפליקציות, כולל משחקים, עיצוב והצגת נתונים. ככל שמעבדים גרפיים וכלים ליצירת תוכן ממשיכים להשתפר, דגמים תלת-ממדיים גדולים ומורכבים יותר יהפכו לנפוצים ויעזרו להניע יישומים חדשים במציאות מדומה (VR) ובמציאות רבודה (AR) סוחפות. בגלל המורכבות הגדולה יותר של המודל, דרישות האחסון והרוחב הפס חייבות להתעדכן בהתאם לגידול המהיר בנתוני תלת-ממד.

בעזרת Draco, אפליקציות שמשתמשות בגרפיקה תלת-ממדית יכולות להיות קטנות משמעותית בלי לפגוע באיכות החזותית. למשתמשים זה אומר שאפשר להוריד אפליקציות מהר יותר, לטעון מהר יותר גרפיקה תלת-ממדית בדפדפן, ולשדר סצנות של VR ו-AR עם חלק קטן מרוחב הפס, לבצע רינדור מהיר ולקבל תוצאות מעולות.

מה זה דרייקו?

‫Draco היא ספרייה לדחיסה ולפריסה של רשתות גיאומטריות תלת-ממדיות וענני נקודות. הפורמט נועד לשפר את האחסון והשידור של גרפיקה תלת-ממדית.

‫Draco תוכנן ונבנה כדי לאפשר דחיסה יעילה ומהירה. הקוד תומך בדחיסת נקודות, בפרטי קישוריות, בקואורדינטות של טקסטורה, בפרטי צבע, בנורמלים ובכל מאפיין גנרי אחר שמשויך לגיאומטריה. ‫Draco מופץ כקוד מקור ב-C++‎ שאפשר להשתמש בו כדי לדחוס גרפיקה תלת-ממדית, וגם כמפענחים ב-C++‎ וב-JavaScript לנתונים המקודדים.

מה תלמדו

  • איך משתמשים ב-Draco כדי לדחוס מודל תלת-ממדי
  • איך משתמשים במודלים שונים של דחיסה ואיך הם משפיעים על האיכות והגודל של המודל
  • איך צופים במודל תלת-ממדי באינטרנט

הדרישות

2. תהליך ההגדרה

משכפלים את מאגר GitHub באמצעות שורת הפקודה הבאה:

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

עוברים לספרייה הראשית של Draco.

cd draco

3. בניית המקודד

כדי להתחיל לקודד ולפענח באמצעות Draco, נתחיל קודם בבניית האפליקציות.

יצירת מקודד

  • מריצים את cmake מספרייה שבה רוצים ליצור קובצי build, ומעבירים את הנתיב למאגר Draco.
mkdir build

cd build

cmake ../

make

4. קידוד נכס התלת-ממד הראשון

הכלי draco_encoder קורא קובצי OBJ או PLY כקלט, ומפיק קבצים בקידוד Draco. לצורך בדיקה, הוספנו את רשת Stanford's 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. קטע הקוד הבא יטען את מפענח ה-WASM של Draco.
  <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. בטרמינל, מקלידים:
python -m SimpleHTTPServer

  1. פותחים את localhost:8000/DracoDecode.html ב-Chrome. צריך להציג תיבת הודעה עם מספר הנקודות (Num points = 34834) שפוענחו מהמודל.

6. איך מעבדים קובץ Draco באמצעות three.js

עכשיו, אחרי שלמדנו איך לפענח קובץ Draco באמצעות WASM, נשתמש בכלי פופולרי לצפייה בתלת-ממד באינטרנט – three.js. כמו בדוגמה הקודמת, נתחיל בהעתקה והדבקה של קטעי הקוד הבאים בעורך הטקסט.

  1. מתחילים עם קובץ HTML בסיסי
<!DOCTYPE html>
<html>
<head>
  <title>Codelab - Draco three.js Render</title>
  1. מוסיפים קוד לטעינה של three.js ושל Draco three.js loader.
  <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. במקרה הצורך, מפעילים מחדש את שרת האינטרנט.
python -m SimpleHTTPServer

  1. פותחים את localhost:8000/DracoRender.html ב-Chrome. עכשיו קובץ ה-Draco אמור להופיע בדפדפן.

7. כדאי לנסות פרמטרים שונים של קידוד

מקודד Draco מאפשר להגדיר פרמטרים רבים ושונים שמשפיעים על הגודל של הקובץ הדחוס ועל האיכות החזותית של הקוד. נסו להריץ את כמה הפקודות הבאות בשורת הפקודה ולראות את התוצאות.

  1. הפקודה הבאה מבצעת קוונטיזציה של המיקומים של המודל באמצעות 12 ביטים (ברירת המחדל היא 11).
./draco_encoder -i ../testdata/bun_zipper.ply -o out12.drc -qp 12

  1. שימו לב לגודל של הקובץ out12.drc בהשוואה לקובץ bunny.drc בקטע הקודם. שימוש ביותר ביטים של קוונטיזציה יכול להגדיל את הגודל של הקובץ הדחוס.

‫3.הפקודה הבאה מבצעת קוונטיזציה של המיקומים במודל באמצעות 6 ביטים.

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

  1. שימו לב לגודל של הקובץ out6.drc בהשוואה לקובץ bunny.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. מזל טוב

סיימתם את שיעור ה-Lab בנושא דחיסת רשת של Draco, והצלחתם ללמוד על הרבה תכונות חשובות של Draco.

אנחנו מקווים שהסברנו בצורה ברורה איך Draco יכול לעזור לכם להקטין את נכסי התלת-ממד שלכם ולשפר את היעילות שלהם בהעברה באינטרנט. מידע נוסף על Draco והספרייה העדכנית זמינים ב-github.