Making This Amazing Earth Effect with JavaScript and WebGL
By: Johnny Simpson
Tags: 3d, JavaScript, Threejs, Web Development, Web Design

Step 1: NASA, no, really

So the first thing you are going to want to do is go to NASA and find the height map they have of earth. NASA actually have a number of great data resources on earth, like ocean depth maps, but perhaps that is for a future project.

Next step is to remove the oceans. Easier said than done — I had to find the aforementioned ocean map and subtract the oceans, as at sea level the NASA height map is entirely black, making it hard to differentiate. I did this in Photoshop, but it’s also available in the git repository, so you don’t even have to do this.

But why do we need this map?

So the darkness of every pixel on this map represents how close it is to sea level. We can take that data, and using shaders, translate the pixels to a different height. We can make mountains higher, and earth lower, and we can even use this data to change the colour of the pixels. Pretty cool, right?

Step 2: Three.js

Three.js is a 3D WebGL package that helps you do WebGL a lot easier. What is WebGL? It’s the way we make 3D things on the web, and it’s a web standard.

How do we create a sphere with our earth texture?

First we need to import the appropriate packages from three.js. You can create a new file called script.js and add the below code. Note — import only works on a server, so you can’t just open index.html and expect this to work, you need to run it on your localhost.

Alright, now let’s get started. All of this is going to start after the DOM has loaded, but we’ll use three to build. I haven’t included all the code below — you can find that in the codepen or git repo — but a few key points to note:

On line 13, I have set the number of polygons to 1024. This takes a little time to load, but for the effect we want, the more polygons, the better. You can reduce 1024 for faster loading during testing if you like.

I have set the number of polygons to 1024. This takes a little time to load, but for the effect we want, the more polygons, the better. You can reduce 1024 for faster loading during testing if you like. On line 15 , we mention ‘uniforms’. These are variables we can edit in Javascript, but are passed to the shaders. Shaders are what we’ll use to make the height maps we talked about in a moment.

, we mention ‘uniforms’. These are variables we can edit in Javascript, but are passed to the shaders. Shaders are what we’ll use to make the height maps we talked about in a moment. On line 19 we mention the texture, which is our picture of earth.

we mention the texture, which is our picture of earth. On line 21/22 we mention the shaders. These are in the HTML document, and are written in a C-like language

we mention the shaders. These are in the HTML document, and are written in a C-like language On line 45 we update the rotation of the sphere. This happens in real time, and you can update pretty much any property you want from Javascript giving you a lot of flexibility.

Step 3: Shaders

Shaders are the part most Javascript developers may struggle with. They have their own language, and require a little research to master, but they are stored in the HTML document within <script> tags, which should be familiar.

Suffice to say, we have two shaders — vertex shaders, which alters every pixel on the shape, and fragment shaders, which alter the color of the pixels.

Our vertex shader is relatively straight forward

On line 1 , we have vUv, which is the coordinate of the pixel we are currently at. We can do vUv.x and vUv.y to get our (x, y) coordinates

, we have vUv, which is the coordinate of the pixel we are currently at. We can do vUv.x and vUv.y to get our (x, y) coordinates On line 7 , we import the texture from our ‘getTexture1’ variable. This was defined as a uniform in our Javascript variable before (see above). We can get a lot of data from this — the x, y, and z coordinates, and the r, g, b and a color details. In shaders, each color is out of 1, so expect a number between 0 and 1.

, we import the texture from our ‘getTexture1’ variable. This was defined as a uniform in our Javascript variable before (see above). We can get a lot of data from this — the x, y, and z coordinates, and the r, g, b and a color details. In shaders, each color is out of 1, so expect a number between 0 and 1. On line 8 , we take the red data, and the alpha data, and multiply it by 20. What does that do? It calculates how red a point is (in our case indicating how black it is), and how transparent it is. So transparent elements will be 0, and displacement will equal zero overall. For very white areas, bumpData will be 1, which will give a max value of 20.

, we take the red data, and the alpha data, and multiply it by 20. What does that do? It calculates how red a point is (in our case indicating how black it is), and how transparent it is. So transparent elements will be 0, and displacement will equal zero overall. For very white areas, bumpData will be 1, which will give a max value of 20. On line 12, we pass our ‘height’ data into gl_Position, which essentially stores the final position of all pixels being passed to the GPU.

Okay — so now all our pixels are positioned correctly, lets color them correctly, with a fragment shader. We’re going to use the same tricks as before, the only complexity is our Javascript colors from the uniform values were rgb values out of 255 — so we have made a function which divides them all by 255 to get a value out of 1. We also return a different color for entirely transparent pixels (i.e. water).

Conclusion

And there we have it, an icy world that rotates with artificially high peaks. To learn more, you can check out the three.js documentation. I would also recommend updating some of the numbers and the uniform color variables in Javascript to see what you end up with. Another cool trick you can do is turn the bloom up to max to get some pretty trippy results

Relevant Links