码迷,mamicode.com
首页 > 编程语言 > 详细

用体渲染的方法在Unity中渲染云

时间:2018-03-30 00:14:29      阅读:996      评论:0      收藏:0      [点我收藏+]

标签:理解   com   jpg   0ms   返回   code   down   密度   可能性   

最近在知乎上看到一篇文章讲云层的渲染(https://zhuanlan.zhihu.com/p/34836881?utm_medium=social&utm_source=qq)
原文简单的讲了噪声生成云体的办法,以及一个光照模型。
看了之后很感兴趣,加上本科毕设做的就是体渲染,于是打算在unity里山寨一个出来。
原原文(知乎上的文章引用的文章)是2015年地平线黎明时分制作团队的一个talk(http://advances.realtimerendering.com/s2015/The%20Real-time%20Volumetric%20Cloudscapes%20of%20Horizon%20-%20Zero%20Dawn%20-%20ARTR.pdf),讲的更清楚一些。
其中印象最深的是一段视频(https://www.youtube.com/watch?v=FhMni-atg6M)。
这里要说明一下的是,地平线团队制作的这套云层渲染,是服务于游戏中的天气系统的。
游戏中的天气系统会模拟云层的状况(包括分布、密度等);然后根据这个信息去渲染云层。
上面的视频中演示的是,天气系统模拟的一个下雨的场景(视频左下角有天气系统的输出,可以看到中间的一片雨云),可以看到雨云不断接近到视点,然后开始下雨,临场感非常强。

这篇文章会主要讲讲实现过程中的问题,特别是一些细节,毕竟其他的东西上面都有了,就不再重复了。
先看看实现的效果:
技术分享图片
技术分享图片
技术分享图片
三张图片用了不同的Height Signal和Coverage Texture;其中第三张图是想山寨一下视频里的效果(emmmm感觉不太成功)

原文中分成了四个部分进行说明,包括建模、光照、渲染和优化,这篇文章也会按照这个顺序去说一下各种奇怪的问题。

建模

建模指的是通过噪声去生成云层的3D texture。
原文中有2张3D Texture,一是Worley-Perlin噪声加上三张不同频率的Worley噪声,最后云层建模通过raymarch这张贴图获取一个点上的密度值。
这里就是第一个奇怪的点,为什么会有4张噪声贴图,毕竟raymarch的时候最终只取一个浮点数啊。
想来想去,似乎唯一的可能性就是,4张贴图对应了不同的云的种类。结合下文可以看到,这套系统是可以指定某个位置的渲染的云的种类的。
(最后我只用了一个Worley-Perlin去渲染)
同样,第二张3D Texture中,是3张不同频率的Worley噪声,我也只能理解为是对应不同的云的种类了。

这里要注意的是,生成的噪声必须是tilable的,不然天上的云都是一块一块儿的了。
关于如何生成tilable的Perlin噪声跟Worley噪声,刚好有一篇文章讲了这个:https://lightbulbbox.wordpress.com/2015/11/11/clouds-by-perlin-and-worley/
文章中还配有图片,还是挺好懂的。

到这里,我们的的云的基本形状就完成了。实际去sample的话,会是相当严实的一整块东西(如果worley噪声没有调整过的话),因为在贴图中,大部分地方都是非0的(特别是叠加起来的噪声)。我们可以直接调整采样结果,将低于某个值的采样归0,用一下的式子:
tempResult = saturate((tempResult - _Cutoff) / (1-_Cutoff));
低于_CutOff的值会被裁掉,大于_CutOff的会被重新映射到(0,1]上。
在ppt中,提到了天气系统会提供一张2D的云层覆盖图;可以直接乘上这张图的采样结果,就可以自定义云层的分布了。(嗯,比如在贴图里写一句话,就可以让云显示出这句话了。感觉很low)
到这一步已经有云的样子了,然后是限制云层高度,用一张贴图,叫Height Signal,用采样的高度去采样这种贴图即可。
(这里还需要定义一下云层的起始位置跟上限高度,根据画面需求随意发挥吧)

在用第一张3D Texture取样完毕后,会用第二张3D Texture作为Detaill Texture,去减去一开始的采样。其中特意提到了,只在云层的边缘做这个操作。
我的实现方法是计算一个“边缘值”,表示该点有多接近云边缘,通过以下的式子

    float edge = saturate(_DetailMask - lowresSample / _CloudDentisy);  //_CloudDentisy是整体的云层密度,lowresSample之前采样时已经乘上了这个值,现在还原回来再计算;

其中_DetailMask表示的是,低于_DetailMask的采样被认作是边缘。
然后在减去时将edge值乘上Detail Texture的采样即可。

    return saturate(lowresSample - edge * _Detail * sampleResult * _CloudDentisy);       

上面这句返回了对lowresSample进行减去操作之后的密度值。
ppt中说用了三张2d的Curl Noise去distort这个detail texture来表现出流动的效果,不是很明白是怎么操作的,等我搞懂了curl noise再来更新。

接下去的部分就是配合天气系统的演示了,这里当然不可能山寨一个天气系统出来,就跳过了。

关于显示不同种类的云,ppt中只有几句话带过了,
个人推测是将不同云的HeightSignal录入,然后根据天气系统的输入,按照某种规则去选择不同的HeightSignal去采样。
天气系统的输入包括(ppt中提到的)云层覆盖率、降水率和云的种类,对应一张2D贴图的rgb通道。
ppt中提到的HeightSignal有三种,分别是Stratus、Cumulus和Cumulonimbus
(要说明的是,这里最主要的渲染对象是低层的云,即Stratus跟Cumulus(还有一个两者的结合体stratocumulus,不用管),和Cumulonimbus)
其中Cumulonimbus只会在大暴雨的情况下出现(即上文视频中的那一大片云)。
结合ppt中提到的,当降雨率大于70%时,不管什么云都会变成Cumulonimbus,这一事实。
可以认为,天气系统输入的云的种类,会控制HeightSignal的采样在Stratus跟Cumulus之间blend。
当降雨率大于70%时,则去采样Cumulonimbus的HeightSignal(当然会跟普通的采样稍微有点blend,不然太怪了)。
这样就实现了不同种类的云的渲染。

光照

光照部分的核心是一个公式
技术分享图片
该公式描述了云里面的一个点的能量接收情况(乘上HG之后则是该点的能量传递到视点)(我随便说的,我也不知道.jpg)
d表示的是深度,来自于Beer‘s Law,该公式描述了深度和能量传递的关系;
r是他们自己观察得到的一个效果,名为Powder,指的是从光源方向观察这类物体时,会出现边缘变暗的情况。(但是我复现不出来,最终实现的时候去掉了,emmm)
HG是Henyey–Greenstein公式,描述能量在各向异性物质中传播的规律。有它我们可以表现出,朝着太阳看云时,云的边缘处的发光。具体公式如下(复制自http://www.oceanopticsbook.info/view/scattering/the_henyeygreenstein_phase_function):
技术分享图片
cosΨ是传播角度的cos值,在这里应该是视线跟日光方向的夹角(应该是吧,嗯)
其中g值控制各向异性的规律。当g在(0,1)时,光会倾向向前传播,(-1,0)时则会向后,0则是各向同性。
设置成0.2左右就有比较明显的,边缘发光的效果了。

P是雨云的能量吸收比例。

这里面唯一神秘的量就是d深度了,这是需要我们自己算的,但是没有什么直观的办法。
具体的实现则在下一部分渲染中提到。

渲染

ppt中没有提到渲染实现的细节。我这里简单提一下体渲染在Unity中的实现办法。
首先是模型。不管渲染什么东西,都必须先有个模型(Proxy mesh)。
有两种办法,第一个是用一个模型去罩住你想要渲染的体。这个说法有点奇怪,因为直觉的讲是根据模型的位置去渲染体;但是就比如我们的情况下,云的位置是固定的,我们不希望变动模型的位置、缩放之后云也跟着变了。
这个方法的最大问题是,进入模型内部后,模型被裁切,啥都没了,可以通过开启背面渲染解决,但是总会有负担。
第二种办法是在摄像机前渲染一个矩形片面覆盖住视野。如果对深度不在意的话,应该可以写成后处理效果去实现(后处理效果其实就是渲染个四边形)。
为了方便起见我目前是用的一个巨大的盒子飘在天上emmmm。(所以上面的演示截图3中,远处的云看不到,其实是模型超出frustrum了)

渲染部分提到的第一点是raymarch加速。简单来说就是根据低级采样(第一个贴图采样的结果)的结果去决定步长。
当某次采样采样到的结果等于0时,这一块儿就是没有云的,我们可以保持一个比较大的步长去raymarch。
如果采样到不为0时,我们则切换到一个较小的步长去采样(记得在这之前先倒退一步)。
如果在精细采样期间,连续几次采样到0,则再切换会较大的步长去采样。(还有这种操作.jpg)

第二点是上一部分提到的深度计算。
用了一个看起来乱来,但是效果很好的办法,对着太阳的方向的一个锥形进行6次采样。最后的结果作为深度值。
技术分享图片
同时,当一个点的alpha值超过0.3之后,还会切换到一个更加cheap的采样方法作为优化。

后面提了一下中层云的渲染,其实就是普通的贴图;

上面这些做完了之后,ppt中提到,最终的渲染需要20ms才能完成一帧。
我做完之后,实际更惨,需要50ms。
但是稍微修改了一下参数,竟然可以降低到5ms左右。
其中最主要的是改成只用3D贴图的一个通道,帧数立刻暴增。可以推测带宽是最主要的瓶颈。
另外还有一个云的大小的值,用于将贴图分辨率跟实际的云的大小对应。当该值较低时,帧数也会变低。推测是云足够大时,采样的位置在贴图里足够接近,并行化程度较高(纯粹猜的,以前看到过类似的案例)。

优化

优化部分讲的很简单,主要是每次渲染只渲染一个quarter buffer,然后更新最终图像中1/16个像素;上一帧没有的信息直接用低分辨率的顶上。(我目前还没实现,感觉是个技术活)

过段时间再提升一下完成度,我会把整个工程放出来作为参考emmmm。

用体渲染的方法在Unity中渲染云

标签:理解   com   jpg   0ms   返回   code   down   密度   可能性   

原文地址:https://www.cnblogs.com/yangrouchuan/p/8673130.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!