Trabalhar com um renderizador de blocos 3D

Os blocos 3D fotorrealistas estão no formato glTF padrão do OGC, o que significa que você pode usar qualquer renderizador compatível com a especificação de blocos 3D do OGC para criar visualizações 3D. Por exemplo, o Cesium é uma biblioteca de código aberto fundamental para renderização de visualizações 3D.

Trabalhar com o CesiumJS

O CesiumJS é uma biblioteca JavaScript de código aberto para visualização 3D na Web. Para mais informações sobre o uso do CesiumJS, consulte Aprenda a usar o CesiumJS.

Controles de usuário

O renderizador de blocos do CesiumJS tem um conjunto padrão de controles do usuário.

Ação Descrição
Vista panorâmica Clique com o botão esquerdo e arraste
Visualização em zoom Clique com o botão direito do mouse e arraste ou role a roda do mouse
Girar a visualização Ctrl + clique com o botão esquerdo/direito e arraste ou clique com o botão do meio e arraste

Práticas recomendadas

Há várias abordagens que podem ser usadas para diminuir os tempos de carregamento 3D do CesiumJS. Exemplo:

  • Ative as solicitações simultâneas adicionando a seguinte instrução à renderização em HTML:

    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = <REQUEST_COUNT>
    

    Quanto maior o REQUEST_COUNT, mais rápido os blocos são carregados. No entanto, ao carregar em um navegador Chrome com REQUEST_COUNT maior que 10 e cache desativado, você pode encontrar um problema conhecido do Chrome. Para a maioria dos casos de uso, recomendamos um REQUEST_COUNT de 18 para desempenho ótimo.

  • Ative a opção de pular níveis de detalhes. Para mais informações, consulte este problema do Cesium.

Ative a showCreditsOnScreen: true para mostrar corretamente as atribuições de dados. Para mais informações, consulte Políticas.

Métricas de renderização

Para encontrar a taxa de frames, observe quantas vezes por segundo o método requestAnimationFrame é chamado.

Para saber como a latência do frame é calculada, consulte a classe PerformanceDisplay.

Exemplos de renderizador do CesiumJS

É possível usar o renderizador CesiumJS com os Blocos 3D da API Map Tiles simplesmente fornecendo o URL do tileset raiz.

Exemplo simples

O exemplo a seguir inicializa o renderizador do CesiumJS e carrega o conjunto de blocos raiz.

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <title>CesiumJS 3D Tiles Simple Demo</title>
  <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
  <link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
  <div id="cesiumContainer"></div>
  <script>

    // Enable simultaneous requests.
    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

    // Create the viewer.
    const viewer = new Cesium.Viewer('cesiumContainer', {
      imageryProvider: false,
      baseLayerPicker: false,
      geocoder: false,
      globe: false,
      // https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/#enabling-request-render-mode
      requestRenderMode: true,
    });

    // Add 3D Tiles tileset.
    const tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({
      url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
      // This property is needed to appropriately display attributions
      // as required.
      showCreditsOnScreen: true,
    }));
  </script>
</body>

Para saber mais sobre requestRenderMode, consulte Como ativar o modo de renderização de solicitação.

A página HTML é renderizada conforme mostrado aqui.

Integração da API Places

É possível usar o CesiumJS com a API Places para extrair mais informações. Você pode usar o widget de preenchimento automático para ir até a janela de visualização do Places. Este exemplo usa a API Places Autocomplete, que é ativada ao seguir estas instruções, e a API Maps JavaScript, que é ativada ao seguir estas instruções.

<!DOCTYPE html>
<head>
 <meta charset="utf-8" />
 <title>CesiumJS 3D Tiles Places API Integration Demo</title>
 <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
 <link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
 <label for="pacViewPlace">Go to a place: </label>
 <input
   type="text"
   id="pacViewPlace"
   name="pacViewPlace"
   placeholder="Enter a location..."
   style="width: 300px"
 />
 <div id="cesiumContainer"></div>
 <script>
   // Enable simultaneous requests.
   Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

   // Create the viewer.
   const viewer = new Cesium.Viewer("cesiumContainer", {
     imageryProvider: false,
     baseLayerPicker: false,
     requestRenderMode: true,
     geocoder: false,
     globe: false,
   });

   // Add 3D Tiles tileset.
   const tileset = viewer.scene.primitives.add(
     new Cesium.Cesium3DTileset({
       url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
       // This property is required to display attributions as required.
       showCreditsOnScreen: true,
     })
   );

   const zoomToViewport = (viewport) => {
     viewer.entities.add({
       polyline: {
         positions: Cesium.Cartesian3.fromDegreesArray([
           viewport.getNorthEast().lng(), viewport.getNorthEast().lat(),
           viewport.getSouthWest().lng(), viewport.getNorthEast().lat(),
           viewport.getSouthWest().lng(), viewport.getSouthWest().lat(),
           viewport.getNorthEast().lng(), viewport.getSouthWest().lat(),
           viewport.getNorthEast().lng(), viewport.getNorthEast().lat(),
         ]),
         width: 10,
         clampToGround: true,
         material: Cesium.Color.RED,
       },
     });
     viewer.flyTo(viewer.entities);
   };

   function initAutocomplete() {
     const autocomplete = new google.maps.places.Autocomplete(
       document.getElementById("pacViewPlace"),
       {
         fields: [
           "geometry",
           "name",
         ],
       }
     );
     autocomplete.addListener("place_changed", () => {
       viewer.entities.removeAll();
       const place = autocomplete.getPlace();
       if (!place.geometry || !place.geometry.viewport) {
         window.alert("No viewport for input: " + place.name);
         return;
       }
       zoomToViewport(place.geometry.viewport);
     });
   }
 </script>
 <script
   async=""
   src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initAutocomplete"
 ></script>
</body>

Drone girando

É possível controlar a câmera para animar o conjunto de blocos. Quando combinada com a API Places e a API Elevation, essa animação simula um drone voando sobre qualquer ponto de interesse.

Este exemplo de código mostra o lugar que você selecionou no widget de preenchimento automático.

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <title>CesiumJS 3D Tiles Rotating Drone View Demo</title>
  <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
  <link href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
  <label for="pacViewPlace">Go to a place: </label>
  <input type="text" id="pacViewPlace" name="pacViewPlace" placeholder="Enter a location..." style="width: 300px" />
  <div id="cesiumContainer"></div>
  <script>
    // Enable simultaneous requests.
    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

    // Create the viewer and remove unneeded options.
    const viewer = new Cesium.Viewer("cesiumContainer", {
      imageryProvider: false,
      baseLayerPicker: false,
      homeButton: false,
      fullscreenButton: false,
      navigationHelpButton: false,
      vrButton: false,
      sceneModePicker: false,
      geocoder: false,
      globe: false,
      infobox: false,
      selectionIndicator: false,
      timeline: false,
      projectionPicker: false,
      clockViewModel: null,
      animation: false,
      requestRenderMode: true,
    });

    // Add 3D Tile set.
    const tileset = viewer.scene.primitives.add(
      new Cesium.Cesium3DTileset({
        url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",
        // This property is required to display attributions.
        showCreditsOnScreen: true,
      })
    );

    // Point the camera at a location and elevation, at a viewport-appropriate distance.
    function pointCameraAt(location, viewport, elevation) {
      const distance = Cesium.Cartesian3.distance(
        Cesium.Cartesian3.fromDegrees(
          viewport.getSouthWest().lng(), viewport.getSouthWest().lat(), elevation),
        Cesium.Cartesian3.fromDegrees(
          viewport.getNorthEast().lng(), viewport.getNorthEast().lat(), elevation)
      ) / 2;
      const target = new Cesium.Cartesian3.fromDegrees(location.lng(), location.lat(), elevation);
      const pitch = -Math.PI / 4;
      const heading = 0;
      viewer.camera.lookAt(target, new Cesium.HeadingPitchRange(heading, pitch, distance));
    }

    // Rotate the camera around a location and elevation, at a viewport-appropriate distance.
    let unsubscribe = null;
    function rotateCameraAround(location, viewport, elevation) {
      if(unsubscribe) unsubscribe();
      pointCameraAt(location, viewport, elevation);
      unsubscribe = viewer.clock.onTick.addEventListener(() => {
        viewer.camera.rotate(Cesium.Cartesian3.UNIT_Z);
      });
    }

    function initAutocomplete() {
      const autocomplete = new google.maps.places.Autocomplete(
        document.getElementById("pacViewPlace"), {
          fields: [
            "geometry",
            "name",
          ],
        }
      );
      
      autocomplete.addListener("place_changed", async () => {
        const place = autocomplete.getPlace();
        
        if (!(place.geometry && place.geometry.viewport && place.geometry.location)) {
          window.alert(`Insufficient geometry data for place: ${place.name}`);
          return;
        }
        // Get place elevation using the ElevationService.
        const elevatorService = new google.maps.ElevationService();
        const elevationResponse =  await elevatorService.getElevationForLocations({
          locations: [place.geometry.location],
        });

        if(!(elevationResponse.results && elevationResponse.results.length)){
          window.alert(`Insufficient elevation data for place: ${place.name}`);
          return;
        }
        const elevation = elevationResponse.results[0].elevation || 10;

        rotateCameraAround(
          place.geometry.location,
          place.geometry.viewport,
          elevation
        );
      });
    }
  </script>
  <script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initAutocomplete"></script>
</body>

Desenhar polilinhas e rótulos

Este exemplo de código demonstra como adicionar polilinhas e rótulos a um mapa. É possível adicionar polilinhas a um mapa para mostrar rotas de carro e a pé, mostrar limites de propriedades ou calcular a duração de trajetos de carro e a pé. Também é possível extrair atributos sem renderizar a cena.

Você pode fazer um tour personalizado por um bairro ou mostrar propriedades vizinhas que estão à venda e adicionar objetos 3D, como outdoors, à cena.

Você pode resumir uma viagem, listando as propriedades que você visualizou, mostrando esses detalhes em objetos virtuais.

<!DOCTYPE html>
<head>
  <meta charset="utf-8" />
  <title>CesiumJS 3D Tiles Polyline and Label Demo</title>
  <script src="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Cesium.js"></script>
  <link 
    href="https://ajax.googleapis.com/ajax/libs/cesiumjs/1.105/Build/Cesium/Widgets/widgets.css"
    rel="stylesheet"
  />
</head>
<body>
  <div id="cesiumContainer"></div>
  <script>
    // Enable simultaneous requests.
    Cesium.RequestScheduler.requestsByServer["tile.googleapis.com:443"] = 18;

    // Create the viewer.
    const viewer = new Cesium.Viewer("cesiumContainer", {
      imageryProvider: false,
      baseLayerPicker: false,
      requestRenderMode: true,
      geocoder: false,
      globe: false,
    });

    // Add 3D Tiles tileset.
    const tileset = viewer.scene.primitives.add(
      new Cesium.Cesium3DTileset({
        url: "https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY",

        // This property is required to display attributions as required.
        showCreditsOnScreen: true,
      })
    );

    // Draws a circle at the position, and a line from the previous position.
    const drawPointAndLine = (position, prevPosition) => {
      viewer.entities.removeAll();
      if (prevPosition) {
        viewer.entities.add({
          polyline: {
            positions: [prevPosition, position],
            width: 3,
            material: Cesium.Color.WHITE,
            clampToGround: true,
            classificationType: Cesium.ClassificationType.CESIUM_3D_TILE,
          },
        });
      }
      viewer.entities.add({
        position: position,
        ellipsoid: {
          radii: new Cesium.Cartesian3(1, 1, 1),
          material: Cesium.Color.RED,
        },
      });
    };

    // Compute, draw, and display the position's height relative to the previous position.
    var prevPosition;
    const processHeights = (newPosition) => {
      drawPointAndLine(newPosition, prevPosition);

      const newHeight = Cesium.Cartographic.fromCartesian(newPosition).height;
      let labelText = "Current altitude (meters above sea level):\n\t" + newHeight;
      if (prevPosition) {
        const prevHeight =
          Cesium.Cartographic.fromCartesian(prevPosition).height;
        labelText += "\nHeight from previous point (meters):\n\t" + Math.abs(newHeight - prevHeight);
      }
      viewer.entities.add({
        position: newPosition,
        label: {
          text: labelText,
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
          pixelOffset: new Cesium.Cartesian2(0, -10),
          showBackground: true,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        }
      });

      prevPosition = newPosition;
    };

    const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
    handler.setInputAction(function (event) {
      const earthPosition = viewer.scene.pickPosition(event.position);
      if (Cesium.defined(earthPosition)) {
        processHeights(earthPosition);
      }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  </script>
</body>

Órbita da câmera

No Cesium, é possível orbitar a câmera em torno de um ponto de interesse, evitando colisões com edifícios. Como alternativa, você pode tornar os edifícios transparentes quando a câmera se move por eles.

Primeiro, bloqueie a câmera em um ponto e, em seguida, crie uma órbita para mostrar o recurso. Para fazer isso, use a função lookAtTransform da câmera com um listener de eventos, conforme demonstrado neste exemplo de código.

// Lock the camera onto a point.
const center = Cesium.Cartesian3.fromRadians(
  2.4213211833389243,
  0.6171926869414084,
  3626.0426275055174
);

const transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);

viewer.scene.camera.lookAtTransform(
  transform,
  new Cesium.HeadingPitchRange(0, -Math.PI / 8, 2900)
);

// Orbit around this point.
viewer.clock.onTick.addEventListener(function (clock) {
  viewer.scene.camera.rotateRight(0.005);
});

Para mais informações sobre como controlar a câmera, consulte Controlar a câmera.

Trabalhar com o Cesium para Unreal

Para usar o plug-in Cesium for Unreal com a API 3D Tiles, siga as etapas abaixo.

  1. Instale o plug-in do Cesium para Unreal.

  2. Crie um novo projeto do Unreal.

  3. Conecte-se à API Photorealistic 3D Tiles do Google.

    1. Abra a janela Cesium selecionando Cesium > Cesium no menu.

    2. Selecione Tileset de blocos 3D em branco.

    3. No World Outliner, abra o painel Details selecionando este Cesium3DTileset.

    4. Mude a Origem de Do íon Cesium para Do URL.

    5. Defina o URL como o URL dos Blocos 3D do Google.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Ative a opção Mostrar créditos na tela para mostrar as atribuições corretamente.
  4. Isso carrega o mundo. Para mover para qualquer LatLng, selecione o item CesiumGeoreference no painel Outliner e edite a Origin Latitude/Longitude/Height no painel Details.

Trabalhar com o Cesium para Unity

Para usar blocos fotorrealistas com o Cesium para Unity, siga as etapas abaixo.

  1. Crie um novo projeto do Unity.

  2. Adicione um novo registro de escopo na seção "Gerenciador de pacotes" (Editor > Configurações do projeto).

    • Nome: Césio

    • URL: https://unity.pkg.cesium.com

    • Escopo(s): com.cesium.unity

  3. Instale o pacote Cesium for Unity.

  4. Conecte-se à API Photorealistic 3D Tiles do Google.

    1. Abra a janela Cesium selecionando Cesium > Cesium no menu.

    2. Clique em Tileset de blocos 3D em branco.

    3. No painel esquerdo, na opção Origem do conjunto de blocos em Origem, selecione Do URL (em vez de "Do Cesium Ion").

    4. Defina o URL para o URL dos Blocos 3D do Google.

    https://tile.googleapis.com/v1/3dtiles/root.json?key=YOUR_API_KEY
    
    1. Ative a opção Mostrar créditos na tela para mostrar as atribuições corretamente.
  5. Isso carrega o mundo. Para mover para qualquer LatLng, selecione o item CesiumGeoreference na Hierarquia da cena e edite a Latitude/Longitude/Altura de origem no Inspector.

Trabalhar com o deck.gl

O deck.gl, com tecnologia WebGL, é um framework JavaScript de código aberto para visualizações de dados de alto desempenho e grande escala.

Atribuição

Para mostrar as atribuições de dados corretamente, extraia o campo copyright dos blocos gltf asset e o mostre na visualização renderizada. Para mais informações, consulte Mostrar atribuições de dados.

Exemplos de renderizador do deck.gl

Exemplo simples

O exemplo a seguir inicializa o renderizador deck.gl e carrega um lugar em 3D. No código, substitua YOUR_API_KEY pela sua chave de API.

<!DOCTYPE html>
<html>
 <head>
   <title>deck.gl Photorealistic 3D Tiles example</title>
   <script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
   <style>
     body { margin: 0; padding: 0;}
     #map { position: absolute; top: 0;bottom: 0;width: 100%;}
     #credits { position: absolute; bottom: 0; right: 0; padding: 2px; font-size: 15px; color: white;
        text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;}
   </style>
 </head>

 <body>
   <div id="map"></div>
   <div id="credits"></div>
   <script>
     const GOOGLE_API_KEY = YOUR_API_KEY;
     const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;
     const creditsElement = document.getElementById('credits');
     new deck.DeckGL({
       container: 'map',
       initialViewState: {
         latitude: 50.0890,
         longitude: 14.4196,
         zoom: 16,
         bearing: 90,
         pitch: 60,
         height: 200
       },
       controller: {minZoom: 8},
       layers: [
         new deck.Tile3DLayer({
           id: 'google-3d-tiles',
           data: TILESET_URL,
           loadOptions: {
            fetch: {
              headers: {
                'X-GOOG-API-KEY': GOOGLE_API_KEY
              }
            }
          },
           onTilesetLoad: tileset3d => {
             tileset3d.options.onTraversalComplete = selectedTiles => {
               const credits = new Set();
               selectedTiles.forEach(tile => {
                 const {copyright} = tile.content.gltf.asset;
                 copyright.split(';').forEach(credits.add, credits);
                 creditsElement.innerHTML = [...credits].join('; ');
               });
               return selectedTiles;
             }
           }
         })
       ]
     });
   </script>
 </body>
</html>

Visualizar camadas 2D sobre os blocos fotorrealistas em 3D do Google

A TerrainExtension do deck.gl renderiza dados 2D em uma superfície 3D. Por exemplo, é possível sobrepor o GeoJSON de um edifício em cima da geometria dos blocos fotorrealistas em 3D.

No exemplo a seguir, uma camada de edifícios é visualizada com os polígonos adaptados à superfície dos blocos fotorrealistas em 3D.

<!DOCTYPE html>
<html>
 <head>
   <title>Google 3D tiles example</title>
   <script src="https://unpkg.com/deck.gl@latest/dist.min.js"></script>
   <style>
     body { margin: 0; padding: 0;}
     #map { position: absolute; top: 0;bottom: 0;width: 100%;}
     #credits { position: absolute; bottom: 0; right: 0; padding: 2px; font-size: 15px; color: white;
        text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;}
   </style>
 </head>

 <body>
   <div id="map"></div>
   <div id="credits"></div>
   <script>
     const GOOGLE_API_KEY = YOUR_API_KEY;
     const TILESET_URL = `https://tile.googleapis.com/v1/3dtiles/root.json`;
     const BUILDINGS_URL = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/google-3d-tiles/buildings.geojson'
     const creditsElement = document.getElementById('credits');
     const deckgl = new deck.DeckGL({
       container: 'map',
       initialViewState: {
         latitude: 50.0890,
         longitude: 14.4196,
         zoom: 16,
         bearing: 90,
         pitch: 60,
         height: 200
       },
       controller: true,
       layers: [
         new deck.Tile3DLayer({
           id: 'google-3d-tiles',
           data: TILESET_URL,
           loadOptions: {
            fetch: {
              headers: {
                'X-GOOG-API-KEY': GOOGLE_API_KEY
              }
            }
          },
          onTilesetLoad: tileset3d => {
             tileset3d.options.onTraversalComplete = selectedTiles => {
               const credits = new Set();
               selectedTiles.forEach(tile => {
                 const {copyright} = tile.content.gltf.asset;
                 copyright.split(';').forEach(credits.add, credits);
                 creditsElement.innerHTML = [...credits].join('; ');
               });
               return selectedTiles;
             }
           },
           operation: 'terrain+draw'
         }),
         new deck.GeoJsonLayer({
           id: 'buildings',
           // This dataset is created by CARTO, using other Open Datasets available. More info at: https://3dtiles.carto.com/#about.
           data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/google-3d-tiles/buildings.geojson',
           stroked: false,
           filled: true,
           getFillColor: ({properties}) => {
             const {tpp} = properties;
             // quantiles break
             if (tpp < 0.6249)
               return [254, 246, 181]
             else if (tpp < 0.6780)
               return [255, 194, 133]
             else if (tpp < 0.8594)
               return [250, 138, 118]
             return [225, 83, 131]
           },
           opacity: 0.2,
           extensions: [new deck._TerrainExtension()]
         })
       ]
     });
   </script>
 </body>
</html>