美文网首页水体渲染
【GDC 2012】Water Technology of Un

【GDC 2012】Water Technology of Un

作者: 离原春草 | 来源:发表于2024-04-29 09:47 被阅读0次

    今天学习的是顽皮狗在GDC 2012上关于水体渲染的分享,照例对文中的一些关键内容做一个简单总结:

    1. 通过一个shader将反射、折射、浮沫、churn(水下的剧烈扰动)、基于深度的着色都覆盖进去了,且所有参数都是美术同学可控制的
    2. 基于flow map(两次采样并混合)实现了水流效果,其中spline用来标注流动方向,用2D贴图来标注各个点的流动速度、浮沫、相位等数据(以grid的顶点为单位)
    3. 水体mesh的没有用效果较差的perlin噪声,也没有用美术同学控制比较痛苦的FFT,也没有完全用随着octave数目上升性能消耗急剧上升的gerstner wave,而是用了4层gerstner wave,4层wave particle以及一层大波浪来实现
    4. mesh的数据结构用的是clipmap,不过做了一些针对水体的处理优化,包括T-Joints的消除、mesh的blending以及分patch的绘制处理逻辑(利用SPU的并行特性进行加速)
    5. 由于mesh是以patch组织的,因此剔除也可以基于patch来做,包括2 pass的剔除、portal culling等
    6. 为了避免水波侵入船体,这里会以船体的甲板来构建box,对超出box的顶点将之压低
    7. 水下的雾效是直接用一个面片(顶点随波浪顶点一起起伏)实现的
    8. 水体上某个位置的高度查询不是通过对mesh ray casting得到的,而是采用类似牛顿寻根的迭代算法得到
    9. 水体的渲染只用了2.7ms,消耗比较高的部分为tessellation+wave displacement,大概花了8ms
    Page 1

    深海是动作冒险游戏,核心是战斗跟探索而非水上gameplay。

    场景种类多样,包括丛林、神庙、洞穴、废墟等,里面会夹杂各类水体,如水坑、溪流、河流、池塘湖泊、大海等。

    水体元素对游戏玩法有着较为重要的作用,在一代代的作品中也在持续提升。

    Page 3 Page 4 Page 5 Page 6

    水体的表现形式多样,从小尺寸的水坑到大尺寸的海洋

    Page 7 Page 8

    Gameplay也有跟水体有关的玩法,比如浸水之后的衣物潮湿

    Page 9

    在水体上的移动速度也会导致不一样的表现

    Page 10

    水体的效果需要跟游戏画面的风格相匹配。

    Page 11

    这里参考过很多此前的工作,其中Scientific visualization的工作在后续的工作中有很好的体现。

    Page 12

    水体的实现覆盖了多种效果:

    1. 水流:通过(基于顶点或像素)滚动的UV,或者基于向量场生成的法线贴图来实现
    2. 折射:基于水深来抖动和着色
    3. 浮沫:在梯度场上给一个阈值,超出阈值的即添加浮沫效果
    4. 剧烈的扰动:同样是基于深度的着色,这里会使用浮沫贴图来对水体的深度进行调制,之后进行混合,以实现volumetric效果的模拟。

    所有的参数都需要支持美术同学的控制。

    Page 13 Page 14

    水体的渲染有如下的一些要点:

    1. 使用bump贴图来对法线进行扰动
    2. 基于扰动的法线来计算得到fresnel项,并基于这个数据完成折射与反射的混合
    3. 在折射、反射效果之上,再叠加浮沫,这里还可以将浮沫的效果跟折射数据做混合来实现泥浆效果
    4. 基于剧烈的扰动(churn)来实现volumetric效果
    5. 折射的着色跟当前着色点处的折射光线到水底的距离(深度)有关:
    • 这个深度的着色函数可以是线性的,也可以是指数的
    • 基于深度的着色可以受到剧烈扰动(churn)的影响,这里的做法是用浮沫贴图的某个通道数据(因为受海浪高度影响,这里其实也可以理解成基于高度)来对深度颜色(depth color,水体因为深度不同而呈现的颜色,通过查表得到)与扰动颜色(churn color,暂时不清楚这个颜色代表的是啥)进行混合(不是太明白这里的含义)
    • 在浮沫移动(被海浪的高度所调制)的时候,浮沫的颜色就会发生变化,看起来就像是浮沫在滚动
    1. 反射数据也可以直接添加到折射数据之上,而不是跟折射数据进行混合

    再次重申,所有的参数都可以交由美术同学控制。

    Page 15

    接下来一层层的解析下水体着色中的各个关键因素。

    Page 16

    去除了bump之后的效果长这样:

    Page 17

    叠加bump的效果,可以看到海面高光扰动细节更为丰富真实

    Page 18

    前面说过,折射跟反射是通过fresnel系数完成混合的(物理公式),先看下只有折射效果的样子

    Page 19

    再基于水深来对水体的基色与折射做一个混合,这里没有叠加bump的扰动效果

    Page 20

    叠加扰动后效果大概是这样的

    Page 21

    再加上软影效果

    Page 22

    接着再基于海浪的幅度来对叠加浮沫。

    foam跟churn会在水面之上跟之下叠加一个贴图(水下的浮沫效果就是水流扰动效果?),这俩都会受flow map的影响。

    Page 23

    加上高光效果

    Page 24

    最终效果如下图所示

    Page 25

    接下来看看法线贴图是怎么处理的,这是水体渲染着色的关键。

    Page 26

    Flow shader主要是基于Flow Visualization using Moving Textures方法(文中介绍了如何基于贴图的UV调整来实现流动效果,下面也会简介实现细节)实现的,主要的思想是基于向量场来对mesh的UV、Displacement等属性进行扰动对流(advect)

    Page 27

    这里给出了flow实现的一个例子,在这个例子中,会使用VS来计算三角面片之间blend的数值,需要注意,当我们想让水流往东边流动,那UV就应该朝着西边移动。

    可以看到,这里的flow需要完成两张贴图的采样,每张贴图的采样UV都会随着时间线性增长,同时,两张贴图的UV相差0.5个相位,同时两者混合的权重相加等于1,某个贴图的权重最高(=1.0)的时候,正好是其UV处于0.5的时候。

    Page 28

    这里可以看到,流动方向不需要细化到像素级别,只要到grid的顶点级别即可

    Page 29

    除了基础的流动效果之外,还可以添加其他的效果,这里通过一个second fetch来实现额外的流体效果。

    Page 30

    这里给出了优化flow效果的几种方法,听起来有点没头没脑

    Page 31

    这里来看下Flow Shader的实现逻辑:

    1. 先是基于当前时间g_time计算出两张贴图采样时的相位差fTime
    2. 基于相位差,得到当前UV的偏移flowUV1/2
    3. 基于偏移的UV对flow map进行采样,得到tx1/2
    4. 按照前面的理解,将采样得到的扰动信息作为UV的扰动再做一次额外的采样(优化flow效果,可选)
    5. 基于前面的混合公式完成两个采样结果的混合

    这里的shader是基础版本,如果想要提升效果,还可以尝试一些其他的手段,比如:

    1. 添加一个起始的偏移,不同贴图的起始偏移可以不一样
    2. 通过对采样uv再加上0.5个flowDir的偏移来消除flow的distortion(原理是?)
    3. 对fTime的计算再加上一个offsetPhase来分散相位切换处的水流(spread around,需要在PS中完成)
    Page 32

    这里展示了神海系列中使用到的maya工具,这个工具会使用spline来标注流动的方向,颜色贴图来标注流动速度(灰度就行了吧,还需要3色吗?)。

    此外,如果需要的话,还可以生成foam map以及phase map(displacement需要)

    Page 33

    这里是最终效果

    Page 34

    接下来看看基于flow实现的displacement(水体mesh的高低起伏)。这里说到,水体mesh的顶点是按照各向异性的圆形移动的,不同顶点的相位有所不同(听起来是gerstner wave那一套)。

    如果为每个顶点指定一个相位,那么整片水域就会需要一个相位场,而通过基于flow map来调控这个相位场,使得跟随flow的方向或者垂直flow的方向,就能够实现不同的波浪效果。

    Page 35

    下面展示在相同的flow作用下,通过调整flow对相位的调节算法来实现不同的波浪效果

    Page 36 Page 37 Page 38

    flow除了用在水体之外,还可以用于一些其他的效果,如沙、云、雪等

    Page 39

    前面的水体效果在深海1/2中是够用了,那3呢

    Page 40

    3中提出了一个玩法设想:在狂风巨浪中有一艘大船,突然转向穿入波涛中。

    此前的基于displacement mesh的方式就不太能支持了。

    Page 41 Page 42

    期望实现如下图所示的效果:

    Page 43

    emmm...

    Page 45

    先来明确下需求:

    1. 水体渲染区域大,且要能支持100+的高浪
    2. 能够跟船舶等发生交互(不考虑动画实现方案)
    3. 支持游泳
    Page 46

    海洋的实现中,海浪是程序化生成的(即运行时基于程序控制实现),参数可以调整,且同样的参数输出的效果是固定的,不随时间而变化的,可控性强。

    Page 47

    先来解读下程序化实现的含义。

    这里需要找到一个合适的模型来兼顾性能与表现,首先可以肯定的是不能完全走物理模拟来实现,耗费太高。

    Perlin噪声实现的方案效果有点差,看起来有点假。

    FFT方案效果比较好,但是参数控制起来有点痛苦,美术同学调整会很麻烦。

    Page 48

    再来看看参数化控制的含义,这里给了一个函数,参数有:

    1. uv
    2. 时间t
    3. 其他参数
      最终输出一个xyz的顶点坐标。

    因为期望海洋的任何点的数据都可以通过计算得到,这里就不能使用grid,而是希望能够做到跟任何参数化公式的兼容,因此这里就需要给出一个组合式的海浪系统。

    这里需要注意的是,我们这里最终是需要生成一个vector displacement,通过这个方式,我们可以不用高度场就能实现一个效果很漂亮的海浪。

    Page 49

    最后看看一致性:即希望参数一致的情况下,效果要能实现精准的回放,以满足cutscene的效果需要。

    Page 50

    最终选择的波浪是通过Gerstner Wave实现,这种波浪有如下的特点:

    1. 实现简单,但是高频细节较少
    2. 如果叠加使用的数目过多(20+),就会导致性能消耗急剧升高
    Page 51

    FFT虽然效果很真实,但是想要调制出正确的参数就会比较困难,对于美术同学来说,负担尤其重。此外,在grid分辨率较低的时候,还会出现tiling的瑕疵。

    Page 52

    最终这里采用的是Siggraph 2007上提出的Wave Particle方法,但在实现上,跟原文做了一些区分:

    1. 不再使用点源,而是在一个圆形区域里,放置随机分布的粒子来模拟开放水域波纹扰动的混乱特性
    2. 在某个速度边界的约束下对未知跟速度做随机,可以产生一个tileable的向量displacement场
    Page 53

    这里给出波浪的解析公式,xy(代表的是啥?难道是粒子在xy方向上的位移?)都只受时间驱动,其中x还受一个参数\beta控制,从曲线分布来看,这个数值越大,产生的波浪就越尖锐。

    Page 54

    释放600个粒子后的海域波纹效果长这样

    Page 55

    Wave Particle方法有如下的几项优势:

    1. 美术同学调整会非常直观
    2. 不会出现tiling问题
    3. 性能好,适合SPU的并行计算
    4. 能够保持一致性的效果:每个粒子的新的位置是通过初试位置、速度与时间推导而来,不会跳变
    Page 56

    采用FBM的思想,通过不同频率、振幅的displacement field来叠加可以得到更为真实的效果。

    Page 57

    这里还采用了类似clipmap的思路来对不同octave数据进行叠加,还可以产生LOD效果。

    Page 58

    实际上,这里并没有完全抛弃gerstner wave,而是在四层gerstner wave的基础上叠加了四层wave particle grid

    Page 59

    还增加了一个flow grid的概念(听起来是以grid的顶点为数据存储的基本单位),在一个grid中完成flow、foam以及振幅因子数据的集成。

    下图是只有flow curve作用下的示意图

    Page 60

    这里则是在grid中的flow vector作用下的示意图

    Page 61

    前面说过,还存储了wave的振幅乘法因子。

    Page 62

    最终生成水体mesh的时候,会将foam的数据调制转化为顶点色。

    Page 63

    看下大图效果

    Page 64

    再来回顾下最初的目标。

    Page 65

    这里还差了大尺寸的海浪的效果。

    Page 66

    所以在之前的两类wave上,再增加一个大尺寸海浪。

    这个大尺寸的海浪会由美术同学手动调制后叠加上去,这种海浪通常会用在一些crash wave scene(没太明白这个意思),同时,还会用于阻止玩家游离gameplay area。

    Page 67

    大尺寸海浪是通过对一个矩形区域的覆盖处理来实现。

    Page 68

    这个曲线的u方向会被用来模拟一条样条曲线

    Page 69

    之后沿着v方向对样条曲线进行复制,从而起到将平面拱起的效果。

    Page 70

    但是这样得到的波形在两端就会出现裂缝,为了实现平滑的衔接,需要将拱起的波形朝着两遍做抹平处理。

    最终的波形就是通过下图中的公式来给出。

    Page 71

    之后,也可以根据需要对波形进行缩放、平移来实现动画效果。

    Page 72

    下图展示了通过这种方法实现的大尺寸波浪效果。

    Page 73 Page 74

    下图给出了整个波浪系统的计算公式,可以看到,整体的波浪由三部分累加得到:

    1. 多个不同频率的gerstner wave
    2. 多个不同频率的wave particle grid
    3. 美术调制的大尺寸波浪

    大尺寸波浪使用的是一个标准的不支持旋转的样条曲线,当然也可以考虑其他的如贝塞尔等曲线,但是代码量会更重。

    grid(u, v)函数返回的是一个标量,按照前面的描述,在不同的场合有不同的含义,这里代表的是对波形进行累乘的因子。

    Page 75

    接下来看看水体的mesh是如何表达的,总的来说,可以通过多种方法来表达:

    1. 屏幕空间的projected grid方法,可能会导致锯齿问题(?)
    2. 也可以使用quasi的projected grid方法,但是在大尺寸的displacement的实现上,就可能满足不了需要(所有projected grid方法都有这种问题吧)。
    Page 76

    这里采用的是基于Siggraph 04年的Clipmap方法实现的非常规Clipmap方案,所谓的非常规就是针对水体做了改造。

    Page 77

    主要做了如下的几点改动:

    1. 为了避免跨level之间衔接的T-joints(裂缝)问题,做了不同的split(划分)方法
    2. 在多个level之间做了动态混合(目的是?)
    3. 采用了patch的方式,以进一步利用SPU
    Page 78

    这里给出clipmap的示意图。在相机移动的时候,各级的clipmap也会跟随移动,这里会保证任意点在相机移动前后,采样的数据都是相同的,从而避免抖动以及锯齿问题。

    Page 79

    这里将第0级(完整的quad)之外的其他level叫做ring,精度越低,ring的级别越高。

    Page 80

    下面截了一堆图用来展示相机移动后ring的包裹范围,应该还介绍了一些其他的内容,但是在备注中没有说到,后面有需要可以从原始视频中找信息。

    Page 81 Page 82 Page 83 Page 84 Page 85 Page 86 Page 87 Page 88

    用单个ring来解释

    Page 89

    绿色的是原始的patch(一个quad就是一个patch),红色的则是额外添加的patch

    Page 90

    ?没看懂这里想要表达的是什么。

    Page 91

    附加的patch可以起到承上启下的作用,以规避两级ring之间的顶点密度不一致导致的裂缝。

    Page 92

    裂缝的修复是通过如下的顶点layout完成的。

    Page 93

    这张图画的不是很明显,在修复裂缝的那一圈顶点或quad之内的顶点会调整其位置,使得网格密度有一个从粗到细的变化,以避免数据跳变太过严重,产生视觉瑕疵。

    Page 94

    这里用颜色标注出修复区跟混合区。

    Page 95

    接着来看下,怎么做剔除以提高绘制效率的,整个剔除分为两个pass完成:

    1. 先基于patch的四个角上的顶点添加一点余量做成box,之后做一次快速的相交测试
    2. 之后未被剔除的box会经历displacement的处理,就能得到一个更为准确的box,再基于这个数据做一轮新的剔除
    Page 96 Page 97

    这里给出了clipmap渲染的wireframe模式。

    Page 98

    在舞厅中依然是可以看见水面的。

    Page 99 Page 100

    为了优化室内视角下的剔除力度,这里还用了portal剔除的思想。

    对于那些浪的尺寸较大的情况,如果不做处理会导致浪穿透甲板进入到室内,这里的做法是用一个box来对wave进行求交测试,当相交的时候,就需要对不可见的部分的顶点做压低处理,将之压平到跟box的底部一致。

    Page 101

    这里还有一个画面需要兼顾,主角会进入到船舱进行破坏。

    Page 102

    船舱中的水体也是采用跟海洋一样的方式进行绘制的,只是shader跟参数会有所不同。

    Page 103

    这里看下线框模式。

    Page 104

    当船翻的时候,还会出现新的情况。

    Page 105 Page 106

    再看回舞厅。

    Page 107 Page 108

    由于一次性只能绘制一个海洋,因此在游轮上,需要经常的切换不同的参数来绘制不同的水体效果,比如在两帧之间切换shader跟参数。

    由于游轮倾斜了90度,所以天光部分就变得困难。船外的水体还需要考虑水上跟水下的部分,此外,还会有一个洪水涌入的环节。

    为了用一个平面来完成剔除,还要将船的移动约束在一个平面上。

    Page 109

    从窗户外可以看到水上跟水下的部分。

    Page 110

    洪水涌入部分的实现可以参考GDC 2012中Creating the Flood Effects in U3

    Page 111

    这里来看下天光部分,这里是俯视角下的示意图。

    Page 112

    同样以patch为粒度进行剔除,不可见部分用红色表示。

    Page 113

    之后剔除掉不需要被天光影响的区域,用黄色表示。

    Page 114

    还依然可见的部分用紫色表示。

    Page 115

    对于天光来说,在渲染上,就是通过一个平面剔除来规避天光的影响(绿色部分),而水面mesh顶点则是通过压低到对齐船体box来实现。

    Page 116

    水下的雾效是通过一个‘窗帘’面片(边缘顶点对齐波浪起伏)来模拟的,这个面片会跟随水体而移动。

    Page 117 Page 118

    通过线框模式可以看得更清楚。

    Page 119 Page 120

    这里给出了窗帘的mesh效果。

    Page 121 Page 122

    对水上物体的浮力的计算,这里会对相交区域的多个点进行采样,并调整对应位置的水体顶点高度。

    Page 123 Page 124

    游轮这边的处理要复杂一点:

    1. 会沿着船身进行采样
    2. 但是不会对所有的波浪都采样,只选取那些低频的波浪
    3. 对于船头,会用一个弹簧来模拟
    Page 125

    在实现过程中,有比较多的需要对单点的水面高度进行查询的需要,比如角色游泳等,而要想获得精确的数据却并不那么容易。

    Page 126

    这个问题可以用数学符号抽象一下:对于一个给定的点p<u,v>,需要找到水体对应的世界坐标r(u, y, v)

    这里采用的是Ryan Broner的搜索方法,其原理跟Secant方法类似,不过是运算在3D而非2D空间:直接对wave field数据进行搜索,而非构建一个mesh之后走ray casting

    Page 127

    根据p的uv坐标,我们可以运算出其在波浪方程作用后的世界坐标,如下图所示的1点。

    Page 128

    将1点的坐标做一下投影,得到新的uv坐标与1点的高度,如下图中橙色的1。

    Page 129

    从p点到橙色的1点,会有一个数据差。

    Page 130

    将这个数据差反向施加到p点上

    Page 131

    得到一个新的点2。

    Page 132 Page 133

    基于2点,我们可以通过波浪方程得到其水体上的位置,如红色的2点。

    Page 134

    对红色的点再次做投影

    Page 135

    得到新的跟p点的差值

    Page 136

    同时得到原始2点跟投影2点的差值

    Page 137

    基于这两个差值的方向,我们可以直接选取2点的投影点作为3点的位置。

    Page 138

    重复上述过程

    Page 139

    当3点的水体位置出现在问号点的右侧(看投影点在p点右侧),就重复上述过程。

    Page 140 Page 141 Page 142 Page 143

    这里不再是直接将p点反向减去差值,而是将3点反向减去差值,这也可以理解,毕竟红色的3点是3点计算得到的。

    Page 144 Page 145

    经过多轮迭代,最终我们得到了命中问号点的uv坐标与问号点的高度。

    Page 146 Page 147 Page 148 Page 149

    下面介绍下Mesh是如何计算的:

    1. 对于每个ring,会跑一个SPU任务来对patch进行处理,这里每个任务会以3为周期处理各个patch(不知道这么做的好处在哪里,实现负载均衡?)
    2. 通过double buffer来实现数据的输入跟输出
    Page 150

    由于每个任务产生的mesh patch都能很好的缝合周边,因此这里也不需要一个额外的pass来对patch做缝合了。

    最终输出的mesh包括了多个mesh部分。

    Page 151

    整个计算的流程如下图所示:

    1. 在PPU中完成相机的计算、job的发起,并设置barrier等待所有内容的计算完成
    2. 在SPU中则负责wave particle的创建、更新(之后就已经有了所需要的displacement grid数据),水面点位的查询,clipmap的计算等
    3. PPU等待所有的SPU任务完成后就会进入渲染阶段
    Page 152

    这里给出最终的性能数据:

    1. wave particle的计算花费了0.9ms
    2. 点位查询花费0.1ms
    3. 8ms用于tessellation与波浪的displacement,这里是在5个SPU下计算的,差不多有7个ring的21个任务
    4. 渲染只花费了2.7ms(大约50+个patch)
    5. double buffer内存大概是1MB
    Page 153 Page 154 Page 155 Page 156

    相关文章

      网友评论

        本文标题:【GDC 2012】Water Technology of Un

        本文链接:https://www.haomeiwen.com/subject/oqpoxjtx.html