I'm looking for this kind of effect MINUS the lights and snow (Another problem).

  • It needs to change depending on the time of year.
  • Doesn't need snow or city lights.

Now I'm pretty new to shaders (learnt them yesterday in my spare time) but so far I have achieved:

What I have so far

This moves across the screen display light on both sides.

Now I'm completely lost, as to how I can make it seem like a light map I.E. it needs to be more square/sharper (As it's going off the edges) & have the bottom and top times of year.. I though maybe I could pass sphere mesh to the vertex? Or do something with the map-normal. Or maybe use blending with two textures but I've looked around and it looks extremely difficult.

Code I have so far:

Fragment / Pixel shader:

    //attributes from vertex shader
varying vec4 vColor;
varying vec2 vTexCoord;

//our texture samplers
uniform sampler2D u_texture;   //diffuse map
uniform sampler2D u_normals;   //normal map

//values used for shading algorithm...
uniform vec2 Resolution;      //resolution of screen
uniform vec3 LightPos;        //light position, normalized
uniform vec4 LightColor;      //light RGBA -- alpha is intensity
uniform vec4 AmbientColor;    //ambient RGBA -- alpha is intensity 
uniform vec3 Falloff;         //attenuation coefficients
uniform float lightX;         //X Position of light. Can also feed Y for times of the year.

void main()
    //RGBA of our diffuse color
    vec4 DiffuseColor = texture2D(u_texture, vTexCoord);

    //RGB of our normal map
    vec3 NormalMap = texture2D(u_normals, vTexCoord).rgb;

    int numberOfLights = 3;
    vec3 lightPoses[3];
    vec3 lightDirections[3];
    //LightX goes to 0, then back to 1.
    lightPoses[0] = vec3(lightX - 1.0, LightPos.y, LightPos.z);
    lightPoses[1] = vec3(lightX, LightPos.y, LightPos.z);
    lightPoses[2] = vec3(lightX + 1.0, LightPos.y, LightPos.z);

    //TODO Introduce one extra light for top and bottom. OR Figure out how to squash the Y.
    //TODO Needs to be sharper light.

    vec3 Sum = vec3(0.0);

    //Go though both lights.
    for(int index=0; index < numberOfLights; index++)
        //The delta position of light
        vec3 LightDir = vec3(lightPoses[index].xy - (gl_FragCoord.xy / Resolution.xy), LightPos.z);

        //Correct for aspect ratio
        LightDir.x = LightDir.x * (Resolution.x / Resolution.y);

        //Make it bigger. (smaller the value the bigger.)
        LightDir *= vec3(0.55, 0.4, 1.0);

        //Determine distance (used for attenuation) BEFORE we normalize our LightDir
        float D = length(LightDir);

        //normalize our vectors
        vec3 N = normalize(NormalMap * 2.0 - 1.0);
        vec3 L = normalize(LightDir);

        //Pre-multiply light color with intensity
        //Then perform "N dot L" to determine our diffuse term
        vec3 Diffuse = (LightColor.rgb * LightColor.a) * max(dot(N, L), 0.0);

        //pre-multiply ambient color with intensity
        vec3 Ambient = AmbientColor.rgb * AmbientColor.a;
        //Because there are more lights, take off total ambient power.
        Ambient *= vec3(1.0 / float(numberOfLights));

        //Calculate attenuation (The amount of fade the light has.)
        float Attenuation = 1.0 / (Falloff.x + (Falloff.y*D) + (Falloff.z*D*D));

        //the calculation which brings it all together
        vec3 Intensity = Ambient + Diffuse * Attenuation;

        vec3 FinalColor = DiffuseColor.rgb * Intensity;

        Sum += FinalColor;

    gl_FragColor = vec4(Sum, DiffuseColor.a);

Vertex Shader:

//combined projection and view matrix
uniform mat4 u_projTrans;

//"in" attributes from our SpriteBatch
attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;

//"out" varyings to our fragment shader
varying vec4 vColor;
varying vec2 vTexCoord;

void main() {
    vColor = a_color;
    vTexCoord = a_texCoord0;
    gl_Position = u_projTrans * a_position;
  • \$\begingroup\$ Have you looked into this book? it provides an example of drawing the planet and simulating lighting and clouds. I suggest that you start from there. \$\endgroup\$
    – glampert
    May 25, 2015 at 14:12
  • \$\begingroup\$ Problem is I'm trying to do this in 2D, I could do it in 3D but that's not the challenge and that would be easy from the looks of it. Everywhere I look it's 3D. I need a 2D example :-/ \$\endgroup\$ May 25, 2015 at 17:54

1 Answer 1


What you need is to retroproject the point on your map, from the inverse Mercator into 3d space, then place the sun as a directional light (not point like your image shows), and adjust the point for earth tilting (according to earth-sun rotation plane), then you can evaluate the light.

It will automatically give you the weird valley shape that we see in airplanes navigation screen, and the little gif you point to.

You don't need to actually have 3d data. but you need to do 3d calculations in the shader. This is going to be a bit tad hard to debug because the 3d elements you will manipulate never have a visual representation. So the only way is to get the math right by trial error.

You could only avoid going to 3d space by writing the formulas created by the steps described above, and compacting the whole sequence in just one formula. It would have the same effect, but it needs harder algorithm planning (e.g. on paper).

  1. take fragment positions (screen pos, via gl_fragcoord or something)
  2. retro project on the sphere with reverse mercator.
  3. place in sun-space. (e.g. by using a position matrix for the earth)
  4. usual directional light compute (n dot l)
  5. write out color


I did it for you

uniform float time_0_X;    // day/night cycle
uniform vec2 fInverseViewportDimensions;

#define PI 3.1415926;

void main(void)
   // 2d coordinates from the screen. but you should take them from your quad's UV.
   vec2 coord = 0.5 * vec2(gl_FragCoord.x + 0.5, gl_FragCoord.y + 0.5) * fInverseViewportDimensions;

   float earthTilt = radians(15);   // somewhere around spring ?

   // transform into cylindrical coordinates:
   vec2 angles = vec2(coord.x * 2, coord.y) * PI;
   angles.y += earthTilt;  // apply as an offset. this is the easiest way to go.
   angles.x += time_0_X;
   vec4 sincos;
   // get sincos of both angles for later use.
   sincos.xy = sin(angles);
   sincos.zw = cos(angles);

   // make an unitary vector in the direction of the 2 angles. start pointing to z. (usual convention)
   vec3 dir = vec3(0, 0, 1);
   // make the first rotation matrix about Y axis.
   mat3 rotmat = mat3(sincos.z, 0, -sincos.x,
                      0, 1, 0,
                      sincos.x, 0, sincos.z);
   // rotate dir around Y
   dir = rotmat * dir;
   // make an orthogonal, still on the XZ plane.
   vec3 dirortho = vec3(dir.y, -dir.x, 0);
   // we are going to need to rotate about this "dirortho" axis by angles.y.
   // make a rotation about X that corresponds to the same angle:
   mat3 rotx = mat3(1, 0, 0,
                    0, sincos.w, sincos.y,
                    0, -sincos.y, sincos.w);
   // make a vertical:
   vec3 vert = vec3(0, 0, 1);
   // we have a TBN basis, make a matrix out of it to change space into this new rotated space.
   mat3 tbn = mat3(dirortho, dir, vert);
   // change the rotx matrix into this new tbn base:
   mat3 rotaboutortho = rotx * tbn;
   // finally:
   dir = rotaboutortho * dir;

   // now light it:

   vec3 sunlight = vec3(1,1,1); // white
   vec3 lightdir = vec3(0,0,1);  // come from somewhere in the plane xz

   // lambert irradiance evaluator, not normalized by PI (non physical based):
   float lambert = dot(lightdir, dir);  // dir is also the normal at your point.

   // do a trick here, we are going to amplify and saturate the lambert factor to avoid to create smooth regions
   // and make a hard gradient from day to night.
   float hard = clamp(lambert * 1000, 0, 1);
   vec3 color = sunlight * hard;

   gl_FragColor = vec4(color, 0);

You can put this in rendermonkey if you want to try. I used the GL2, screenspace samples, "nightsky" effect as a base.
The code in its current state is giving a wave of black and white, I'm not sure about the degree of correctness but it seems to look quite reasonable.
You notice I didn't use inverse mercator, but inverse cylindrical, because from the look of your earth texture, Greenland is quite compressed so it must be a cylindrical projection and not mercator.

  • \$\begingroup\$ Hm looks like I'll be going back to the tutorials, could you point me in the right direction? (Seems like a mammoth task since I'm new to Shaders & Math.) \$\endgroup\$ May 26, 2015 at 10:45
  • 1
    \$\begingroup\$ @iLoveUnicorns there are no tutorial that will teach you that. You already know enough if you have been able to create a texture + point light shader. This thing is purely on the math side. If you are new to linear algebra, that is going to be tough indeed. You need to be at ease with base change, matrices, and 3d vector space in general. If you are stuck, I advise to change strategy and use a mask texture that looks like the shape you need. You edit it in gimp/paint whatever. It will be valid only for 1 axial tilt and 1 orbital position though. enc.tfode.com/Axial_tilt \$\endgroup\$
    – v.oddou
    May 27, 2015 at 1:31
  • \$\begingroup\$ If you need seasons to vary, you will need to edit multiple of those textures, and create a volume map (3d texture), so the depth coordinate can be the seasonal value. \$\endgroup\$
    – v.oddou
    May 27, 2015 at 1:32
  • \$\begingroup\$ Hello, it compiles but now I'm just getting a red screen, any ideas? (No texture is being passed?) \$\endgroup\$ May 31, 2015 at 9:07
  • 1
    \$\begingroup\$ Debugging shaders is hard, one way is to progressively output values as color by inserting early return and mapping expected values of the visualized variable to 0-1. for example to display a unitary vector (like a normal or dir here) do return vec4(dir *0.5 + 0.5 ,1) this kind of things. bluish are directions towards z, redish toward x... scalars can be visualized as shades of grey etc etc. And then, progressively step down the shader code until finding where unexpected values happens. \$\endgroup\$
    – v.oddou
    Jun 1, 2015 at 1:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .