What feels
like a logical progression after my recent experiments with gas giant
rendering, I thought I would next take a look a adding some rings to my planet,
turning it into more of a Saturn than a Jupiter. Reading up on them, Saturn's rings are a pretty complex structure the formation of which is not completely understood - as ever Wikipedia is a good starting point for information about them. Although they extend from 7,000 to 80,000 Km above the planet's equator they are only a handful of metres thick, being made up of countless particles of mainly water ice (plus a few other elements) from the truly tiny to around ten metres across.
Gas giant with rings as seen at dusk from a nearby planet |
My approach
for rendering such a complex system is similar in many ways to that for the gas giant itself, rather than
ray-tracing though I opted for a more conventional geometric representation of
the rings and used a single strip of geometry encircling the planet. For now the rings are fixed in the XZ plane
around the planet’s origin but as the planet itself can have an arbitrary axis
in my solar simulation that doesn’t feel like too much of a limitation to be
going on with.
Rendering the
basic geometry produces this:
What’s
immediately apparent is once again we fall foul straight away of the polygonal
nature of our graphics – although the tri-strip here is a pretty coarse
approximation to a circle, no matter how many triangles we add to it we are
still going to be able to see straight edges if we get close enough. Fortunately the solution is simple and by
adding a check to the pixel shader to reject pixels that are more than our
maximum radius from the planet origin or less than our minimum we immediately
get a nice smooth circular ring:
The only
trick here is to ensure the vertices of the ring geometry are far enough away
from the planet origin that the closest distance of the edges of the triangles
is at least the maximum ring radius rather than the vertices themselves being
that distance otherwise the outer edges of the ring will be clipped by the
geometry:
Vertices (in black) need to be further away than the ring radius to prevent the ring (in red) being clipped by the triangle edges |
A little trig shows this simply to be:
vertRadius = ringRadius/cos(PI/numSides)
This only applies to the outer edge of the ring as on the inner edge by definition the triangle edges are closer to the centre than the vertices.
Another point to watch is the rings have to of course go both in front and behind the planet itself. This sounds simple but is made slightly more complex that it first appears as I don't have a Z-buffer available when rendering at the planetary scale due to each planet being rendered in it's own co-ordinate space - they are simply rendered back to front - so they don't share a common Z space that could be used for a Z buffer. The range of depths would be massive anyway and likely to cause flimmering artifacts. Add on top of this the fact that planetary atmospheres need to alpha blend on top of whatever is behind them (including the far side of the rings) and a Z buffer isn't really an option.
Instead I render the rings in two passes, the first pass drawn before the encircled planet to provide the bulk of the rings and to allow the planet's atmosphere to alpha blend onto them. The second pass is drawn after the planet has been rendered and uses an additional ray-sphere and stencil test to only render to the portion of the screen covered by the planet and only where the rings are in front of the planet surface.
vertRadius = ringRadius/cos(PI/numSides)
This only applies to the outer edge of the ring as on the inner edge by definition the triangle edges are closer to the centre than the vertices.
Another point to watch is the rings have to of course go both in front and behind the planet itself. This sounds simple but is made slightly more complex that it first appears as I don't have a Z-buffer available when rendering at the planetary scale due to each planet being rendered in it's own co-ordinate space - they are simply rendered back to front - so they don't share a common Z space that could be used for a Z buffer. The range of depths would be massive anyway and likely to cause flimmering artifacts. Add on top of this the fact that planetary atmospheres need to alpha blend on top of whatever is behind them (including the far side of the rings) and a Z buffer isn't really an option.
Instead I render the rings in two passes, the first pass drawn before the encircled planet to provide the bulk of the rings and to allow the planet's atmosphere to alpha blend onto them. The second pass is drawn after the planet has been rendered and uses an additional ray-sphere and stencil test to only render to the portion of the screen covered by the planet and only where the rings are in front of the planet surface.
Of course one
great big slab isn’t very convincing so to get something a bit more realistic I
again opted for a similar technique to the gas giant, I took a nice high
resolution image of Saturn from the internet and cut out a slice of it’s rings,
filtering the result down to a 1x2048 colour ramp texture.
Mapping this
texture onto the ring geometry immediately produces something far more
pleasing:
Colour ramp mapped onto ring geometry |
As the ring
geometry has no thickness however, viewing it from acute angles produces some
unsightly single pixel artifacts.
Although in no way scientific, pulling the Fresnel hammer out of the
toolbox however makes the effect far less objectionable:
Rings edge-on without Fresnel term showing unsightly aliasing |
Rings edge-on with acute Fresnel term - aliasing is reduced at the expense of increased transparency |
Note I’m
using a pretty narrow Fresnel band for this purpose otherwise the ring tends to
fade out too much of the time. The
number of rings in the effect can be varied with a simple scale value just like
for the gas giant.
More Noise
Although the
rings here are pretty decent (largely due to stealing them from a real image I
suspect), there is a distinct lack of detail when viewed from anything
resembling close up.
Ultimately it
would be cool to be able to fade in large numbers of actual meshes to represent
the larger ice chunks that make up the rings when the view is near to or
actually within the rings, but even without that I thought there was something
that could be done to help represent the higher frequency detail these ice
particles and ‘ice-asteroids’ present.
As with so
many other procedural effects, I decided that a bit of noise would probably add
some interest, so I added some code to sample the same noise texture I used for
previous effects. The problem here
though is that the particle detail in the rings is very high frequency in the
cosmic scale of things but from a purely visual basis I wanted to show variety
in the rings from all ranges.
To do this I
re-used a trick from the terrain texturing shader where the scale of the
texture co-ordinates is calculated on the fly using the partial derivative of
the actual texture co-ordinates from the pixel - in this case from the position
of the point being shaded on the plane of the rings. The texture co-ordinates are scaled up or
down in powers of two to produce as close to screen size texels as possible -
in fact two scales are used by rounding the perfect scale up and down to the
closest power of two then blending between these two texture samples to produce
a smooth transition.
Ring noise sampled at uniform texel density produces aliasing and tiling at distance |
Ring noise sampled at adaptive density to provide more uniform screen coverage without tiling |
The texture
co-ordinate effect you can see here is somewhat akin to mip-mapping but instead
of using a lower resolution version of the texture to represent the same area
of surface at a larger distance, I am instead using the same resolution texture
to represent a larger area at that distance thereby eliminating the tiling effect often seen with high frequency textures at range.
Unlike mip-mapping it also works in the opposite direction, using the
same resolution texture to represent smaller and smaller surface areas as it
gets closer to the camera and each texel covers more than one pixel.
Using this
signed noise value to lighten or darken each shaded point produces a nice fine
grain effect in the rings I hope is vaguely representative of the countless millions of icy
particles that in fact make them up.
As with
everything else in my little solar system the rings need to be illuminated by
the Sun, this is done simply using standard dot product lighting but the little
twist here is to use the noise value from the previous step to slightly perturb
the normal uses for the calculation.
This is a gross simulation of the arbitrary facets of the lumps of ice
making up the rings being illuminated by the Sun and simply adds a bit of
randomisation to the lighting.
A more
dramatic effect which adds solidity to the rings is the shadow of the planet
cast on to them. Rather than using
shadow mapping techniques this is calculated by doing a ray intersection in the
pixel shader from the pixel towards the sun.
To soften the ubiquitous hard edge produced by ray-traced shadows the
distance the ray travels through the planet is used to provide a soft edge
where the distance is small falling off quickly to solid shadow. While not strictly physically accurate when classically treating sunlight as a parallel source, I feel it adds to the effect.
Shadow of the gas giant cast onto the rings |
Close up of edge of shadow showing the simulated penumbra |
Finally the
colours in the rings can be remapped again using the same RGB vector system I
used before so each final colour component is a dot product of the shader
output and the modulation vector passed in to the shader for that channel
Colour re-mapped rings |
More drastically colour re-mapped rings :-) |
For now
though, I'll wrap up with another couple of views of my gas giant's rings,
comments as always are welcome!