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

在这里输入图像描述

你究竟在你的气氛中寻找什么? 它可能就像在地球上方渲染另一个稍大的透明球体那样简单,或者可能非常复杂,实际上折射入射到它的光线。 (几乎像皮肤渲染中使用的子表面散射)。

我从来没有尝试过这样的效果,但是一些快速的谷歌search显示了一些有希望的结果。 例如,我认为这个效果看起来相当不错,作者以后也会更加详细地介绍它。 如果你对更多的技术细节感兴趣, 这个技巧详细介绍了大量的理论背景。 我相信还有更多,你只是要捅一下。 (真相告诉我不知道这是一个很受欢迎的渲染主题!)

如果您对这些技术的某些方面有困难,特别是适用于Three.js,请不要犹豫,问!

[UPDATE]

啊,对不起。 是的,如果没有以前的着色器知识,这会让你感觉有些不舒服。

第二个链接上的代码实际上是一个DirectX FX文件,核心代码是HLSL,所以它不是简单地插入WebGL,但两种着色器格式足够相似,以至于在它们之间进行转换通常不是问题。 如果你真的知道着色器,那就是。 我build议阅读着色器的工作方式,然后再尝试着去研究这样复杂的效果。

我将从这个简单的教程开始,简单地介绍如何使用Three.js来运行基本的着色器。 一旦你知道如何使用Three.js和GLSL教程(比如这个 )来着色,将会给你一个着色器如何工作的基础知识,以及你可以用它做什么。

我知道,这似乎有很多工作,但如果你想在WebGL中做先进的视觉效果(这当然符合先进的效果),你绝对必须了解着色器!

再次,如果你正在寻找一个快速的解决scheme,总是有我所说的透明球体选项。 🙂

那么一个老的,已经回答的问题,但我想增加我的解决scheme,在那里开始考虑。 沿着大气散射和GLSL很长一段时间,并来到这个VEEERRRYYY简化版本(如果animation停止刷新页面或查看GIF的更多的东西):

[ 示例[1]

  1. 行星是和椭球(中心x,y,z和半径rx,ry,rz)
  2. 气氛也是椭圆形的(相同但是气氛高度更大)
  3. 所有的渲染都是正常完成的,但最重要的是为近旁的观察者星球添加了1遍
  4. 那个传球是单一的四合一全屏
  5. 里面的片段计算像素射线与这两个椭球的交点
  6. 采取可见的部分(不是后面,不是在地面之后)
  7. 计算大气中的射线长度
  8. 作为r,g,b的函数来扭曲原始颜色(通过光线长度缩放的参数)
    • 一些颜色是采取一些给定的…
    • 极大地影响颜色,因此可以通过几个属性来模拟不同的气氛
  9. 它在内部工作,也在外面(从远处)
  10. 可以添加近距离的星星作为光源(我使用最大3星级系统)

结果是惊人的看到下面的图像:

在这里输入图像描述在这里输入图像描述在这里输入图像描述在这里输入图像描述在这里输入图像描述

顶点:

 /* 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; } 

分段:

 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 rx is elipsoid rx^-2, ry = ry^-2 and rz=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*rx) +(dp.y*dp.y*ry) +(dp.z*dp.z*rz); a*=2.0; b=(p0.x*dp.x*rx) +(p0.y*dp.y*ry) +(p0.z*dp.z*rz); b*=2.0; c=(p0.x*p0.x*rx) +(p0.y*p0.y*ry) +(p0.z*p0.z*rz)-1.0; d=((b*b)-(2.0*a*c)); if (d<0.0) return false; d=sqrt(d); l0=(-b+d)/a; l1=(-bd)/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=(-bd)/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; cr*=1.0-br; cg*=1.0-bg; cb*=1.0-bb; c+=b*qqq; } if (cr<0.0) cr=0.0; if (cg<0.0) cg=0.0; if (cb<0.0) cb=0.0; h=0.0; if (h<cr) h=cr; if (h<cg) h=cg; if (h<cb) h=cb; if (h>1.0) { h=1.0/h; cr*=h; cg*=h; cb*=h; } return c; } void main(void) { gl_FragColor.rgb=atmosphere(); } 

对不起,但它的一个非常古老的来源…应该可能转换为核心configuration文件

[编辑1]抱歉忘记添加我的input散射常量的地球大气

  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。 这是目前的硬件限制,因为我不能通过非flat fp64插值器从顶点到片段。 一种方法可能是将坐标系转换为片段,但还没有尝试过。 这里有一些变化:

  • 新来源使用64位浮点数
  • 并添加uniform int lights ,这是使用的灯的数量
  • 还有一些B0的含义变化(它们不再是波长相关的常数而是颜色),所以你需要稍微改变一下统一值填入CPU代码。
  • 一些性能改进增加了

[顶点]

 /* 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 rx is elipsoid rx^-2, ry = ry^-2 and rz=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 all+b.l+c=0; l0,l1=?; a=(dp.x*dp.x*rx) +(dp.y*dp.y*ry) +(dp.z*dp.z*rz); b=(p0.x*dp.x*rx) +(p0.y*dp.y*ry) +(p0.z*dp.z*rz); b*=2.0; c=(p0.x*p0.x*rx) +(p0.y*p0.y*ry) +(p0.z*p0.z*rz)-1.0; // discriminant d=sqrt(bb-4.ac) 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=(-bd)/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 all+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(bb-4.ac) 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=(-bd)/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]; ca=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(pb); h=exp(h/planet_h)/2.78; b=B0.rgb*h*dl; cr*=1.0-br; cg*=1.0-bg; cb*=1.0-bb; c.rgb+=b*ll; } if (cr<0.0) cr=0.0; if (cg<0.0) cg=0.0; if (cb<0.0) cb=0.0; h=0.0; if (h<cr) h=cr; if (h<cg) h=cg; if (h<cb) h=cb; if (h>1.0) { h=1.0/h; cr*=h; cg*=h; cb*=h; } return c; } void main(void) { gl_FragColor.rgba=atmosphere(); } 

[统一值]

 // Earth re=6378141.2 // equatoreal radius rx,ry rp=6356754.79506139 // polar radius rz 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 

欲了解更多信息(和更新的图片),也参见相关:

  • 是否有可能在尺寸和质量上做出逼真的n体太阳系模拟?