Well an old and already answered question but I wanted to add my solution for beginner consideration out there. Have playing along Atmospheric scattering and GLSL for a long time and developed this VEEERRRYYY Simplified version of Atmospheric scattering (if animation stops refresh page or view the GIF in something more decend):
[
- planet is and ellipsoid (center
x,y,
z and radiuses rx,ry,rz
)
- atmosphere is also ellipsoid (the same but bigger by atmosphere height)
- all render is done normally but on top of that is added 1 pass for near observer planet
- that pass is single quad covering whole screen
- inside fragment it computes the intersection of pixel ray with these 2 ellipsoids
- take the visible part (not behind, not after ground)
- compute the ray length inside atmosphere
- distort original color as function of
r,g,b
scaled params by ray length (something like integrating along the path)
- some color is taken some given ...
- greatly affects color so its possible to simulate different atmospheres by just few attributes
- it work well inside and also outside the atmosphere (from distance)
- can add close stars as light source (i use max 3 star system)
the result is stunning see images below:
Vertex:
/* SSH GLSL Atmospheric Ray light scattering ver 3.0
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
use with single quad covering whole screen
no Modelview/Projection/Texture matrixes used
gl_Normal is camera direction in ellipsoid space
gl_Vertex is pixel in ellipsoid space
gl_Color is pixel pos in screen space <-1,+1>
const int _lights=3;
uniform vec3 light_dir[_lights]; // direction to local star in ellipsoid space
uniform vec3 light_col[_lights]; // local star color * visual intensity
uniform vec4 light_posr[_lights]; // local star position and radius^-2 in ellipsoid space
uniform vec4 B0; // atmosphere scattering coefficient (affects color) (r,g,b,-)
[ToDo:]
add light map texture for light source instead of uniform star colide parameters
- all stars and distant planets as dots
- near planets ??? maybe too slow for reading pixels
aspect ratio correction
*/
varying vec3 pixel_nor; // camera direction in ellipsoid space
varying vec4 pixel_pos; // pixel in ellipsoid space
void main(void)
{
pixel_nor=gl_Normal;
pixel_pos=gl_Vertex;
gl_Position=gl_Color;
}
Fragment:
varying vec3 pixel_nor; // camera direction in ellipsoid space
varying vec4 pixel_pos; // pixel in ellipsoid space
uniform vec3 planet_r; // rx^-2,ry^-2,rz^-2 - surface
uniform vec3 planet_R; // Rx^-2,Ry^-2,Rz^-2 - atmosphere
uniform float planet_h; // atmoshere height [m]
uniform float view_depth; // max. optical path length [m] ... saturation
// lights are only for local stars-atmosphere ray colision to set start color to star color
const int _lights=3;
uniform vec3 light_dir[_lights]; // direction to local star in ellipsoid space
uniform vec3 light_col[_lights]; // local star color * visual intensity
uniform vec4 light_posr[_lights]; // local star position and radius^-2 in ellipsoid space
uniform vec4 B0; // atmosphere scattering coefficient (affects color) (r,g,b,-)
// compute length of ray(p0,dp) to intersection with ellipsoid((0,0,0),r) -> view_depth_l0,1
// where r.x is elipsoid rx^-2, r.y = ry^-2 and r.z=rz^-2
float view_depth_l0=-1.0,view_depth_l1=-1.0;
bool _view_depth(vec3 p0,vec3 dp,vec3 r)
{
float a,b,c,d,l0,l1;
view_depth_l0=-1.0;
view_depth_l1=-1.0;
a=(dp.x*dp.x*r.x)
+(dp.y*dp.y*r.y)
+(dp.z*dp.z*r.z); a*=2.0;
b=(p0.x*dp.x*r.x)
+(p0.y*dp.y*r.y)
+(p0.z*dp.z*r.z); b*=2.0;
c=(p0.x*p0.x*r.x)
+(p0.y*p0.y*r.y)
+(p0.z*p0.z*r.z)-1.0;
d=((b*b)-(2.0*a*c));
if (d<0.0) return false;
d=sqrt(d);
l0=(-b+d)/a;
l1=(-b-d)/a;
if (abs(l0)>abs(l1)) { a=l0; l0=l1; l1=a; }
if (l0<0.0) { a=l0; l0=l1; l1=a; }
if (l0<0.0) return false;
view_depth_l0=l0;
view_depth_l1=l1;
return true;
}
// determine if ray (p0,dp) hits a sphere ((0,0,0),r)
// where r is (sphere radius)^-2
bool _star_colide(vec3 p0,vec3 dp,float r)
{
float a,b,c,d,l0,l1;
a=(dp.x*dp.x*r)
+(dp.y*dp.y*r)
+(dp.z*dp.z*r); a*=2.0;
b=(p0.x*dp.x*r)
+(p0.y*dp.y*r)
+(p0.z*dp.z*r); b*=2.0;
c=(p0.x*p0.x*r)
+(p0.y*p0.y*r)
+(p0.z*p0.z*r)-1.0;
d=((b*b)-(2.0*a*c));
if (d<0.0) return false;
d=sqrt(d);
l0=(-b+d)/a;
l1=(-b-d)/a;
if (abs(l0)>abs(l1)) { a=l0; l0=l1; l1=a; }
if (l0<0.0) { a=l0; l0=l1; l1=a; }
if (l0<0.0) return false;
return true;
}
// compute atmosphere color between ellipsoids (planet_pos,planet_r) and (planet_pos,planet_R) for ray(pixel_pos,pixel_nor)
vec3 atmosphere()
{
const int n=8;
const float _n=1.0/float(n);
int i;
bool b0,b1;
vec3 p0,p1,dp,p,c,b;
// c - color of pixel from start to end
float l0,l1,l2,h,dl;
c=vec3(0.0,0.0,0.0);
b0=_view_depth(pixel_pos.xyz,pixel_nor,planet_r);
if ((b0)&&(view_depth_l0>0.0)&&(view_depth_l1<0.0)) return c;
l0=view_depth_l0;
b1=_view_depth(pixel_pos.xyz,pixel_nor,planet_R);
l1=view_depth_l0;
l2=view_depth_l1;
dp=pixel_nor;
p0=pixel_pos.xyz;
if (!b0)
{ // outside surface
if (!b1) return c; // completly outside planet
if (l2<=0.0) // inside atmosphere to its boundary
{
l0=l1;
}
else{ // throu atmosphere from boundary to boundary
p0=p0+(l1*dp);
l0=l2-l1;
}
// if a light source is in visible path then start color is light source color
for (i=0;i<_lights;i++)
if (light_posr[i].a<=1.0)
if (_star_colide(p0-light_posr[i].xyz,dp,light_posr[i].a))
c+=light_col[i];
}
else{ // into surface
if (l0<l1) b1=false; // atmosphere is behind surface
if (!b1) // inside atmosphere to surface
{
l0=l0;
}
else{ // from atmosphere boundary to surface
p0=p0+(l1*dp);
l0=l0-l1;
}
}
dp*=l0;
p1=p0+dp;
dp*=_n;
/*
p=normalize(p1);
h=0.0; l2=0.0;
for (i=0;i<_lights;i++)
if (light_posr[i].a<=1.0)
{
dl=dot(pixel_nor,light_dir[i]); // cos(ang: light-eye)
if (dl<0.0) dl=0.0;
h+=dl;
dl=dot(p,light_dir[i]); // normal shading
if (dl<0.0) dl=0.0;
l2+=dl;
}
if (h>1.0) h=1.0;
if (l2>1.0) l2=1.0;
h=0.5*(2.0+(h*h));
*/
float qqq=dot(normalize(p1),light_dir[0]);
dl=l0*_n/view_depth;
for (p=p1,i=0;i<n;p-=dp,i++) // p1->p0 path throu atmosphere from ground
{
_view_depth(p,normalize(p),planet_R); // view_depth_l0=depth above atmosphere top [m]
h=exp(view_depth_l0/planet_h)/2.78;
b=B0.rgb*h*dl;
c.r*=1.0-b.r;
c.g*=1.0-b.g;
c.b*=1.0-b.b;
c+=b*qqq;
}
if (c.r<0.0) c.r=0.0;
if (c.g<0.0) c.g=0.0;
if (c.b<0.0) c.b=0.0;
h=0.0;
if (h<c.r) h=c.r;
if (h<c.g) h=c.g;
if (h<c.b) h=c.b;
if (h>1.0)
{
h=1.0/h;
c.r*=h;
c.g*=h;
c.b*=h;
}
return c;
}
void main(void)
{
gl_FragColor.rgb=atmosphere();
}
Sorry but its a really old source of my ... should be probably converted to core profile
[Edit 1] sorry forget to add my input scattering constants for Earth atmosphere
double view_depth=1000000.0; // [m] ... longer path is saturated atmosphere color
double ha=40000.0; // [m] ... usable atmosphere height (higher is too low pressure)
// this is how B0 should be computed (for real atmospheric scattering with nested volume integration)
// const float lambdar=650.0*0.000000001; // wavelengths for R,G,B rays
// const float lambdag=525.0*0.000000001;
// const float lambdab=450.0*0.000000001;
// double r=1.0/(lambdar*lambdar*lambdar*lambdar); // B0 coefficients
// double g=1.0/(lambdag*lambdag*lambdag*lambdag);
// double b=1.0/(lambdab*lambdab*lambdab*lambdab);
// and these are my empirical coefficients for earth like
// blue atmosphere with my simplified integration style
// images above are rendered with this:
float r=0.198141888310295;
float g=0.465578010163675;
float b=0.862540960504986;
float B0=2.50000E-25;
i=glGetUniformLocation(ShaderProgram,"planet_h"); glUniform1f(i,ha);
i=glGetUniformLocation(ShaderProgram,"view_depth"); glUniform1f(i,view_depth);
i=glGetUniformLocation(ShaderProgram,"B0"); glUniform4f(i,r,g,b,B0);
// all other atributes are based on position and size of planet and are
// pretty straightforward so here is just the earth size i use ...
double r_equator=6378141.2; // [m]
double r_poles=6356754.8; // [m]
[edit2] 3.9.2014 new source code
I had some time recently to implement zoom to mine engine and figured out that original source code is not very precise from distance above 0.002 AU. Without Zoom it is just a few pixels so nothing is seen, but with zoom all changes so I tried to improve accuracy as much as I could.
After some more tweaks I get it to be usable up to 25.0 AU and with interpolation artifacts up to 50.0-100.0 AU. That is limit for current HW because I can not pass non flat fp64
to interpolators from vertex to fragment. One way around could be to move the coordinate system transform to fragment but haven't tried it yet. Here are some changes:
- new source uses 64 bit floats
- and add
uniform int lights
which is the count of used lights
- also some changes in B0 meaning (they are no longer wavelength dependent constant but color instead) so you need to change uniform value fill in CPU code slightly.
- some performance improvements was added
[vertex]
/* SSH GLSL Atmospheric Ray light scattering ver 3.1
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);
use with single quad covering whole screen
no Modelview/Pr