Moon Demo Screenshot


A while back I wanted to learn a bit about GLSL and how to write shaders. I already had some experience with Three.JS and using its WebGL renderer and I knew that it allowed custom shaders to be utilized through its ShaderMaterial object. I read up a bit on GLSL shaders and how, they worked. One tutorial I followed to write my first basic shader was An Introduction to Shaders written by Paul Lewis.

After I was able to recreate the shaders in the tutorial and apply them to a mesh in a Three.JS scene, I wanted to do something a little more involved. I wondered about applying lighting per pixel using GLSL shaders. I decided I could try to make a 3D demo of the Moon and use lighting shaders to simulate the Moon changing phases. I wanted my demo to be as photorealistic as possible.

You can check out the live demo, or view the source code on GitHub to check out the result.

The Hunt

In order to create a photorealistic demo I set out to find the highest quality public domain maps of the Moon available. I found out that there is good data published through the Map a Planet initiative and available on the USGS PDS site. The best data we have for our Moon was actually gathered by the Clementine spacecraft. It is possible to process a greyscale image of the entire surface of our Moon using this data, and as far as I know the best maps of the lunar surface we have today were derived from this data. I was delighted to know that all this great data available from USGS is public domain.

The best map I could find was processed by Jens Meyer and apparently also darkened up by Steve Albers. It and other high quality maps can be found on Steve Albers homepage and available free for personal non-commercial use. I say this map is the best in the sense that it has the highest resolution and detail compared to other maps I could find.

The original resolution of the map found on Steve Albers' homepage is 8192x4096. I am using a version scaled down in both width and height by 50% giving a texture at resolution 4096x2048. I find this size suitable for my demo.

My scaled down version is shown here:

Scaled Moon Map

Just Need A Few More Things

After this I generated 6 randomized subtle star patterns in order to create a a starry skybox for my scene. I also used the NVIDIA Normal Map Filter in order to generate a normal map for the Moon map.

Scaled Moon Normal Map

The Shaders

Here is the vertex shader which creates a Tangent-Binormal-Normal matrix for each vertex, passing it to the fragment shader.

attribute vec4 tangent;

uniform vec2 uvScale;
uniform vec3 lightPosition;

varying vec2 vUv;
varying mat3 tbn;
varying vec3 vLightVector;

void main() {
    vUv = uvScale * uv;

    // Create the Tangent-Binormal-Normal Matrix used for transforming
    // coordinates from object space to tangent space
    vec3 vNormal = normalize(normalMatrix * normal);
    vec3 vTangent = normalize( normalMatrix * );
    vec3 vBinormal = normalize(cross( vNormal, vTangent ) * tangent.w);
    tbn = mat3(vTangent, vBinormal, vNormal);

    // Calculate the Vertex-to-Light Vector 
    vec4 lightVector = viewMatrix * vec4(lightPosition, 1.0);
    vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
    vLightVector = normalize( -;

    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);

The frament shader is passed the TBN matrix for each vertex and uses it to transform the vectors from the normal map to tangent space.

uniform sampler2D textureMap;
uniform sampler2D normalMap;
varying vec2 vUv;
varying mat3 tbn;
varying vec3 vLightVector;

void main() {
    // Transform texture coordinate of normal map to a range (-1.0, 1.0)
    vec3 normalCoordinate = texture2D(normalMap, vUv).xyz * 2.0 - 1.0;

    // Transform the normal vector in the RGB channels to tangent space
    vec3 normal = normalize(tbn * normalCoordinate.rgb);

    // Intensity calculated as dot of normal and vertex-light vector
    float intensity = max(0.07, dot(normal, vLightVector));

    // Adjustments to alpha and intensity per color channel may be made
    vec4 lighting = vec4(intensity, intensity, intensity, 1.0);

    // Final color is calculated with the lighting applied
    gl_FragColor = texture2D(textureMap, vUv) * lighting;

blog comments powered by Disqus