我们先登上shadertoy,可以很好地编写着色器,然后从最简单的天空入手今天的旅程。
我们把全屏的红色值,绿色值和蓝色值分别设置为0.2、0.45和1,看看显示的效果如何
![](http://tiebapic.baidu.com/forum/w%3D580/sign=47e2e90e50ce36d3a20483380af23a24/d1b3550fd9f9d72a7c0e05c9912a2834369bbbff.jpg?tbpicau=2025-02-23-05_99b5f86969959e90ad80d1611cf21447)
可以看到我们已经设置好了天空的蓝色,但是这颜色并不科学,事实上,天空之所以呈现出某种特定的蓝色,是因为阳光在地球大气中发生了瑞利散射,而瑞利散射可以唯一确定各个波长的光发生散***例,我们渲染上用到的三原色R,G,B波长分别为:700nm,546.1nm和435.8nm,可以根据瑞利散射的公式计算得RGB的比例。然后写入着色器中。
![](http://tiebapic.baidu.com/forum/w%3D580/sign=d4bd352a73d3d539c13d0fcb0a86e927/9af0ddf9d72a6059387af4ce6d34349b013bbaff.jpg?tbpicau=2025-02-23-05_c74d40c91abfe33b917013533ae61233)
可以看到现在的颜色没有任何变化,所以要引入球面坐标系,来表达天空的位置
![](http://tiebapic.baidu.com/forum/w%3D580/sign=0b9916379039b6004dce0fbfd9513526/1206d32a6059252dca7d08d0719b033b59b5b9ff.jpg?tbpicau=2025-02-23-05_b11584997d84f115668bf2b89238001c)
如同是一张星空的球面全景图,图中的网格分别代表位置xy
float pi=3.14;
float ax=2.0*pi*uv.x;
float ay=pi*(0.5-uv.y);
然后我们设定地面不是一个地球的球面,而是向水平方向无限延伸的平面,地球的大气层高度为1,只会发生瑞利散射,那么天空看起来是这个样子的
![](http://tiebapic.baidu.com/forum/w%3D580/sign=2984f5ddf3003af34dbadc68052bc619/1cd56459252dd42a3763147f463b5bb5cbeab8ff.jpg?tbpicau=2025-02-23-05_50d59dbd4ffccf0f3dc7d41e5b1397ed)
如图,接近天空地平线的地方会很亮,这是因为高度角很小的光路穿过大气层的长度比高度角为90°时长很多,而地球的大气粒子相当于蓝色的发光体,中间没有任何阻碍,现在假设大气粒子会吸收光线,对R,G,B的吸收量不同,比例相当于瑞利散射的散射比例,把每个大气粒子发出的光再发生衰减的情况求积分得,I=I0*(1-exp(-αz))/α
![](http://tiebapic.baidu.com/forum/w%3D580/sign=f56e96e47ff33a879e6d0012f65d1018/aba6212dd42a283414cc23df1eb5c9ea17cebfff.jpg?tbpicau=2025-02-23-05_934432d52bb1e6e25c2dfb6176176d2b)
![](http://tiebapic.baidu.com/forum/w%3D580/sign=97571a177f87e9504217f3642039531b/eed2d02a2834349b226c7b518cea15ce34d3beff.jpg?tbpicau=2025-02-23-05_109cf432aafdc03ac3f1f3f1abab9b17)
可以看到效果已经很接近实际的天空了,貌似忘了除以衰减系数α,算了,先不管了,假设已经做好了,我们只看图的上半部分,下半部分是地面,再考虑太阳对天空颜色的影响,由于地球的自转和观察者的纬度,太阳每天会东升西落且偏向南方,
//纬度
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));
![](http://tiebapic.baidu.com/forum/w%3D580/sign=18a41a63ac50352ab16125006342fb1a/1fd52c34349b033b79e2e90e50ce36d3d739bdff.jpg?tbpicau=2025-02-23-05_3eb78057f678421827c32f2d064b21d3)
下图为随着阳光穿过大气层厚度减少,从左到右颜色的变化
![](http://tiebapic.baidu.com/forum/w%3D580/sign=19d0c9b4702ac65c6705667bcbf3b21d/e3cb309b033b5bb5eabd352a73d3d539b400bcff.jpg?tbpicau=2025-02-23-05_34e4a667ca5a248263a3b4fe03eed285)
由于米氏散射的存在,天空会以太阳为0°,向四周染色上阳光的颜色
![](http://tiebapic.baidu.com/forum/w%3D580/sign=cd0715ce835c1038247ececa8210931c/ff64073b5bb5c9ea319916379039b60038f3b3ff.jpg?tbpicau=2025-02-23-05_a8ed5ec105f242fcede54a5752252d55)
由于我们是在全景图中绘图,就需要求解全景图中两点(x1,y1),(x2,y2)之间的角度,直接用相应的结论
全景图中两点(x1,y1),(x2,y2)之间的角度=acos(cos(y1)*cos(y2)*cos(x1-x2)+sin(y1)*sin(y2));
其中(x2,y2)是太阳的位置,(x1,y1)为米氏散射的某个像素的位置
根据米氏散射的公式就可以直接画出米氏散射的样子
![](http://tiebapic.baidu.com/forum/w%3D580/sign=107de6b8553853438ccf8729a312b01f/c8c45fb5c9ea15ce1384f5ddf3003af33887b2ff.jpg?tbpicau=2025-02-23-05_08c3e0255cb5d35df36977f57bf2b2af)
早晨和傍晚米氏散射的效果为红色光,即天空在太阳周围染上红色,也就是霞光,但是从不同视角发生米氏散射的大气层的厚度不同,我们再乘上(1.0-exp(-0.575*D)),(大气层的视觉效果)![](http://tiebapic.baidu.com/forum/w%3D580/sign=e00b30dc1643fbf2c52ca62b807fca1e/904acdea15ce36d3f36e96e47ff33a87eb50b1ff.jpg?tbpicau=2025-02-23-05_f7da765b4bc9162477929146519db368)
现在米氏散射在正午会不明显,而在产生霞光的时候会很亮且会变成红色,
![](http://tiebapic.baidu.com/forum/w%3D580/sign=376f73a7bef2b211e42e8546fa816511/021511ce36d3d53991571a177f87e950372ab0ff.jpg?tbpicau=2025-02-23-05_bfeffcbb92a533a806ec072f2d39cffd)
把天空的颜色调暗了,只看图片的上半部分,可以明显看到由于米氏散射在产生霞光的时候会很亮且会变成红色
我们根据常识可以知道,天空由于阴阳的变化在早晨到中午的亮度会发生变化,阳光的亮度在一天中的变化为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)
![](http://tiebapic.baidu.com/forum/w%3D580/sign=7b14db16f7119313c743ffb855390c10/de3132d3d539b60012a41a63ac50352ac45cb7ff.jpg?tbpicau=2025-02-23-05_46a2e1c34dc25075ae291ce7bfc8240a)
可以看到傍晚的颜色已经有一点惊艳了,中途又回头修改了瑞利散射的吸收率,现在天空的瑞利散射的吸收率对rgb一视同仁,对任何颜色没有区别,如图所示
![](http://tiebapic.baidu.com/forum/w%3D580/sign=d2a592f5d613b07ebdbd50003cd69113/fd2cd139b6003af313d0c9b4702ac65c1238b6ff.jpg?tbpicau=2025-02-23-05_a6163a37ed145b0d6747e87dc1ad4921)
真正的成品中还在考虑了低空的瑞利散射,大气灰尘和雾霾。如图
![](http://tiebapic.baidu.com/forum/w%3D580/sign=4c403135b6c4b7453494b71efffc1e78/9150a00f4bfbfbeded71816325f0f736aec31f85.jpg?tbpicau=2025-02-23-05_d84095728cc8c5702c335563969d4cfc)
我们把全屏的红色值,绿色值和蓝色值分别设置为0.2、0.45和1,看看显示的效果如何
![](http://tiebapic.baidu.com/forum/w%3D580/sign=47e2e90e50ce36d3a20483380af23a24/d1b3550fd9f9d72a7c0e05c9912a2834369bbbff.jpg?tbpicau=2025-02-23-05_99b5f86969959e90ad80d1611cf21447)
可以看到我们已经设置好了天空的蓝色,但是这颜色并不科学,事实上,天空之所以呈现出某种特定的蓝色,是因为阳光在地球大气中发生了瑞利散射,而瑞利散射可以唯一确定各个波长的光发生散***例,我们渲染上用到的三原色R,G,B波长分别为:700nm,546.1nm和435.8nm,可以根据瑞利散射的公式计算得RGB的比例。然后写入着色器中。
![](http://tiebapic.baidu.com/forum/w%3D580/sign=d4bd352a73d3d539c13d0fcb0a86e927/9af0ddf9d72a6059387af4ce6d34349b013bbaff.jpg?tbpicau=2025-02-23-05_c74d40c91abfe33b917013533ae61233)
可以看到现在的颜色没有任何变化,所以要引入球面坐标系,来表达天空的位置
![](http://tiebapic.baidu.com/forum/w%3D580/sign=0b9916379039b6004dce0fbfd9513526/1206d32a6059252dca7d08d0719b033b59b5b9ff.jpg?tbpicau=2025-02-23-05_b11584997d84f115668bf2b89238001c)
如同是一张星空的球面全景图,图中的网格分别代表位置xy
float pi=3.14;
float ax=2.0*pi*uv.x;
float ay=pi*(0.5-uv.y);
然后我们设定地面不是一个地球的球面,而是向水平方向无限延伸的平面,地球的大气层高度为1,只会发生瑞利散射,那么天空看起来是这个样子的
![](http://tiebapic.baidu.com/forum/w%3D580/sign=2984f5ddf3003af34dbadc68052bc619/1cd56459252dd42a3763147f463b5bb5cbeab8ff.jpg?tbpicau=2025-02-23-05_50d59dbd4ffccf0f3dc7d41e5b1397ed)
如图,接近天空地平线的地方会很亮,这是因为高度角很小的光路穿过大气层的长度比高度角为90°时长很多,而地球的大气粒子相当于蓝色的发光体,中间没有任何阻碍,现在假设大气粒子会吸收光线,对R,G,B的吸收量不同,比例相当于瑞利散射的散射比例,把每个大气粒子发出的光再发生衰减的情况求积分得,I=I0*(1-exp(-αz))/α
![](http://tiebapic.baidu.com/forum/w%3D580/sign=f56e96e47ff33a879e6d0012f65d1018/aba6212dd42a283414cc23df1eb5c9ea17cebfff.jpg?tbpicau=2025-02-23-05_934432d52bb1e6e25c2dfb6176176d2b)
![](http://tiebapic.baidu.com/forum/w%3D580/sign=97571a177f87e9504217f3642039531b/eed2d02a2834349b226c7b518cea15ce34d3beff.jpg?tbpicau=2025-02-23-05_109cf432aafdc03ac3f1f3f1abab9b17)
可以看到效果已经很接近实际的天空了,貌似忘了除以衰减系数α,算了,先不管了,假设已经做好了,我们只看图的上半部分,下半部分是地面,再考虑太阳对天空颜色的影响,由于地球的自转和观察者的纬度,太阳每天会东升西落且偏向南方,
//纬度
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));
![](http://tiebapic.baidu.com/forum/w%3D580/sign=18a41a63ac50352ab16125006342fb1a/1fd52c34349b033b79e2e90e50ce36d3d739bdff.jpg?tbpicau=2025-02-23-05_3eb78057f678421827c32f2d064b21d3)
下图为随着阳光穿过大气层厚度减少,从左到右颜色的变化
![](http://tiebapic.baidu.com/forum/w%3D580/sign=19d0c9b4702ac65c6705667bcbf3b21d/e3cb309b033b5bb5eabd352a73d3d539b400bcff.jpg?tbpicau=2025-02-23-05_34e4a667ca5a248263a3b4fe03eed285)
由于米氏散射的存在,天空会以太阳为0°,向四周染色上阳光的颜色
![](http://tiebapic.baidu.com/forum/w%3D580/sign=cd0715ce835c1038247ececa8210931c/ff64073b5bb5c9ea319916379039b60038f3b3ff.jpg?tbpicau=2025-02-23-05_a8ed5ec105f242fcede54a5752252d55)
由于我们是在全景图中绘图,就需要求解全景图中两点(x1,y1),(x2,y2)之间的角度,直接用相应的结论
全景图中两点(x1,y1),(x2,y2)之间的角度=acos(cos(y1)*cos(y2)*cos(x1-x2)+sin(y1)*sin(y2));
其中(x2,y2)是太阳的位置,(x1,y1)为米氏散射的某个像素的位置
根据米氏散射的公式就可以直接画出米氏散射的样子
![](http://tiebapic.baidu.com/forum/w%3D580/sign=107de6b8553853438ccf8729a312b01f/c8c45fb5c9ea15ce1384f5ddf3003af33887b2ff.jpg?tbpicau=2025-02-23-05_08c3e0255cb5d35df36977f57bf2b2af)
早晨和傍晚米氏散射的效果为红色光,即天空在太阳周围染上红色,也就是霞光,但是从不同视角发生米氏散射的大气层的厚度不同,我们再乘上(1.0-exp(-0.575*D)),(大气层的视觉效果)
![](http://tiebapic.baidu.com/forum/w%3D580/sign=e00b30dc1643fbf2c52ca62b807fca1e/904acdea15ce36d3f36e96e47ff33a87eb50b1ff.jpg?tbpicau=2025-02-23-05_f7da765b4bc9162477929146519db368)
现在米氏散射在正午会不明显,而在产生霞光的时候会很亮且会变成红色,
![](http://tiebapic.baidu.com/forum/w%3D580/sign=376f73a7bef2b211e42e8546fa816511/021511ce36d3d53991571a177f87e950372ab0ff.jpg?tbpicau=2025-02-23-05_bfeffcbb92a533a806ec072f2d39cffd)
把天空的颜色调暗了,只看图片的上半部分,可以明显看到由于米氏散射在产生霞光的时候会很亮且会变成红色
我们根据常识可以知道,天空由于阴阳的变化在早晨到中午的亮度会发生变化,阳光的亮度在一天中的变化为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)
![](http://tiebapic.baidu.com/forum/w%3D580/sign=7b14db16f7119313c743ffb855390c10/de3132d3d539b60012a41a63ac50352ac45cb7ff.jpg?tbpicau=2025-02-23-05_46a2e1c34dc25075ae291ce7bfc8240a)
可以看到傍晚的颜色已经有一点惊艳了,中途又回头修改了瑞利散射的吸收率,现在天空的瑞利散射的吸收率对rgb一视同仁,对任何颜色没有区别,如图所示
![](http://tiebapic.baidu.com/forum/w%3D580/sign=d2a592f5d613b07ebdbd50003cd69113/fd2cd139b6003af313d0c9b4702ac65c1238b6ff.jpg?tbpicau=2025-02-23-05_a6163a37ed145b0d6747e87dc1ad4921)
真正的成品中还在考虑了低空的瑞利散射,大气灰尘和雾霾。如图
![](http://tiebapic.baidu.com/forum/w%3D580/sign=4c403135b6c4b7453494b71efffc1e78/9150a00f4bfbfbeded71816325f0f736aec31f85.jpg?tbpicau=2025-02-23-05_d84095728cc8c5702c335563969d4cfc)