风格化的动态天空球

Last modified date

做一个可以自由配色的,风格化的动态天空球,并且支持日夜变化和天气变化。

大多数引擎自带的天空球要么是照片捕捉的静态材质,要么是基于物理计算的。物理计算的天空球用到了比较复杂的光学散射函数,例如米氏散射(Mie Scattering),瑞丽散射(Rayleigh Scattering)等等。通过大气密度,辐照度等等参数控制。我对光学一窍不通,顶多也就是抄他们现成的公式做。

但是基于物理的天空球局限性很大,只适合模拟真实。如果要做风格化的天空配色,或者是做二次元卡通风格的,就不适用了。所以我要做一个可以高自由度调色的动态天空球。

天空球配色

首先要解决的就是如何配色。用科学家的光照函数显然是无法做到自由配色的,所以我干脆做成手动填色的方式。

  • 把配色数据做成数组形式,像动画关键帧那样,在一天24小时里自由添加关键点,配置这一点上的颜色参数。
  • 渐变配色采用3个节点就足够了,可以调出风格的颜色变化。
  • 运行时在GPU里Lerp前后两个关键点的参数,然后传递给Shader或者Lighting设置里。

以后可以到网上搜好看的天空图片,直接吸它的颜色填进去,想要什么样的配色都可以搞出来。

太阳,月亮和星空的做法

太阳和月亮都可以通过shader直接画在天空球的mesh上,不需要额外的GameObject。

直接用主平行光源的欧拉角度算出其投射到天空球的坐标当作太阳的位置。

太阳是在PS阶段在用“Distance(PositionWS, SunPosWS);”的方式算,算出一个圆形区域,再Resample成太阳的形状。同时额外Resample一份作为太阳月亮的光晕。

月亮因为有具体的纹理,所以要额外采一张月亮的贴图。月亮的UV可以在屏幕空间ScreenSpace里面做,因为在地球上看月亮的尺寸是不变的,所以在屏幕空间算一个固定尺寸的uv画月亮就行了。缺点就是不管什么角度看月亮都是同一个旋转角度的,当然这也可以在shader里换算成正确角度,但也没什么必要。

星空有很多做法,简单点的就是在shader里算个躁点,或者采一张tile的躁点图。我目前是采里一张贴图。贴图的R通道做躁点,GB两个通道当作银河的Mask,可以配蓝紫两种颜色模拟银河效果。

云的做法

我把云也直接画在天空球上,这样可以把整个系统都依附在一个天空球Mesh上,同时也是想把这个系统做到足够优化,支持移动端。

云的做法就是把UV按照光源方向稍作偏移,采样3次噪声纹理,然后加减算法可以获得模拟受光面,背光面,中间段的区域,然后进行着色,模拟出体积感。之前一篇也介绍过:Volumetric Clouds – 体积云的做法

但这次不做分层渲染,而是直接画在天空球上。天空球是圆形的,所以要先在shader里把Y轴高度压缩一下,做成扁一点的穹拱,这样比较符合符合玩家观察的视角:头顶云层高,远处云层慢慢和地平线相接。(因为云层的UV是直接用世界坐标换算的,所以可以通过世界坐标的Y轴反向调整UV采样,达到这样的效果)

做高空和低空两层云,可以让天空纵深感加强。最后还要给云层加个Distance Fade,让远处的云逐渐消失。

当然把云层一起画到天空球上也有一些缺点,就是无法把其他东西穿插在天空球和云层之间了,例如飞鸟和流星。同时玩家也无法飞到云层里去。

整合天空球,日月星空,和云

把以上这些内容整合起来,把日月星空和云层的参数配置也放到关键点上配置。这样可以很自由的调整整个天空环境的效果。

如上图,TimePoint是时间点(0~24)。可配置天空颜色,云层颜色,光晕颜色,主光源颜色和光晕半径。根据这些参数可以算出雾的颜色,环境色,等等。然后填入Lighting设置和天空球的Shader。

地平线的效果细化

日月接近地平线的时候有点突兀。一般来说地平线上会有大气折射使太阳逐渐变形,虚化;并且地平线上的云层会比较多,日月还未落下就可能会先被云雾遮挡。因为之前采过云的噪声纹理,虽然云层被Distance Fade隐藏掉了,但天际线边缘还是有云的纹理数据的。这时候就可以拿出来当作地平线上的云层Mask来遮挡日月。

阴晴变化

之前设置的24小时日出日落的参数都是晴天的,所以要另外再加一套白天和一套夜晚的阴天参数,用来随时lerp成阴天。(因为阴天基本就是灰色调的,不需要做24小时变化了,只要白天黑夜的区别即可)

阴天的雾和环境色设置得厚重点,再配上雨水粒子,就比较有感觉了。

完成

最后我在编辑器里做了个Json数据读写的功能,可以方便的编辑和修改。这样动态天空球基本上就做完了。