Building the World Wonders globe

The Google World Wonders site ships with a 3D globe widget that lets visitors spin the planet and click markers on World Heritage Sites. It started as the WebGL Globe from the Google Data Arts Team, but the final version swapped out the bar graph data, replaced the textures with vector continent geometry, and added CSS-based clickable markers.

The globe is rendered in Three.js on top of WebGL. The original WebGL Globe code was stripped down to a bare spinning sphere, then rebuilt with custom shaders and geometry to match the site's restrained, monochrome design brief.

Reworking the globe surface

The stock WebGL Globe uses a raster texture for the Earth. Zoom the camera in close and that texture turns blocky, so the team looked for a vector alternative. They pulled the continent mesh from Mozilla's open-source GlobeTweeter demo, which is derived from Natural Earth data.

The first problem was that the GlobeTweeter model wasn't perfectly spherical, so it didn't sit flush against the globe. A quick custom mesh-splitting algorithm rounded it out enough to be placed just above the globe surface, with a black 2px line underneath as a shadow. That setup creates the look of floating continents.

Several visual directions were tried and discarded. Neon outlines gave the globe a Tron-like glow, a dark-on-dark look was too low-contrast, and a white glowing globe with black landmasses didn't fit the project's tone. The team also considered a glazed porcelain finish but couldn't get a shader to produce the effect in time.

The shaders that made it into the black-and-white experiments used a backlit diffuse model: pixel lightness depends on how close the surface normal is to the screen plane. Center pixels pointing at the camera read dark, while edge pixels read light, which makes the globe look like it's reflecting a bright background. In one version, the globe texture doubled as a gloss map so shallow continental shelf areas caught more light than the deep ocean.

That basic vertex shader plus a tweaked fragment shader is what eventually shipped:

    'ocean' : {
      uniforms: {
        'texture': { type: 't', value: 0, texture: null }
      },
      vertexShader: [
        'varying vec3 vNormal;',
        'varying vec2 vUv;',
        'void main() {',
          'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
          'vNormal = normalize( normalMatrix * normal );',
          'vUv = uv;',
        '}'
      ].join('\n'),
      fragmentShader: [
        'uniform sampler2D texture;',
        'varying vec3 vNormal;',
        'varying vec2 vUv;',
        'void main() {',
          'vec3 diffuse = texture2D( texture, vUv ).xyz;',
          'float intensity = pow(1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) ), 4.0);',
          'float i = 0.8-pow(clamp(dot( vNormal, vec3( 0, 0, 1.0 )), 0.0, 1.0), 1.5);',
          'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * intensity;',
          'float d = clamp(pow(max(0.0,(diffuse.r-0.062)*10.0), 2.0)*5.0, 0.0, 1.0);',
          'gl_FragColor = vec4( (d*vec3(i)) + ((1.0-d)*diffuse) + atmosphere, 1.0 );',
        '}'
      ].join('\n')
    }

The production look ended up as a dark globe with light-grey landmasses lit from above. Black oceans were swapped for dark gray in the final version. The low-contrast palette keeps the content and markers visually dominant.

Positioning HTML markers on a WebGL scene

Markers are regular HTML elements, styled with CSS gradients and a rotated div for the triangle pointer. Picking CSS over WebGL markers was partly a development speed decision—styling and click handling are simple—and partly a bet that the marker code could be reused for the site's 2D map. In hindsight, the author notes, WebGL markers would have performed better.

Each marker has a corresponding invisible Object3D in the Three.js scene. To draw a marker at the right screen location, the globe and marker matrices are multiplied with a zero vector to get the marker's scene position, which is then projected through the camera. The resulting screen coordinates are applied to the marker element with CSS transforms:

var mat = new THREE.Matrix4();
var v = new THREE.Vector3();

for (var i=0; i<locations.length; i++) {
  mat.copy(scene.matrix);
  mat.multiplySelf(locations[i].point.matrix);
  v.set(0,0,0);
  mat.multiplyVector3(v);
  projector.projectVector(v, camera);
  var x = w * (v.x + 1) / 2; // Screen coords are between -1 .. 1, so we transform them to pixels.
  var y = h - h * (v.y + 1) / 2; // The y coordinate is flipped in WebGL.
  var z = v.z;
}

The team hit several browser rendering pitfalls while animating a few dozen moving divs. The fastest approach turned out to be:

  • Move markers with CSS transforms only.
  • Avoid opacity fading—it triggered a slow path in Firefox.
  • Keep all markers in the DOM even when they rotate behind the globe.

Clicking a marker expands it into a list of place names. This part is all ordinary DOM, so the links and text rendered with no extra WebGL code.

Cutting the mesh down to size

The continent geometry ships as a JSON mesh, which put the team in a bind: the file was around 3 MB, far too heavy for the front page of the site. Gzip got it down to 350 kB, which was still too big.

Won Chun, who had worked on compressing the Google Body meshes, stepped in and reduced the triangle soup to indexed triangles with 11-bit compressed coordinates. The gzipped result: 95 kB.

Compressing the mesh delivered two benefits. Bandwidth dropped, but parsing time did too—a few hundred kB of binary data is far quicker to turn into native numbers than 3 MB of stringified coordinates. With that reduction, initial page load dropped below a second on a 2 Mbps connection.

The team also experimented with loading Natural Earth Shapefiles directly to shrink the model further. Drawing flat landmasses from Shapefiles requires triangulating the polygons, including holes for lakes, and splitting triangles down to safe sizes. The triangulation with holes never got sorted out in time. Had it worked, the landmass model would have compressed to roughly 8 kB.

What's left to polish

The marker animation when a site rotates past the horizon could still be smoother, and the team admits the expansion animation on click could be nicer. Performance work remains on the mesh-splitting algorithm and the markers themselves, but the globe is in solid shape for its first release.