You can find all the post in this series here:
- Part 1. Volumetric Atmospheric Scattering
- Part 2. The Theory Behind Atmospheric Scattering
- Part 3. The Mathematics of Rayleigh Scattering
- Part 4. A Journey Through the Atmosphere
- Part 5. A Shader for the Atmospheric Sphere
- Part 6. Intersecting The Atmosphere
- Part 7. Atmospheric Scattering Shader
- 🔒 Part 8. An Introduction to Mie Theory
You can download the Unity package for this tutorial at the bottom of the page.
Intersecting the Atmosphere
As discussed before, the only way we can calculate the optical depth of a segment that passes through the atmosphere, is via a numerical integration. This means dividing out interval in smaller segments of length , and calculating the optical depth of each one assuming its density is constant.
In the image above, the optical depth of the is calculated with 4 samples, each one only taking into account the density at the centre of the segment itself.
The first step is, obviously, finding the points and
. If we operate under the assumption that the objects we are rendering a sphere, Unity will try to render its surface. Each pixel on the screen corresponds to a point on the sphere. In the diagram below, that point is called
for origin. In a surface shader,
corresponds to the
worldPos
variable inside the Input
structure. This is how far the shader goes; the only piece of information available to us are , the direction
which indicates the direction of the view ray, and the atmospheric sphere centred at
with radius
. The challenge is to calculate
and
. The fastest way is to use a geometrical approach, reducing the problem to finding the intersection between the atmospheric sphere and the view ray from the camera.
First of all, we should notice that ,
and
are all lying on the view ray. This means that we can refer to their position not as a point in 3D space, but as the distance on the view ray from the origin. While
is the actual point (represented in the shader as a
float3
), will be its distance from the origin
(as a
float
). Both and
are two equally valid ways to indicate the same point, and it holds that:
where the overline notation indicates the length of the segment between some arbitrary points
and
.
For efficiency reasons, in the shader code we will use and
, and calculate it from
:
We should also notice that the segments and
have the same length. What we need now to find the intersection points is to calculate
and
.
The segment the easiest to calculate. If we look at the diagram above, we can see that
is the projection of the vector
onto the view ray. Mathematically this projection can be done with the dot product. If you are familiar with shaders you might know the dot product as a measure of how “aligned” two directions are. When it is applied to two vectors and the second one has unitary length, it becomes a projection operator:
One should notice that is a 3D vector, not the length of the segment between
and
.
What we need to calculate next is the length of the segment . This can be calculated using Pythagoras’ theorem on the triangle
. In fact, it holds that:
which means that:
The length of is still unknown. However, it can be calculated by applying once again Pythagoras’ theorem on the triangle
:
We know have all the quantities that we need. To sum it up:
That set of equation contains square roots. They are only defined on non-negative numbers. If , then there is no solution, meaning that the view ray does not intersect the sphere.
We can translate this into the following Cg function:
bool rayIntersect ( // Ray float3 O, // Origin float3 D, // Direction // Sphere float3 C, // Centre float R, // Radius out float AO, // First intersection time out float BO // Second intersection time ) { float3 L = C - O; float DT = dot (L, D); float R2 = R * R; float CT2 = dot(L,L) - DT*DT; // Intersection point outside the circle if (CT2 > R2) return false; float AT = sqrt(R2 - CT2); float BT = AT; AO = DT - AT; BO = DT + BT; return true; }
There is not a single value, but three to return: ,
and whether or not there is an intersection. The lengths of those two segments are returned using the
out
keywords, which makes any change the function does on those parameters persistent after its termination.
⭐ Suggested Unity Assets ⭐
Colliding with the Planet
There is an additional issue that we have to take into account. Certain view rays hit the planet, hence their journey through the atmosphere reaches an early termination. One approach could be to revise the derivation presented above.
An easier, yet less efficient approach, is to run rayIntersect
twice, and then to adjust the ending point if needed.
This translates to the following code:
// Intersections with the atmospheric sphere float tA; // Atmosphere entry point (worldPos + V * tA) float tB; // Atmosphere exit point (worldPos + V * tB) if (!rayIntersect(O, D, _PlanetCentre, _AtmosphereRadius, tA, tB)) return fixed4(0,0,0,0); // The view rays is looking into deep space // Is the ray passing through the planet core? float pA, pB; if (rayIntersect(O, D, _PlanetCentre, _PlanetRadius, pA, pB)) tB = pA;
Coming Next…
This post showed how it is possible to find intersections between a sphere and a ray. We will use this in the next post to calculate the entrance and exit points of the view ray with the atmospheric sphere.
You can find all the post in this series here:
- Part 1. Volumetric Atmospheric Scattering
- Part 2. The Theory Behind Atmospheric Scattering
- Part 3. The Mathematics of Rayleigh Scattering
- Part 4. A Journey Through the Atmosphere
- Part 5. A Shader for the Atmospheric Sphere
- Part 6. Intersecting The Atmosphere
- Part 7. Atmospheric Scattering Shader
- 🔒 Part 8. An Introduction to Mie Theory
Download
Become a Patron!
You can download all the assets necessary to reproduce the volumetric atmospheric scattering presented in this tutorial.
Feature | Standard | Premium |
Volumetric Shader | ✅ | ✅ |
Clouds Support | ❌ | ✅ |
Day & Night Cycle | ❌ | ✅ |
Earth 8K | ✅ | ✅ |
Mars 8K | ❌ | ✅ |
Venus 8K | ❌ | ✅ |
Neptune 2K | ❌ | ✅ |
Postprocessing | ❌ | ✅ |
Download | Standard | Premium |
💖 Support this blog
This website exists thanks to the contribution of patrons on Patreon. If you think these posts have either helped or inspired you, please consider supporting this blog.
📧 Stay updated
You will be notified when a new tutorial is released!
📝 Licensing
You are free to use, adapt and build upon this tutorial for your own projects (even commercially) as long as you credit me.
You are not allowed to redistribute the content of this tutorial on other platforms, especially the parts that are only available on Patreon.
If the knowledge you have gained had a significant impact on your project, a mention in the credit would be very appreciated. ❤️🧔🏻
Hi Alan, this is a great tutorial series. I am getting a page not found for part 8 and also the cheat sheet, fyi. Not sure if they’re just not done yet, or if the links are broken.
Part8 about Mie: https://www.alanzucconi.com/?p=7578
Cheat Sheet: https://www.alanzucconi.com/?p=7766
Hey!
Unfortunately, I haven’t had the chance to finalise them yet! T_T
Hello, Alan. Thank you for a great tutorial.
I’ve got a question about _PlanetCentre variable. Are you setting it with material.SetVector from a C# script? It’s a bit confusing, because you calculated centre and added it to Input struct in a previous chapter. Are you not using that? And if you are, how do you pass a value from Input struct to a lighting function?
Hey!
Most the properties (such as _PlanetCentre) are properties that I initialise manually directly in the Material inspector!
Hi Alan!
I’d like to ask in which function does the last snippet go?
I’m new to shader writing and got lost at this part.
Thank you for your clarification!