WebGL Overlay View를 사용한 3D 지도 환경 빌드

1. 시작하기 전에

이 Codelab에서는 Maps JavaScript API의 WebGL 기반 기능을 사용하여 3차원에서 벡터 지도를 제어하고 렌더링하는 방법을 배울 수 있습니다.

최종 3D 핀

사전 준비 사항

이 Codelab에서는 자바스크립트 및 Maps JavaScript API를 어느 정도 알고 있다고 가정합니다. Maps JS API 사용의 기본 사항을 알아보려면 웹사이트에 지도 추가하기 (자바스크립트) Codelab을 참고하세요.

과정 내용

  • 자바스크립트용 벡터 맵을 사용하여 지도 ID를 생성합니다.
  • 프로그래매틱 방식으로 틸트 및 회전으로 지도 제어하기
  • WebGLOverlayViewThree.js를 사용하여 지도에 3D 객체를 렌더링합니다.
  • moveCamera으로 카메라 이동에 애니메이션을 적용합니다.

필요한 사항

  • 결제가 사용 설정된 Google Cloud Platform 계정
  • Maps JavaScript API가 사용 설정된 Google Maps Platform API 키
  • 자바스크립트, HTML 및 CSS에 대한 기본 지식
  • 원하는 텍스트 편집기 또는 IDE
  • Node.js

2. 설정하기

아래의 사용 설정 단계에서 Maps JavaScript API를 사용 설정해야 합니다.

Google Maps Platform 설정

Google Cloud Platform 계정 및 결제가 사용 설정된 프로젝트가 없는 경우 Google Maps Platform 시작하기 가이드를 참고하여 결제 계정 및 프로젝트를 만듭니다.

  1. Cloud Console에서 프로젝트 드롭다운 메뉴를 클릭하고 이 Codelab에 사용할 프로젝트를 선택합니다.

  1. Google Cloud Marketplace에서 이 Codelab에 필요한 Google Maps Platform API 및 SDK를 사용 설정합니다. 이 동영상 또는 이 문서의 단계를 따릅니다.
  2. Cloud Console의 Credentials 페이지에서 API 키를 생성합니다. 이 동영상 또는 이 문서의 단계를 따릅니다. Google Maps Platform에 대한 모든 요청은 API 키를 필요로 합니다.

Node.js 설정

아직 설치하지 않았다면 https://nodejs.org/로 이동하여 컴퓨터에 Node.js 런타임을 다운로드하고 설치합니다.

Node.js는 이 Codelab의 종속 항목을 설치할 때 필요한 패키지 관리자인 NPM과 함께 제공됩니다.

프로젝트 스타터 템플릿 다운로드

이 Codelab을 시작하기 전에 다음을 수행하여 스타터 프로젝트 템플릿과 더불어 전체 솔루션 코드를 다운로드합니다.

  1. https://github.com/googlecodelabs/maps-platform-101-webgl/에서 이 Codelab용 GitHub 저장소를 다운로드하거나 포크합니다. 스타터 프로젝트는 /starter 디렉터리에 있으며, Codelab을 완료하는 데 필요한 기본 파일 구조를 포함합니다. 작업해야 하는 모든 항목은 /starter/src 디렉터리에 있습니다.
  2. 스타터 프로젝트를 다운로드한 후 /starter 디렉터리에서 npm install을 실행합니다. 이렇게 하면 package.json에 나열된 모든 필수 종속 항목이 설치됩니다.
  3. 종속 항목이 설치된 후에는 디렉터리에서 npm start를 실행합니다.

스타터 프로젝트는 로컬로 작성하는 코드를 컴파일하고 실행하는 webpack-dev-server를 사용하도록 설정되었습니다. 또한 webpack-dev-server는 개발자가 코드를 변경할 때마다 브라우저에서 앱을 자동으로 다시 로드합니다.

실행 중인 전체 솔루션 코드를 확인하려면 /solution 디렉터리에서 위의 설정 단계를 완료하면 됩니다.

API 키 추가하기

시작 앱에는 JS API 로더를 사용하여 지도를 로드하는 데 필요한 모든 코드가 포함되어 있으므로 API 키와 지도 ID만 제공하면 됩니다. JS API 로더는 HTML 템플릿에서 script 태그로 지도 JS API 인라인을 로드하는 기존 메서드를 추상화하는 간단한 라이브러리로, 이를 사용하여 자바스크립트 코드로 모든 작업을 처리할 수 있습니다.

API 키를 추가하려면 시작 프로젝트에서 다음 단계를 따르세요.

  1. app.js를 여세요.
  2. apiOptions 객체에서 API 키를 apiOptions.apiKey 값으로 설정합니다.

3. 지도 ID 생성 및 사용하기

Maps JavaScript API의 WebGL 기반 기능을 사용하려면 벡터 지도를 사용 설정한 지도 ID가 필요합니다.

지도 ID 생성하기

지도 ID 생성기

  1. Google Cloud Console에서 'Google Maps Platform' > '지도 관리'로 이동합니다.
  2. '새 지도 ID 만들기'를 클릭합니다.
  3. '지도 이름' 입력란에 지도 ID의 이름을 입력합니다.
  4. '지도 유형' 드롭다운에서 '자바스크립트'를 선택합니다. '자바스크립트 옵션'이 표시됩니다.
  5. '자바스크립트 옵션'에서 '벡터' 라디오 버튼, '기울기' 체크박스, '회전' 체크박스를 선택합니다.
  6. 선택사항. '설명' 필드에 API 키의 설명을 입력합니다.
  7. '다음' 버튼을 클릭합니다. '지도 ID 세부정보' 페이지가 나타납니다.

    지도 세부정보 페이지
  8. 지도 ID를 복사합니다. 다음 단계에서 지도를 로드합니다.

지도 ID 사용하기

벡터 지도를 로드하려면 지도를 인스턴스화할 때 옵션에서 지도 ID를 속성으로 제공해야 합니다. 원하는 경우 Maps JavaScript API를 로드할 때 동일한 지도 ID를 제공할 수도 있습니다.

지도 ID로 지도를 로드하려면 다음 단계를 따르세요.

  1. 지도 ID를 mapOptions.mapId 값으로 설정합니다.

    지도를 인스턴스화할 때 지도 ID를 제공하면 Google Maps Platform에서 특정 지도에 어떤 지도를 로드할지 알려줍니다. 동일한 앱 내 여러 앱에서 같은 지도 ID를 재사용하거나 여러 뷰를 재사용할 수 있습니다.
    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    };
    

브라우저에서 실행 중인 앱을 확인합니다. 기울이기 및 회전을 사용 설정한 벡터 지도가 성공적으로 로드됩니다. 기울이기 및 회전이 사용 설정되어 있는지 확인하려면 Shift 키를 누른 상태에서 마우스로 드래그하거나 키보드의 화살표 키를 사용하세요.

지도가 로드되지 않는 경우 apiOptions에 유효한 API 키를 제공했는지 확인하세요. 지도가 기울어지고 회전되지 않으면 apiOptionsmapOptions에서 기울기 및 회전을 사용 설정한 지도 ID를 제공했는지 확인하세요.

지도 기울이기

이제 app.js 파일이 다음과 같이 표시됩니다.

    import { Loader } from '@googlemaps/js-api-loader';

    const apiOptions = {
      "apiKey": 'YOUR_API_KEY',
      "version": "beta"
    };

    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    }

    async function initMap() {
      const mapDiv = document.getElementById("map");
      const apiLoader = new Loader(apiOptions);
      await apiLoader.load();
      return new google.maps.Map(mapDiv, mapOptions);
    }

    function initWebGLOverlayView (map) {
      let scene, renderer, camera, loader;
      // WebGLOverlayView code goes here
    }

    (async () => {
      const map = await initMap();
    })();

4. WebGLOverlayView 구현

WebGLOverlayView을 사용하면 벡터 기본 맵을 렌더링하는 데 사용되는 동일한 WebGL 렌더링 컨텍스트에 직접 액세스할 수 있습니다. 즉, WebGL 및 인기 WebGL 기반 그래픽 라이브러리를 사용하여 2D 및 3D 객체를 지도에서 직접 렌더링할 수 있습니다.

WebGLOverlayView는 사용할 수 있는 지도의 WebGL 렌더링 컨텍스트에 5개의 후크를 노출합니다. 다음은 각 후크의 간단한 설명입니다.

  • onAdd(): WebGLOverlayView 인스턴스에서 setMap을 호출하여 오버레이가 지도에 추가될 때 호출됩니다. 여기에서 WebGL 컨텍스트에 직접 액세스할 필요가 없는 WebGL과 관련된 모든 작업을 해야 합니다.
  • onContextRestored(): WebGL 컨텍스트가 사용 가능하지만 무언가가 렌더링되기 전에 호출됩니다. 여기에서 객체를 초기화하고 상태를 결합하고 WebGL 컨텍스트에 액세스해야 하지만 onDraw() 호출 외부에서 실행될 수 있는 다른 모든 작업을 해야 합니다. 이를 통해 이미 GPU를 많이 사용하는 지도의 실제 렌더링에 과도한 오버헤드를 추가하지 않고 필요한 모든 항목을 설정할 수 있습니다.
  • onDraw(): WebGL이 지도 렌더링을 요청하고 요청한 모든 작업이 시작되면 프레임당 한 번 호출됩니다. 지도 렌더링에 성능 문제가 발생하지 않도록 onDraw()에서 가능한 한 적은 작업을 실행해야 합니다.
  • onContextLost(): 어떠한 이유로든 WebGL 렌더링 컨텍스트가 손실되면 호출됩니다.
  • onRemove(): WebGLOverlayView 인스턴스에서 setMap(null)을 호출하여 오버레이가 지도에서 삭제될 때 호출됩니다.

이 단계에서는 WebGLOverlayView의 인스턴스를 만들고 세 가지 수명 주기 후크인 onAdd, onContextRestored, onDraw를 구현합니다. 깔끔하고 쉽게 추적할 수 있도록 이 Codelab의 시작 템플릿에 제공된 initWebGLOverlayView() 함수에서 오버레이의 모든 코드가 처리됩니다.

  1. WebGLOverlayView() 인스턴스를 생성합니다.

    오버레이는 google.maps.WebGLOverlayView의 Maps JS API에서 제공합니다. 시작하려면 initWebGLOverlayView()에 대기 상태를 만들어 인스턴스를 만드세요.
    const webGLOverlayView = new google.maps.WebGLOverlayView();
    
  2. 수명 주기 후크를 구현합니다.

    수명 주기 후크를 구현하려면 다음을 initWebGLOverlayView()에 추가하세요.
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, coordinateTransformer}) => {};
    
  3. 지도에 오버레이 인스턴스를 추가합니다.

    이제 오버레이 인스턴스에서 setMap()을 호출하고 initWebGLOverlayView()에 다음을 추가하여 지도를 전달합니다.
    webGLOverlayView.setMap(map)
    
  4. initWebGLOverlayView 호출

    마지막 단계는 app.js의 하단에 즉시 호출된 함수에 다음을 추가하여 initWebGLOverlayView()를 실행하는 것입니다.
    initWebGLOverlayView(map);
    

이제 initWebGLOverlayView 및 즉시 호출되는 함수는 다음과 같습니다.

    async function initWebGLOverlayView (map) {
      let scene, renderer, camera, loader;
      const webGLOverlayView = new google.maps.WebGLOverlayView();

      webGLOverlayView.onAdd = () => {}
      webGLOverlayView.onContextRestored = ({gl}) => {}
      webGLOverlayView.onDraw = ({gl, coordinateTransformer}) => {}
      webGLOverlayView.setMap(map);
    }

    (async () => {
      const map = await initMap();
      initWebGLOverlayView(map);
    })();

이것으로 WebGLOverlayView를 구현하기만 하면 됩니다. 다음으로, Three.js를 사용하여 지도에 3D 객체를 렌더링하는 데 필요한 모든 것을 설정해 보겠습니다.

5. three.js 장면 설정

WebGL은 모든 객체의 직접 측면을 정의한 후 일부 객체를 정의해야 하므로 매우 복잡할 수 있습니다. 훨씬 더 쉽게 할 수 있도록 이 Codelab에서는 WebGL 위에 단순화된 추상화 레이어를 제공하는 Graphics.js를 사용합니다. Three.js는 WebGL 렌더기 만들기부터 일반적인 2D 및 3D 객체 도형 그리기, 카메라, 객체 변환 등에 이르기까지 다양한 작업을 처리하는 다양한 편의 기능을 제공합니다.

Three.js에는 항목을 표시하는 데 필요한 세 가지 기본 객체 유형이 있습니다.

  • 장면: 모든 객체, 광원, 질감 등이 렌더링되고 표시되는 '컨테이너'입니다.
  • 카메라: 장면의 관점을 나타내는 카메라입니다. 여러 카메라 유형을 사용할 수 있으며 장면 하나에 카메라를 하나 이상 추가할 수 있습니다.
  • 렌더기: 장면에 있는 모든 객체의 처리와 표시를 처리하는 렌더러입니다. Three.js에서는 WebGLRenderer이 가장 흔히 사용되지만 클라이언트가 WebGL을 지원하지 않는 경우 대체로 사용할 수 있는 몇 가지 도구도 있습니다.

이 단계에서는 Three.js에 필요한 모든 종속 항목을 로드하고 기본 장면을 설정합니다.

  1. 3.js 로드

    이 Codelab에는 3.js 라이브러리와 GLTF 로더라는 2개의 종속 항목이 필요합니다. GLTF 로더는 GL 트레이싱 형식 (gLTF)를 참고하세요. Three.js는 다양한 3D 객체 형식에 대한 특수 로더를 제공하지만 gLTF를 사용하는 것이 좋습니다.

    아래 코드에서는 Three.js 라이브러리 전체를 가져옵니다. 프로덕션 앱에서는 필요한 클래스만 가져와야 할 수 있지만 이 Codelab에서는 라이브러리 전체를 가져와 작업을 단순하게 유지합니다. 또한 GLTF 로더는 기본 라이브러리에 포함되지 않으며 종속 항목의 별도 경로에서 가져와야 합니다. 3.js에서 제공하는 모든 로더에 액세스할 수 있는 경로입니다.

    Three.js 및 GLTF 로더를 가져오려면 app.js 상단에 다음을 추가하세요.
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. three.js 장면을 만듭니다.

    장면을 만들려면 onAdd 후크에 다음을 추가하여 Three.js Scene 클래스를 인스턴스화합니다.
    scene = new THREE.Scene();
    
  3. 장면에 카메라를 추가합니다.

    앞서 언급했듯이 카메라는 장면의 시야를 나타내며 Three.js가 장면 속 객체의 시각적 렌더링을 처리하는 방법을 결정합니다. 카메라가 없으면 장면이 사실상 '표시'되지 않습니다. 즉, 객체가 렌더되지 않으므로 표시되지 않습니다.

    Three.js는 관점, 깊이 같은 측면에서 렌더기가 객체를 처리하는 방식에 영향을 미치는 다양한 카메라를 제공합니다. 이 장면에서는 사람이 눈을 장면으로 인식하는 방식을 에뮬레이션하도록 설계된 Three.js에서 가장 일반적으로 사용되는 카메라 유형인 PerspectiveCamera를 사용합니다. 즉, 카메라에서 멀리 있는 객체는 가까이 있는 객체보다 작게 표시되고 장면에 사라진 지점이 있습니다.

    장면에 원근형 카메라를 추가하려면 onAdd 후크에 다음을 추가합니다.
    camera = new THREE.PerspectiveCamera();
    
    PerspectiveCamera를 사용하면 근거리 및 원거리, 가로세로 비율, 시야를 비롯한 시점을 구성하는 속성을 구성할 수도 있습니다. 3D로 작업할 때 염두에 두어야 하는 중요한 개념인 시청각 절단기를 구성하는 속성들이 합쳐져 있지만 이 Codelab에서는 다루지 않습니다. 기본 PerspectiveCamera 구성만으로도 충분합니다.
  4. 장면에 광원을 추가합니다.

    기본적으로 Three.js 장면에서 렌더링된 객체는 적용된 텍스처와 관계없이 검은색으로 표시됩니다. 이는 Three.js 장면이 객체가 실제 광원에서 모방되는 방식을 모방하기 때문이며, 여기서 색상의 가시성은 사물을 반사하는 빛에 따라 달라집니다. 즉, 조명이 없고 색상도 없습니다.

    Three.js는 다음과 같은 두 가지 광원 유형을 제공합니다.

  5. AmbientLight: 세트의 모든 객체를 모든 각도에서 균일하게 조명하는 디퓨즈 광원을 제공합니다. 이렇게 하면 모든 객체에 있는 텍스처가 표시되도록 장면에 기준의 빛을 줄 수 있습니다.
  6. DirectionalLight: 장면의 방향에서 발생하는 빛을 제공합니다. 위치된 빛이 실제로 작동하는 방식과 달리 DirectionalLight에서 방출되는 광선은 모두 평행하며 광원에서 멀어질수록 확산되지 않습니다.

    각 광원의 색상과 강도를 구성하여 광원 효과를 집계할 수 있습니다. 예를 들어 아래 코드에서 주변광은 전체 장면에 부드러운 흰색 광원을 제공하고 방향 광원은 하향 각도로 객체를 던지는 보조 광원을 제공합니다. 방향 광원에서 각도는 position.set(x, y ,z)를 사용하여 설정되며 각 값은 해당 축을 기준으로 합니다. 예를 들어 position.set(0,1,0)는 y축의 수직 아래쪽에 수직으로 향하는 빛을 배치합니다.

    장면에 광원을 추가하려면 onAdd 후크에 다음을 추가합니다.
    const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 );
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
    directionalLight.position.set(0.5, -1, 0.5);
    scene.add(directionalLight);
    

이제 onAdd 후크가 다음과 같이 표시됩니다.

    webGLOverlayView.onAdd = () => {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera();
      const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 );
      scene.add(ambientLight);
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
      directionalLight.position.set(0.5, -1, 0.5);
      scene.add(directionalLight);
    }

이제 장면이 설정되었으며 렌더링할 준비가 되었습니다. 그런 다음 WebGL 렌더러를 구성하고 장면을 렌더링합니다.

6. 장면 렌더링

장면을 렌더링할 시간입니다. 지금까지 Three.js로 만든 모든 코드는 코드에서 초기화되었지만 아직 WebGL 렌더링 컨텍스트로 렌더링되지 않았기 때문에 기본적으로 존재하지 않습니다. WebGL은 캔버스 API를 사용하여 브라우저에서 2D 및 3D 콘텐츠를 렌더링합니다. 이전에 캔버스 API를 사용한 적이 있다면 모든 것이 렌더링되는 HTML 캔버스의 context에 익숙할 것입니다. 여러분이 모를 수 있는 점은 이 인터페이스가 브라우저에서 WebGLRenderingContext API를 통해 OpenGL 그래픽 렌더링 컨텍스트를 노출하는 인터페이스입니다.

WebGL 렌더기를 더 쉽게 처리할 수 있도록 Three.js는 WebGL 3.js가 브라우저에서 장면을 렌더링할 수 있도록 WebGL 렌더링 컨텍스트를 비교적 쉽게 구성할 수 있는 래퍼인 WebGLRenderer를 제공합니다. 하지만 지도의 경우 브라우저에서 Three.js 장면을 렌더링하기만 해도 충분하지 않습니다. Three.js는 지도와 동일한 렌더링 컨텍스트로 렌더링되어야 하며, 그러면 Three.js 장면의 지도와 객체 모두 동일한 월드 공간으로 렌더링됩니다. 따라서 렌더러는 지도의 객체와 장면의 객체(예: 오클루전) 간의 상호작용을 처리할 수 있습니다. 즉, 객체가 객체를 숨겨 보이지 않게 하는 멋진 기능입니다.

꽤 복잡한 것 같죠? 다행히 Three.js가 다시 사용됩니다.

  1. WebGL 렌더기를 설정합니다.

    Three.js WebGLRenderer의 새 인스턴스를 만들 때 장면을 렌더링할 특정 WebGL 렌더링 컨텍스트를 제공할 수 있습니다. onContextRestored 후크에 전달되는 gl 인수를 기억합니다. 이 gl 객체는 지도의 WebGL 렌더링 컨텍스트입니다. WebGLRenderer 인스턴스에 컨텍스트, 캔버스, 속성을 제공하기만 하면 됩니다. 전부 gl 객체를 통해 사용할 수 있습니다. 이 코드에서 렌더기의 autoClear 속성도 false로 설정되어 렌더기가 프레임마다 출력을 지우지 않습니다.

    렌더기를 구성하려면 onContextRestored 후크에 다음을 추가합니다.
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. 장면을 렌더링합니다.

    렌더기가 구성되면 WebGLOverlayView 인스턴스에서 requestRedraw를 호출하여 다음 프레임이 렌더링될 때 다시 그려야 한다고 오버레이에 알리고 렌더기에서 render를 호출합니다. Three.js 장면과 카메라를 전달하여 렌더링합니다. 마지막으로 WebGL 렌더링 컨텍스트의 상태를 지웁니다. 이는 WebGL Overlay View를 사용할 때 공유 GL 상태를 사용하므로 GL 상태 충돌을 방지하기 위한 중요한 단계입니다. 모든 그리기 호출에서 상태가 재설정되지 않으면 GL 상태 충돌로 인해 렌더기가 실패할 수 있습니다.

    이렇게 하려면 각 프레임에서 실행되도록 onDraw 후크에 다음을 추가합니다.
    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);
    renderer.resetState();
    

이제 onContextRestoredonDraw 후크가 다음과 같이 표시됩니다.

    webGLOverlayView.onContextRestored = ({gl}) => {
      renderer = new THREE.WebGLRenderer({
        canvas: gl.canvas,
        context: gl,
        ...gl.getContextAttributes(),
      });

      renderer.autoClear = false;
    }

    webGLOverlayView.onDraw = ({gl, transformer}) => {
      webGLOverlayView.requestRedraw();
      renderer.render(scene, camera);
      renderer.resetState();
    }

7. 지도에 3D 모델 렌더링

자, 이제 모든 준비를 완료했습니다. WebGl Overlay View를 설정하고 Three.js 장면을 생성했지만, 문제가 없습니다. 이제 장면에서 3D 객체를 렌더링할 차례입니다. 이를 위해 이전에 가져온 GLTF 로더를 사용합니다.

3D 모델은 다양한 형식이 있지만 Three.js의 경우 gLTF 형식이 크기 및 런타임 성능으로 인해 선호되는 형식입니다. 이 Codelab에서는 장면에서 렌더링할 수 있는 모델이 이미 /src/pin.gltf에 제공되어 있습니다.

  1. 모델 로더 인스턴스를 만듭니다.

    다음을 onAdd에 추가합니다.
    loader = new GLTFLoader();
    
  2. 3D 모델을 로드합니다.

    모델 로더는 비동기식이며 모델이 완전히 로드된 후 콜백을 실행합니다. pin.gltf를 로드하려면 다음을 onAdd에 추가합니다.
    const source = "pin.gltf";
    loader.load(
      source,
      gltf => {}
    );
    
  3. 장면에 모델을 추가합니다.

    이제 loader 콜백에 다음을 추가하여 모델을 장면에 추가할 수 있습니다. gltf가 아닌 gltf.scene에 추가 중입니다.
    scene.add(gltf.scene);
    
  4. 카메라 프로젝션 매트릭스를 구성합니다.

    마지막으로 모델이 지도에서 올바르게 렌더링되도록 하려면 Three.js 장면에서 카메라의 투영 매트릭스를 설정해야 합니다. 투영 매트릭스는 Three.js Matrix4 배열로 지정되며, 3D 공간의 회전, 회전, 기울기, 배율 등의 점과 그 지점을 정의합니다.

    WebGLOverlayView의 경우 투영 매트릭스를 사용하여 기본 지도를 기준으로 Three.js 장면을 렌더링하는 위치와 방법을 렌더기에 알려줍니다. 하지만 문제가 발생했습니다. 지도상의 위치는 위도 및 경도 좌표 쌍으로 지정되지만, Three.js 장면의 위치는 Vector3 좌표입니다. 짐작했겠지만 두 시스템 간의 전환 계산은 간단하지 않습니다. 이 문제를 해결하기 위해 WebGLOverlayView에서 coordinateTransformer 객체를 fromLatLngAltitude라는 함수가 포함된 OnDraw 수명 주기 후크에 전달합니다. fromLatLngAltitudeLatLngAltitude 또는 LatLngAltitudeLiteral 객체(선택적으로 장면의 변환을 정의하는 인수 집합)를 가져온 후 모델 뷰 프로젝션(MVP) 매트릭스에 포함됩니다. 참고하세요. 지도에서 Three.js 장면을 렌더링하고자 하는 위치와 변환 방법을 지정하기만 하면 됩니다. 나머지는 WebGLOverlayView에서 처리합니다. 그런 다음 MVP 매트릭스를 Three.js Matrix4 배열로 변환하고 카메라 프로젝션 매트릭스를 3.js로 설정할 수 있습니다.

    아래 코드에서 두 번째 인수는 WebGl Overlay View에 지면에서 120m 높이의 Three.js 장면의 고도를 설정하도록 알려주므로 모델이 플로팅으로 표시됩니다.

    카메라 프로젝션 매트릭스를 설정하려면 onDraw 후크에 다음을 추가합니다.
    const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 120
    }
    const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
    camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
    
  5. 모델을 변환합니다.

    핀이 지도에 수직으로 배치되지 않은 것을 확인할 수 있습니다. 3D 그래픽에서 방향을 결정하는 세계 공간은 자체 x, y, z축뿐만 아니라 독립적인 축 집합도 있는 자체 객체 공간도 있습니다.

    이 모델의 경우 일반적으로 Y축을 향하는 핀의 '상단'으로 간주하는 객체가 생성되지 않았으므로 객체는 방향을 변경하도록 해야 합니다. rotation.set를 호출하여 세계 공간을 기준으로 원하는 방법 Three.js에서 회전은 도 단위가 아닌 라디안으로 지정됩니다. 일반적으로 도 단위로 생각하기가 더 쉬우므로 degrees * Math.PI/180 수식을 사용하여 적절한 변환을 해야 합니다.

    또한 모델은 크기가 너무 작아 scale.set(x, y ,z)를 호출하여 모든 축에 균등하게 크기를 조정합니다.

    모델을 회전하고 확장하려면 onAddloader 콜백에 scene.add(gltf.scene) 앞에 다음을 추가하여 장면에 gLTF를 추가합니다.
    gltf.scene.scale.set(25,25,25);
    gltf.scene.rotation.x = 180 * Math.PI/180;
    

이제 핀이 지도를 기준으로 똑바로 표시됩니다.

직립 핀

이제 onAddonDraw 후크가 다음과 같이 표시됩니다.

    webGLOverlayView.onAdd = () => {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera();
      const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 ); // soft white light
      scene.add( ambientLight );
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
      directionalLight.position.set(0.5, -1, 0.5);
      scene.add(directionalLight);

      loader = new GLTFLoader();
      const source = 'pin.gltf';
      loader.load(
        source,
        gltf => {
          gltf.scene.scale.set(25,25,25);
          gltf.scene.rotation.x = 180 * Math.PI/180;
          scene.add(gltf.scene);
        }
      );
    }

    webGLOverlayView.onDraw = ({gl, transformer}) => {
      const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 100
      }

      const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
      camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);

      webGLOverlayView.requestRedraw();
      renderer.render(scene, camera);
      renderer.resetState();
    }

다음 단계는 카메라 애니메이션입니다.

8. 카메라 애니메이션

이제 지도에서 모델을 렌더링했고 모든 항목을 3차원으로 이동할 수 있게 되었습니다. 이제 다음 작업은 프로그래밍 방식으로 해당 이동을 제어하는 것입니다. moveCamera 함수를 사용하면 지도의 중심, 확대/축소, 기울이기, 방향 속성을 동시에 설정하여 사용자 환경을 세밀하게 제어할 수 있습니다. 또한 애니메이션 루프에서 moveCamera을 호출하여 초당 약 60프레임의 프레임으로 유동적인 전환을 생성할 수 있습니다.

  1. 모델이 로드될 때까지 기다립니다.

    원활한 사용자 환경을 만들려면 gLTF 모델이 로드될 때까지 카메라 이동을 시작하는 것이 좋습니다. 이렇게 하려면 로더의 onLoad 이벤트 핸들러를 onContextRestored 후크에 추가합니다.
    loader.manager.onLoad = () => {}
    
  2. 애니메이션 루프를 만듭니다.

    애니메이션 루프를 만드는 방법에는 setInterval 또는 requestAnimationFrame를 사용하는 두 가지 방법이 있습니다. 이 경우 Three.js 렌더러의 setAnimationLoop 함수를 사용합니다. 3.js가 새 프레임을 렌더링할 때마다 콜백에서 선언하는 코드를 자동으로 호출합니다. 애니메이션 루프를 만들려면 이전 단계의 onLoad 이벤트 핸들러에 다음을 추가하세요.
    renderer.setAnimationLoop(() => {});
    
  3. 애니메이션 루프에서 카메라 위치를 설정합니다.

    그런 다음 moveCamera를 호출하여 지도를 업데이트합니다. 여기서 지도를 로드하는 데 사용된 mapOptions 객체의 속성이 카메라 위치를 정의하는 데 사용됩니다.
    map.moveCamera({
      "tilt": mapOptions.tilt,
      "heading": mapOptions.heading,
      "zoom": mapOptions.zoom
    });
    
  4. 각 프레임에서 카메라를 업데이트합니다.

    마지막 단계! 각 프레임의 끝에서 mapOptions 객체를 업데이트하여 다음 프레임의 카메라 위치를 설정합니다. 이 코드에서 if 문을 사용하여 최대 기울기 값인 67.5에 도달할 때까지 기울이기를 사용한 다음 카메라가 완전히 360도 회전을 완료할 때까지 프레임마다 제목이 약간 변경됩니다. 원하는 애니메이션이 완료되면 nullsetAnimationLoop에 전달되어 애니메이션이 취소되지 않습니다.
    if (mapOptions.tilt < 67.5) {
      mapOptions.tilt += 0.5
    } else if (mapOptions.heading <= 360) {
      mapOptions.heading += 0.2;
    } else {
      renderer.setAnimationLoop(null)
    }
    

이제 onContextRestored 후크가 다음과 같이 표시됩니다.

    webGLOverlayView.onContextRestored = ({gl}) => {
      renderer = new THREE.WebGLRenderer({
        canvas: gl.canvas,
        context: gl,
        ...gl.getContextAttributes(),
      });

      renderer.autoClear = false;

      loader.manager.onLoad = () => {
        renderer.setAnimationLoop(() => {
           map.moveCamera({
            "tilt": mapOptions.tilt,
            "heading": mapOptions.heading,
            "zoom": mapOptions.zoom
          });

          if (mapOptions.tilt < 67.5) {
            mapOptions.tilt += 0.5
          } else if (mapOptions.heading <= 360) {
            mapOptions.heading += 0.2;
          } else {
            renderer.setAnimationLoop(null)
          }
        });
      }
    }

9. 축하합니다

모든 것이 계획대로 진행되었다면 다음과 같이 큰 3D 핀이 포함된 지도가 표시됩니다.

최종 3D 핀

학습한 내용

이 Codelab에서는 여러 내용을 배웠습니다. 주요 이점은 다음과 같습니다.

  • WebGLOverlayView 및 수명 주기 후크를 구현합니다.
  • Three.js를 지도에 통합
  • 카메라와 조명을 비롯하여 Three.js 장면을 만들기 위한 기본사항
  • Three.js를 사용하여 3D 모델 로드 및 조작하기
  • moveCamera를 사용하여 지도의 카메라를 제어하고 애니메이션을 적용합니다.

다음 과정

WebGL 및 일반적으로 컴퓨터 그래픽은 복잡한 주제이므로 항상 배워야 할 사항이 많습니다. 다음은 시작하기 위한 몇 가지 리소스입니다.