minecraft吧 关注:2,543,528贴子:31,805,954
  • 7回复贴,共1

从0写一个可用于实战的物理天空着色图

只看楼主收藏回复

我们先登上shadertoy,可以很好地编写着色器,然后从最简单的天空入手今天的旅程。
我们把全屏的红色值,绿色值和蓝色值分别设置为0.2、0.45和1,看看显示的效果如何

可以看到我们已经设置好了天空的蓝色,但是这颜色并不科学,事实上,天空之所以呈现出某种特定的蓝色,是因为阳光在地球大气中发生了瑞利散射,而瑞利散射可以唯一确定各个波长的光发生散***例,我们渲染上用到的三原色R,G,B波长分别为:700nm,546.1nm和435.8nm,可以根据瑞利散射的公式计算得RGB的比例。然后写入着色器中。

可以看到现在的颜色没有任何变化,所以要引入球面坐标系,来表达天空的位置

如同是一张星空的球面全景图,图中的网格分别代表位置xy
float pi=3.14;
float ax=2.0*pi*uv.x;
float ay=pi*(0.5-uv.y);
然后我们设定地面不是一个地球的球面,而是向水平方向无限延伸的平面,地球的大气层高度为1,只会发生瑞利散射,那么天空看起来是这个样子的

如图,接近天空地平线的地方会很亮,这是因为高度角很小的光路穿过大气层的长度比高度角为90°时长很多,而地球的大气粒子相当于蓝色的发光体,中间没有任何阻碍,现在假设大气粒子会吸收光线,对R,G,B的吸收量不同,比例相当于瑞利散射的散射比例,把每个大气粒子发出的光再发生衰减的情况求积分得,I=I0*(1-exp(-αz))/α


可以看到效果已经很接近实际的天空了,貌似忘了除以衰减系数α,算了,先不管了,假设已经做好了,我们只看图的上半部分,下半部分是地面,再考虑太阳对天空颜色的影响,由于地球的自转和观察者的纬度,太阳每天会东升西落且偏向南方,
//纬度
float beta = pi/6.0;
//地球的自转
float spin =0.2/2.0*iTime;
//太阳的水平角
float sunax;
if(sin(spin)<.0) sunax=-acos((cos(spin))/(sqrt(cos(spin)*cos(spin)+sin(beta)*sin(beta)*sin(spin)*sin(spin))));
else sunax=acos((cos(spin))/(sqrt(cos(spin)*cos(spin)+sin(beta)*sin(beta)*sin(spin)*sin(spin))));
//太阳高度角
float sunay=-asin(cos(beta)*sin(spin));
在太阳接近地平线的时候,由于穿过的大气层很厚,太阳会变成红色
vec3 suncolor = vec3(exp(-0.1666*d),exp(-0.44975*d),exp(-1.10894776*d));

下图为随着阳光穿过大气层厚度减少,从左到右颜色的变化

由于米氏散射的存在,天空会以太阳为0°,向四周染色上阳光的颜色

由于我们是在全景图中绘图,就需要求解全景图中两点(x1,y1),(x2,y2)之间的角度,直接用相应的结论
全景图中两点(x1,y1),(x2,y2)之间的角度=acos(cos(y1)*cos(y2)*cos(x1-x2)+sin(y1)*sin(y2));
其中(x2,y2)是太阳的位置,(x1,y1)为米氏散射的某个像素的位置
根据米氏散射的公式就可以直接画出米氏散射的样子

早晨和傍晚米氏散射的效果为红色光,即天空在太阳周围染上红色,也就是霞光,但是从不同视角发生米氏散射的大气层的厚度不同,我们再乘上(1.0-exp(-0.575*D)),(大气层的视觉效果)
现在米氏散射在正午会不明显,而在产生霞光的时候会很亮且会变成红色,

把天空的颜色调暗了,只看图片的上半部分,可以明显看到由于米氏散射在产生霞光的时候会很亮且会变成红色
我们根据常识可以知道,天空由于阴阳的变化在早晨到中午的亮度会发生变化,阳光的亮度在一天中的变化为sin(sunay),阳光在大气中通过长度的变化为h*sqrt((1.0/tan(sunay))*(1.0/tan(sunay))+1.0),两式相乘为h*sqrt((1.0/tan(sunay))*(1.0/tan(sunay))+1.0)*sin(sunay),会得到理想状态下的天空明度变化,当太阳运行到地面,即图片下半部分时,天空应该全黑,所以我们还需要if语句,当sin(sunay)>0时,skycol=skycol,当sin(sunay)<0时,skycol=vec3(0),即纯黑色,至此,我们完成了比较简单的天空着色,如果还能看得懂,接下来,那么我们继续,
事实上,由于瑞利散射,阳光会散射掉蓝色光而剩下红色光,那么阳光的颜色与天空的颜色一定互为反色,即如果阳光是红色的,那么天空一定是蓝色的,由于我们已经知道阳光的颜色vec3 suncolor = vec3(exp(-0.1666*d),exp(-0.44975*d),exp(-1.10894776*d));那么我们可以认为天空的颜色是vec3 skycolor=vec3(1,1,1)-suncolor;但是这个时候我们已经默认了阳光在大气中通过的长度,所以天空的亮度在一天中的变化为sin(sunay)

可以看到傍晚的颜色已经有一点惊艳了,中途又回头修改了瑞利散射的吸收率,现在天空的瑞利散射的吸收率对rgb一视同仁,对任何颜色没有区别,如图所示

真正的成品中还在考虑了低空的瑞利散射,大气灰尘和雾霾。


IP属地:江西1楼2022-03-22 23:18回复
    是大佬,现在mc貌似还没有搞这方面的


    IP属地:安徽来自Android客户端3楼2022-03-23 00:44
    收起回复
      void mainImage( out vec4 fragColor, in vec2 fragCoord )
      {
      float pi=3.1415926535;
      // Normalized pixel coordinates (from 0 to 1)
      vec2 uv = fragCoord/iResolution.xy;
      float ax=2.0*pi*uv.x;
      float ay=-pi*(0.5-uv.y);
      float h =1.0/3.0;
      //Distance of observation line in atmosphere
      float D = h*sqrt((1.0/tan(ay))*(1.0/tan(ay))+1.0);
      float sky= 1.0-exp(-0.44975*D*3.0);
      vec3 col = 3.9*vec3(sky,sky,sky);
      //纬度
      float beta = pi/6.0;
      //地球的自转
      float spin = 0.2/2.0*iTime;
      //的水平角
      float sunax;
      if(sin(spin)<.0) sunax=-acos((cos(spin))/(sqrt(cos(spin)*cos(spin)+sin(beta)*sin(beta)*sin(spin)*sin(spin))));
      else sunax=acos((cos(spin))/(sqrt(cos(spin)*cos(spin)+sin(beta)*sin(beta)*sin(spin)*sin(spin))));
      //高度角


      IP属地:江西来自Android客户端7楼2022-03-24 09:10
      回复
        float sunay=asin(cos(beta)*sin(spin));
        //阳光穿过大气层的长度距离
        float d = h*sqrt((1.0/tan(sunay))*(1.0/tan(sunay))+1.0);
        vec3 suncolor = vec3(exp(-0.1666*d),exp(-0.44975*d),exp(-1.10894776*d));
        //已知两点的球面坐标,求两点的夹角
        float alpha=acos(cos(ay)*cos(sunay)*cos(ax-sunax)+sin(ay)*sin(sunay));
        //米氏散射系数
        float G1 = 0.25;
        float G2 = 0.55;
        float G3 = 0.75;
        float G4 = -0.5;
        //米氏散射公式
        float mie1 = 0.95*(1.0-G1*G1)/(1.0+G1*G1-2.0*G1*cos(alpha));
        float mie2 = 0.95*(1.0-G2*G2)/(1.0+G2*G2-2.0*G2*cos(alpha));
        float mie3 = 0.95*(1.0-G3*G3)/(1.0+G3*G3-2.0*G3*cos(alpha));
        float mie4 = 0.95*(1.0-G4*G4)/(1.0+G4*G4-2.0*G4*cos(alpha));
        col = sin(sunay)*col*(1.0-suncolor);
        col+=1.3*suncolor*(0.5*mie1+2.0*mie2+0.5*mie3+0.85*mie4)/3.85*(1.0-exp(-0.225*1.5*D));
        if(ay<0.0)col=vec3(0);
        // Output to screen
        fragColor = vec4(col,1.0);
        }


        IP属地:江西来自Android客户端8楼2022-03-24 09:10
        回复


          IP属地:江西来自Android客户端9楼2022-03-25 10:23
          回复