# Rayleigh scattering shader

Show 80 post(s) from this thread on one page
Page 1 of 2 12 Last
• 06-17-2009
psychopath
I decided to have a try at writing a sky scattering shader, but as with everything, it hasn't gone perfectly.

The main issues I'm having, are that the light direction doesn't seem to have much effect on sky colour. Also, the lighting/colouring that there is, seems to be duplicated on the opposite side of the skysphere for some reason.

[vertex]
Code:

```uniform vec3 eyePosition; const vec3 lightDir = vec3(1, -1, 1); const vec3 Br = vec3(6.95e-6, 1.18e-5, 2.44e-5); const vec3 Bm = vec3(4e-7, 6e-7, 2.4e-6); const vec3 Brm = Br+Bm; const vec4 Esun = vec4(1.0, 1.0, 1.0, 15.0); const float g = 0.0; float log2_e = 1.0/log(2.0); const float e = 2.71828; const float PI = 3.141592654; varying vec3 scattering; varying vec3 extinction; varying vec3 normal; varying vec3 light; vec3 Fex(float s) {         vec3 ex = exp(Brm * -s);         return ex; } vec3 rayleigh(float theta) {         float pi316 = 3.0/(16.0*PI);         float phase = 1.0 + (theta*theta);                 vec3 ret = pi316*phase*Br;         return ret; } vec3 mie(float theta) {         float pi14 = 1.0/(4.0*PI);         float g1 = (1.0-g)*(1.0-g);         float g2 = 1.0+(g*g);         float g3 = 2.0*g;         float phase = g1 / pow(g2 - g3*theta, 1.5);                 vec3 ret = pi14*phase*Bm;         return ret; } vec3 inScatter(float s, float theta) {         vec3 num = rayleigh(theta) + mie(theta);         num *= Esun.w;         num *= 1.0-Fex(s);         vec3 ret = num/Brm;         return ret; } void main() {         vec4 pos = gl_ModelViewProjectionMatrix*gl_Vertex;         light = normalize(lightDir);         normal = normalize(gl_Normal.xyz);         vec3 wDir = normalize(pos.xyz - eyePosition);         float dist = pos.z;         float cosTheta = dot(normal, light);         scattering = inScatter(dist, cosTheta);         extinction = Fex(dist);         light = lightDir;         gl_Position = pos; }```
[fragment]
Code:

```varying vec3 scattering; varying vec3 extinction; varying vec3 normal; varying vec3 light; void main() {         float Idiff = dot(normalize(normal), normalize(light));         vec4 surfaceColour = vec4(0.0, 0.0, 0.0, 1.0); //colourless atmosphere         vec4 final = (surfaceColour*vec4(extinction, 1.0)) + vec4(scattering, 1.0);         gl_FragColor = final; }```
I can't see anything wrong with the calculations, by comparing with what I've found in papers and other sources.

I'm not quite sure what angle I'm supposed to be using for cosTheta though. The angle between the view vector and the light vector? Or between the vertex normal and the light vector?

And what's an appropriate value for "g" in mie()?
• 06-18-2009
VirtualAce
If I remember correctly negative values of g will give a brighter spot and positives will give a less bright spot in the sky.
The angle is between the view vector and the light vector. It is attempting to simulate the in-scattering that occurs in the time it takes for the ray to go from the source to your eye.
The biggest problem I had with this formula was that it works in terms of light wavelength and not in terms of RGB. You will need a matrix that will transform the results from wavelength into RGB.

This PDF from GDC 2002 will help you immensely:
http://ati.amd.com/developer/gdc/200...anPreetham.pdf

I believe this site talks about converting from wavelength to RGB:
Color wavelength

The first post in this thread links to several articles about multiple scattering shaders:
Atmospheric Scattering - DevMaster.net Forums
Archived version:
Atmospheric Scattering [Archive] - DevMaster.net Forums

A gamedev forum thread about scattering - click on the links to download some very good documents on the topic.
Atmospheric Scattering - GameDev.Net Discussion Forums

More about conversion between color spaces:
Colour Rendering of Spectra

BASIC code for plotting chromaticity coordinates: (helpful for color space conversions)
Chromaticity Code

C code for color conversions:
http://www.fourmilab.ch/documents/specrend/specrend.c

A screenshot of one of many good renders I did manage to get with the formula from the PDF I linked you to. I had to mess around with Rayleigh, Mie, and G quite a bit to get this render.
• 06-19-2009
psychopath
Thanks for all the links.

I'm a little bit further ahead, but it's still not quite right. The duplication is gone, but I still don't have any night/sunset/sunrise effect. I re-calculated the Mie coefficients, and discovered what I was using was completely wrong.

The coefficients still had to be multiplied by seemingly arbitrary values (I found them in a gamedev.net post - I'll have to read again to see where they come from). Otherwise, the Mie numbers seemed to be too much larger than Rayleigh, and the sky then had no colour.

I also now calculate the distance as:
d = (1.05 - normal.y) * 15000

One of the papers I read mentioned using:
f_dist = pow(normal.y, 0.2) //fifth root
dist = (1.05 - f_dist) * 190000

But I found that caused more issues.

The best I've been able to come up with so far:
• 06-19-2009
VirtualAce
You are very close. I had huge issues with getting the orange/red colors at sunset. In my tests it almost seemed as though the angle of the sun added little to nothing to the equation even though mathematically this did not make sense.

Looks very good so far. I know firsthand how hard this is so two thumbs up for even trying it. People think knowing the equations is all there is to it but when you actually try to program it you realize there is far more at work here.

Many of the sites seem to pick arbitrary values for Mie and Rayleigh which was very confusing. I'm still not 100% sure how to get it all to work. Unfortunately there is no source for the excellent demo the GDC article provided.
• 06-21-2009
psychopath
Getting closer. I've got sunset colours now. However, the sky is now much too dark even when the sun is high in the sky.

Seems like the coefficients and multipliers are more important to the effect than the equations themselves. The way distance is calculated has a big impact too.

Here's the latest shot, with the sun rising. Doesn't show off the orange/red very well, but it's there at least. Also, the distance calculated as a root causes some poly's at the horizon to be black.
• 06-21-2009
VirtualAce
To me it looks like the extinction values are incorrect. Extinction is what I think will cause the reds and oranges.
• 06-22-2009
psychopath
I was under the impression that mie scattering and/or atmospheric density/depth were responsible for the red and orange?

Right now, I'm calculating extinction as exp(s * (Br+Bm) ). Which should actually be -s, but that causes the sky to turn completely black. I tried colouring the sky with the values from the extinction function, and it's either completely white or completely black (depending on positive or negative s). Should there be more variation?
• 06-22-2009
VirtualAce
Indeed there should. If you look at the GDC plates/slides you will see at the end that they show how all the separate renders are put together to arrive at the final rendered scene. One of them is a render of the extinction values. The extinction will give the falloff that you see in my render. It should be near white close to the camera and gradually attenuate to the horizon color the farther out it gets.

Mie scattering is reponsible for things like smog, pollution and humidity - large particle scattering.
Rayleigh is responsible for the blue color of the sky - small particle scattering.
Extinction is responsible for distant objects appearing brighter than those that are nearer. On a clear dry day (low turbidity or low Mie scattering) extinction will cause objects far away and near the horizon to be near white in color. Extinction is caused partially by in-scatter. The more time the ray has for in-scatter the more light will enter it. By the time it reaches the eye it will be a bit 'washed' out due to in-scatter. Rays from objects close to the eye do not go through enough atmosphere or distance to be significantly affected by in-scatter.

You cannot produce a good render without proper extinction values.

It also appears you are only rendering the skydome with the shader. If you do this you will not see the full effects of the shader since the horizon won't fade off like it is supposed to. In my render I'm only rendering the terrain with the shader which explains why the sky doesn't fade off in the distance to match the horizon. I 'faked' it by tweaking the colors a bit. If you notice there appears to be a small blue haze that begins about mid-Z and that is due 100% to extinction values. As the Z increases the in-scatter is greater and the colors get more and more washed out. The rate of wash-out is what fakes the eye into believing distance. A linear wash-out is not believable because it is not how it happens in real life. This is one of the greatest downfalls of linear fog. Density fog attempts to correct this by using a logarithmic fallof but it is still very inaccurate and does not fool the eye at all. I think my render fools the eye into believing those mountains in the distance are extremely far away - when in reality they are only about 10000 units away from the camera. It is the haze and the gradual washout that fools the eye into believing this is a render that stretches for miles and miles. The distant mountains almost fool you into thinking they are 10 to 15 miles from the camera. This is the main reason the shader was invented b/c it is so good at approximating what happens in the atmosphere that it really fools the eye.

If I were to render the scene without the shader the distant mountains would not look distant at all b/c their color would be the same as the close mountains. The eye would not be fooled at all. This shader is so powerful that you can tell the mountains in the distance are very far away and yet I have given no other terrain objects by which to judge distance. The only way your eye judges the distance is by the colors. You can also see G at work in the haze if you look at the right mountain. You will see a semi-circle pattern in the haze color graduations on the mountains which is basically the 'bright spot' of the render. The 'bright spot' in this render is just above the terrain in the clouds but you cannot see it b/c the skydome is not using the shader.

In your renders the bright-spot on the skydome is clearly evident, however it is not evident on the terrain yet it should be. The bright spot should be completed so to speak by the terrain thereby giving the full effect of the shader. Once the sky and terrain are in sync you will get some very nice looking sunsets and sunrises.

Attachments:
1. Fake high contrast 60 FOV render that clearly shows the terrain extincting to white.
2. Fake high contrast 30 FOV render that shows closeup of what is happening due to extinction
3. Slide from the GDC 02 presentation that presents all of the formulas in the sky-scatter shader.
4. 1024x768@100hz 1 million far plane render with 'faked' sky that does not use Preetham sky-scatter shader. (You can fake it but sunrise and sunset become very difficult)
• 06-24-2009
psychopath
As you suggested, I added the shader to the terrain as well. It looks pretty good, but I'm not 100% sure it's exactly how it's supposed to be. I can't see anything wrong specifically with extinction though.

To get what appeared to be a good render, I had to adjust the Rayleigh coefficient multiplier for the terrain independently of what I had set in the sky shader.

The first shot is with this independent adjustment, the second is with identical values. The second shot seems (to me at least) to be *too* washed out, for the distance the camera is away (although, it could be that I need to play with my world scaling as well, maybe?).

Also, please ignore the black triangle artifacts at the horizon :p.

EDIT: I should also probably be applying this to the water as well, I guess?
• 06-24-2009
VirtualAce
Not enough terrain to tell whether or not the extinction is working.

Extinction = exp (-BetaRayleigh + BetaMie) * distance

Br and Bm are the original coefficients without taking into account the angle.
Now if you look at in-scatter you will see that all it is doing is finding out the 'change in in-scatter' between the normal coefficients and the ones that are computed by the two equations for Br(theta) and Bm(theta). Looks extremely close to a derivative. Then if you notice the last part of the equation it is identical to the extinction factor. Esun is the contribution due to the sun. In-scatter then becomes:

(Br(theta) + Bm(theta) / Br + Bm) *Esun * (1 - extinction)

I really think you have a problem with the in-scatter equation b/c I'm not seeing any in-scatter or extinction on the terrain. The skydome is extincting correctly as it moves closer to the horizon. Your terrain (if stretched to the horizon) should mimic this behavior.

I'm not sure if Esun should be expressed as some value between 0.0f and 1.0f. According to the equation it does look as if it expects a normalized sun value.
Once you get all this figured out then you must take the results and run them through a matrix that will convert the values to RGBs.

If you want to get extremely accurate you could integrate the first equation for a set number of values from 0.0f to S. You could take 3 samples along the ray which is nothing close to what it needs to be, but the bottom two equations are designed to make up for this inaccuracy. For real sampling you would compute this equation for all atomic points between the sun and the viewer or 0.0f and S with 0.0f being the sun. It would probably run at about 1 frame per hour. :D

Do a couple of renders of extinction only. You should see it fading from orange to red at mid-day. There is a slide in the GDC document that illustrates this.
And to answer your question...yes you should probably be applying this to water. This is going to make your water shader a big beast if you are not using fragments.
• 06-26-2009
psychopath
Quote:

Originally Posted by Bubba
I'm not sure if Esun should be expressed as some value between 0.0f and 1.0f. According to the equation it does look as if it expects a normalized sun value.

AFAIK, Esun is supposed to be an RGB value, so I'm assuming that it is between 0.0 and 1.0.

Quote:

Originally Posted by Bubba
Once you get all this figured out then you must take the results and run them through a matrix that will convert the values to RGBs.

The in-scatter result should be converted to RGB? I made an attempt at that, but it didn't seem to turn out quite right (colour-wise).

I tried rendering the extinction values as you said, and it appeared to be the same as the GDC shot (whiter closer to the camera, orange farther away). When the sun goes down, the orange/red become much more visible on the terrain, but not in the sky. I'm not sure what would cause one to work, and one to not.

Here's the latest shot, with a different terrain and smaller sky-sphere:
• 06-26-2009
VirtualAce
Wow!! That is very, very close. Now all you need to fix is the sunrise and sunset and you have yourself a very nice ground-based multiple scattering sky shader.

And then when you get it working you can show me the source so I can correct mine. :D

I'm wondering if the GDC demo tweaked the colors of the sun at sunrise and sunset to get the sky to look right. However you are computing the angle so it should work for the sky vertices as well as the terrain vertices. Quite odd.

What are you using for your original Rayleigh and Mie coefficients and where did you find the info at for them. I could never figure out if they were RGBs or wavelengths.

Going back to the GDC slides it does appear they are doing something different to compute theta for the skydome. Theta for the terrain is arccosine(dot(look,normalize(Sun - cameraPos))). For the skydome it looks like they might be doing:

arccosine(dot(look,normalize(Sun - SkyDomeVertex)))

or

arccosine(dot(look,normalize((Sun - SkyDomeVertex) - cameraPos)))

I reference this slide:
• 06-28-2009
psychopath
Quote:

Originally Posted by Bubba
What are you using for your original Rayleigh and Mie coefficients and where did you find the info at for them.

I calculated them using code for a demo I found online, which I just realised is the GDC demo code. I think. I can't even find it online anymore, but it was linked somewhere.

The coefficients are calculated using wavelengths, but I can't tell for sure if they're turned into RGB values somewhere along the way. Work has been keeping me too busy to put much thought into this in the last couple of days :(.

The coefficient calculation is in Atmosphere.cpp.

Scattering.zip
• 06-30-2009
psychopath
I think my sky colour problem may be related to incorrect distance calculation.

From what I've read, it seems like the distance (that the light has to travel through the atmosphere), is supposed to be longer near/at the horizon, which (in part) causes the red colouring of the sky at sunset towards the horizon.

Most distance calculations are something along the lines of:
Code:

`float s = (world_view_matrix * in_vertex_position).z;`
However, wouldn't this cause the distance to be the same at every vertex, since we're dealing with a perfectly symmetrical sphere?
• 06-30-2009
VirtualAce
Indeed it would. However there is a flaw in your hypothesis about the distance being the issue. The distance to the sun is constant (at least for our purposes) whether the sun is high or low in the atmosphere. We both know the size is related to how much air the light passes through and is simply an optical illusion.

Therefore the color is red/orange at sunset b/c the sunlight is traveling through more air particles thus having more time to alter the wavelength. The distance to the sun has not changed enough to affect the color.

Our color is 'simulated' by this formula and is where the angle of the sun in relation to the viewer comes in to play. It is my estimation this angle may be incorrect or being computed incorrectly. The biggest influence on both the Mie and Rayleigh equations is the angle. If this is incorrect or constant then you will get a high-noon render regardless of where the sun is in your system. Post the code, if you would, that is computing the angle.
Show 80 post(s) from this thread on one page
Page 1 of 2 12 Last