Lesson 2 of 10 · The Solar System in GLSL
Mercury
Grey rock, no air. You move the sun.
The Sun gave light; Mercury takes it. The trick: dot(n, sun). If the normal faces the sun it is 1; sideways, 0; facing away, negative, and max(..., 0.0) turns that into night. It is Lambert's law, the same as in Ray Marched, now with the sun on the pointer.
Mercury has no atmosphere: nothing carries light to the dark side, and night is black. By day it reaches 430 °C; by night it drops to −180 °C. One solar day there lasts 176 Earth days. It reflects 14% of the light: 0.13 in the code.
The craters are a 3D Voronoi: crater() gives the distance to the nearest impact divided by its radius. Under 1, floor; right at 1, rim. Here they are colour only: relief comes with Mars. Move the pointer over the planet. Below: Spin, Craters and Sun.
A rental bowling ball. With everyone's dents.
dot(n, sun) = how much sun the point gets. Below 0 = night. No air = black night. s < 1 = inside a crater.craters from 5.0 to 11.0: meteor shower.The code
precision highp float;
// The exact sphere: how far along the ray it is hit, or -1 if the ray misses
float sphere(vec3 ro, vec3 rd, float r){
float b = dot(ro, rd), c = dot(ro, ro) - r*r, h = b*b - c;
return h < 0.0 ? -1.0 : -b - sqrt(h);
}
// WoGL close: knee at 0.70, sRGB, chroma 1.3
vec3 wogl(vec3 c){
float l = dot(c, vec3(0.2126, 0.7152, 0.0722)), k = max(l - 0.7, 0.0);
c *= l > 0.0 ? (l - k + k/(1.0 + k))/l : 0.0;
c = pow(clamp(c, 0.0, 1.0), vec3(0.4545));
l = dot(c, vec3(0.2126, 0.7152, 0.0722));
return clamp(mix(vec3(l), c, 1.3), 0.0, 1.0);
}
// Noise in 3D: the usual fb with one more dimension. On a globe it leaves no seam.
float h3(vec3 p){ return h2(p.xy + p.z*vec2(37.0, 17.0)); }
float n3(vec3 p){
vec3 i = floor(p), f = fract(p);
f = f*f*(3.0 - 2.0*f);
return mix(mix(mix(h3(i), h3(i + vec3(1,0,0)), f.x), mix(h3(i + vec3(0,1,0)), h3(i + vec3(1,1,0)), f.x), f.y),
mix(mix(h3(i + vec3(0,0,1)), h3(i + vec3(1,0,1)), f.x), mix(h3(i + vec3(0,1,1)), h3(i + vec3(1,1,1)), f.x), f.y), f.z);
}
float f3(vec3 p){
float v = 0.0, a = 0.5;
for (int i = 0; i < 5; i++){ v += a*n3(p); p = p*2.03 + vec3(1.7, 9.2, 4.1); a *= 0.5; }
return v;
}
// A 2D turn (to spin the globe about its axis)
mat2 turn(float a){ float c = cos(a), s = sin(a); return mat2(c, -s, s, c); }
// The background: black, with a star now and then
vec3 sky(vec2 f){
float s = h2(floor(f));
return vec3(0.0002, 0.0003, 0.0006) + vec3(0.7, 0.7, 0.8)*step(0.9982, s)*h2(floor(f) + 3.1);
}
// Craters: Voronoi in 3D. Each cell holds one impact of its own size.
float crater(vec3 x){
vec3 g = floor(x), f = fract(x);
float best = 9.0;
for (int k = -1; k <= 1; k++) for (int j = -1; j <= 1; j++) for (int i = -1; i <= 1; i++){
vec3 o = vec3(float(i), float(j), float(k));
float h = h3(g + o);
vec3 r = o + vec3(h, h3(g + o + 3.7), h3(g + o + 9.1))*0.8 + 0.1 - f;
best = min(best, length(r)/(0.12 + 0.34*h*h*h)); // few large, many small
}
return best; // less than 1 = inside a crater
}
float spin = 0.10; // how fast it turns
float craters = 5.0; // craters per radius
float light = 3.2; // the sun's strength
void main(){
vec2 p = (gl_FragCoord.xy*2.0 - R)/min(R.x, R.y);
vec3 ro = vec3(0.0, 0.0, 3.2), rd = normalize(vec3(p, -2.4)); // the camera looks at the center
vec3 sun = normalize(vec3(M.x*4.0 - 3.8, M.y*3.0 - 1.0, 1.0)); // you move the sun: it is the pointer
vec3 col = sky(gl_FragCoord.xy);
float t = sphere(ro, rd, 1.0);
if (t > 0.0){
vec3 n = normalize(ro + rd*t);
vec3 q = n; q.xz = turn(T*spin)*q.xz; // the point, on the turning planet
float s = crater(q*craters);
float rock = 0.13*(0.7 + 0.6*f3(q*4.0)); // Mercury's albedo: 0.14
rock *= 1.0 - 0.16*smoothstep(0.85, 0.55, s); // the crater floor
rock *= 1.0 + 0.22*smoothstep(0.18, 0.0, abs(s - 1.0)); // the rim
rock *= 1.0 + 0.10*smoothstep(1.9, 1.0, s)*step(1.0, s); // what flew out
float lambert = max(dot(n, sun), 0.0); // how much sun this point gets
col = vec3(1.0, 0.94, 0.86)*rock*lambert*light;
}
gl_FragColor = vec4(wogl(col), 1.0);
}