javascript - ¿Cómo puedo representar una ''atmósfera'' sobre una representación de la Tierra en Three.js?
html5 (2)
Durante los últimos días, he estado tratando de que la textura de Three.js funcione. El problema que he tenido es que mi navegador estaba bloqueando la carga de texturas, que se resolvió siguiendo las instrucciones que se encuentran here .
De todos modos, estoy haciendo un juego de navegador de espacio para una de mis clases que demuestra cómo navegar naves espaciales a través del espacio. Entonces, estoy renderizando un montón de planetas, la Tierra es uno de ellos. He incluido una imagen de mi representación de la Tierra a continuación. Se ve bien, pero lo que estoy tratando de hacer es hacerlo más realista al agregar una ''atmósfera'' alrededor del planeta.
He mirado a mi alrededor, y he encontrado algunas creaciones realmente bonitas que tratan con el brillo, pero desafortunadamente no creo que se apliquen a mi situación.
Y aquí está el código que agrega la tierra a mi escena (es una versión modificada del código que obtuve de un tutorial de 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);
}
¿Qué es exactamente lo que buscas en tu ambiente? Podría ser tan simple como representar otra esfera transparente un poco más grande sobre la parte superior de su globo, o podría ser muy muy compleja, en realidad refractando la luz que entra en ella. (Casi como la dispersión subsuperficial utilizada en el renderizado de la piel).
Nunca he intentado tal efecto, pero un rápido Google Googling muestra algunos resultados prometedores. Por ejemplo, creo que este efecto parece bastante bueno, y el autor incluso lo siguió con una variante más detallada más adelante. Si está interesado en un desglose más técnico, esta técnica detalla muchos de los antecedentes teóricos. Estoy seguro de que hay más, solo tienes que hurgar un poco. (A decir verdad, ¡no sabía que este era un tema de representación tan popular!)
Si está teniendo problemas con algún aspecto de esas técnicas específicamente como se aplica a Three.js, ¡no dude en preguntar!
[ACTUALIZAR]
Oh, lo siento. Sí, eso es un poco demasiado para lanzarte sin el conocimiento previo del sombreador.
El código en el segundo enlace es en realidad un archivo DirectX FX, el código central es HLSL, por lo que no es algo que simplemente se conecte a WebGL, pero los dos formatos de sombreado son lo suficientemente similares como para que no sea un problema traducir entre ellos. Si realmente sabes shaders, eso es. Recomendaría leer sobre cómo funcionan los shaders antes de intentar sumergirse en un efecto complicado como este.
Comenzaría con algo tan simple como este tutorial , que simplemente habla de cómo hacer funcionar un sombreador básico con Three.js. Una vez que sepa cómo hacer que un shader trabaje con Three.js y GLSL, los tutoriales (como este ) le brindarán los conceptos básicos de cómo funciona un shader y qué puede hacer con él.
Sé que parece mucho trabajo por adelantado, pero si quieres hacer efectos visuales avanzados en WebGL (y esto ciertamente encaja en la lista de efectos avanzados) ¡ debes entender los sombreadores!
Por otra parte, si está buscando una solución rápida, siempre estaba esa opción de esfera transparente de la que estaba hablando. :)
Bueno, una pregunta antigua y ya contestada, pero quería agregar mi solución para que la consideren un principiante. Tener plaing a lo largo de la dispersión atmosférica y GLSL por un largo tiempo y venir a esta versión simplificada VEEERRRYYY (si la animación deja de actualizar la página o ver el GIF en algo más decendente):
El
- planeta es y elipsoide (centro x, y, z y radios rx, ry, rz)
- La atmósfera también es elipsis (igual pero más grande por la altura de la atmósfera)
- todo el render se realiza normalmente, pero además de eso se agrega 1 pase para el planeta observador cercano
- Ese pase es un quad individual que cubre toda la pantalla.
- en el fragmento interno se calcula la intersección de rayos de píxeles con estos 2 elipsoides
- tomar la parte visible (no detrás, no después de tierra)
- calcular la longitud del rayo dentro de la atmósfera
- distorsiona el color original como función de r, g, b params escalados por la longitud del rayo (algo así como la integración a lo largo del camino)
- un poco de color se toma algo dado ...
- Afecta mucho el color, por lo que es posible simular diferentes atmósferas con solo unos pocos atributos.
- Funciona bien dentro y también fuera de la atmósfera (desde la distancia).
- puede agregar estrellas cercanas como fuente de luz (uso un sistema máximo de 3 estrellas)
el resultado es impresionante ver imágenes a continuación:
Vértice:
/* 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;
}
Fragmento:
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();
}
Lo siento, pero es una fuente muy antigua de mi ... probablemente debería convertirse al perfil principal
[Editar 1] perdón olvide agregar las constantes de dispersión de entrada para la atmósfera de la Tierra
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 nuevo código fuente
Recientemente, tuve algo de tiempo para implementar el zoom en el motor de la mina y descubrí que el código fuente original no es muy preciso desde una distancia superior a 0.002 UA. Sin Zoom, solo quedan unos pocos píxeles para que no se vea nada, pero con el zoom todos los cambios, así que intenté mejorar la precisión tanto como pude.
- Aquí la mejora de la precisión de la intersección de rayos y elipsoides es la pregunta relacionada con esto.
Después de algunos ajustes más, puedo usarlo hasta 25.0 UA y con artefactos de interpolación hasta 50.0-100.0 UA. Ese es el límite para el HW actual porque no puedo pasar el flat fp64
no flat fp64
a los interpoladores de vértice a fragmento. Una forma de hacerlo podría ser mover la transformación del sistema de coordenadas a un fragmento, pero aún no lo he probado. Aquí hay algunos cambios:
- nueva fuente utiliza flotadores de 64 bits
- y agregar
uniform int lights
que es el recuento de luces usadas - también algunos cambios en el significado de B0 (ya no son una constante dependiente de la longitud de onda sino un color), por lo que debe cambiar un poco el valor uniforme y completar el código de la CPU.
- Se agregaron algunas mejoras de rendimiento.
[vértice]
/* 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;
}
[fragmento]
#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();
}
[valores uniformes]
// 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
Para más información (y nuevas imágenes) ver también relacionados:
- ¿Es posible realizar una simulación realista del sistema solar n-body en cuestión de tamaño y masa?
[Edit3]
Aquí hay un pequeño código del lado de la CPU que uso en mi motor para representar la atmósfera usando el sombreador de arriba:
if (sys->_enable_bodya) // has planet atmosphere?
if (view_depth>=0.0)
{
glColor4f(1.0,1.0,1.0,1.0);
double a,b,p[3],d[3];
sys->shd_engine.unbind();
sys->shd_scatter.bind(); // this is the atmospheric shader
if (1) //*** GLSL_uniform_supported (leftover from old GL engine version)
{
int j;
double *w;
AnsiString s;
a=re; b=rp; a=divide(1.0,a*a); b=divide(1.0,b*b); // radius of planet re equatoral and rp polar and ha is atmosphere thickness
sys->shd_scatter.set3f("planet_r",a,a,b);
a=re+ha; b=rp+ha; a=divide(1.0,a*a); b=divide(1.0,b*b);
sys->shd_scatter.set3f("planet_R" ,a,a,b);
sys->shd_scatter.set1f("planet_h" ,ha);
sys->shd_scatter.set1f("view_depth",view_depth); // visibility distance
sys->shd_scatter.set4f("B0",B0[0],B0[1],B0[2],B0[3]); // saturated atmosphere color and overglow
sys->shd_scatter.set1i("lights",sys->local_star.num); // local stars
for (j=0;j<sys->local_star.num;j++)
{
a=sys->local_star[j].r;
w=sys->local_star[j].p;
s=AnsiString().sprintf("light_posr[%i]",j);
sys->shd_scatter.set4f(s,w[0],w[1],w[2],divide(1.0,a*a));
w=sys->local_star[j].d;
s=AnsiString().sprintf("light_dir[%i]",j);
sys->shd_scatter.set3f(s,w[0],w[1],w[2]);
vector_mul(p,sys->local_star[j].col,10.0);
s=AnsiString().sprintf("light_col[%i]",j);
sys->shd_scatter.set3f(s,p[0],p[1],p[2]);
}
}
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);
a=1.0;
b=-2.0*view.scr->views[view.scr->view].znear;
// color = pixel pos in screen space <-1,+1> ... no Projection/ModelView is used :)
// vertex = pixel pos in elypsoid space
// normal = eye-pixel direction in elypsoid space
zsort.rep0.g2l_dir(d,zsort.obj_pos0);
glDepthMask(0);
glBegin(GL_QUADS);
a=divide(1.0,view.zoom);
glColor4d(-1.0,-1.0,0.0,1.0); vector_ld(p,-a,-a,b); view.scr->fromscr(p,p); view.eye0.l2g(q,p); zsort.rep0.g2l_dir(q,q); vector_sub(p,q,d); vector_one(q,q); glNormal3dv(q); glVertex3dv(p);
glColor4d(+1.0,-1.0,0.0,1.0); vector_ld(p,+a,-a,b); view.scr->fromscr(p,p); view.eye0.l2g(q,p); zsort.rep0.g2l_dir(q,q); vector_sub(p,q,d); vector_one(q,q); glNormal3dv(q); glVertex3dv(p);
glColor4d(+1.0,+1.0,0.0,1.0); vector_ld(p,+a,+a,b); view.scr->fromscr(p,p); view.eye0.l2g(q,p); zsort.rep0.g2l_dir(q,q); vector_sub(p,q,d); vector_one(q,q); glNormal3dv(q); glVertex3dv(p);
glColor4d(-1.0,+1.0,0.0,1.0); vector_ld(p,-a,+a,b); view.scr->fromscr(p,p); view.eye0.l2g(q,p); zsort.rep0.g2l_dir(q,q); vector_sub(p,q,d); vector_one(q,q); glNormal3dv(q); glVertex3dv(p);
glEnd();
glDepthMask(1);
glDisable(GL_BLEND);
sys->shd_scatter.unbind();
sys->shd_engine.bind();
}
Se extrae del motor de la mina, por lo que usa muchas cosas que no tiene, pero se le da una idea de cómo se usan las cosas ... por cierto l2g
significa transformación de coordenadas locales a globales, g2l
es al revés. Si _dir
está presente como l2g_dir
, significa que la transformación está manejando el vector en lugar de la posición, por lo que no hay traducciones. La pantalla fromscr
convierte <-1,+1>
a 3D (cámara local) y vector_one
normaliza un vector a la unidad uno. Espero no haber olvidado explicar algo ...