Draco 도형 압축으로 3D 데이터 최적화

1. 개요

3D 그래픽은 게임, 디자인 및 데이터 시각화를 포함하여 많은 애플리케이션의 기본적인 부분입니다. 그래픽 프로세서와 제작 도구가 계속해서 개선됨에 따라 더 크고 복잡한 3D 모델이 보편화되고 몰입형 가상 현실 (VR) 및 증강 현실 (AR)에서 새로운 응용 분야를 발전시키는 데 도움이 될 것입니다. 모델의 복잡성이 증가하면서 저장용량 및 대역폭 요구사항이 폭발적으로 증가하는 3D 데이터를 따라잡을 수밖에 없게 되었습니다.

Draco를 사용하면 3D 그래픽을 사용하는 애플리케이션을 시각적 충실도의 저하 없이 상당히 줄일 수 있습니다. 사용자의 입장에서는 이제 앱을 더 빠르게 다운로드하고, 브라우저의 3D 그래픽을 더 빠르게 로드할 수 있으며, VR 및 AR 장면을 대역폭의 일부분으로도 전송할 수 있게 되었고 속도가 빠르고 멋지게 표현될 수 있게 되었습니다.

드레이코란 무엇인가요?

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 인코딩과 디코딩부터 시작하겠습니다. 먼저 앱을 빌드해 보겠습니다.

빌드 인코더

  • 빌드 파일을 생성하려는 디렉터리에서 cmake를 실행하고 Draco 저장소 경로를 전달합니다.
mkdir build

cd build

cmake ../

make

4. 첫 3D 애셋 인코딩

draco_encoder는 OBJ 또는 PLY 파일을 입력으로 읽고 Draco로 인코딩된 파일을 출력합니다. 테스트를 위해 Stanford의 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로 인코딩된 메시를 다운로드하는 함수를 추가합니다. 'downloadEncodingMesh' 함수는 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’ 함수를 사용하여 'downloadEncodedMesh'를 호출하는 Draco 디코더 모듈을 생성합니다. 함수를 사용하여 'decodeMesh'를 호출하는 인코딩된 Draco 파일을 다운로드합니다. 함수를 사용하여 인코딩된 Draco 메시를 디코딩합니다.
    // Create the Draco decoder module.
    createDracoDecoderModule();
  </script>
</head>
<body>
</body>
</html>
  1. 이 파일을 'DracoDecode.html'로 저장합니다.
  2. Python 웹 서버를 시작합니다. 터미널 유형에서 다음을 실행합니다.
python -m SimpleHTTPServer

  1. Chrome에서 localhost:8000/DracoDecode.html을 엽니다. 그러면 모델에서 디코딩된 포인트 수 (Num points = 34834)가 포함된 알림 메시지 상자가 표시됩니다.

6. third.js를 사용하여 Draco 파일 렌더링

이제 WASM을 사용하여 Draco 파일을 디코딩하는 방법을 알게 되었으므로 널리 사용되는 웹 3D 뷰어인 third.js를 사용해 보겠습니다. 이전 예에서와 같이 먼저 다음 코드 섹션을 복사하여 텍스트 편집기에 붙여넣습니다.

  1. 기본 HTML 파일로 시작
<!DOCTYPE html>
<html>
<head>
  <title>Codelab - Draco three.js Render</title>
  1. third.js 및 Draco two.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. 3.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. 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에 관해 자세히 알아보고 최신 라이브러리를 확인하세요.