乐趣区

关于unity:雪地脚印-体积云

【博物纳新】专栏是 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/ 帧,这个耗时是十分高的,可想而知在中低端机上该耗时将会更加重大。综上所述,该插件并不适宜利用在手机我的项目中,不过能够用来学习借鉴。

退出移动版