排列三17500基本走势图: 士郎 Physically Based Volumetric Rendering (1)

13
回复
1490
查看
打印 上一主题 下一主题
[ 复制链接 ]
排名
1
昨日变化

排列三2014079期正版藏机图 www.d0po.cn 7752

主题

8308

帖子

3万

积分

Rank: 16

UID
1231
好友
186
蛮牛币
10622
威望
30
注册时间
2013-7-29
在线时间
3983 小时
最后登录
2019-5-23

活力之星原创精华达人突出贡献奖财富之证游戏蛮牛QQ群会员蛮牛妹VIP

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
这篇文章的实现的灵感来自寒霜引擎在SIGGRAPH 2015分享的一套基于物理的雾效,总的来说,这套雾效在屏幕空间构建一个体素贴图,把屏幕空间的坐标转换到世界坐标,并把该体素当前的光照情况记录到体素贴图中,最后再累计起来,形成一套比较接近于真实的雾效,因为是在视锥空间形成的体素(Frustum Voxel),因此在GDC上,寒霜引擎称其为Froxel。

那么看到这里问题来了,这种方法和以前文章中讲过的Ray March有什么不同呢,先说结论,采样方法并没有不同,累计方法是不一样的,在光栅化的体积渲染中,采样的方法大多数都是大同小异的,也就是从一个起点(比如摄像机的位置),到一个终点(比如视椎体的远裁面,某个自定义的裁面,或者是深度图采样到的像素)中间进行插值采样,采样数次后将结果叠加起来放到屏幕上,那么被阴影遮挡的位置就会黑,而被光照到的位置就会亮,这样就成功的模拟了空气中的丁达尔效应。

再来说说不同,Froxel是将采样结果存储到体素贴图中,再进行累计的,而Raymarch是一边采样一边累计的,也就是说本来Raymarch一个步骤完成的累计和采样,在Froxel中被分到了两步,那么分成两步来做有什么好处和坏处呢。先来说一下好处,一般Raymarch都是在降采样的RenderTarget中进行处理,因为即使是顶级PC显卡也很难承受全分辨率的多次采样,降采样后Raymarch依然需要使用深度图做采样终点,由于深度信息的流失,就会导致Edge Bleeding,也就是边缘漏光的情况,这样的效果是比较丑的,这个在我们之前的文章中的实现也出现了,同时,如果有透明物体,粒子等需要多次采样的着色点,很显然直接累计的Raymarch也无法满足,而使用体素就可以让采样点在任何位置获取到累计结果,因为累计的每一步都已经存储到了Tex3D中供Shader读取。

再来说一下Froxel的缺点,因为不使用深度图,所以采样一定是不精确的,Z轴的漏光现象不可避免,因此只能“祈祷”美术在处理场景时能尽量避免这种情况发生,也就是说Z轴采样的精度下降了,不过不依赖深度图倒也不完全算是一个坏事,因为不依赖深度图所以不会出现Edge Bleeding,同时在主机端还可以使用Async Compute实现面向硬件层的优化,据悉最新版本的Unity在主机DX12上的支持和优化有了长足的提升,主机开发者们可以了解一下。Froxel的另一个缺点就是消耗要高很多,因为Tex3D会带来非??植赖腎O压力,这对于显存来说是致命的,因此在相同消耗的情况下,Froxel的分辨率就要低很多了。


采样与累计:
我们使用的是160 * 90 * 256的体素,屏幕分辨率是160 * 90,而Z轴步数是256,这个选择是一个消耗和效果比较平衡的选择,消耗上来讲其采样数量相当于2560 * 1440,也就是一个普通的2K屏幕的后处理,这个消耗可以说完全可以接受,在中高端平台不应出现端倪,而效果呢,很显然256步配合随机采样和时间采样是绝对足够的,但是160 * 90的分辨率显然是很低的,所以那种清晰锐利的体积光效果可以说拜拜了,只能通过累计和采样使其变的比较糊,当然一般情况下这也符合“雾效”的美术定位。
灯光采样部分我们直接启动一个3D的Dispatch来完成每个体素块的采样,体素块位置的准备代码如下:

线程组使用的是8, 2, 64的设计,其中VOXELZ是为了更容易控制变量而加的宏定义,8 * 2 * 64正好达到1024,也就是线程组的最大限制,也正好是160, 90, 256的倍数,不会造成浪费,getRandomFloat3是从一个外界传入的StructuredBuffer获取种子,并在使用wang hash算法生成随机数后将新随机数作为种子存入原来的StructuredBuffer,从而获取到一个三维的偏移,这个算法用来做随机运算消耗较低且效果较好,获取到随机数以后将随机数叠加到体素的三个轴的归一化向量上,完成随机采样点的选择,因为体素是在视锥体上线性分割的,所以这里将UnityCG.cginc里的LinearEyeDepth函数反转成EyeDepthToProj,从而可以用线性的深度反推投影坐标,并得到世界坐标。拥有了世界坐标以后按理说就可以开始灯光采样了,但是因为之前使用了随机采样,因此在屏幕上一定会异常闪烁,所以我们需要加一个Temporal Blend混合上一帧结果,当然,为了日后做开放世界大地形,这里还特地留下了一个_SceneOffset变量,用于在平移整个场景时将此值传入Shader,防止Temporal Pass出现问题,_TemporalWeight我们传入了一个0.7,这个值对于消除噪点维持画面稳定已经足够了,并且远远不到会因为混合产生难以忍受的残影的程度,当然若采样点已经超出了上一帧的屏幕,就需要直接放弃掉这个采样点了。
首先是直接光的采样,直接光的光源分为Global Light和Local Light两种,Global Light也就是掌管日月的Directional Light,这不需要任何可见性剔除,直接在每一步采样即可:

这里LightFlag是一个用来区分光源可见性的,由于是Uniform Variable,因此这个if并不会产生分支,不存在消耗,之所以要使用一个LightFlag是因为光源种类较多,直接使用宏定义,可维护性很差,所以直接使用一个位运算来做是再好不过的了,虽然光源种类多,但是shadowmap种类基本可以划分为正交投影,Cubemap和透视投影这三种,这种可控的数量直接宏定义一波带走。这里的光照计算只是将光照简单的累计上去了,实际可以根据美术的需求加上米氏散射,瑞丽散射等,比如我们这里就尝试传入CPU端计算好的固定值,并传入进去来计算一下米氏散射(这里直接暴力套公式和魔数):

米氏散射公式
[size=0.9em]套入到光照计算中
到这一步,可以说是非常好理解的,我们仅仅是推出了每个体素的世界坐标,并用这个世界坐标转换到Cascade Shadowmap的坐标系下获取了一个光照情况,辣么问题来了,挖掘……额不是,如何将体素数据放到屏幕上呢,这时候就需要一个累计过程,按照常识来说,我们在雾中看远处的物体要比近处的物体难以辨认许多,这是因为光线在穿过雾时被雾的色彩给“同化”了,导致到达肉眼的光线变成了雾的色彩,使物体本身色彩难以辨认。
所以我们基于物理的累计也会使用这样的思想,从近到远使浓度依次叠加,同时使色彩也依次叠加,最后使用浓度作为alpha value,直接在全屏后处理时经过alpha blend即可。
累计的计算方法如下,这个方法也是寒霜引擎提到的基于物理的计算方法:

这个方法传入了当前体素和先前体素的信息,_ScreenSize则储存了体素的总长度,即256,将累计过程根据density加权累计到体素,使最终体素储存的浓度和色彩以指数级分布。
在Dispatch中调用这个函数实现累计:

实现累计以后我们就可以顺理成章的开一个后处理函数,并使用像素点的深度获得体素坐标,混合到像素输出色彩上,当然如果目前的管线是Forward管线,并且没有深度图的话,这个计算也可以直接在物体的fragment中计算的:

计算结果如下:


能够看到已经有了一个不错的效果了,但是还是差强人意,首先低分辨率带来的粗粒度像素感非常严重,毕竟160 * 90的分辨率确实非常低,同时噪点也比较多,噪点多这一点纯粹是因为我们给Tex3D设置的Temporal Weight比较低,但是这并不会造成坤然,因为同时我们还有一个屏幕空间的Temporal Filter,也就是老朋友Temporal AA了,所以按照往常的老套路,直接在采样时添加一个对屏幕空间UV的扰动,使每个像素点采样到的体素位置不一样:

其中RandomSeed是一个每帧都会改变的随机种子,这会使得每帧的随机数都不一样,完美契合Temporal Filter的特性,实现二次降噪并消除像素颗粒感:

粗粒度的闪烁噪点已经变成了细粒度的稳定噪点,雾中有细小的噪点也比较符合现实效果,可以说有了这一步之后,效果有了大幅度的提升。
走到这里时,可以说整个Froxel体素雾的计算过程已经打通了,但是战斗仅仅进行了一半,因为单纯一盏太阳光肯定是不行的,接下来我们将使用提前实现的Tile based lighting data计算局部光照,并把全局光照的间接光囊括进来,使雾效不会这样非黑即白的刺眼。
局部的直接光可以有许多种,点光源,球光源,面光源,聚光源等等,当然这里我们并不会讲解每一种光源的计算方法,因为光源的计算已经脱离了本篇文章的讨论范畴,所以我们仅仅只讲解点光源和聚光源这两种最典型的光源形状。
局部光照的采样部分最重要的是做好可见性剔除,保证每个采样点需要考虑的光源尽可能的少,寒霜引擎使用的是Tile Based Light,也就是等比例切割屏幕的XY轴从而获取每个像素块的受光情况:

所以为了实现这一步,我们使用Scriptable Rendering Pipeline提供的CullResults.visibleLights获取所有可见的光并在Compute Shader中完成剔除,这类步骤在之前的文章中已经讲得比较详细了,这里直接放上地址不再赘述:

这里的Tile Based Lighting只是比之前的Voxel Based Lighting少了一个Z轴的剔除(只需要将所有光源与雾的有效距离的面进行比对即可),最后将剔除的结果放到一个格式为RInt的Tex3D中,这个Tex3D的xy代表了每个Tile的坐标,而Z则是light index list。

我们没有使用之前文章中讲过的Voxel based的三维的裁剪方法,而是使用了一个二维的Tile based裁剪方法呢,这其实是一个性能比较后选择结果,虽然Voxel based lighting比Tile based lighting在剔除上多了一个维度,结果更加精确,但是剔除本身的消耗也是指数级增长的,而且实际游戏开发时也很难出现在Z轴的同一块区域罗列巨量小型光源的情况,考虑到Froxel的有效距离只有30-50米左右,这种情况就更不可能了,所以直接在Froxel的采样过程遍历Tile内所有灯光并在灯光计算内部判断可见性,如果不可见直接continue跳过这个灯光这样看起来简单粗暴,并且非常违反“GPU并行计算”思想的方法(毕竟for loop + if continue的办法确实怎么想怎么不“并行”),反而在性能上要比Voxel based lighting要强不少,所以我们最终就决定用这种方法了。

说完可见性剔除,就该来说说着色的实现了,与简单粗暴的Directional light不同的是局部光源常常受衰减影响,而这个衰减的计算和调控对于美术来讲却并不容易掌握,换个说法就是表现不直接,有可能“看着很对换个角度就不对了”,因此一套完全基于物理的光照强度单位——流明(Lumen),以及完全基于物理的光通量(luminous flux)计算方法将被引入到目前的渲染管线中。

根据基于物理的推导公式,我们对光照有以下求法:

而SRP已经提供了lightColor * intensity的finalColor,所以在传入灯光数据的Job中,我们直接对VisibleLight.finalColor / (4 * π)就能完成这个运算,也就是lightColor * lightIntensity /(4 * π),这部分代码非常简单没必要放上来了,我们着重讲解灯光的衰减计算部分:

按照之前讲过的Tile based lighting计算原理,所有灯光都被储存在了这个Tex3D中,其中首位元素,也就是index = 0的元素储存着灯光的总数,因此从次位元素开始遍历,一直遍历到终位元素即可获得所有光照。 在获得了灯光信息PointLight这个struct以后,首先经过一个距离判断,如果当前采样点距离超过点光源直接continue,这个在上面已经讲过了,这对于整体性能是有提升的,米氏散射在计算太阳光时已经讲到过了,这里不再赘述,而DistanceFallOff则是控制衰减的核心函数,这个函数的定义是这样的:


其中invSqrAttRadius这个变量也就是上方传入的1 / range,这个数字可以看作常量,因为灯光的范围和强度都不会随采样点变化而变化,attenuation则计算出了1 / (distance * distance)的值,最后再经过上方的smoothFallOff的计算得到最后的曲线大致如下:

这是light range为10米时的函数曲线,因为衰减度要除范围的平方,因此实际上大范围的灯光在一定距离开外也是基本接近于没有光照的(否则人类早被太阳烤化了),这样真实的衰减计算与传统的衰减计算在效果上也有一定区别。
阴影的计算没什么需要特殊说的,这个与普通着色器的阴影计算也没有任何区别,最后点光源的衰减效果如下:

附带上阴影的效果如下:

和之前的太阳光效果一样,效果属于朦胧柔和型附带一些微弱的噪点(图片质量不够高可能看不出来),这比较符合我们预期的美术效果。
聚光灯的实现和点光源在原理上并无不同,只是Shadowmap的类型变成了透视投影,并多了几组参数,这里我们直接上源码分析:

整个聚光灯分为圆锥的几何信息部分和灯光本身的部分,圆锥的几何部分包括顶点到底边的半角弧度,高,顶点位置,而灯光信息则包含了聚光光源的角度(内角和外角两个角度用于计算角度衰减),一个近裁面距离(用于开发近面光源效果,如台灯),灯光本身的亮度(已经在传入之前就套入了流明的计算),通过内外角得到一个Scale用于进行两角度的插值,最后在AngleFallOff完成这个计算,除此之外其他计算与点光源并无不同,AngleFallOff的计算公式如下:

lightAngleScale是内外角的差的倒数,而这个公式就使用这个传入的值计算当前角度的衰减度,保证在内角时衰减为1,在外角时衰减为0,最后计算结果如下:

内角为0° 外角为90°

内外角皆为90°
使用这套衰减计算,内角较小时边缘光照强度会非常弱,而内角与外角相等时则毫无角度衰减,比起传统的计算方法,这给了美术更多的操作空间。
浓度分布:
说完光照,就该来说一下雾本身的分布了,在现实中雾的分布从来不会是平均的,游戏场景中也一样,比如游戏的主角的任务是去一个失火的大楼里救火,即使外界天气是晴空万里,大楼内也早已经是浓烟滚滚,这种时候我们就需要在场景中划分出雾的区域(fog volume),使雾效的布置更加精确以满足策划和美术的需求。
因此,我们制定了一个Component称为FogVolumeComponent:

这个实现实际上与Reflection Probe的做法并无太大区别,就是在屏幕空间(体素空间)画出一块区域,这个组件的代码如下:

这个实现简单而粗暴,纯粹是为了It works而做的,所以在实际项目中肯定要多加完善,具体思路是直接把所有数据收集到一个自己写好的NativeList中而不是使用List储存组件索引,这么做是为了在使用Job System的过程中最大化缓存友好,保证视锥体剔除的效率,同时为了反向追踪Component本身,这里直接从托管堆中提取地址并存到void*中,其实这个做法在普通C#程序中是非常危险的,但是Unity并不是传统意义上的C#,Component的地址是被NativeHook保存的,除非手动调用Destroy或者场景被Unload,否则我们不需要担心可能由GC造成的不安全因素。
FogVolume是局部雾的体积信息,日后还会加入Tex3D的UV来开发出Density map的实现,但是因为篇幅原因Density map的实现将会在下一篇中来介绍,所以这里FogVolume选项只储存着基本的几何信息:

启动一个Job负责视锥体剔除,实际上我们目前几乎所有的数据类型的操作都是在Job中完成的,这样在所有的Job累计完成之后统一启动,再在CommandBuffer阶段通过Complete确保某个Job已经完成,可以最大限度的解放主线程,发挥多线程CPU的优势,同时Unity Burst Compiler可以提供SIMD的加速和数学运算的优化,在处理此类并行度较高且较为规律的运算时可以大放异彩,当然缺点也有,Burst compiler支持的是传统的C语言写法,所以编写的时候确实有种回到20世纪的感觉……还要格外留意与指针和内存的管理,避免产生问题:

完成剔除结果后将数据传输到Compute Shader中进行遍历,注意,比起光源,Fog Volume直接在Froxel中进行遍历而不是先经过Tile based剔除,之所以少这么一步剔除的原因是实际在开发过程中整个场景Fog volume的数量是非常有限并且一般Fog volume的体积都比较大(毕竟下雾不可能只下一小块,这不符合常识),综合考虑普遍需求,我们省略掉精确剔除的消耗,并直接使用粗粒度的视锥体剔除,Shader部分的遍历代码如下:

直接遍历顶点,并把顶点转换到Fog volume的局部坐标后进行比对,如果超出范围直接忽略掉,否则将浓度加到当前的体素中,局部雾效的结果如下:

可以看到局部雾效有很明显的轮廓,超出雾的区域的部分都是看不见雾的,也就是可见度不受雾效影响的,而体积内部则拥有一个超高的浓度,并足以高到令点光源和太阳光的轮廓清晰可见,这也完全达到了我们的实现目标。
Froxel体积雾的基本原理在本篇文章中已经讲的比较清楚了,然而效果还暂时停留在It works阶段,实在谈不上美观,具体表现在光照采样单一(只有直接光,没有间接光)和浓度采样单一(没有Density map,整个Fog volume使用统一浓度),因此在下一篇中我们将通过增加影响雾效的全局光照和模拟真实雾在空气中的流动的Density voxel map,构建出更加美观的效果。

点评

Physically Based Volumetric Rendering  发表于 2019-1-19 02:35
7日久生情
2262/5000
排名
1393
昨日变化

0

主题

701

帖子

2262

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
135463
好友
0
蛮牛币
47
威望
0
注册时间
2016-1-23
在线时间
671 小时
最后登录
2019-5-26
沙发
2019-1-18 20:50:07 只看该作者
6666666677777777777
4四处流浪
457/500
排名
6774
昨日变化

0

主题

41

帖子

457

积分

Rank: 4

UID
248481
好友
0
蛮牛币
233
威望
0
注册时间
2017-10-12
在线时间
220 小时
最后登录
2019-5-25
板凳
2019-1-19 09:08:56 只看该作者
谢谢分享!
6蛮牛粉丝
1051/1500
排名
5542
昨日变化

0

主题

658

帖子

1051

积分

Rank: 6Rank: 6Rank: 6

UID
300432
好友
1
蛮牛币
1451
威望
0
注册时间
2018-10-18
在线时间
133 小时
最后登录
2019-3-27
地板
2019-1-19 09:38:31 只看该作者
6666666666
5熟悉之中
804/1000
排名
48130
昨日变化

0

主题

575

帖子

804

积分

Rank: 5Rank: 5

UID
9367
好友
0
蛮牛币
0
威望
0
注册时间
2013-12-2
在线时间
225 小时
最后登录
2019-5-24
5#
2019-1-19 11:18:26 只看该作者
感谢分享~~~~
7日久生情
2203/5000
排名
2612
昨日变化

2

主题

1106

帖子

2203

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
209046
好友
0
蛮牛币
3027
威望
0
注册时间
2017-3-30
在线时间
541 小时
最后登录
2019-5-24
6#
2019-1-21 08:54:28 只看该作者
66666666666666666
7日久生情
1752/5000
排名
4092
昨日变化

0

主题

1085

帖子

1752

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
254705
好友
1
蛮牛币
1625
威望
0
注册时间
2017-11-16
在线时间
305 小时
最后登录
2019-5-25
7#
2019-1-21 08:55:59 只看该作者
666666666666666666666666666666
5熟悉之中
661/1000
排名
4267
昨日变化

0

主题

68

帖子

661

积分

Rank: 5Rank: 5

UID
255447
好友
2
蛮牛币
1500
威望
0
注册时间
2017-11-21
在线时间
249 小时
最后登录
2019-4-26
8#
2019-1-21 10:40:39 只看该作者
6666666666666666666666666666666666666
5熟悉之中
536/1000
排名
6835
昨日变化

0

主题

191

帖子

536

积分

Rank: 5Rank: 5

UID
300324
好友
0
蛮牛币
210
威望
0
注册时间
2018-10-17
在线时间
151 小时
最后登录
2019-5-25
9#
2019-1-24 10:36:40 只看该作者
站长妹纸萌萌哒!
8常驻蛮牛
5152/10000
排名
1669
昨日变化

0

主题

3683

帖子

5152

积分

Rank: 8Rank: 8

UID
185339
好友
1
蛮牛币
3589
威望
0
注册时间
2016-11-20
在线时间
691 小时
最后登录
2019-5-24
10#
2019-1-25 10:59:48 只看该作者
{:90:}
7日久生情
2754/5000
排名
2230
昨日变化

1

主题

1768

帖子

2754

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
119154
好友
0
蛮牛币
2913
威望
0
注册时间
2015-8-21
在线时间
355 小时
最后登录
2019-5-26
11#
2019-1-28 13:04:40 只看该作者
谢谢楼主大大。
2初来乍到
136/150
排名
19336
昨日变化

0

主题

50

帖子

136

积分

Rank: 2Rank: 2

UID
131276
好友
0
蛮牛币
9
威望
0
注册时间
2015-12-9
在线时间
56 小时
最后登录
2019-4-19
12#
2019-2-11 14:51:28 只看该作者
在寒霜的引擎的下的效果真的很 牛 效果刚刚的在点计算上的方法也是不一样的公式 在寒霜的引擎的下的效果真的很 牛 效果刚刚的在点计算上的方法也是不一样的公式 v在寒霜的引擎的下的效果真的很 牛 效果刚刚的在点计算上的方法也是不一样的公式
5熟悉之中
933/1000
排名
2401
昨日变化

0

主题

30

帖子

933

积分

Rank: 5Rank: 5

UID
176105
好友
0
蛮牛币
1256
威望
0
注册时间
2016-10-17
在线时间
310 小时
最后登录
2019-5-23
13#
2019-2-22 09:00:47 只看该作者
66666666666666666666
您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

  • 【人事】中共临汾市委组织部公示3名拟任职干部 2019-05-18
  • 习近平与人民日报那些事 2019-05-10
  • 很多常用药同属一家族 2019-04-30
  • 小牛犊天生两条腿 走起路来像袋鼠 2019-04-30
  • 就因为“阶级亲”,才应把这些难民送到欧洲。欧洲生活水平高呀,让亲人生活的更好。不能让他们到中国受苦受难呀。 2019-04-27
  • 资管新规来了!打破刚兑  投资者怎么办? 2019-04-27
  • 紫光阁中共中央国家机关工作委员会 2019-04-24
  • 图解:习近平主席这12个金句振奋人心! 2019-04-24
  • 您访问的页面找不回来了 2019-04-07
  • 五莲科技局以“三大”助力动能转换 2019-04-07
  • 匹夫有责之一百一十二—道义大义的博客—强国博客—人民网 2019-03-30
  • 守住青山不放松 护好绿水不辞难——在渝全国人大代表聚焦“共抓大保护、不搞大开发”专题调研记略 2019-03-28
  • 经营者要想“我心换你心”,就要未雨绸缪,让不诚信的诱惑少一点,如此才能在市场中生存下来。反过来说,承担不起亏损就关门歇业,是否也是一种不诚信呢? 2019-03-21
  • 追风上市公司“跌落神坛”-热门标签-华商网数码 2019-03-21
  • 文化艺术交易场所沙龙第一期活动在京顺利举行 2019-03-16
  • 500| 644| 155| 938| 300| 183| 478| 384| 163| 842|