在实现之前,先谈谈程序化天空的基本思路,大致如下:
1.sun/moon disk mask,实现一个遮罩用于绘制太阳/月亮;
2.color gradient,用于实现天空颜色的渐变/变化;
3.horizon Line,地平线效果;
这些是最基本的程序化天空需要实现的效果(Unity 默认天空球),在此基础上又有云、雾、大气散射等方面进一步去美化,还可以打造一些特殊的场景如星光、极光等。
一、Sun/Moon Disk Mask
算这个mask的基本思路就是求球面上一个圆形区域,还记得在我们是怎么计算Blinn-Phong 高光的吗?思路几乎是差不多的。
一个简单的disk mask:
float3 sunDotV = dot(sunDir,viewDir);
//a,b为可调节的参数
float sunDisc = saturate(pow(sunDotV,a) * b);
UE的实现方案很成熟,效果也很好,实现了颜色上从里到外的渐变,这里就直接搬过来用了:
float3 sunDotV = dot(sunDir,viewDir);
//a,b为可调节的参数
float sunRadius = sqrt(max((-sunDotV * 0.5 + 0.5) - _SunRadius * 0.01,a * 0.0001));
float sunDisc = pow(b * sunColor/sunRadius,2.0f);
这片遮罩被计算出来后需要绘制到太阳和月亮相应的位置去,这部分在C#代码中根据光照方向来计算;
计算月亮时我们需要一个UV坐标来采样月亮的贴图,这里涉及到一个空间坐标转换为UV坐标的计算,因为是在球面上(天空就是一个球体),所以对于月亮的X和Y轴,我们可以用下面的伪代码计算然后将XY的夹角映射到UV坐标上:
X = moonUpDir;
Y = cross(moonDir,moonUpDir);
u = dot(normalize(X),posOS);
v = dot(normalize(Y),posOS);
如果看不太懂上文的几何计算,推荐看下games101中讲vector计算相关代表的几何意义的部分或者这个小姐姐的视频。
因为是球面所以算出来两个相同uv的区域,表现上会看到两个月亮,我们筛掉第二个即可。
再加上上文和太阳同理的mask,计算结果如图:
二、Color gradient
这部分很简单,做个随太阳高度渐变的颜色插值然后调色即可。或者制作一些Ramp图来采样。
三、Horizon Line
这部分也很简单,思路就是利用求得的mask然后和天空的颜色做一次混合。
half dotViewUp = dot(viewDir, half3(0,1,0));
float horizonMask = saturate(1.0 - abs(dotViewUp));
horizonMask = pow(horizonMask, _HorizonLineExponent);
horizonMask = saturate(horizonMask);
上面这段代码是danielshervheim佬提供的,不过嘛,地球毕竟是圆的(笑),我们给它做点小小的优化,让这个mask更贴合地平线实际的效果。
如图:
混合后的颜色:
引用&参考
1.https://www.eastshade.com/creating-a-dynamic-sky-in-unity/
2.https://github.com/danielshervheim/unity-stylized-sky
3.https://www.youtube.com/watch?v=kfM-yu0iQBk