z0b's realm

Sunlight maps

Ever wondered how to draw a sunlight map? They're those maps that show the day and night areas of the Earth's surface. I'll show you how to create them. I'm assuming you already know the sun's latitude and longitude.

Basic idea

First you take the sun's lat/lon position and use spherical mapping to project it to a 3D point.

To lit a 3D sphere, compute surface normal for every point on the sphere. Then take the dot product between this normal and the 3D light position. This number tells how much light that point receives. Shade the pixel accordingly.

To do this in 2D, treat the X axis ranging from -2π to 2π and Y axis from -π to π. Use spherical mapping again to obtain a 3D point, take the dot product and finally shade.

Software (C++)

C++ shading map

A straightforward C++ implementation. Pass in the light lat/lon position, the desired size of the output image and a minimum light level, and it returns you an 8-bit shade map you can use to adjust pixel brightnesses or mix two images or whatever you want to do. The code is heavily commented.

Hardware (GLSL)

GLSL shading map

This is a pretty straightforward (and unoptimized) GLSL fragment shader conversion of the C++ code. Unsurprisingly, it utterly demolishes the C++ version in speed. The downside is you can't easily get the output back.

This could be optimized somewhat if you don't need to texture the output. In that case, adjust the texture coordinates (see below).

A live WebGL demo is available here. It uses the above two shaders, but in slightly modified form. This is because WebGL uses different coordinate system (MVP matrix is not needed) and it wants precision modifiers.

To use this, draw a full-screen quad (I think it should work on 3D surfaces, but I haven't tested) with proper texture coordinates, like this (a full-screen quad):

const float w = 512.0f,
            h = 256.0f;

const float vertices[] =
{
    0.0f, 0.0f, 0.0f, 0.0f,
       w, 0.0f, 1.0f, 0.0f,
       w,    h, 1.0f, 1.0f,
    0.0f,    h, 0.0f, 1.0f,
};

...

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

...

glBindBuffer(GL_ARRAY_BUFFER, vbo);

glUseProgram(shadeMap);
GLint pos = glGetAttribLocation(shadeMap, "vertex");

glEnableVertexAttribArray(pos);
glVertexAttribPointer(pos, 4, GL_FLOAT, GL_FALSE, sizeof(float) * 4, (GLvoid *)0);

// shader parameters omitted here
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

// cleanup omitted

It should work on 3D rectangles, just set the texture coordinates correctly.

The texture coordinates are important, because they're used to compute the 3D position. If you don't want to texture map (or you get the coordinates elsewhere), you can change them from -2PI to 2PI (S) and -PI to PI (T); then you can rewrite the shader to use the coordinates directly. This should make it slightly faster. Another idea would be to precompute the X, Y and Z positions for the entire viewport and store them in a normal map. This would reduce the drawing complexity to just one dot product per pixel.

Parameters

The following parameters control the shader:

  • tex: The texture you want to shade. Remove if you don't want texturing.
  • color: Used to colorize the final output (essentially emulates glColor3f). Usually set to (1.0, 1.0, 1.0, 1.0).
  • lightPos: vec3 of the light position, the C++ version shows how to calculate it from lat/lon coords.
  • minLight: Minimum light level. Can be used to prevent fully black areas. Good values are between 0.1 and 0.3.
  • mvp: ModelViewProjection matrix, usually just an orho matrix for 2D projection.