Build the Solar System with WebGL & Three.js

avatar
Marshal Murphy
July 29, 2020 - 11 MIN READ

Since beginning my journey into the Front End, I have always been interested in how to create 3-dimensional renderings in the browser.

Somewhat related, I’ve recently also been exploring the creation of augmented reality experiences in React Native… but more on that later.

For now let’s dive into an introduction on how to use WebGL with ThreeJS.

To accomplish this we will be constructing a fun, 3D, not-to-scale rendering of our solar system!

Feature

I say not-to-scale because, well, space is vast.

It's going to take the SpaceX Starship 6 months to travel to Mars in 2024, a distance which takes light a full 3 minutes.

If we make our little 3D browser-rendering to scale, our planets would be infinitesimally tiny in a black void...

Just like in real life! 🤣

No, instead we shall aim for visually pleasing over real-world accuracy.

Here is a gif of our finished project in the browser:

Final

And here is the final project repository.

Let’s have some fun.🚀


WebGL and ThreeJS

Compared to modern web tech, WebGL (based on OpenGL) is relatively old.

Created in 2006 and adopted by Mozilla and Opera a year later, WebGL has been steadily iterated upon over the years and is now a standard tech included in modern browsers.

ThreeJS, the primary tech we'll be using today, is a library that allows us to create and display animated 3D computer graphics in the browser.

Built on WebGL, it allows us to create shapes, load textures, customize lighting, manipulate and animate perspectives, customize the orbit controls of the camera... among many other possibilities.

Today we are going to use an experimental library called canvas-sketch by Matt DesLauriers.

Check out his course on FrontEnd Masters if you feel so inclined.

This library will handle the heavy lifting of setting up ThreeJS for us, so we can simply focus on building.


Project Setup & Installation

First off, we will need to get set up with the canvas-sketch CLI.

In your terminal, install it by entering the following:

> npm install canvas-sketch-cli -g

Once installation is finished, create a folder where you'd like to store this project and move into it.

I'll name this project solar-system-threejs and throw it onto the Desktop:

> cd Desktop
> mkdir solar-system-threejs
> cd solar-system-threejs

With the canvas-sketch CLI installed, let's run a command to scaffold our ThreeJS project in this folder we created.

> canvas-sketch sketch.js --new --template=three

The setup here is very quick, and will finish with a server running on port 9966.

Open your browser and head over to localhost:9966 and you will see the following 3D sphere wireframe.

Image

In the future, to launch your project you will simply enter canvas-sketch followed by the entry file.

canvas-sketch sketch.js

Oh, one last thing!

ThreeJS allows us to create spheres to be our planets, but we need to provide specific images that map to spheres in order for them to look like planets.

Head over HERE and download the planet images as equirectangular projections that I've prepared for us.

Store them in an assets folder within the main root directory of the project.

Image

Cool! This is where we'll start.


Set Up the Scene

To begin, we need place the light source at the center of the solar system.

In 3D space, we have 3 axes: X, Y, and Z.

Image

Therefore the center point in 3D space is [0, 0, 0].

Try playing around with the perspective and axes on a 3D scene over here in an interactive guidebook (created by canvas-sketch creator Matt DesLauriers) to gain an understanding.

ThreeJS comes with all sorts of helpers to help aid in constructing 3D renderings.

Let's begin by placing a pointlight helper at [0, 0, 0], and creating a grid helper to help us see our horizontal plane.

In sketch.js, replace all of the code with the following:

This will result in a helper "diamond" at the origin to show us where our light is coming from, and a grid to show us our X axis.

Don't be overwhelmed. We will go through this boilerplate together.

In the mean time, you should see this:

Image

Let's dissect the code above to understand what we are doing to set this scene up.

  1. Imports & constants

    • Import Three.js, orbit controls and canvas-sketch.
    • Create constants for settings and screen dimensions.
  2. Renderer

    • From Three.js we use WebGLRenderer to set up the canvas element, as well as set the background color.
  3. Camera

    • For a scene, we need to set up the camera from which the user views the scene. Here we use a perspective camera, which means objects will morph slightly from our view point, just like in real life.
    • Position the camera in the scene with camera.position.set(0, 10, 30).
  4. Orbit Controls

    • Allow the user to move around the scene by dragging the canvas viewpoint.
    • 1-Finger click+drag = pivot around center.
    • 2-Finger click+drag = move center.
  5. Scene

    • The scene is everything you see, and everything you create must be added to this scene.
    • Notice that we ultimately use the renderer to render this scene object.
  6. Lighting

    • Add lights to the scene.
    • Here we add a point light to the center at [0, 0, 0], which projects light from that point out in every direction.
  7. Helpers

    • Point light helper
    • Grid helper.

These things are fairly straightforward, and not necessary to really dive deep into right now.

This article is long enough as it is, and you can always read the docs for each utility method in your own anyways.

Let’s build some planets!


Textures, Materials and Meshes

With this basic scene created, we can now begin by creating objects and adding them to our scene.

To create a planet, we must first create a sphere geometry.

We can do this using the SphereGeometry method from Three.js, passing values for the radius, width, and height of the sphere.

This geometry isn't the shape itself, but rather just the implementation of what the shape should be.

In order to create the shape and add it to the scene, we must pass this geometry object as a parameter to a new mesh.

A Mesh is an object that is constructed from the most primitive object in 3D land: the polygon.

To create our sphere, we will create a new mesh, passing in the geometry above as a parameter.

We can then set the position and scale of the mesh, and add it to our scene.

All together, update your code so that the sphere mesh is being constructed after the creation of the scene.

Now we have a white sphere on our plane:

Image

Now let's make this the planet Mercury!

Using the TextureLoader from Three, we will load the image of the equirectangular projection of Mercury as a texture.

With this texture we will then create a new MeshStandardMaterial, which allows us to map that image to a 3D object, like our sphere.

Finally we pass this material as a second parameter when creating the Mesh.

Now we have a sphere that looks like Mercury.

Cool.

You know what, let's add a little spin to this planet.

In the render return block of our main sketch() function, add this line to rotate the planet on it's y axis:

Image

Alright, making progress!

In case you encountered any issues, here is the code up to this point.

Next we are going to apply the same process to create and position the other 8 planets.

(Yes this includes our old friend Pluto).


Creating Groups & Adding the Planets

To recap the above section, our process for creating a planet is:

  1. Load a texture.
  2. Create a material using the texture.
  3. Create a mesh object using a geometry and the material.
  4. Position and scale the mesh object.
  5. Add the mesh object to the scene.

Let's run through this again with our next 2 planets from the Sun: Venus and Earth.

Image

Great, we've added 2 more planets!

But we have run into 2 problems.

  1. We are going to be creating a lot of repetitive code.
  2. How do we rotate the planets around a center point, ie. the Sun?

Let's start with the latter.

To rotate each planet around the origin [0, 0, 0], we must place each planet in a sort-of container called a Group.

We will amend our code to create a group, add the mesh to the group instead of the scene, and then add the group to the scene.

To show this works as we intend, let's add a rotation to the mercuryGroup above the rotation we added for the mercuryMesh.

Feel free to remove the grid helper at this point.

Image

Excellent.

Now let's write a function to generate planets so that we don't repeat ourselves so much...

... and use this by passing in the params for each planet.

Since everything repeats for each planet, I don't see the need to walk through them individually.

The values I am using for scaling the planets and positioning them in space are very much ad hoc, my goal being to just create something visually pleasing while ensuring the planets are smaller/bigger in relation to each other.

Update your code with the following to add all the planets, as well as the individual rotations and orbits around the center.

Image

Take your time with the code above.

Play around with the positioning in space, the scale, the rotations.

Have a little fun with it.


Creating the Sun

We are almost finished!

Last up is to create the Sun.

With all these new planets, let's first reposition the camera a bit along the x-axis so that we are centered to the scene rather than the origin.

Update the camera and orbit controls like so:

Image

Beautiful.

Now, the Sun☀️.

Up to this point we have been using the PointLightHelper to show us where our light is coming from.

However, what we want is to see a big, glowing sun there instead.

In the same way that we created the planets, so too shall we create the Sun.

The only difference this time is that the Sun does not need to be in a Group because the Sun is not rotating around another object; it is the object.

So, again, let's load the texture for the Sun, create a sunMaterial, create a sunMesh, and add it to the scene.

(Just add each sun-related line of code above that of Mercury)

And now we have the Sun added to our scene at the center of the solar system!

Image

Wait...

Why is the Sun a big black ball?

I'll tell you why!

This is happening because the pointLight we are using is within the Sun and projecting outwards.

Therefore, while our planets are lit, no light is being cast on the Sun itself, and therefore the sun is completely dark.

To hack this such that our sun is illuminated, we are going to add a bunch of spotlights pointing towards the origin.

We also have to make sure these spotlights are between the Sun and the first object, Mercury, so as to not light Mercury or the other planets unnaturally from both sides.👻

Under lighting, we are going to add a new spotlight, and as well as the SpotLightHelper, for the time being.

The Spotlight takes 4 parameters: color, intensity (of the light), distance (when the light ends), and angle.

Image

Using the SpotLightHelper, we are able to see where we have positioned the light source (25, 0, 0), the angle of the light, and the distance the light travels.

With this we have illuminated 1 side of the Sun only.

Now, treating this like a cube, we will illuminate the other 5 sides.

I wrote a function called createSpotlights to simplify this and keep the code DRY, so let's add it after the function createPlanets , and call it from the LIGHTING section by passing in the scene.

Now let's take a look at our big, beautiful Sun.

Image


Final Project Code

With everything in place, let's comment out the helpers, uncomment the rotations, and add a rotation for the Sun!

This will bring our final code for this project to this:

It's a big chunk of code, but after walking through it I hope it is a bit more digestible.

Here is our Solar System in action.

Image


What We Learned and Where to Go

A lot of code, though mainly due to the repetitive nature of creating 10 very similar objects.

What did we learn today?

  • How a 3D space works with 3 axes.
  • How to create a sphere geometry.
  • Creating a mesh: a 3D shape that can be added to a scene.
  • Adding a texture to a mesh.
  • Scaling meshes and positioning them in 3D space.
  • Using time to animate a mesh with a rotation.
  • Rotate a mesh around other points using Groups.

Want to learn more?

I'd highly recommend checking out Matt DesLauriers' course on FrontEnd masters to gain some beginner knowledge of WebGL & ThreeJS.

Learning how to create objects in 3D space is very useful to creating games.

Interested in making games? Poke around Construct and Godot.

See you next time ✌️.

Marshal Murphy 2020