Download finished shader

The shader is finished. I explained my method of displaying a light-weight facsimile of atmospheric scattering in the previous devlog post. I posted the shader source code here. Try out the shader viewer in your browser here. To download the whole Unity 5.4 project – with the shader files and executables for viewing the shader on Android (TV), Windows, and Linux – download the whole package here.

The method of visualising the planet atmosphere

Sunrise over the Americas

The final colour of each fragment is calculated in the vertex program and fragment program of the shader. These are calculations for atmosphere colour tint, general illumination of each vertex, edge illumination to account for the perspective, and blending of the atmosphere and planet surface. The shader uses simplifications assuming practically spherical planets, like the Earth.

Atmosphere colour

The atmosphere light was tinted with a gradient for angles 0 to 180 degrees from the centre of illumination, as described here.

gradient value equalised degrees.png

General illumination

Only half of a spherical planet is directly illuminated by a directional light like sun light. Because of light scattering in the atmosphere, some light scatters into the other side of the planet. The graph below shows the general illumination intensity as a function of the degrees from the centre of illumination in my shader. The width of the big slump after the perpendicular angle can be adjusted, and is 27 degrees as default. The planet’s surface texture sampling is multiplied by this intensity. The overlayed atmosphere is multiplied by this intensity and also the perspective factor.

illumination graph.png

Edge illumination

Looking straight at a spherical planet with an atmosphere around it, one will see a longer distance through the atmosphere further towards the edges of the sphere. If the viewer can see longer through the atmosphere more light is scattered back towards the viewer, as it is scattered back from more points. This effect is the strongest at the very edges of the planet contour as the viewer can see right through the atmosphere without the view being obscured by the planet surface.

path of light 512

In my shader – instead of adding all the light going back through integration – I used a ramp function to increase the intensity of the atmosphere light according to the angle between the direction towards the viewer and the vertex’s normal.

float _FresnelExponent = 5; // default value
float angleNormalViewer = sin(acos(dot(normalDirection, viewDirection)));
float perspectiveFactor = 0.3 + 0.2 * pow(angleNormalViewer, _FresnelExponent) + 0.5 * pow(angleNormalViewer, _FresnelExponent * 20);

The last term will only give a big contribution for very perpendicular angles, i.e. right at the edge of the sphere. The closest sphere point to the viewer has 0 degrees between direction to viewer and normal, so the perspective factor will then be 0.3, signifying the least amount of atmosphere seen through and therefore less atmosphere intensity.

perspective graph

This perspective factor is then multiplied by the general illumination to calculate the intensity of the atmosphere at each vertex.

Specular reflection

The shader samples the alpha channel of the planet surface texture as fragment specularity. Basically, water is reflecting white sun light whereas land has only diffuse reflection. I use a Phong shader calculation for specular reflection. I had to do the specular calculation in the fragment shader to avoid a triangular look.

Surface and atmosphere combined

I developed the transparent atmosphere shader to be used on a bigger sphere around a sphere with an opaque shader of the planet surface. Since the Earth’s atmosphere is so close to the surface anyway, I thought it would be better to combine these two separate shaders into a shader for one surface – halving the vertex count. I used the same blending equation as traditional transparency to blend the calculated surface colour and the atmosphere.

In my Unity solution, either two spheres with surface or transparent atmosphere shaders can be activated or – as by default – the activated single sphere uses the opaque shader for both planet surface and atmosphere. Both setups work the same. The shader is ultimately only a visual approximation of atmospheric light scattering suitable for GPU-light hardware like mobile units, not a scientifically accurate simulation.

How to make a simple space skybox with sun in Unity

 

Star skyIn the Unity menu click Assets > Create > Material and in the material’s inspector, select Skybox > 6 Sided as shader. For all six texture slots you assign a space textures by dragging them into each slot. You can use this CC0-licensed public domain star sky texture for all of them if you are fine with subtly repeating stars. Open the texture’s import settings and set the Wrap Mode to Clamp and Apply.

starsky 1024 Download full resolution here.

In your camera object properties, set Clear Flags to Skybox.  In Unity the menu, select Window > Lighting and select the material you created as Skybox under the Environmental Lighting section of the Scene section of the Lighting box. skybox properties

Voilà! You have a starry skybox. To add a sun, create a lens flare object with Assets > Create > Lens Flare. For Flare Texture use a sun texture without alpha channel – for example this CC0-licensed public domain sun sprite I made. Set Texture Layout to 1 Texture, Size to 10, Color to white, and check Zoom and Fade.

sun sprite Download full resolution here.

Add a lens flare component to your directional light with Add Component > Effects > Lens Flare. Select the lens flare you created in the Flare property and check Directional. You can set Fade Speed to 50. Now you should have a nice looking starry skybox with a sun that follows your directional light.

The colour of the sky

The outer layers of the atmosphere contain aerosols that don’t reflect light rays. When light hits aerosols they are refracted in different directions depending on what part of the aerosol they hit. Since there is an innumerable amount of aerosols in the atmosphere and infinite light rays, as they pass through the aerosols they are eventually scattered in all directions. This light scattering is called Rayleigh scattering. The sun’s white light is refracted more or less depending on the light’s wave lengths. That means the light colours are scattered more or less, and are thereby separated.

In inner atmosphere there are also dust particles (like sand etc.) that are too big and opaque to refract, but instead reflect light. This is called Mie scattering. Mie scattering does therefore not alter the light colour, so it is more white. I decided to only implement Rayleigh scattering since this is the most interesting part of atmospheric light scattering. I also did not include clouds in my visualisation.

Only half of a planet is directly illuminated by the sun. Each photon changes direction each time it randomly hits an aerosol. Theoretically it could thereby be refracted along the planet’s curve every time to circle around the whole planet several times. Of course that is not very likely but considering the theoretical infinite number of light rays, it does happen. From around 90 degrees from the centre of the illuminated half some light scatters around to the dark side of the planet – but it fades quickly.

Only light that is scattered back to the viewer is visible as light in the atmosphere. The blue light scatters most, so more blue light is scattered in the direction of the viewer than green, yellow, and red. That means that atmospheric light in the directly illuminated half of the planet has a blue-ish tint. Like-wise, as blue scatters away more aggressively, there is less blue left in the fringes of the illuminated half of the planet – “where the sun rises or sets”. That is why the atmosphere is more yellow and red where the sun does not directly illuminate.

With tint gradient

I thought about many ideas for how to implement this in a shader. One idea was to have separate equations for each of the RGB channels. I decided to instead tint the final light with a look-up table of a light colour gradient in the form of a one-dimensional texture. The light colour gradient was made from an actual photo of light spectrum from aerosol-refracted sun light, modified to have around the same brightness value across the gradient.  I sampled the colour left to right in proportion to the angle 0-180 degrees from the planet’s centre of illumination. Since the light intensity is modified by all colours other than multiplication with white, I multiplied with the inverse of the sample colour’s brightness.

gradient value equalised.png

 

Android TV & OpenGL ES 2 constraints

I am testing on my only Android device, the Amazon Fire TV Stick. The SOC Broadcom Capri 28155 was low-end a few years ago, but modern low-end smartphones have surpassed it in terms of performance and shader model capabilities, which makes it a good candidate to test the shader’s performance on. It is an Android TV compatible device that plugs into a TV HDMI port so it naturally lacks touch screen input. It is controlled with the simple remote for the Amazon Fire TV Stick that is basically like the standard Android TV remote with an extra Android menu button – the same amount of buttons as the Android TV Mini-Controller.

The device only supports up to 2K textures so that resolution was used for the surface texture. I wanted to tint the atmosphere using texture sampling in the vertex shader for performance but had to move it to the fragment shader since the tex2dlod function was not implemented in OpenGL ES 2.0.

I did not use very complicated controls for viewing the planet on Android TV. The remote D-pad is used to rotate the camera around the planet horizontally and vertically. I tried using the D-pad middle button to switch between rotating the camera and planet (so that the light could hit the Earth with different angles) but it was very confusing. I left these controls on the arrow keys for PC, but added a script I found online for more free WASD and mouse controls. The R and F keys can be used to go up and down as well for better 3D controls. For Android smartphone devices I added gyro controls for rotation and made the touch screen work as the mouse look-around controls, but I could not test that on an actual smartphone.

Fast facsimile of atmospheric scattering

I reviewed papers and materials on accurately visualising atmospheric scattering according to Nishita’s algoritm. These algorithms require integration upon integration to sum up all the light that is scattered in the direction of the viewer for every vertex and therefore very computationally heavy. I tried an optimized one – with a fast lookup table and only one intensity channel – and it only ran at 90 FPS on my 2015 CPU Intel Core i7-6500U. It would run too slow on the modest Android hardware I am aiming for.

I studied the visualisations to find features that could be replicated. I made another sphere with a transparent shader with a size relative to the planet similar to the outer atmosphere radius (6 471 km) and the Earth radius (6 371 km). My plan is that the outer sphere’s shader should replicate the atmosphere lighting’s features (multi-scattering), and the planet surface shader should replicate the Earth surface’s reflection (single-scattering). I started working on the atmosphere shader and used a Unity diffuse shader for the planet surface for now.

First fresnel part 2.gif

My first version was a Fresnel shader to simulate the longer length of atmosphere the viewer sees towards the edges of the planet. With just this shader, the edges of the planet were always lit from all directions regardless of sun position, as seen above. This was multiplied by a diffuse shader that lit up even obtuse angles to simulate the light scattering around into the unlit half of the planet.

With diffuse.jpg