Как я могу оказать "атмосферу" на рендеринг Земли в Three.js?

В течение последних нескольких дней я пытался заставить текстурирование Three.js работать. Проблема, с которой я столкнулся, заключается в том, что мой браузер блокировал текстуры от загрузки, что было решено, следуя инструкциям здесь.

В любом случае, я делаю космическую навигационную игру для одного из моих классов, который демонстрирует навигацию по космическому кораблю. Итак, я даю кучу планет, а Земля - ​​одна из них. Я включил изображение моего земного рендеринга ниже. Это выглядит хорошо, но я пытаюсь сделать это более реалистичным, добавив "атмосферу" вокруг планеты.

Я огляделся, и я нашел действительно опрятные поисковые разработки, которые касаются свечения, но я не думаю, что они примените к моей ситуации, к сожалению.

И вот код, который добавляет землю к моей сцене (это измененная версия кода, которую я получил из учебника Three.js):

    function addEarth(x,y){

        var sphereMaterial =
        new THREE.MeshLambertMaterial({
            //color: 0x0000ff,
            map: earthTexture
        });

        // set up the sphere vars
        var radius = 75;
        segments = 16;
        rings = 16;

        // create a new mesh with
        // sphere geometry - we will cover
        // the sphereMaterial next!
        earth = new THREE.Mesh(

        new THREE.SphereGeometry(
        radius,
        segments,
        rings),

        sphereMaterial);

        earth.position.x = x;
        earth.position.y = y;

        // add the sphere to the scene
        scene.add(earth);
    }

enter image description here

Ответ 1

Что именно вы ищете в своей атмосфере? Это может быть так же просто, как сделать еще немного более прозрачную сферу поверх верхней части земного шара, или она может быть очень сложной, на самом деле преломляя свет, который входит в нее. (Почти как подповерхностное рассеяние, используемое при рендеринге кожи).

Я никогда не пробовал такой эффект сам, но некоторые быстрые Googling показывают некоторые обнадеживающие результаты. Например, я думаю, что этот эффект выглядит довольно красиво, и автор даже следил за ним с более подробным вариантом позже. Если вы заинтересованы в более техническом пробое этой технике, подробно рассказывается о теоретическом фоне. Я уверен, что там больше, вам просто нужно немного совать. (По правде говоря, я не знал, что это была такая популярная тема для показа!)

Если у вас возникли проблемы с некоторыми аспектами этих методов, особенно применимыми к Three.js, не стесняйтесь спрашивать!

[ОБНОВЛЕНИЕ]

Ах, извините. Да, это немного, чтобы бросить вас без предварительного знания шейдеров.

Код второй ссылки - фактически файл DirectX FX, основным кодом которого является HLSL, поэтому это не то, что просто подключалось к WebGL, но два шейдерных формата были достаточно похожи, что, как правило, это не проблема для перевода между ними, Если вы действительно знаете шейдеры, то есть. Я бы рекомендовал прочитать, как работают шейдеры, прежде чем пытаться погрузиться в такой сложный эффект.

Я бы начал с чего-то простого, такого как этот учебник, в котором просто говорится о том, как получить базовый шейдер, запущенный с помощью Three.js. Как только вы узнаете, как получить шейдер, работающий с учебными пособиями по теме "Три .js и GLSL" (например, этот), вы познакомитесь с основами работы шейдера и тем, что вы можете сделать с ним.

Я знаю, что это похоже на большую работу, но если вы хотите делать расширенные визуальные эффекты в WebGL (и это, безусловно, соответствует вектору продвинутых эффектов), вы абсолютно должны понимать шейдеры!

И снова, если вы ищете быстрое исправление, всегда есть тот прозрачный параметр сферы, о котором я говорил.:)

Ответ 2

Хорошо старый и уже ответивший вопрос, но я хотел добавить свое решение для обсуждения на начальном этапе. Долгое время прокладывайте вдоль атмосферного рассеяния и GLSL и приходите к этой упрощенной версии VEEERRRYYY (если анимация перестает обновлять страницу или просматривает GIF в чем-то более подходящем):

[example[1]

  • планета и эллипсоид (центр x, y, z и радиусы rx, ry, rz)
  • атмосфера также эллипсод (такой же, но больший по высоте атмосферы)
  • весь рендер выполняется нормально, но поверх этого добавляется 1 проход для ближайшей планеты наблюдателя.
  • этот проход представляет собой один квадрат, охватывающий весь экран
  • внутри фрагмента он вычисляет пересечение пиксельного луча с этими двумя эллипсоидами
  • возьмите видимую часть (не позади, а не после земли)
  • вычислить длину луча внутри атмосферы
  • искажает исходный цвет как функцию r, g, b масштабированных параметров по длине луча (что-то вроде интегрирования по пути)
    • какой-то цвет принимается некоторыми заданными...
    • сильно влияет на цвет, поэтому его можно смоделировать различные атмосферы всего несколькими атрибутами.
  • он хорошо работает внутри, а также за пределами атмосферы (с расстояния)
  • может добавлять близкие звезды в качестве источника света (я использую систему с 3 звездами)

результат ошеломляет см. ниже:

enter image description hereenter image description hereenter image description hereenter image description hereenter image description here

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();
    }

Извините, но его действительно старый источник моего... должен быть, вероятно, преобразован в основной профиль

[Редактировать 1] извините, забудьте добавить мои входные коэффициенты рассеяния для атмосферы Земли

    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 новый исходный код

У меня было некоторое время в последнее время для внедрения масштабирования в шахтный двигатель и выяснил, что исходный код не очень точен с расстояния выше 0,002 AU. Без масштабирования это всего лишь несколько пикселей, поэтому ничего не видно, но с масштабированием всех изменений, поэтому я попытался повысить точность настолько, насколько мог.

После некоторых дополнительных настроек я могу использовать его до 25,0 AU и с артефактами интерполяции до 50,0-100,0 AU. Это ограничение для текущего HW, потому что я не могу передать non flat fp64 в интерполяторы от вершины к фрагменту. Одним из способов могло бы стать преобразование системы координат в фрагмент, но еще не пробовал. Вот некоторые изменения:

  • Новый источник использует 64-битные поплавки.
  • и добавьте uniform int lights, который является подсчетом используемых огней
  • также некоторые изменения в значении B0 (они больше не зависят от длины волны, а цвет вместо этого), поэтому вам нужно слегка изменить равномерное заполнение значения в ЦП.
  • добавлены некоторые улучшения производительности.

[вершина]

/* 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/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 int  lights;                 // actual number of lights
    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
varying vec4 pixel_scr;       // pixel in screen space <-1,+1>

varying vec3 p_r;               // rx,ry,rz
uniform vec3 planet_r;          // rx^-2,ry^-2,rz^-2 - surface

void main(void)
    {
    p_r.x=1.0/sqrt(planet_r.x);
    p_r.y=1.0/sqrt(planet_r.y);
    p_r.z=1.0/sqrt(planet_r.z);
    pixel_nor=gl_Normal;
    pixel_pos=gl_Vertex;
    pixel_scr=gl_Color;
    gl_Position=gl_Color;
    }

[фрагмент]

#extension GL_ARB_gpu_shader_fp64 : enable
double abs(double x) { if (x<0.0) x=-x; return x; }

varying vec3 pixel_nor;              // camera direction in ellipsoid space
varying vec4 pixel_pos;              // pixel in ellipsoid space
varying vec4 pixel_scr;              // pixel in screen space
varying vec3 p_r;                    // rx,ry,rz                        

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 int  lights;                 // actual number of lights
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 color coefficients (r,g,b,ambient)

// 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
const double view_depth_max=100000000.0; // > max view depth
double view_depth_l0=-1.0, // view_depth_l0 first hit
       view_depth_l1=-1.0; // view_depth_l1 second hit
bool  _view_depth_l0=false;
bool  _view_depth_l1=false;
bool _view_depth(vec3 _p0,vec3 _dp,vec3 _r)
    {
    dvec3 p0,dp,r;
    double a,b,c,d,l0,l1;
    view_depth_l0=-1.0; _view_depth_l0=false;
    view_depth_l1=-1.0; _view_depth_l1=false;
    // conversion to double
    p0=dvec3(_p0);
    dp=dvec3(_dp);
    r =dvec3(_r );
    // quadratic equation a.l.l+b.l+c=0; l0,l1=?;
    a=(dp.x*dp.x*r.x)
     +(dp.y*dp.y*r.y)
     +(dp.z*dp.z*r.z);
    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;
    // discriminant d=sqrt(b.b-4.a.c)
    d=((b*b)-(4.0*a*c));
    if (d<0.0) return false;
    d=sqrt(d);
    // standard solution l0,l1=(-b +/- d)/2.a
    a*=2.0;
    l0=(-b+d)/a;
    l1=(-b-d)/a;
    // alternative solution q=-0.5*(b+sign(b).d) l0=q/a; l1=c/q; (should be more accurate sometimes)
//  if (b<0.0) d=-d; d=-0.5*(b+d);
//  l0=d/a;
//  l1=c/d;
    // sort l0,l1 asc
    if ((l0<0.0)||((l1<l0)&&(l1>=0.0))) { a=l0; l0=l1; l1=a; }
    // exit
    if (l1>=0.0) { view_depth_l1=l1; _view_depth_l1=true; }
    if (l0>=0.0) { view_depth_l0=l0; _view_depth_l0=true; return true; }
    return false;
    }

// 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)
    {
    dvec3 p0,dp,r;
    double a,b,c,d,l0,l1;
    // conversion to double
    p0=dvec3(_p0);
    dp=dvec3(_dp);
    r =dvec3(_r );
    // quadratic equation a.l.l+b.l+c=0; l0,l1=?;
    a=(dp.x*dp.x*r)
     +(dp.y*dp.y*r)
     +(dp.z*dp.z*r);
    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;
    // discriminant d=sqrt(b.b-4.a.c)
    d=((b*b)-(4.0*a*c));
    if (d<0.0) return false;
    d=sqrt(d);
    // standard solution l0,l1=(-b +/- d)/2.a
    a*=2.0;
    l0=(-b+d)/a;
    l1=(-b-d)/a;
    // alternative solution q=-0.5*(b+sign(b).d) l0=q/a; l1=c/q; (should be more accurate sometimes)
//  if (b<0.0) d=-d; d=-0.5*(b+d);
//  l0=d/a;
//  l1=c/d;
    // sort l0,l1 asc
    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)
vec4 atmosphere()
    {
    const int n=8;
    const float _n=1.0/float(n);
    int i;
    bool b0,b1;
    vec3 p0,p1,dp,p,b;
    vec4 c;     // c - color of pixel from start to end

    float h,dl,ll;
    double l0,l1,l2;
    bool   e0,e1,e2;
    c=vec4(0.0,0.0,0.0,0.0);    // a=0.0 full background color, a=1.0 no background color (ignore star)
    b1=_view_depth(pixel_pos.xyz,pixel_nor,planet_R);
    if (!b1) return c;                          // completly outside atmosphere
    e1=_view_depth_l0; l1=view_depth_l0;        // first atmosphere hit
    e2=_view_depth_l1; l2=view_depth_l1;        // second atmosphere hit
    b0=_view_depth(pixel_pos.xyz,pixel_nor,planet_r);
    e0=_view_depth_l0; l0=view_depth_l0;        // first surface hit
    if ((b0)&&(view_depth_l1<0.0)) return c;    // under ground
    // set l0 to view depth and p0 to start point
    dp=pixel_nor;
    p0=pixel_pos.xyz;
    if (!b0)                                    // outside surface
        {
        if (!e2)                                // inside atmosphere to its boundary
            {
            l0=l1;
            }
        else{                                   // throu atmosphere from boundary to boundary
            p0=vec3(dvec3(p0)+(dvec3(dp)*l1));
            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 (_star_colide(p0.xyz-light_posr[i].xyz,dp.xyz,light_posr[i].a*0.75)) // 0.75 is enlargment to hide star texture corona
            {
            c.rgb+=light_col[i];
            c.a=1.0; // ignore already drawed local star color
            }
        }
    else{                                       // into surface
        if (l1<l0)                              // from atmosphere boundary to surface
            {
            p0=vec3(dvec3(p0)+(dvec3(dp)*l1));
            l0=l0-l1;
            }
        else{                                   // inside atmosphere to surface
            l0=l0;
            }
        }
    // set p1 to end of view depth, dp to intergral step
    p1=vec3(dvec3(p0)+(dvec3(dp)*l0)); dp=p1-p0;
    dp*=_n;

    dl=float(l0)*_n/view_depth;
    ll=B0.a; for (i=0;i<lights;i++)             // compute normal shaded combined light sources into ll
     ll+=dot(normalize(p1),light_dir[0]);
    for (p=p1,i=0;i<n;p-=dp,i++)                // p1->p0 path throu atmosphere from ground
        {
//      _view_depth(p,normalize(p),planet_R);   // too slow... view_depth_l0=depth above atmosphere top [m]
//      h=exp(view_depth_l0/planet_h)/2.78;

        b=normalize(p)*p_r;                     // much much faster
        h=length(p-b);
        h=exp(h/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.rgb+=b*ll;
        }
    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.rgba=atmosphere();
    }

[равномерные значения]

// Earth
re=6378141.2         // equatoreal radius r.x,r.y
rp=6356754.79506139 // polar radius r.z
planet_h=60000      // atmosphere thickness R(r.x+planet_h,r.y+planet_h,r.z+planet_h)
view_depth=250000   // max view distance before 100% scattering occur 
B0.r=0.1981         // 100% scattered atmosphere color
B0.g=0.4656
B0.b=0.8625
B0.a=0.75           // overglow (sky is lighter before Sun actually rise) it is added to light dot product

// Mars
re=3397000
rp=3374919.5
ha=30000
view_depth=300000
B0.r=0.4314
B0.g=0.3216
B0.b=0.196
B0.a=0.5

Для получения дополнительной информации (и более новых изображений) см. также связанные: