Waking up rather perturbed this early semi-xmas morning, I decided to implement planetary rings. Not very hard, really, just a plane going through the origin of the planet. However, it is the fragment shader that really does the job, imposing cutoff for length + generating noise patterns. In the end, turned out pretty decent for 2 hours work I’d say!
Ring shadow is ray-traced through the planet sphere, so shadows are genuine
The shadow ray-tracing works as such: if a ray with origin at the ring and direction towards the light-source intersects the planet sphere, there is shadow. This basically means solving a standard second order-equation, so if B^2-4AC<0, the ray does not intersect the planet and thus there is no shadow.
Planetary rings seen from other planet
Planetary rings are nothing but 1-dimensional Perlin noise P(l), where l = |x|. Then impose cutoff: if (l ≤ min or l ≥ max) discard the pixel. Yay!
Planetary rings seen from self planet
With bump mapping!
Here’s the whole fragment shader, in case you were wondering. Remember, the input is just *one* GL QUAD with 4 vertices, blending included:
float getRings(in float l) {
float c = (0.6 + snoise2(vec2(l*0.7,0)));
c+=0.4*snoise2(vec2(l*5.0,0));
return c;
}
float rayIntersectSphere(in vec3 d, in vec3 o, in float r) {
float A = dot(d,d);
float B = 2.0*dot(d,o);
float C = dot(o,o) - r*r;
float D = B*B-4.0*A*C;
if (D ≤ 0.0) return 0.0;
float t0 = (-B - sqrt(B*B - 4.0*A*C))/(2.0*A);
if (t0≤0.0)
return 0.0;
return 1.0;
}
void main(void)
{
float l = length(myPos)/fInnerRadius;
if (l≤ringMin || l≥ringMax)
discard;
float c = getRings(l*10.0);
if (rayIntersectSphere(v3LightPos, myPos, fInnerRadius)==1.0 )
c*=0.3;
gl_FragColor = ringColor*c;
gl_FragColor.w = 0.7*c;
}
Some comments : snoise2d is a 2d perlin noise function and fInnerRadius is a uniform containing the radius of the planet while myPos is a varying vec3 containing the interpolated fragment pixel position, our origin of the ray (just myPos = gl_Vertex.xyz in the vertex shader)