【Siggraph 2013】Oceans on a Shoestring: Shape Representation, Meshing and Shading

今天介绍的时Shoestring的海洋渲染方案,该方案在Siggraph 2013中做了分享,照例做下总结:

  1. 本文将水体的渲染分为三块来介绍,分别是波形shape,网格mesh以及渲染shading
  2. 波形针对了高低端机采用了不同的策略,低端机用的是gerstner wave+抛物线调整的方式实现,高端机则用的是FFT
  3. 网格就直接使用了Projected Grid来实现,为了避免镜头移动与旋转过程中的闪烁问题,这里通过polar mesh做了优化,虽然还是存在些微瑕疵,但问题已经得到了很好的抑制。
  4. shading部分就比较常规了,经验介绍说浮沫以及跟随水深的颜色控制是非常有价值的
  5. 针对浮力这块没有做过多的介绍,因为是联机玩法,所以需要采用全局的数据,而不能是各个玩家的局部数据来实现物理的模拟计算。
Page 01

这里用两个视频展示了最终的效果:

Page 05
Page 06

这里介绍了海洋研发的一些背景:

  1. 希望实现水跟地的无缝衔接
  2. 支持多个主机平台,其中包含了低端主机Wii(只支持固定管线,相关计算需要放到CPU上)
Page 07

水体的实现主要可以分为三大部分:

  1. 形状Shape,介绍的是水体上顶点该以什么公式来驱动和模拟
  2. 网格Mesh,介绍的是水体数据的组织形式
  3. 着色Shading,介绍的是水体的效果表现

先来看下第一部分Shape

Page 09

海洋表面的特点是宽广、变化复杂。

Page 10

完全覆盖海洋每一处的细节,在实时上依然是不现实的,因此这里考虑将海洋分为两级来考虑。

Global覆盖的是全海域上的基础形状,而Local则是处于美术、策划玩法考虑,针对局部的短距的海域添加的一些额外的计算与效果。

同时,依然会通过法线贴图来为海域fake出细节表现。

Page 11

傅里叶变换会得到如下图所示的结果,这里是针对单个patch的海洋表面的模拟。

Page 12

目前已经有多个模拟海洋波浪的频域模型,可以考虑基于Ressendorf 2001的FFT方案来做尝试,通过对频率的限制,我们可以得到一个tileable & loopable的波形。

Page 13

这个方法用在了Xbox跟PS平台:

  1. 使用了预计算的FFT贴图来得到多套displacement贴图
  2. Displacement贴图数据是8bit的定点数(实测精度已经足够了,运算都是用定点数完成的,最后转成浮点数),分辨率是64x64的,为了得到动画效果,一共需要64帧
  3. 总体内存消耗大约为1.5MB

上述逻辑是在CPU完成的,如果放到GPU上,可以用VS来采样,效率更高,且利用硬件的mipmap采样能力,还能自动过滤远处的高频信息避免抖动。

Page 14

1.5MB的内存对于Wii来说还是太高了,这里会尝试使用其他的不用运行时bake的方法以降低内存消耗。

Page 15

这里首先想到了Trochoid wave也就是Gerstner Wave,这种波浪在波峰跟波谷上能够很好的模拟海水的波浪的外观。

Page 16

Gerstner Wave是通过参数化方程实现的,一个不方便的地方是不能够通过代数的方法计算出某个点的波浪高度,如果需要的话,就需要通过迭代的方式来逼近,带来的计算量会高一点。

这里决定通过分段的多项式来模拟,从而加速对某点数值的求取。

Page 17
Page 18

这是原始曲线

Page 19

可以分解为3段抛物线(parabola)

Page 20

第一个参数是cap width,用于描述峰值的sharpness。

Page 21

这里给出了通过调整cap width来实现sharpness效果变更的示意图

Page 22

这里演示了如何基于抛物线实现gerstner wave的模拟,从效果上来说,其实是希望峰值越sharp越好,不过需要注意不要引起插值问题。

在wii上,可以通过将这三个部分累加起来,同时在不同的地方采用不同的sharpness,从而规避掉海浪的重复。

Page 23

下面来看看怎么添加一些局部细节。

一个简单的方法就是调整海浪的高度,比如将这个球状(或者圆形)物件放到对应的位置,并基于其上的数据来调整海浪的高度。

Page 25

第二种方法就是采用wave particle。

Page 26

wave particle不但可以影响海浪的波形高度,还可以用于控制浮沫效果。

wave particle生成的波形,还可以随着扩散生成新增的particle,同时降低其能量幅度,模拟自然界波形传播过程。

Page 27

原文作者采用正弦波作为函数的kernel,这里采用的则是smoothstep。这种做法可以将部分项分解出来,供高度以及高度的微分项进行复用,从而减少计算的复杂度。

Page 28

通过这种方式可以用来实现涟漪效果,当然小尺寸的涟漪还可以通过法线贴图来实现,但是大尺寸的话,就得有波形的配合,只用法线贴图就不太够了。

Page 29

涟漪的效果最好是一个正向(拉高)的涟漪尾随一个反向(压低)的涟漪,之后循环反复。

Page 30

需要一种能够快速查询wave particle效果的高度的方法。这里给出的解决方案是将wave particle的高度通过光栅化的算法写入到一个grid中,为了支持不同尺寸的wave particle,同时降低光栅化的性能消耗,可以考虑用多个覆盖不同尺寸的grid来承载不同尺寸的wave particle。

Page 31

顶点数不足的时候,会导致插值的精度问题,这个我呢提在CPU的meshing上显得尤为明显。

Page 32

对于不明显的区域,可以直接放弃分配顶点,注重性价比。

Page 33

只在距离玩家足够近的情况下才显示mesh的高低起伏。shape的移除有上述几种逻辑:

  1. 移除小尺寸的波浪
  2. 移除傅里叶变换与wave particle的小尺寸波浪(缩小幅度)
  3. 在GPU上,FFT波形可以通过mipmap的方式来自动实现LOD效果。

通过降幅并不会解决采样不足带来的问题,但是可以使得问题不那么明显。

Page 34

总结一下,经过上述设计之后,波形的效果跟性能都已经足够满足需要了。

Page 35

下面看看如何基于shape得到mesh。

Page 36

采样位置 & 三角化

Page 37

对于mesh,这里希望是能够跟随形状而调整(从而实现该密的地方密)。

而且可以基于视角来调整mesh的形状(听起来就是projected grid的策略)。

在局部来说,则是希望mesh的密度聚集在一些频率高的位置(具体可以参考附录)。

Page 39

第一种是clipmap

Page 40

在预研阶段就被排除掉了,原因是推测可能在实现难度与执行效率上会存在问题,但是没有具体的证据。

Page 41

第一个真正尝试的方法是Projected Grid。

Page 42

有如上的特点:

  1. 实现简单,性能还挺不错的
  2. 会跟随视角的变化而自适应调整
  3. 在实现上遇到了插值问题:因为顶点会跟随镜头而移动,所以不能保证每次采样的顶点正好是同一个点,就会导致前后两帧同一个位置的高度会不一样。
Page 43

如图所示,在顶点密度不足的时候,就会出现这个问题,因此远景的水波更容易穿帮。

Page 44

在相机静止的时候,一切都是正常的

Page 45

然而当相机移动或者旋转的时候,波形就开始跳变。

Page 47

关于这个问题,如果我们能够保证在相机移动或者旋转的时候,屏幕空间的顶点投射后得到的顶点跟上一帧投射的顶点能够基本匹配(可错位匹配),那问题就能很好的解决。

Page 48

这里引入了一种新的顶点组织与映射方式:Polar Mesh。

Page 49
Page 50
Page 51

比如某个mesh上的顶点在旋转的时候,其轨迹是一段圆弧。

Page 52

所以我们可以将mesh的形状做一下调整以适应其旋转的轨迹:让顶点以环形的方式围绕玩家,而非grid形式。

Page 53

之后通过调整mesh的旋转角度,使得顶点正好匹配某个pattern,从而使得前后两帧的顶点位置基本重叠。

Page 54

经过这个处理之后,在旋转相机的时候,顶点就不会再跳变了。

Page 55
Page 56

这里展示了效果

Page 57

再来看下相机的移动,先看下向前移动。

Page 58

其实这种移动可以看成是每段圆弧不断朝着中心缩短,而这个变化同样可以保障还在圆弧上的顶点的位置不变。这个移动在向前跟向后都能保证这个特性的成立。

那镜头在向左跟向右移动呢?看起来似乎是不能跟上一帧的顶点保持公用世界空间的顶点了。

Page 59

先来看下,往前往后移动的时候,一个圆弧怎么被分割成两段(或者合并为一)。

Page 60

这里说的是通过一条射线与二叉树进行求交来得到各个圆弧的半径。这里的问题是:

  1. 为什么是二叉树?
  2. 射线的方向与出射点怎么选取?

为啥是二叉树,这是因为mesh的精度是逐级递减的,用2D平面来看,就是四叉树,那放到单维度就是二叉树。

射线的方向要怎么控制,取决于多久之后要进入下一级LOD。

其实所谓的二叉树只是为了方便描述,在实际执行过程中,倒也不用完全采用这个方式,而是按照一定的pattern来控制单个方向轴上的顶点分布即可。

Page 61

这里解释了按照二叉树的算法,这里会有一个节点会缺失。

Page 62

之所以要构建这么个结构,其实就是为了模拟玩家在往前移动的时候,mesh的精度的不断自适应。

其实按照前面说的,我们可以将移动方向上的顶点按照一定的间距进行排列,最靠近相机的位置,间距最小,可以看成是LOD0,之后有LOD1等等。

我们可以控制一下各个LOD的切换范围,据此来控制每一级LOD的顶点数目,在相机往前移动的过程中,如果顶点数目还能维持住,那就不用变化,当顶点数目距离目标的少一的时候,就从下一级LOD借一个顶点分割为两个。这是个递归的过程。

这种做法相对于上面介绍的这种来说,好处是我们可以保证在相机移动的过程中顶点基本上是稳定的,但是就缺少了前面那种顶点在移动的过程中逐渐分裂,平缓从LOD1过渡到LOD0的效果,但是这种平滑过程说实话还是导致了顶点位置的变更,那么不就是依然会存在前面描述的那种精度抖动吗?

Page 63

这里给了个视频演示

Page 64

这是之前的效果,顶点闪烁很明显

Page 65

采用polar mesh优化后,虽然不明显了,但还是依然存在,尤其是相机边缘区域。

Page 66

在添加动画效果之前,闪烁明显

Page 67

添加动画效果之后,闪烁还是有,不过没那么明显了。

Page 68

再来介绍一下射线跟二叉树相交的实现逻辑,这里将这条线称之为detail curve。

Page 69

这里的实现逻辑不需要对二叉树做遍历,只需要组织好数据结构,就可以基于一种递归的思路来得到想要的结果。

Page 70
Page 71
Page 72
Page 73
Page 74
Page 75
Page 76

对于每一条分支,都用一个红框来标注其下的两条子分支的覆盖范围,当两条分支都已经有了交点,说明不需要再继续往下细分了,如果交点就在末端,那么需要考虑开始着手细分到下一级了,如果没有交点那就要继续往下探索,最终得到这些交点。

为了避免每帧都需要对树进行遍历,可以考虑将交点记录下来,之后每帧更新位置,这种方法快一些,但是实现起来会相对复杂。

Page 77

总结

Page 78

性能消耗,不考虑wave particle的话,大约是3.3ms(用的FFT,64x64的分辨率,64帧),加上wave particle就是6ms,不过这里的硬件本身比较差,换到好一点的PC,可能就是1~2ms的消耗。

Page 79

Wii用的是gerstner wave,覆盖范围更小一点,没有wave particle的时候,大约是0.9ms。

Page 80

再次总结,一些细节就放在附录里了。

Page 81

最后一部分是shading。

Page 82

水体mesh的wireframe效果:

  1. 采用triangle strip的方式组织,中间的长长的直线是退化三角形,目的是为了实现triangle之间的衔接
  2. 整体的面数比较低,因为meshing是在CPU计算完成的,所以数据量不能太高
  3. 整体的mesh的精度并没有随着视距而变得粗糙,这是二叉树方案的后遗症。
Page 83

这里是环境反射效果(environment map)

Page 84

添加了两个scrolling的normal map以添加高光效果。

远处的法线数据问题有个bug,没有处理完,这里直接用了一个大的法线贴图来基于距离来做blend,以降低远处的细节表现(参考下图远景效果)。

Page 85

这是前面说的,第三层法线的效果,用于实现大尺寸的反射、高光效果。

Page 86

折射效果

Page 87

添加了浮沫,输入一张海岸的foam map,同时考虑沿着海岸的海浪数据而实现。

Page 88

这里给了视频,展示了动画效果。

Page 89

浮沫包含VS/PS两层数据:

  1. VS层面需要:
  • 波形的高度数据
  • wave particle的数据
  1. PS层面需要:
  • 贴图数据,比如海岸线等贴图数据
Page 90

最终浮沫的实现是基于Torres 2012的多层浮沫方案来的,通过修改UV,借用UV动画来得到动画效果。

Page 91

这里给了一个视频展示基于波形得到的浮沫效果。

Page 92

水体颜色分为三层,用三种颜色表示,之后基于水体的深度来进行插值。

Page 93
Page 94

这里给出了效果的比对,虽然不是非常真实,但看起来还挺像那么回事的。

Page 95

次表面散射的方案也是基于Torres2012的实现来的

Page 96

这里是添加了次表面散射的效果。

Page 97

给了个视频。

Page 98

经验总计:

  1. Foam效果还是非常明显的,是肯定需要添加的
  2. 根据水的深度调整颜色也是必须的
  3. 在屏幕空间上通过噪声的方式给波形添加细节花了一点时间,但是收益却不明显
  4. 曾经还想过将高光计算的光照方向跟阴影计算的光照方向分离来实现效果的自由调校,但最后没有成功。
Page 99

形状的总结:

  1. 高端机用FFT,低端机用Gerstner wave+parabolic wave
  2. 通过wave particle来实现交互效果
Page 100

网格的总结:

  1. 采用Projected Grid,之后通过Polar Mesh来优化相机移动与旋转过程中的问题(不过到最后也没有介绍说左右移动情况下的问题)
  2. 适合低端机用,性能看起来不错
Page 101

Shading的总结

Page 102

后面可以尝试的优化:

  1. 在Wii上也采用FFT方案,统一
  2. 将Polar Mesh逻辑从CPU搬到GPU
Page 103
Page 104
Page 105

整体的演示效果

Page 106

附录中的具体内容:

  1. 如何实现对Gerstner wave的高度采样
  2. 如何采样displacement map
  3. 如何实现shape的自适应采样(根据设备或者距离的不同?)
  4. 抛物线方程
  5. 如何实现wave particle的采样
  6. 物理模拟的考虑
  7. detail curve的更多信息
  8. 如何确保projected grid能够覆盖完整视野(尤其是当Mesh被挤压导致边缘收缩的情况下)
  9. 其他的保障顶点前后多帧稳定的方案
  10. 折射相关
Page 108
Page 110
Page 111

通过数值迭代的方式来实现Gerstner wave高度查询。

Page 112

而FFT实现的displacement map的高度查询,也同样是通过数值迭代的方式来得到。

Page 113

实现的时候,顶点的排布密度能够跟波形的频率相吻合,从而保证关键信息都是ok的。

Page 114

这里考虑smoothstep的曲线来实现对波形的模拟

Page 115

基于这种方式可以实现想要的效果,同时在部分计算时的效率也可以得到保证。

Page 116
Page 117

这里给出了抛物线方程的实现细节。

Page 118
Page 119

wave particle的高度采样计算方式

Page 120
Page 121

多个wave particle的话,采样结果需要叠加。

Page 122

浮力的考虑比较简单

Page 123

物理模拟的mesh跟渲染的mesh需要保持一致,而这里的mesh精度由于较低(且跟玩家视角有关系),可能会存在问题

Page 124

要想保持多个玩家的模拟结果一致,数据就不能是局部的,而得是全局一致的。

Page 125
Page 126
Page 127
Page 128
Page 129
Page 130
Page 131

关于Detail Curve,这里做了较多的说明

Page 132
Page 133

这里介绍了怎么保证mesh能够永远覆盖住屏幕范围,不会穿帮。

Page 134
Page 135
Page 136

这里介绍了左右移动情况下的顶点闪烁问题,不过不是太明白这里的解决方案,感觉好像并不是能很好的解决这个问题。

其实,可以考虑用两套顶点,一套是polar mesh,另一套就是常规的grid mesh,当左右移动的时候就以grid mesh为准,或者直接就用覆盖X-Z两个轴的grid mesh为准,可以同时兼顾移动与旋转等多种情况下的mesh闪烁问题。

Page 137
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容