Lesson 1 of 10 · The Solar System in GLSL
The Sun
An exact sphere, no marching. Brighter at the center than at the edge.
In Ray Marched you reached the sphere in steps. A sphere has a closed form: the ray hits it where t² + 2bt + c = 0. The trick: if b*b - c is negative, the ray misses; if not, t = -b - sqrt(b*b - c). One calculation, zero steps.
A sphere at the origin has the point itself as its normal. mu is the cosine between the normal and the ray: 1 at the disc's center, 0 at its edge. At the edge you see higher, cooler layers of the photosphere, 5,772 K on average. That is why it dims: 1 - limb*(1 - mu), with limb at 0.6 for visible light.
On top, granulation: the Voronoi of 04:10, flattened onto the globe. Each cell is rising gas, about 1,000 km across, lasting 8 to 20 minutes. Off the disc, near measures the closest the ray came to the center: the corona lives there. Below are Limb, Cells and Corona.
A pot of oatmeal at a boil, seen from above.
b*b - c < 0 = the ray misses. mu = cosine between normal and ray. 1 - limb*(1 - mu) = the limb's law. near > 1 = outside, in the corona.float sphere(vec3 ro, vec3 rd, float r): how far along the ray it meets a sphere of radius r, or −1 if it misses. Every body in this section is drawn with it.float t = sphere(ro, rd, 1.0): the hit. If t > 0.0, the pixel is on the Sun.ro + rd*t: the point where the ray lands, in 3D.vec3 n = normalize(ro + rd*t): that point’s normal. On a sphere at the origin, it is the point itself.vec3 wogl(vec3 c): the WoGL close. It fits the light into the screen and sets the color; every piece in this section ends with it.float limb = 0.60;: the numbers at the top are the sliders. Each one moves one of them.limb from 0.60 to 0.0: a paper disc. Raise it to 1.0: the whole edge goes dark. Take cells from 26.0 to 40.0: puffed rice.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);
}
// Granulation: the Voronoi of 04:10. Each cell is a bubble of rising gas.
float cell(vec2 x){
vec2 g = floor(x), f = fract(x);
float d = 8.0;
for (int j = -1; j <= 1; j++) for (int i = -1; i <= 1; i++){
vec2 o = vec2(float(i), float(j));
vec2 s = 0.5 + 0.4*sin(T*0.35 + 6.2831*vec2(h2(g + o), h2(g + o + 7.3)));
vec2 r = o + s - f;
d = min(d, dot(r, r));
}
return sqrt(d);
}
float limb = 0.60; // limb darkening: 0 = a flat disc
float cells = 26.0; // convection cells across
float corona = 0.35; // the halo outside
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 col = vec3(0.0002, 0.0003, 0.0006);
float t = sphere(ro, rd, 1.0);
if (t > 0.0){
vec3 n = normalize(ro + rd*t); // a sphere's normal is the point itself
float mu = dot(n, -rd); // 1 at the disc's center, 0 at its edge
float c = cell(n.xy/(1.0 + n.z)*cells); // the globe flattened, with no seam
float gas = 0.88 + 0.2*(1.0 - smoothstep(0.1, 0.8, c));
float I = 1.0 - limb*(1.0 - mu); // the limb's law
vec3 photo = vec3(1.0, 0.62, 0.28)*1.7; // the photosphere: 5,772 K
col = photo*I*gas;
col *= mix(vec3(1.0, 0.62, 0.36), vec3(1.0), mu); // the edge also reddens
}
// Outside: the closest the ray came to the center. More than 1 = it missed.
float b = dot(ro, rd), near = sqrt(max(dot(ro, ro) - b*b, 0.0));
if (near > 1.0){
float a = atan(p.y, p.x);
float rays = 0.5 + 0.5*fb(vec2(cos(a), sin(a))*3.0 + near*3.0 - T*0.1);
col += vec3(1.0, 0.56, 0.24)*corona*exp(-(near - 1.0)*9.0)*rays;
}
gl_FragColor = vec4(wogl(col), 1.0);
}