I've been messing with atmospheric scattering again, on a mission to alleviate the weird issues that were going on before. At first, I set out to rewrite the original functions from scratch - they weren't my code, and I didn't really understand how they worked. So redoing them step by step would be a good way to learn that.

But as I went on, this ended up morphing into a different method entirely. Sort of. Let me try and explain, as simply as I can...

(which means 95% of what you are about to read won't make any sense)

The process for rendering the atmosphere, at its heart, based around raymarching: the act of walking a point across some range, and using each point to contribute to the final rendered result. This must be done for every pixel, so as you can imagine, the performance impact of every operation in the pile adds up fast. The actual calculation of rayleigh/mie scattering is barely an issue in comparison.

As explained quite well in this post:

http://davidson16807.github.io/tectonics.js//2019/03/24/fast-atmospheric-scattering.htmlthe case of atmospheric scattering is a particularly dense one. As the point is walked through the atmosphere, it needs to take in contributions not just from the atmosphere between the point and the camera, but between the point and the sun. That means the number of operations is [view ray sample count] * [light ray sample count] * [light count], which is...

*a lot.*So instead, we turn to approximations, as does the link above. This allows us to just do a bit of math to calculate the atmosphere for the light ray, rather than doing dozens more samples every time. But this is where things changed with my new version, however. Helpful as they were, the approximations as detailed in the link had a problem... See here:

We (myself and the author of that post) never did quite work out what was going on. But the gist of it is that there was a strange "banding" when viewing the atmosphere from within at low angles, and it was honestly so bad it prevented me from showing many pictures of the atmosphere from certain angles. Something as-yet-unidentified in the math just starts to break down.

Still, I recommend reading the original post to get an understanding of how the original works (integrals are still a bit black magic to me, if I'm being honest).

But as mentioned, this time around, I came up with a different way to do it. I realized that, because the atmosphere is being calculated as a spherically symmetric object, looking in

*any* direction can be condensed down to two values: altitude, and angle. Like this:

Based on this, you can calculate the amount of atmosphere in a ray extending out from a point at a given altitude toward an angle. The ideal result is pretty much the same as the first approximation, although I wasn't sure if it would work out.

It turns out I'm not the first one to think of this (damn it!). A chap named Chapman came up with more or less the same concept decades ago:

https://en.wikipedia.org/wiki/Chapman_functionThere are a few approximations for it, but my execution is a bit different, and kinda lazy.

Presently, instead of calculating it on the fly every time, I'm pre-computing a lookup table for a range of angles from straight up to straight down in the Y-axis of the texture and vertically through the atmosphere in the X-axis. Each position on the table performs a long and expensive raymarch of its own (you know, the one I said earlier that we shouldn't be doing... but it's okay, because this happens only once and gets reused) to produce a reasonably accurate value.

So, if we need to find the amount of atmosphere along a ray, we can simply calculate the ray's angle relative to the surface below the point, fetch its altitude, and grab the value from the table that we need. Simple.

(I 100% did this texture lookup because it was easier than figuring out the math for the approximations

it might perform better than doing the math each time too, but I assure you that wasn't on purpose, it's just a side effect of laziness)

How well does this work?

Well, shit. What went wrong? It's

*almost *right.

The answer: It's my fault. It's because it's a texture - there's inherently some limit to its resolution, but the horizon is an instant, sharp line. This isn't as important for the samples that get used for the ray to the sun because it quickly gets averaged out, but it

*is* important for the density along the ray from the camera.

That's where the problem comes from. No matter what, it never

*quite* matches the actual horizon, and that misalignment leads to incorrect densities in the area of the horizon, and leads to the oddness you see above.

The fix was pretty simple. Previously, the view ray used the same lookup as the light rays. I changed it to instead add up the total amount of atmosphere as it steps along, and use that instead. And after that?

** ***Much* better.

At this point you're probably screaming silently into the void over why it's so damn small and pixelated, and the answer is: because it is. To facilitate much easier debug, I'm doing this on the CPU first, instead of in a shader. It takes ages to render, but it lets me dig deep into the life of each pixel:

Of course, it's got me a bit worried that the lower resolution and lack of real-time rendering is hiding something, which I won't know until I do an all-up test with it rendered properly as a shader...

Fingers crossed.

Edit:

Finger-crossing worked!