写在前面
赶在年前写一篇文章。之前翻看2015年的SIGGRAPH Course(关于渲染的可以去selfshadow的博客里找到,很全)的时候看到了关于体积云的渲染。这个课程讲述了开发者为游戏《地平线:黎明时分》所开发的动态天气系统,重点讲了里面的云的模拟和渲染,很有参考价值。
其中,云的建模主要使用了raymarching的方法,他们的启发应该和shadertoy有关,但多了更多的程序控制和艺术效果等。可以从上面的图片看出来,效果很好。
SIGGRAPH上的这个演讲讲的主要是3D动态云彩的渲染,适合于端游这样的大型游戏。后来在翻iq的博客的时候,发现偶像在2005年就写过一篇关于2D动态云模拟的文章,里面用到的算法相对来说简单许多,计算量也很少。这篇文章写于十年前,那时候电脑的计算资源非常有限,因此为了提升性能、减少内存占用等目的,提出了很多trick。尽管现在电脑的运算资源好了许多,但这些trick也没有因此退出舞台,而是可以作用到移动平台。这篇文章就是想要介绍一下iq那篇文章里提到的方法。
2D动态云彩
回想我们现在一般做天空背景的时候是怎么做的。我们首先会准备一个半圆形的天空顶来模拟背景,然后通常会准备几个图片,这些图片中包含了天空背景,例如蓝色的天空和几朵白色的云彩。每个图片作为一层背景,并赋值给一个材质,该材质会对这些图片进行纹理动画,移动每层的纹理来模拟云彩缓慢飘动的效果。这种方法简单有效,因此应用很广泛。
不过有些时候,我们希望游戏的天空背景并不是提前预知好的,或者我们系统实现一个天气系统,可以随着天气变化而动态产生自然的变化。这时候就不能使用提前准备好的图片来模拟云彩和天空了。这篇文章就是想讲一下如何使用程序来动态模拟云彩,尽管本文实现的效果还比较简陋,但相信在程序和美术的共同配合下,有这个需要的朋友可以得到启发,实现出非常漂亮的效果。
本文的计算复杂度很低,往下看之前需要对噪声有一定了解,不了解的可以参见之前的文章【图形学】谈谈噪声。本文最后会实现一个简单的天空模拟,包括天空颜色、星星、飘动的云彩等,同时可以让用户调整云彩的颜色、厚度、尖锐度等。下面的视频显示了一个下雨和晴朗天气下的效果,其中天空部分的模拟使用了本文的方法。
算法实现
其实我们的重点就是云彩的模拟,天空颜色之类的可以是用另外的shader或纹理来实现,例如在上面的视频中我就是使用另一个Pass来渲染天空和星星等效果。我们这里只解释云彩模拟的部分。
云彩的模拟就是使用分形噪声,这张噪声中的值就对应了云彩的厚度。那么怎么能模拟出云彩不规则变化的效果呢?我们可以想当然的想到这需要一张不断变化的二维噪声纹理。在之前的文章【图形学】谈谈噪声中,我们讲到可以使用一张三维噪声纹理来得到平滑变化的二维噪声纹理,其中第三个采样坐标即对应了时间参数。但是,使用3D纹理的代价是要占用大量内存,而我们的目标是要实时并且计算量尽可能小,那么这个方法就不可取了。
现在到了关键的地方了,这就需要使用一个小的trick。我们知道,分形噪声其实是由许多层不同采样大小的噪声(被称为octave)按照一定权重相加后得到的。为了让最后的分形噪声不断变化,我们可以按照不同的速度来移动这些octave层,这样最后得到的分形噪声也就会不断变化。层数不需要太大,本文的实现和iq文中提到的一样,只使用了4个octave。
那么,现在第一步就是先要创建这些octave。
创建噪声纹理
第一步我们首先需要创建出组成分形噪声的各个octave。和【图形学】谈谈噪声一文有稍许不同的是,由于我们需要对这些纹理进行不断平移,为了实现无缝连接,我们需要让这些噪声纹理是无缝的(seamless)。而要得到无缝的2D噪声纹理,通常的方法是首先创建4D噪声纹理,然后在4D空间下取两个相互正交的圆,在圆上进行采样得到一张2D噪声纹理。原因和算法可参考下面的链接:
- http://ronvalstar.nl/creating-tileable-noise-maps
- http://gamedev.stackexchange.com/questions/23625/how-do-you-generate-tileable-perlin-noise
我直接使用了Unity wiki上的代码,它使用4D的Simplex噪声来产生无缝的2D噪声纹理,没采用Perlin噪声的原因是Simplex在高纬度上的计算复杂度要小的多,具体可参见【图形学】谈谈噪声。
这样,我们就得到了4张噪声纹理以及它们按权重相加得到的分形噪声:
在运行时刻,我们只需要按不同的速度来移动这些噪声,这些速度只要不会破坏云彩的模拟效果就