Part 2: Day and Night Skies

The sky is rendered in two distinct layers. The atmosphere is rendered as a shaded dome. The sun, moon, and stars are rendered as bill-boarded sprites. As demo time passes, the sprite layer and the OpenGL directional light sources are rotated together about the origin, providing the illusion that the earth is rotating beneath a dynamic sky.

All of the sky is rendered all of the time. That is, there is no real distinction between the day and night skies. In particular, the stars are still out during the day, they're just overpowered by the blue sky. The only variable is the time of day. Here we examine this process in detail.

The Atmosphere Shader

Shaders must be applied to geometry, so to begin we define the geometry of the atmosphere. We use a simple geodesic dome centered about the viewer. Here we see it in wire-frame.

We want to color this dome based on the location of the sun. The fragment shader knows this location as it has access to the position of OpenGL light source 0, which gives the sun vector. At each fragment we also have the fragment's own position in the sky, which gives the view vector. All we need is a way to convert the relationship between these two vectors into color. Sky color has two components: one that varies with proximity to the horizon, and another that varies with proximity to the sun. Both of these components vary with time of day.

To determine the horizon proximity color we reference a 2D texture. Our texture coordinates are given by the vertical position of the sun and the vertical position of the current fragment. Note that the vertical position of the sun indirectly indicates the time of day. The texture map appears as follows (the checkerboard indicates transparency):

Similarly, to determine the sun proximity color we reference another 2D texture. This time, our coordinates are given by the vertical position of the sun (again, time) and the dot product of the fragment vector with the sun vector. This dot product gives a large value near the sun, and a small one far away, and thus models a glow centered at the sun position.

The sun glow texture gives us the opportunity to tweak the glow over time. In particular, as the sun nears the horizon the glow expands and reddens, providing a convincing sunrise-sunset effect.

Both of these textures can be understood as horizon and glow colors over time. The left edge of each texture is midnight and the right edge is noon. Our texture reference proceeds left to right between midnight and noon, and right to left between noon and the following midnight.

The vertex shader simply passes along the one piece of information that the fragment shader doesn't already have: the position of the fragment.

varying vec3 vertex;

void main()
{
    vertex      = gl_Vertex.xyz;
    gl_Position = ftransform();
}

The fragment shader takes it from there, making the texture look-ups and blending them to form the final pixel. The alpha value of the horizon color map is preserved in order to ensure correct blending with the sprite layer, described below.

uniform sampler2D glow;
uniform sampler2D color;

varying vec3 vertex;

void main()
{
    vec3 V = normalize(vertex);
    vec3 L = normalize(gl_LightSource[0].position.xyz);

    // Compute the proximity of this fragment to the sun.

    float vl = dot(V, L);

    // Look up the sky color and glow colors.

    vec4 Kc = texture2D(color, vec2((L.y + 1.0) / 2.0, V.y));
    vec4 Kg = texture2D(glow,  vec2((L.y + 1.0) / 2.0, vl));

    // Combine the color and glow giving the pixel value.

    gl_FragColor = vec4(Kc.rgb + Kg.rgb * Kg.a / 2.0, Kc.a);
}

The Sprite Layer

The night sky is complex and can be challenging to render. A common approach is to render the sky as a cube map. This is limited by resolution, which raises a quality-speed trade-off. Here we use a technique that provides both quality and speed, at the cost of a fair amount of overdraw.

For starters, the sun and moon are easy. They are simple textured quads rotated to face the viewer. The trick is to realize that this technique can also be applied to the stars. Apparent randomness stems from the random distribution, rotation, and scale of a single, relatively small texture. Here we see the star sprites with their outlines highlighted.

The Combination

The atmosphere dome and sprite layer are simply alpha blended. The net effect is this during the day...

... and this during the night, with a graceful transition between the two.

[Introduction] [Part 1: Scene Geometry] [Part 2: Day and Night Skies] [Part 3: Illumination] [Part 4: Added Detail]