【博物纳新】专栏是UWA旨在为开发者举荐新鲜、易用、乏味的开源我的项目,帮忙大家在我的项目研发之余发现世界上的热门我的项目、前沿技术或者令人惊叹的视觉效果,并摸索将其利用到本人我的项目的可行性。很多时候,咱们并不知道本人想要什么,直到某一天咱们遇到了它。

明天举荐的两个我的项目来自UWA开源库:

1)雪地足迹
2)体积云

01 雪地足迹

一、概览

该我的项目实现的性能是在雪地上踩出足迹,只反对PC平台。

成果如下:

二、实现原理

1. 足迹踩在何处?
咱们能够看到dynamic_texture这个父物体下有一个摄像机(临时叫它副摄像机),并且在运行状态下,每一个实时踩出的足迹都一一对应着框中一个个实例化出的default_sprite预制体,如下图:

图1:dynamic_texture构造

在dynamic_texture附带的DistanceMapPainter中,最次要的工作就是把足迹的根本信息以色彩的模式附给预制体的色彩信息,如下图:

图2:色彩蕴含的信息(from DistanceMapPainter.cs)

那这些预制Sprite的意义是什么呢?不只是传递以色彩为表现形式的地位信息,也在通过a通道传递足迹的“寿命”信息。

图3:色彩的a通道(from sprite_fading.cs)

最重要的是,还会保留深度信息:

图4:Sprite中的深度信息(from DistanceMapPainter.shader)

2. 足迹信息如何传递?
副摄像机在这里会专门“监督”这些实时生成的预制体,而后把它们渲染在PainterRT上(如下图):

图5:副摄像机的渲染指标

那这张“PainterRT”记录着什么呢?直观上来讲,它记录着如图1中花花绿绿的方块图,上文也提到过了这些色彩蕴含的是各个足迹的地位等信息,但它们重叠怎么办呢?

请留神咱们方才提到Sprite预制体中的Shader会保留深度信息,而后看PainterRT的信息:

图6:PainterRT根本信息

它是关上深度缓存的,所以这张RT上保留的是通过了深度检测的点的色彩信息。至于哪些点能通过深度检测呢?依照图4对于深度的写法,那就是处于每个Sprite核心地位最“白”的中央,也就是最靠近摄像机的中央,也就是最初能通过深度检测的中央,那最初这张图的核心处必定能渲染到RT上。

图7:把深度作为色彩输入之后的PainterRT

为了直观地看到深度图的成果,咱们临时把深度作为色彩输入(看完之后记得改回来哦~),能够看到单个的Sprite是一个红色的“山丘”,越凑近核心越白,而咱们看到的这些红色其实曾经是通过了深度测试的了,可见每个山丘的核心是肯定能通过深度测试的,而其余边缘处,即便记录了足迹信息,然而足迹也很可能笼罩不到那儿(当然,这得看你想把足迹画多大了)。

那么会存在两张图齐全重合而使得信息读取不到的状况吗?从上图可知只有不是两张图完完全全重合,就不会相互影响,如果真的是两个山丘齐全重合,那么确实会失落其中一个脚印。这就是为什么操控着角色在一个中央重复彷徨,总有几个足迹画不进去的缘故。

而这张RenderTexture将会作为高空Shader的输出,这样就把地位、角度等信息通过一张图传给了高空Shader。

3. 曲面细分着色器
咱们当初来看最重要的高空Shader——SnowGroundShader。

关上并浏览这个Shader,能够看到这个Shader中应用了细分曲面。

图8:曲面细分着色器和几何着色器的标记

对于曲面细分的教程并不多,能够参考《Unity Shader:曲面细分着色器》。

咱们次要关注细分计算着色器,因为大部分重点在这一部分。

图9:细分计算着色器中获取足迹信息局部

其中_DistanceFiled就是上文中副摄像机渲染的图,centerPos和angle别离读取了其中保留的地位和角度信息。

至于高空的凹凸起伏,就是在顶点的纵向上加上高度,而高度就是从高度图读取的,如下图:

图10:细分计算着色器中顶点的起伏计算

顶点只有变换了,法线就得重构,这里切线空间的三个向量会依据高度图从新计算,存入o.tspace中:

图11:细分计算着色器中法线重构局部

4. 最初的成果
最初的成果在片元着色器中。

首先视觉效果上,上文的o.tspace中保留的因高度扭转而重构的法线会与雪地和足迹的法线之和做一个点乘操作,如下图:

图12:片元着色器中法线合并局部

之后的光照,就是用了Unity封装的LightingStandard_Deferred(该我的项目用了提早渲染),这里就不赘述了。

总之,整个流程就是确定地位和角度、依据高度图调整顶点高度、重构法线。对于曲面细分的局部,细分的越精密,成果就越粗劣。

5. Tips
下图是Tessellation hull constant shader,其性能是用来输入细分因子(Tessellation factor)。细分因子用于在Tessellation阶段通知硬件如何对Patch进行细分。

咱们能够看到这里的factor不是写死的,旨在让须要细分的中央细分,也就是只细分足迹四周的中央,不失为一个优化性能的好方法。

图13:细分因子的实时计算局部


图14:细分因子的实时计算成果

02 体积云

一、概览

体渲染是从三维标量数据生成图像的渲染技术。通过对天然景象的测量或数值仿真,体渲染能够绘制出真切的成果。在视觉艺术和计算机游戏畛域中,体渲染常常用于模仿云、雾和火等天然景象。

该篇的CloudSkybox我的项目是Unity默认程序天空盒着色器的扩大,它就是应用了体渲染技术绘制云。

二、原理概述

该我的项目次要由两局部组成:基于大气散射的天空和基于光线步进的体积云。

在描述天空时,通过简略地模仿大气散射过程的算法,咱们能够渲染出一个带有瑞利和米氏散射景象的天空;而在描述云时,则采纳了噪声采样和光线步进联合的传统办法,来渲染出云层的体积感。

三、具体实现

1. 大气散射
该项目标次要逻辑都在CloudSkybox.shader和ProceduralSky.cginc中,先看一下顶点着色器:

图1:顶点着色器

顶点着色器重点在vert_sky函数,该函数的实现在ProceduralSky.cginc中。它的作用在描述高空、天空和太阳的色彩,用的办法来自于Nvidia上一篇很经典的文章:
https://developer.nvidia.com/...

对于大气散射原理的解说有很多,这里不再细说,只讲一些标志性的代码段:

图2:scale函数

该函数近似于查找表,计算出来的是某个方向的射线上光强的衰减。该办法尽管简化了计算,但前提条件是要求均匀大气密度为2.5%。这是一种比拟简化的办法,当初大都用预计算和查找表的办法代替该办法。

图3:衰减公式实现

这里对应的公式次要是衰减和光学密度公式:

其中scale查表查到的是以某个角度发射的射线门路上的光学密度,而(kInvWavelength * kKr4PI + kKm4PI)对应的是上式的散射系数,所以119行的attenuate就是衰减函数的实现。

最终要满足的是上述方程,而121行的depth和scaledLength别离对应上式的密度函数R,M(h)和微元ds。至于相位函数F()和散射系数是在之后的输入时别离乘上的:


图4:散射公式的欠缺

这里的很多变量是申明在结尾处的一些分母的计算,旨在缩小除法的运算次数。

上述公式图来源于以下知乎文章:

《[Rendering] 基于物理的大气渲染》

《【译】【大气散射】[Elek09] 实时渲染参数化的、有屡次散射的行星大气》

2. 体积云
咱们把摄像机设想成一个射线簇,每条射线都从相机坐标登程,对应着穿过近裁剪立体上每个像素,而云层的描述就是因为这每条射线穿过的云层的密度不同造成的。个别上体积云的渲染,都会采纳光线步进法(Ray marching)来进行云层密度的累加计算,这也是外围局部。

不过在该我的项目中,光线步进法呈现在了两个中央。
第一处是独自封装的MarchLight办法:

图5:光线步进办法

该办法形容了从每个pos登程,沿着光源方向步进到穹顶的射线上,累加的云层密度对光所造成的衰减(通过BeerPowder,也就是比尔定律计算)总共有多少,为片元中计算散射做筹备。

第二处在片元着色器中,如下图:

图6:片元着色器中的光线步进

在该循环中,acc累加了每个像素收回的射线上,每个步进点处的云层密度和光源在该点处产生的单次散射的乘积(193行),而在循环完结后,又交融了天空被云层衰减之后的色彩(199行)。

值得注意的是在算散射时,用到了Henyey-Greenstein相位函数,该函数可用于计算向内散射,以在面对太阳的云具备高亮成果。

图7:相位函数

至于云层密度的采样,是用了tex3Dlod采样当时计算好的基于Perlin和Worley噪声的Texture3D,并且通过_Time.x来管制采样点的偏移,实现了云层的挪动。

图8:噪声采样函数

3. 性能剖析
通过UWA的GOT OnLine工具在高端机型OPPO K9(8G RAM)上对该工程进行的检测来看,数据并不乐观,该机型上的性能简报如下图:

帧率尚有余8帧/秒。下图是CPU耗时状况:

从图中能够看到简直都是Gfx.WaitForPresentOnGfxThread在耗时,该函数表明GPU的运行压力较大,咱们来看GPU的耗时:

GPU的耗时均值为124.31ms/帧,这个耗时是十分高的,可想而知在中低端机上该耗时将会更加重大。综上所述,该插件并不适宜利用在手机我的项目中,不过能够用来学习借鉴。