Low poly terrain using mesh geometry

This sample shows you how to create a winter low poly landscape using Mesh geometries. Low poly maps consist of coarse triangulated terrain and 3D models made of a relatively small number of polygons, also called low poly models. Such maps depict playful, abstract views of the world for touristic or marketing purposes. Additionally, they can be used to create fantasy maps or for educational games that teach geography in a fun way.

The low poly terrain in this sample is a Mesh geometry created from an ElevationLayer using the meshUtils.createFromElevation() method.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
const elevationLayer = new ElevationLayer({
  url: "//elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"
});
meshUtils
  .createFromElevation(elevationLayer, extent, {
    demResolution: 120
  })
  .then((mesh) => {
    // mesh now contains a representation of the terrain
    // as a Mesh geometry
  });

This creates a mesh with smooth shading using real elevation values. To create the low poly effect the MeshComponent.shading is changed to flat:

Use dark colors for code blocksCopy
1
mesh.components[0].shading = "flat";
mesh-flat-smooth

Next, we'll apply colors to each vertex based on the elevation of the vertex. Among others, the vertexAttributes property stores information about the vertex color. This information is stored as a flat array of red, green, blue and alpha components.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
const vPositions = mesh.vertexAttributes.position;

// Every third element in the positions array represents the height value
// [x1, y1, z1, x2, y2, z2, ...]
for (let index = 0; index < vPositions.length; index += 3) {
  // getColorFromHeight returns an object of type Color with
  // color information interpolated from a given height based color ramp
  const color = getColorFromHeight(vPositions[index + 2]);
  vColors.push(color.r, color.g, color.b, 255);
}

mesh.vertexAttributes.color = vColors;

A material of type MeshMaterialMetallicRoughness is set on the MeshComponent, to mimic the effect of snow reflecting back the light. The metallic and roughness properties of this material are used to control how much light the material reflects back. In the sample turn "Light reflection effect" on and off to notice how the terrain mesh displays with and without this material.

Use dark colors for code blocksCopy
1
2
3
4
mesh.components[0].material = new MeshMaterialMetallicRoughness({
  metallic: 0.3,
  roughness: 0.8
});

The mesh is added to the scene by creating a graphic and adding the graphic to a GraphicsLayer:

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
const graphic = new Graphic({
  geometry: mesh,
  symbol: {
    type: "mesh-3d",
    symbolLayers: [
      {
        type: "fill"
      }
    ]
  }
});

terrainSurfaceLayer.add(graphic);

The color vertices are by default multiplied to the color value set on the MeshSymbol3D. In this sample, to display the color assigned to the vertices, color is not set on MeshSymbol3D .

Custom 3D models in GLTF format like the trees can be placed as point symbols ObjectSymbol3DLayer in the scene. To align them to the mesh surface, the height value of each tree location is queried synchronously using an elevationSampler on the Mesh geometry.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
meshUtils.createElevationSampler(mesh)
  .then((elevationSampler) => {
    const geometry = new Point({
      x: 948679.688,
      y: 5991891.073,
      spatialReference: SpatialReference.WebMercator
    });

    // Query elevation of the mesh at tree location
    const geometry3D = elevationSampler.queryElevation(geometry);
    // geometry3D has z-values based on the mesh elevation
  });

This 3D pine tree model was downloaded from Google Poly and is used under a CC-BY 3.0 License.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const treeSymbol = {
  type: "point-3d",
  symbolLayers: [
    {
      type: "object",
      resource: {
        href:
          "./pine_tree.glb"
      },
      height: 100,
      anchor: "bottom"
    }
  ]
};

Additionally, a Mesh can be created from a Polygon. To generate the lake in this sample, the polygon geometry of the lake is queried from aFeatureLayer. The method Mesh.createFromPolygon() converts the Polygon geometry of the lake feature into a Mesh geometry.

This allows to then apply a triangular pattern on it using a colorTexture:

mesh-color-texture

To make the triangles stand out, we'll also apply a normal map generated from the borders of the triangles. In this case, this texture was generated in Adobe Photoshop using Filter -> 3D -> Generate Normal Map.

mesh-normal-texture

The metallicRoughnessTexture applies different roughness and metallic values to each triangle to reflect back different amounts of light. The metallic parameter is encoded in the blue channel and roughness in the green channel.

mesh-metallic-roughness-texture
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const mesh = Mesh.createFromPolygon(lakePolygon);

// Set uv coordinates that are used to map the texture on the geometry
mesh.vertexAttributes.uv = getUVCoordinates(mesh);

mesh.components[0].material = new MeshMaterialMetallicRoughness({
  colorTexture: "./water-color.png",
  normalTexture: "./water-normal.png",
  metallicRoughnessTexture: "./water-metallic-roughness.png"
});

// Normalize mesh vertex positions to use them
// as UV coordinates for the texture mapping
function getUVCoordinates(mesh) {
  const uv = [];
  const xmin = mesh.extent.xmin;
  const xmax = mesh.extent.xmax;
  const ymin = mesh.extent.ymin;
  const ymax = mesh.extent.ymax;

  const position = mesh.vertexAttributes.position;

  for (let i = 0; i < position.length; i+=3) {
    const x = position[i];
    const y = position[i + 1];
    const u = (x - xmin) / (xmax - xmin);
    const v = (y - ymin) / (ymax - ymin);
    uv.push(u);
    uv.push(v);
  }

  return uv;
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.