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 and mub*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.
Reading the codefloat 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.
TryDrop 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);
}