In the previous article I introduced WebGL, and talked about why I’d be using threeJS to demonstrate the ability of WebGL to create native 3D content on web pages. In this article, I show you how to make a 3D camera, and control the interaction between your WebGL content and the HTML page it is contained within.

Music of the Spheres

While it can be used by itself as the sole content of a page, one of the advantages of WebGL is that it is a standards-based web technology, meaning that it can be integrated seamlessly with HTML content. Seeing that <canvas> lacks many accessibility and SEO features, it makes sense to produce a WebGL scene progressively: presenting text as HTML text, and adding WebGL content where appropriate. For our page about Mars, that means we start with the following markup:

<div id="marsloc"></div>
<article id="marsinfo">
<h1>Mars</h1>
  <div>
    <p>Home to both the Solar System’s highest mountain…
  </div>
</article>

marsloc will contain our rendered WebGL content, with the HTML content layered on top. This is controlled with CSS, written as Sass:

body {
  background: black;
  margin: 0;
  min-height: 100vh;
  color: #fff;
}
#marsloc {
  cursor: grab;
}
#marsinfo { 
  position: absolute;
  top: 0;
  width: 100%;
  padding: 2rem;
}
#marsinfo h1 {
  font-size: 8vw;
  margin-top: 0;
  font-weight: 100;
  line-height: 1;
  position: absolute;
}
#marsinfo div {
  width: 40%;
  position: absolute;
  background-color: rgba(0,0,0,0.3);
  right: 0;
  padding: 1.3rem;
  line-height: 1.6;
  font-size: 1.2rem;
  pointer-events: none;
  @media all and (max-width: 540px) {
    width: 100%;
    left: 0;
    top: 40vw;
  }
}

The grab cursor on #marsloc will work as a UI cue for the WebGL content; the div in #marsinfo is provided with pointer-events: none so that the content on top does not interfere with manipulation of the planet.

How To Make A Planet

With the HTML and CSS in place, we’ll load the latest version of threeJS from a CDN at the bottom of the page:

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.js">
</script>

Our next script starts by setting up a suite of variables and constants that we’ll be using in our code:

var container, controls, camera, renderer, scene, light, marsMesh,
clock = new THREE.Clock();
const imgLoc = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/4273/";

The clock will be used to help animate the default motion of our Mars sphere; imgLoc is the location of all of the images, and is defined here so I don’t have to repeat myself later.

FOV

The first element we’ll create is a camera, which is required to “see” the scene we’re about to create. There are several different kinds of camera in threeJS:

  • perspectiveCamera: Lines in the scene that are parallel will eventually converge, if they are extended far enough (think of standing on a railway, looking off into the distance); objects that appear further away from the camera will appear smaller. This is how vision works, and how the world appears naturally.
  • OrthographicCamera: Parallel lines in the scene remain parallel, no matter how far away they extend; for the same reason, objects do not appear smaller with distance. This is particularly useful for rendering UI elements and some architectural views.

For a perspective camera, we also need to define a “field of view” (frequently shortened to “FOV”). Field-of-view is how “wide” the camera “sees”. A narrow field of view - think of the blinkers placed on carriage horses, so they are not spooked or distracted - “focuses” the camera towards a particular part of the scene, at the expense of visually cutting off elements that may fall outside this area. A wide field of view takes in more, but at the cost of making objects appear smaller and further away.

We also need to set the camera’s aspect ratio: how wide the rendered view is, compared to its height. You’re probably most familiar with aspect ratio from the movies: a film featuring a wide aspect ratio tends to feel more “cinematic” and epic, while a low aspect ratio - down to a square - can feel more intimate, but also older.

In almost all cases, we want the aspect ratio to mirror the viewer’s window: that is, the browser width divided by its height.

The final two values for the camera are the near and far clipping plane values. By default, 3D cameras “see” an infinite distance: unlike the real world, the view of a 3D camera is not obscured by particles in the atmosphere or the limits of a lens. In games, this is often referred to as “draw distance”, and one reason why distance in early 3D games was often obscured by fog or tight corridors: fewer elements to draw means the game could run faster.

In our case, we want the camera’s near clipping plane to be very close to it’s imaginary “lens”, and the far clipping plane to be a reasonable distance away, such that the distance between the two will contain our scene elements. The code we’ll use is:

camera = new THREE.PerspectiveCamera(45, 
window.innerWidth / window.innerHeight, 0.1, 10000);

Right now, our camera is floating in infinite black space, and is not pointing at anything in particular. In the next article, I’ll explain the nature of that 3D space, how to light it, and how to add objects to our scene.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/VmJKXe