我们先登上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一视同仁,对任何颜色没有区别,如图所示
真正的成品中还在考虑了低空的瑞利散射,大气灰尘和雾霾。
我们把全屏的红色值,绿色值和蓝色值分别设置为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一视同仁,对任何颜色没有区别,如图所示
真正的成品中还在考虑了低空的瑞利散射,大气灰尘和雾霾。