Shortcuts ⌨️

  • SPACE to play / pause
  • ARROW RIGHT or L to go forward
  • ARROW LEFT or J to go backward
  • ARROW UP to increase volume
  • ARROW DOWN to decrease volume
  • F to toggle fullscreen
  • M to toggle mute
  • 0 to 9 to go to the corresponding part of the video
  • SHIFT + , to decrease playback speed
  • SHIFT + . or ; to increase playback speed

Unlock content 🔓

To get access to 93 hours of video, a members-only Discord server, subtitles, lesson resources, future updates and much more join us for only $95!

Want to learn more? 🤘


That's the end of the free part 😔

To get access to 93 hours of video, a members-only Discord server and future updates, join us for only $95!

Next lesson

Introduction 00:00

Creating a realistic Earth might sound simple. Create a sphere, slap a nice Earth texture on it and we’re good to go, right?

Unfortunately, there is a lot more going on:

  • Cities in the dark side are illuminated
  • There are clouds
  • The Sun’s reflection is mostly visible on the oceans, not on the continents
  • The part between the day and the night called twilight looks red-ish
  • The atmosphere creates a glow all around the Earth, feeling like a volume

We are not going for a physics-based rendering with complex scientific formulas, but that won’t prevent the final result from looking good and realistic.

Setup 02:09

The starter already contains the following:

  • A well-subdivided sphere rotating slowly
  • A basic shader is already included in the src/shaders/earth/ folder with a vertex.glsl and a fragment.glsl
  • An instance of lil-gui with no tweak yet
  • The vite-plugin-glsl dependency to handle GLSL files
  • OrbitControls to rotate around
  • A TextureLoader instance

As for the shader itself:

  • The uv is sent to the fragment as vUv and displayed on gl_FragColor
  • The modelPosition is sent to the fragment as vPosition and used to calculate the viewDirection variable (vector going from the camera to the fragment position)
  • The transformed normal is sent to the fragment as vNormal and normalized again as normal to prevent grid artifacts

It’s quite a lot for a starter, but those are concepts we have already learned and practiced a few times in previous lessons.

Earth textures 05:08

In this lesson, we are going to use Earth textures. Yet, the code can easily be applied to other planets and even exotic or procedurally generated ones.

We are going to use the textures you can find on Solar System Scope. It’s a website providing various planet textures under CC Attribution 4.0 license, which requires providing appropriate credit. If you put the website live, make sure to show those credits somewhere on the page.

The Earth textures are already available in the static/earth/ folder.

The day.jpg and the night.jpg files are two separated textures. We are going to send both to the shader and mix between them according to the orientation of the sun. They have been encoded in sRGB.

The specularClouds.jpg contains the specular texture (where it’s reflective) into the red channel and the clouds texture into the green channel. Combining data like this reduces the amount of memory we allocate to the GPU. The texture is encoded in linear.

All textures have been resized to a 4096x2048 resolution and compressed for the web.


Let’s load all three textures.

At the beginning of the Earth section, use the textureLoader to load() the following textures:

  • ./earth/day.jpg
  • ./earth/night.jpg
  • ./earth/specularClouds.jpg
 * Earth
// Textures
const earthDayTexture = textureLoader.load('./earth/day.jpg')
const earthNightTexture = textureLoader.load('./earth/night.jpg')
const earthSpecularCloudsTexture = textureLoader.load('./earth/specularClouds.jpg')

The day.jpg and night.jpg have been encoded in sRGB since they will be used for display and we need to inform Three.js of that.

Update their colorSpace to THREE.SRGBColorSpace:

// Textures
const earthDayTexture = textureLoader.load('./earth/day.jpg')
earthDayTexture.colorSpace = THREE.SRGBColorSpace

const earthNightTexture = textureLoader.load('./earth/night.jpg')
earthNightTexture.colorSpace = THREE.SRGBColorSpace

const earthSpecularCloudsTexture = textureLoader.load('./earth/specularClouds.jpg')

Send all three of them to the earthMaterial as the following uniforms using the Uniform class:

  • uDayTexture
  • uNightTexture
  • uSpecularCloudsTexture
const earthMaterial = new THREE.ShaderMaterial({
    // ...
        uDayTexture: new THREE.Uniform(earthDayTexture),
        uNightTexture: new THREE.Uniform(earthNightTexture),
        uSpecularCloudsTexture: new THREE.Uniform(earthSpecularCloudsTexture)

In fragment.glsl, retrieve all three uniforms as sampler2D:

uniform sampler2D uDayTexture;
uniform sampler2D uNightTexture;
uniform sampler2D uSpecularCloudsTexture;

Day / night color 13:02

We want to show the uDayTexture on the side of the earth facing the sun and the uNightTexture on the other side.

First, set the color to vec3(0.0):

void main()
    // ...
    vec3 color = vec3(0.0);

    // ...

Pick the color on the uDayTexture using the texture() method and the vUv, then do the same with the uNightTexture. We only need the rgb channels and we can save those in vec3 variables:

void main()
    // ...

    // Day / night color
    vec3 dayColor = texture(uDayTexture, vUv).rgb;
    vec3 nightColor = texture(uNightTexture, vUv).rgb;

    // Final color
    // ...

Let’s try the dayColor on the color:

void main()
    // ...

    // Day / night color
    vec3 dayColor = texture(uDayTexture, vUv).rgb;
    vec3 nightColor = texture(uNightTexture, vUv).rgb;
    color = dayColor;

    // ...

Want to learn more?

That's the end of the free part 😔

To get access to 93 hours of video, a members-only Discord server and future updates, join us for only $95!

How to use it 🤔

  • Download the Starter pack or Final project
  • Unzip it
  • Open your terminal and go to the unzip folder
  • Run npm install to install dependencies
    (if your terminal warns you about vulnerabilities, ignore it)
  • Run npm run dev to launch the local server
    (project should open on your default browser automatically)
  • Start coding
  • The JS is located in src/script.js
  • The HTML is located in src/index.html
  • The CSS is located in src/style.css

If you get stuck and need help, join the members-only Discord server: