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

最近在知乎上看到一篇文章讲云层的渲染(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。

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

时间: 03-29

用体渲染的方法在Unity中渲染云的相关文章

关于Unity中渲染顺序问题

1,Camera是渲染顺序的最大总指挥 顺序由Camera的Depth值决定,值越小视野范围内的游戏物体越先渲染 2,layer是对游戏中所有物体的分类别划分 如UIlayer, waterlayer, 3DModelLayer, smallAssetsLayer, effectLayer等.将不同类的物体划分到不同的层,便于相机拣选,在相机的cullmask中可以选择渲染哪些层,不选择的层则不会渲染.还可以用于射线检测对象的拣选,可以指定只对某些层的对象进行射线检测. 3,canvas上的层级

Unity中Canvas

Render Modes(渲染模式) 1.在screen空间中渲染2.在world空间中渲染 Screen Space-Overlay 在这个渲染模式中,UI元素将在场景的上面.如果场景改变大小或改变分辨率,Canvas将自动改变大小去适配. Screen Space-Camera 这和Screen Space-Overlay类似,但是在这个模式中,这个Canvas放置在了给定距离的摄像机的前面.这些UI元素都是通过摄像机绘制的.这意味着摄像机影响UI的外观.如果摄像机设置为Perspectiv

Unite 2018 | 《崩坏3》:在Unity中实现高品质的卡通渲染(下)

http://forum.china.unity3d.com/thread-32273-1-1.html 今天我们继续分享米哈游技术总监贺甲在Unite Beijing 2018大会上的演讲<在Unity上实现高品质卡通渲染的效果>下篇,上篇请点击此处阅读. 下面为演讲内容: 接下来我们就来介绍一下头发的渲染.头发是卡通渲染角色较为重要且独特的部分.我们想要实现根据光源动态变化的高光和阴影渐变,并且这个实现还应具备直观的所见即所得的色彩调节能力. 和皮肤的材质一样,对于头发的漫反射渲染我们同样

unity中三种调用其他脚本函数的方法

第一种,被调用脚本函数为static类型,调用时直接用  脚本名.函数名().很不实用-- 第二种,GameObject.Find("脚本所在物体名").SendMessage("函数名");  此种方法可以调用public和private类型函数 第三种,GameObject.Find("脚本所在物体名").GetComponent<脚本名>().函数名();此种方法只可以调用public类型函数 unity中三种调用其他脚本函数的

(转)Unity中protobuf的使用方法

在移动手机游戏开发中,目前Unity3D已成为比较主流的开发技术. 那么对于客户端服务器协议的打解包,我们有3中常用的处理方式: 1.自定义结构体:在协议中直接传输代码中自定义的结构体:这种方式的坏处是极大的增加了重复性的工作量,并且不能实现协议前后向兼容,可扩展性差: 2.json.xml等文本协议格式: 使用json.xml等文本协议作为协议格式:这种方式的好处是易于开发,方便协议前后向兼容和扩展,缺点是不能序列化,数据量大,浪费带宽: 3.推荐使用的方式: protobuf协议打解包方式:

C#开发Unity游戏教程之Unity中方法的参数

C#开发Unity游戏教程之Unity中方法的参数 Unity的方法的参数 出现在脚本中的方法,无论是在定义的时候,还是使用的时候,后面都跟着一对括号“( )”,有意义吗?看起来最多也就是起个快速识别方法的作用吧.既然C#的语法规定方法就应该这么写,肯定是有一定道理的.如果是上升到战略意义的道理,连作者也不是很明白,但是作者知道这对括号里可以添加“参数”. Unity中参数的作用 要说明参数的作用,就必须从方法说起.方法可以处理变量中的数据,进而影响游戏对象的行为逻辑,这是本章前面一直在强调的.

Unity原生渲染方案

Unity原生渲染方案 作者:3dimensions 本文为原创内容,转载请注明出处. 做这个的动机是想在原生代码中使用Unity的材质系统绘制,同时由原生代码提供绘制数据,省掉动态模型数据“非托管内存→ 托管内存→ 非托管内存”的传输过程.适用于有大量动态模型数据生成的情况,注意,如果不使用Unity的材质系统,并不需要按这个方案做.方案是我在Miloyip的建议下完成的. 一.目标 在Unity中,动态生成三维模型需要把数据填入Mesh对象中,当中Unity内部需要分配内 存及做数据转换,效

【浅墨Unity3D Shader编程】之五 圣诞夜篇: Unity中Shader的三种形态对比&amp;混合操作合辑

本系列文章由@浅墨_毛星云 出品,转载请注明出处.  文章链接:http://hpw123.net/a/C__/kongzhitaichengxu/2014/1222/164.html 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 邮箱: [email protected] QQ交流群:330595914 更多文章尽在:http://www.hpw123.net 本文算是固定功能Shader的最后一篇,下一次更新应该就会开始讲解表面Shader,而

写给VR手游开发小白的教程:(四)补充篇,详细介绍Unity中相机的投影矩阵

这篇作为上一篇的补充介绍,主要讲Unity里面的投影矩阵的问题: 上篇的链接写给VR手游开发小白的教程:(三)UnityVR插件CardboardSDKForUnity解析(二) 关于Unity中的Camera,圣典里面对每一项属性都做了简要的介绍,没看过的小伙伴传送门在下面 http://www.ceeger.com/Components/class-Camera.html 一.裁剪面 先从这个专业的词汇开始,以下是圣典对裁剪面的介绍: The Near and Far Clip Plane