Mars

Home to both the Solar System's highest mountain (Olympus Mons), and deepest canyon (Valles Marineris), Mars is the also the planet most likely to support life outside of Earth; seasonal methane plumes observed over several decades have yet to be explained.

After addressing 3D space and lighting, I rather rushed to the end of the previous article in order to gain a rendered image. In this piece I’ll backtrack a little, covering threeJS objects and materials in detail, before re-approaching the rendering process.

Details

At the end of the last article, I created a sphere:

let marsGeo = new THREE.SphereGeometry (500, 32, 32),

That sphere is 500 units in radius, and has 32 vertical segments and 32 horizontal segments. By default every object is created at the origin point of the scene (0 0 0), so we don’t have to move it.

What happens if we reduce the number of segments to 4 in each direction for the sphere?

The visual effect of increasing sphere segments: 4. 8. 16 and 32 segments (horizontal and vertical) for a sphere object

You can see that the edge of the sphere is “roughed in” at lower segment counts; the forward-facing part of the object only looks spherical due to a combination of lighting and shaders. (I’ll discuss shaders in a moment).

As a general rule, the more segments an object has, the smoother it will appear. However, this comes at a cost: more segments also means greater computation time for rendering the scene, and greater use of RAM.

The best balance is to determine how much detail you need in an object. If a sphere is very small or very far away, it won’t need many segments in order to look circular. On the other hand, if we were making a close fly-by of the planet, we’d need rather more segments to ensure that the sphere continued to look smooth during our closest approach.

A Material Sandwich

In the last example, I applied a Phong material to the sphere:

marsMaterial = new THREE.MeshPhongMaterial( { color: 0xff6600 } ),

Phong is a shader algorithm, written by Vietnamese computer graphics researcher Bùi Tường Phong. A shader essentially defines how light interacts with a 3D surface: Phong’s algorithm is fast and efficient, and ideal for rendering smooth surfaces. In threeJS, the material is merged with an existing 3D object to create a mesh:

marsMesh = new THREE.Mesh(marsGeo, marsMaterial);

The rendered result, shown in the previous example, is a little simplistic for our purposes: we want a planet, not a shiny orange sphere. To create this impression, we’ll need to include several more material aspects to our sphere, via a Texture Loader.

let loader = new THREE.TextureLoader();

There are many material aspects to most objects. For Mars, the most obvious one is color: rather than being one consistent shade, the Martian surface is extremely varied. The solution is to take one of the many seamless visual maps of the planet made by recent probes - I used images from Celestia - and wrap it around the surface as a material map:

marsMaterial.map = loader.load(imgLoc+"mars-map.jpg");

(You’ll recall from the first article in the series that imgLoc was defined earlier in our code as the file location of our textures. You can download the finished texture here).

WebGL Texture Size

Just like the number of segments in an object, there’s a strong temptation use higher resolution texture maps, since it implies greater detail for a rendered object. However, doing so results in the same problems: greater RAM use and slower performance.

WebGL currently balances this to some degree by imposing two limits on texture bitmaps:

  1. Texture bitmaps must have both width and height that are powers of 2: that is, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096 or 8192 pixels across or high. (The numbers may be different, e.g. 1024 pixels wide by 512 pixels high).
  2. Mobile browsers tend to have lower limits for texture sizes, although this has been rising of late (iOS 10 supports 4096 × 4096, for example; you can test different devices and platforms at webglreport.com.)

In the case of the texture applied in this example, I’ve sized the bitmap to 4096 × 2048 pixels.

The material map applied to Mars

It’s also important that the texture should be seamless: that is, when wrapped around an object, it’s edges should meet up without any apparent visual join.

Rendering

To produce anything visually, our work must be rendered. In our script, that’s accomplished with five lines of code, three of them inside a function:

let renderer = new THREE.WebGLRenderer({antialiasing : true});
marsloc.appendChild(renderer.domElement);

function render(){
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.clear();
    renderer.render(scene, camera); 
}

renderer is the “capture state” of the current scene. While it has many options, the one I have used here is antialiasing, to smooth the rendered sphere of Mars against its background.

marsLoc is the container element we made back in part one; the renderer is appended to that. Inside the render function, the size of this capture state is set to be the size of the browser window. Then, the canvas area is cleared and the scene rendered, using the current camera.

You’ll note that right now the result looks both flat and shiny, is fixed in space, and is not responsive; the steps taken in the next article in this series will correct those issues.

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/PWXNQa