GLSL Atmospheric Scattering with Transparency

The source code for this project can be found here: https://github.com/Merlotec/space_render

In short, atmospheric scattering is a process through which the interference between a light source and an atmosphere can be simulated.

When the sun’s rays travel through the atmosphere of a planet, some of that light is reflected, causing the stunning ‘hue’ of the atmosphere. We can simluated this by calculating the intensity of the hue at the specific point in the atmosphere that we are looking at.

TL;DR

The following shaders implement what I describe below and are basically ready to go. As long as you know which data to bind and how to bind it, it should just be a simple copy paste job.

NB: These shaders were written primarily for Vulkan - I don’t know if they work when used with other rendering backends.

What is Atmospheric Scattering

In short, atmospheric scattering is a process through which the interference between a light source and an atmosphere can be simulated. When the sun’s rays travel through the atmosphere of a planet, some of that light is reflected, causing the stunning ‘hue’ of the atmosphere. We can simluated this by calculating the intensity of the hue at the specific point in the atmosphere that we are looking at.

Overview

So how does it work?

  1. For every fragment (pixel), a ray is projected outwards from the ‘camera’ in the direction that the particular pixel is ‘facing’.
  2. The projected ray is then checked against the outer sphere of the atmosphere, yeilding the points P1 and P2 where the ray intersects the sphere.
  3. If the sphere intersects the atmosphere, another ray is cast to determine whether the ray intersects the planet itself yeilding Q1 and Q2.
  4. The segment of the ray which intersects the atmosphere is determined: if the ray does not intersect the planet the segment is P1 -> P2, if it does the segment is P1 -> Q1, since the atmosphere segment should end when the ray hits the planet itself. Call this new segment S1 -> S2.
  5. The segment is then split up into an arbitrary number of discrete points. The more points, the more accurate the result, but the worse the performance.
  6. For each point, we determine how much light that point receveives. To do this accurately, we perform a similar operation, by checking the distance that the sun’s light must travel though the atmosphere to get to the specific point.

Implementation

So let’s start by writing our fragment shader.

Ray Sphere Intersection

Firtly we need a ray sphere intersection function:

const vec2 RAY_SPHERE_NO_INTERSECTION = vec2(1, -1);

vec2 check_sphere(vec3 p, vec3 dir, float r) {
	float b = dot(p, dir);
	float c = dot(p, p) - r * r;
	
	float d = b * b - c;
	if (d < 0.0) {
		return RAY_SPHERE_NO_INTERSECTION;
	}
	d = sqrt(d);
	
	return vec2(-b - d, -b + d);
}

This function casts a ray out from point p in the direction dir, checking for intersection with a sphere of radius r and centre (0, 0) (on the origin). The x and y values of the vec2 returned contains the distance from point p of the first and second intersections with the sphere respectively. If no intersection takes place, the discriminant of the equation (d) will be less than 0. In this case, we return a pre-defined const vec2 with the x value greater than the y value, which could never be returned if the ray successfully intersected the sphere. This is because the distance to the first point of intersection must always be less than the distance to the second (x and y are both magnitudes).

Written on July 7, 2020