Procedural Geometry: Low Poly Clouds

Clouds are similar to the procedural trees that we created last time. The difference is that we can’t just add some cloud shaped objects together. Instead we will merge several spheres and jitter the vertexes to make them look lumpier.

This article is part of my ongoing series of medium difficulty ThreeJS tutorials. I’ve long wanted something in between the intro “How to draw a cube” and “Let’s fill the screen with shader madness” levels. So here it is.

To start we need three spheres. Like with the trees I’ve merged three SphereGeometry objects into a single geometry.

const geo = new THREE.Geometry()

const tuft1 = new THREE.SphereGeometry(1.5,7,8)
tuft1.translate(-2,0,0)
geo.merge(tuft1)

const tuft2 = new THREE.SphereGeometry(1.5,7,8)
tuft2.translate(2,0,0)
geo.merge(tuft2)

const tuft3 = new THREE.SphereGeometry(2.0,7,8)
tuft3.translate(0,0,0)
geo.merge(tuft3)

To draw the geometry I need a mesh with this geometry and a material. I want a low-poly flat-shaded look so I need to call geo.computeFlatVertexNormals() and set flatShading to true:

cloud = new THREE.Mesh(
geo,
new THREE.MeshLambertMaterial({
color:'white',
flatShading:true,
})
)

This is what it looks like:

Low Poly Cloud from Spheres

To make the facets really pop I’m using both an ambient light of 0.3 and a directional light of 0.7 coming from the side:

const light = new THREE.DirectionalLight( 0xffffff, 0.7 );
light.position.set( 1, 1, 0 ).normalize();
scene.add( light );
scene.add(new THREE.AmbientLight(0xffffff,0.3))

Jittering

One of the ways to makes low-poly object look cute is to move the points around a bit so the shapes are no longer perfectly symmetrical. We can do that by randomly displacing the vertexes just a tiny bit. I created a function called jitter which does this:

// remap value from the range of [smin,smax] to [emin,emax]
const
map = (val, smin, smax, emin, emax) => (emax-emin)*(val-smin)/(smax-smin) + emin
//randomly displace the x,y,z coords by the `per` value
const
jitter = (geo,per) => geo.vertices.forEach(v => {
v.x += map(Math.random(),0,1,-per,per)
v.y += map(Math.random(),0,1,-per,per)
v.z += map(Math.random(),0,1,-per,per)
})
jitter(geo,0.2)

Calling jitter with 0.2 moves the x,y, and z coordinates of each vertex in the geometry by a random value between -0.2 and 0.2. This moves the points around enough that the effect is visible without moving them so much that the viewer can’t tell it’s a sphere anymore. Here’s what it looks like:

Jitter Vertexes

Of course cartoon like clouds have flat bottoms. We can do this easily with a chop function that flattens any points below a certain value:

const chopBottom = (geo,bottom) => geo.vertices.forEach(v => v.y = Math.max(v.y,bottom))
chopBottom(geo,-0.5)
Flattened Bottom

As one final detail to make it look cuter, I added a second directional light from the other side which casts as pinkish glow. This creates a feeling of clouds at sunset.

const light2 = new THREE.DirectionalLight( 0xff5566, 0.7 );
light2.position.set( -3, -1, 0 ).normalize();
scene.add( light2 );
Final Low-Poly Cloud with Sunset Lighting

Live Demo

That’s all there is too it. Just a few primitives merged, then mess with the vertexes a bit to get the effect we want. This same principle can be used for many other objects like buildings, vehicles, and even animals. Check out this cute low poly lion made only with cubes by Karim Maalou.

Next time we’ll work on a generated low-poly mountain and water terrain.

Get Noticed

By the way, if you are working on a cool WebVR experience that you’d like to have showcased right inside Firefox Reality, let us know.