javascript 如何在 Three.js 中渲染地球时渲染“气氛”?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10213361/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I render an 'atmosphere' over a rendering of the Earth in Three.js?
提问by Hassan
For the past few days, I've been trying to get Three.js texturing to work. The problem I've been having is that my browser was blocking textures from loading, which was solved by following the instructions here.
在过去的几天里,我一直在努力让 Three.js 纹理工作。我一直遇到的问题是我的浏览器阻止加载纹理,这是按照此处的说明解决的。
Anyways, I'm making a space-navigator game for one of my classes that demonstrates navigating spacecraft through space. So, I'm rendering a bunch of planets, the Earth being one of them. I've included a picture of my Earth rendering below. It looks okay, but what I'm trying to do is make it look more realistic by adding an 'atmosphere' around the planet.
无论如何,我正在为我的一个班级制作一个太空导航游戏,演示如何在太空中导航航天器。所以,我正在渲染一堆行星,地球就是其中之一。我在下面附上了一张我的地球渲染图。看起来不错,但我想要做的是通过在地球周围添加一个“气氛”来让它看起来更逼真。
I've looked around, and I've found some really neat looking creationsthat deal with glow, but I don't think they apply to my situation, unfortunately.
我环顾四周,发现了一些处理发光的外观非常整洁的作品,但不幸的是,我认为它们不适用于我的情况。
And here's the code that adds the earth to my scene (it's a modified version of code I got from a Three.js tutorial):
这是将地球添加到我的场景中的代码(这是我从 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);
}
采纳答案by Toji
What exactly are you looking for in your atmosphere? It could be as simple as rendering another slightly larger transparent sphere over the top of your globe, or it could be very verycomplex, actually refracting light that enters it. (Almost like subsurface scattering used in skin rendering).
你在你的氛围中究竟在寻找什么?它可以像在地球顶部渲染另一个稍大的透明球体一样简单,也可以非常复杂,实际上是折射进入它的光。(几乎就像皮肤渲染中使用的次表面散射)。
I've never tried such an effect myself, but some quick Googling shows some promising results. For example, I think this effectlooks fairly nice, and the author even followed it up with a more detailed variantlater on. If you're interested in a more technical breakdown this techniquedetails a lot of the theoretical background. I'm sure there's more, you've just got to poke around a bit. (Truth be told I wasn't aware this was such a popular rendering topic!)
我自己从未尝试过这样的效果,但一些快速的谷歌搜索显示了一些有希望的结果。比如,我觉得这个效果看起来还不错,后来作者还做了更详细的变体。如果您对更多的技术分解感兴趣,则该技术详细介绍了许多理论背景。我敢肯定还有更多,你只需要四处看看。(说实话,我不知道这是一个如此受欢迎的渲染主题!)
If you're having trouble with some aspect of those techniques specifically as applies to Three.js don't hesitate to ask!
如果您在这些技术的某些方面遇到问题,特别是适用于 Three.js,请不要犹豫!
[UPDATE]
[更新]
Ah, sorry. Yeah, that's a bit much to throw you into without prior shader knowledge.
啊,对不起。是的,如果没有事先的着色器知识,这会让您陷入困境。
The code on the second link is actually a DirectX FX file, the core code being HLSL, so it's not something that would simply plug into WebGL but the two shader formats are similar enough that it's typically not an issue to translate between them. If you actually know shaders, that is. I would recommend reading up on how shaders work before trying to dive into a complicated effect like this.
第二个链接上的代码实际上是一个 DirectX FX 文件,核心代码是 HLSL,所以它不是简单地插入 WebGL 的东西,但两种着色器格式非常相似,通常在它们之间转换不是问题。如果你真的了解着色器,那就是。在尝试深入研究这样的复杂效果之前,我建议先阅读着色器的工作原理。
I'd start with something simple like this tutorial, which simply talks about how to get a basic shader running with Three.js. Once you know how to get a shader working with Three.js and GLSL tutorials (like this one) will give you the basics of how a shader works and what you can do with it.
我会从一些简单的开始,比如本教程,它只是谈论如何使用 Three.js 运行一个基本的着色器。一旦您知道如何使用 Three.js 和 GLSL 教程(如本教程)让着色器工作,您将了解着色器的工作原理以及您可以用它做什么。
I know that seems like a lot of work up front, but if you want to do advanced visual effects in WebGL (and this certainly fits the bill of advanced effects) you absolutely mustunderstand shaders!
我知道这似乎需要很多前期工作,但是如果您想在 WebGL 中实现高级视觉效果(这当然符合高级效果的要求),您绝对必须了解着色器!
Then again, if you're looking for a quick fix there's always that transparent sphere option I was talking about. :)
再说一次,如果您正在寻找快速解决方案,那么我一直在谈论透明球体选项。:)
回答by Spektre
Well an old and already answered question but I wanted to add my solution for beginer consideration out there. Have plaing along Atmospheric scattering and GLSL for a long time and come to this VEEERRRYYY Simplified version (if animation stops refresh page or view the GIFin something more decend):
好吧,这是一个已经回答的旧问题,但我想添加我的解决方案以供初学者考虑。已经在大气散射和 GLSL 上徘徊了很长时间,来到这个 VEEERRRYYY 简化版(如果动画停止刷新页面或以更优雅的方式查看GIF):
[
[
- planet is and ellipsoid (center x,y,z and radiuses rx,ry,rz)
- atmosphere is also ellipsod (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)
- 行星是椭圆体(中心 x,y,z 和半径 rx,ry,rz)
- 大气也是椭圆体(相同但大气高度更大)
- 所有渲染都正常完成,但最重要的是为附近的观察者星球添加了 1 个通道
- 那个pass是单四片覆盖整个屏幕
- 在片段内部,它计算像素射线与这两个椭球的交集
- 取可见部分(不在后面,不在地面之后)
- 计算大气中的射线长度
- 将原始颜色作为 r,g,b 缩放参数的函数按光线长度扭曲(类似于沿路径积分)
- 一些颜色是一些给定的......
- 极大地影响颜色,因此可以通过几个属性来模拟不同的气氛
- 它在大气层内外都能很好地工作(从远处)
- 可以添加近星作为光源(我使用最大 3 星系统)
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
[编辑 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 new source code
[edit2] 3.9.2014 新源代码
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.
我最近有一些时间为我的引擎实现缩放,并发现原始源代码在 0.002 AU 以上的距离上不是很精确。如果没有 Zoom,它只是几个像素,所以什么也看不到,但是随着 zoom 所有的变化,我试图尽可能地提高准确性。
- here ray and ellipsoid intersection accuracy improvementis the related question to this
- 这里光线和椭球相交精度的提高是与此相关的问题
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:
经过一些更多的调整后,我得到它的可用范围高达 25.0 AU,插值伪影高达 50.0-100.0 AU。这是当前硬件的限制,因为我无法将 non 传递flat fp64
给从顶点到片段的插值器。一种解决方法可能是将坐标系变换移动到片段,但还没有尝试过。以下是一些变化:
- 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
- 新源使用 64 位浮点数
- 并添加
uniform int lights
哪个是使用过的灯的数量 - B0 的含义也有一些变化(它们不再是依赖于波长的常数,而是颜色),因此您需要稍微更改 CPU 代码中的统一值填充。
- 添加了一些性能改进
[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/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;
}
[fragment]
[分段]
#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();
}
[uniform values]
[统一值]
// 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
For more info (and newer images) see also related:
有关更多信息(和更新的图像),请参见相关内容:
[Edit3]
[编辑3]
Here a small CPUside code that I use in my engine to render atmosphere using shader above:
这是我在引擎中使用的一个小的CPU端代码,用于使用上面的着色器渲染气氛:
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();
}
It is extracted from mine engine so it uses a lot of stuff you do not have, but you get the idea how the stuff is used... btw l2g
means transform from local to global coordinate, g2l
is the other way around. If _dir
is present like l2g_dir
it means the transform is handling vector instead of position so no translations. The fromscr
converts screen <-1,+1>
to 3D (camera local) and vector_one
normalizes a vector to unit one. Hope I did not forget to explain something...
它是从我的引擎中提取的,所以它使用了很多你没有的东西,但是你知道这些东西是如何使用的……顺便说一句,l2g
意思是从局部坐标转换到全局坐标,g2l
是相反的。如果_dir
存在,l2g_dir
则表示变换正在处理向量而不是位置,因此没有翻译。在fromscr
转换屏幕<-1,+1>
到3D(照相机本地)和vector_one
归一化的向量到单元中的一个。希望我没有忘记解释一些事情......