关于程序员:​TGDC-开放世界中的水体渲染和仿真

56次阅读

共计 10828 个字符,预计需要花费 28 分钟才能阅读完成。

我是腾讯魔方工作室群的陈家铭,明天和我的搭档、天美工作室群的胡有为一起,分享大面积深度可交互的水体仿真和渲染技术 Aqua。

1. Aqua 我的项目简介

我和有为来自两个部门,是什么起因使咱们一起单干钻研这个水体计划呢?因为咱们加入了腾讯游戏举办的开源打算 Tech Future,目标是增强外部技术交换,推动前沿游戏技术开发。Aqua 就是其中一个我的项目,它的的指标是钻研可交互的水体仿真和渲染技术,由来自不同部门、岗位的小伙伴们,通过一年工夫的业务工夫开发,做出了一些成绩,在本次大会分享。

水体的仿真和渲染始终以来都是常常被钻研的课题,比方育碧《刺客信条》的陆地,顽皮狗《神秘海域》系列中的水体,空幻引擎 4.26 版本公布的水体零碎。

空幻引擎的水体零碎,和咱们的指标比拟靠近,是在编辑时首先以曲线定义好各种水体范畴,而后在运行时通过一个四叉树来结构水体的 Mesh 进行绘制。这个零碎的性能很弱小,然而跟咱们心愿做的全动静水体有点差别, 比如说:咱们心愿能够做到下雨的时候造成洪水,把整个场景吞没。又或者玩家能够通过技能,随便把水放到任何一个中央。

所以,Aqua 团队心愿在这方面有所突破,开发了一套计划,并且制作了一个 tech demo。demo 里,能够看到的水体范畴是 1km x 1km, 而仿真的精度能达到 25cm, 渲染精度能做到 6.5cm,玩家能够像方才提到的用技能在场景中喷水,并且把篝火弄灭,同时喷出来的水也能留在场景外面。除了一些图形成果外,咱们也实现了像载具、浮力等须要跟 CPU 沟通的一些个性,次要是为了验证计划的实用性。也能够在 demo 里看到,下雨会导致水位回升,而后洪水把场景吞没的一个事件。还有,除了能够模仿湖泊外,还能够模仿河流、陆地。

https://v.qq.com/x/page/t3311…

2.1 仿真:多层级水体仿真

Aqua 背地的原理是怎么的呢?先讲讲 Aqua 的仿真算法选形。在游戏外面常见的水体算法有以下几:

PBD/SPH:通过粒子来最实在地模仿水体动静,个别都须要一个很微小数量的粒子。所以当咱们利用在大面积水体外面,比如说几百米,不论是仿真或者渲染要解决的性能问题都是十分艰难。

Wave Particles:它的思路是用一个个粒子来代表一个波浪(Wave),而后把所有波浪都转成高度图来进行渲染,益处是仿真成果很可控,然而较难去模仿水体容量的扭转。

网格法:最传统的,尽管它不能像 SPH 一样能够反对水躍 (Hydraulic jump),但大部份水体的个性都可能模仿进去,而且它有很好的个性和伸缩性。思考到咱们指标是要反对好几百米以上的超大范畴,Aqua 最终抉择了网格法作为计划根底。

然而不能简略用很高分辨率的网格进行仿真,因为内存、计算量在个别的 GPU 上都会扛不住。因而咱们借鉴了 Clip mapping 的原理来进行多层级仿真。思路是把整个仿真范畴以不同精度的网格来笼罩,去掉一些肉眼看不到的细节来换取性能;每层 Clip 的核心大略与相机对齐,水体离相机越近便会有越高的精度。咱们从面积最大的一层开始,之后就把图中红色的边缘部份同步给下一层;就能把 Clip 内部的重要影响传递进去。

整个零碎的流程大略这样:每一层 clip 都会分为“数据收集”和“仿真”两个阶段,首先咱们会收集 clip 范畴内的仿真输出,包含场景或者地形的高度图,次要是用来跟水体进行遮挡或者碰撞。此外,还会收集一系列额定的仿真输出,同时转成贴图,以便给前面的 GPU 仿真应用。

接着,咱们会利用 Compute Shader 来进行仿真的解算;咱们实现了两种网格算法,别离是 LBM 和 Pipe Water, 这个咱们在前面会有进一步的去分享。为了防止水体的速度太快而超出 CFL 条件,咱们还要思考 sub-stepping 的反对,通过放大 delta time 来晋升仿真的稳定性。实现了一级 clip 的仿真后,咱们会进行边缘状态的传递。如此类推,当所有 clips 实现仿真之后,会把后果导出到一个 TextureArray 或者 TextureAtlas 外面,再交给渲染模块进行绘制。

方才提到咱们须要收集场景或者是地形的高度图,实际上是通过空幻的 SceneCapture 性能来达成。用户事后定义好仿真在世界空间最小和最大的高度,当相机在程度挪动超过一个阈值之后,咱们会在最高点,从上往下拍一张深度图,再把深度反转,加上最低点的高度,就能失去场景朝上一面的高度。

另外,咱们还要收集一系列额定的仿真输出,像水的容量或者速度的影响,这里的难点是如何把各种各样的影响对立成固定的输出,以便给 GPU 仿真应用。

仿真算法的相干内容:到目前为止,咱们实现了 PipeWater 和 LBM 两种网格算法,因为工夫关系,细节参考下图所列的相干文章。尽管两个算法的公式都不太一样,但都是在求格子某几个特定方向的水体流量 F_i,比如说 Pipe 是求上下左右 4 个方向,而 LBM 是求 D2Q9 中的 9 个方向。仿真的 CS 会通过以后帧的 F_i , 加上与相邻格子的高度差、黏度、引力等等参数来算出下一帧的 F_i,这样不停迭代。仿真 CS 会用 structured buffer 来保留 F_i, 把两头数据,像容量数据速度导出给渲染应用。

在开始的时侯,咱们提到计划中有一个边缘状态传递的操作,所谓的边缘状态就是指每一层 Clip 边界上的 F_i。在每一层 Clip 开始仿真之前,咱们都必须要从上一层把对应这一层的边界 2 个格子宽度的 F_i 拷过去。这样咱们就能够把 clip 范畴以外的影响传递到这一层的 clip 外面。以上面两个视频为例,第一个视频演示了状态传递敞开,水就流不进场境核心的区域。而第二个视频里因为关上了状态传递,水就能失常流到场景核心外面了。

https://v.qq.com/x/page/x3311…

https://v.qq.com/x/page/x3311…

尽管咱们用了多层级的策略来减少仿真的范畴,但面对好几公里的超大场景,须要用另外一个技巧来增量去扩大仿真范畴。咱们把这个办法称成 Scroll Update 或者是 Sliding Window。首先每层 Clip 各自与相机对齐核心,这样当相机平移后就让 clip 的所覆盖范围也会主动跟随着去更新。因为覆盖范围有所更变,咱们须要把更新前的 F_i, 按世界坐标拷到新的格子里,相机地位会按格子大小进行 snapping, 防止因为拷贝 F_i 时候造成跳变。为了防止太频密的更新,咱们会等相机超过多个格子才触发更新。在挪动方向呈现 Cache miss 区域, 咱们能够从面积较大的一级 Clip 去读取 F_i,进步稳定性。

跟个别的物理仿真一样,当水体流动速度太快,就很容易超出了 CFL 条件就会导致数值溢出的状况(下图爆炸的水体)。这个在仿真精度进步的时候特地容易呈现,因为仿真公式中的 delta x 绝对地升高了, 速度就很容易超出 Cmax 的限度。所以只能通过 sub stepping 来升高 delta t 来解决,简略说就是在一帧外面多跑好几次的仿真。但这样对性能不太敌对,因为计算量间接翻了好几倍。咱们察看到,在多层级的计划中每层 Clip 的 delta x 都会减少了一倍,这就意味着 Sub-step 次数能够减半。通过这样的优化,跟原来不分层级来比拟,能够升高大略 ~90% 左右的计算量。

最初,咱们看看多层级仿真的成果比拟:左图只有一级,而右图是分了两级来进行仿真,能够看到成果都是很靠近的。而从咱们 tech demo 的数据外面去看到,多层级仿真不管在内存和性能上都有显著晋升。

2.2 仿真:作用器零碎

接下来交给有为,介绍水体仿真的作用器零碎和渲染局部。接下来会向大家介绍咱们开发作用器零碎的目标和挑战,以及如何解耦仿真,以及如何自定义本人的作用器材质,最初咱们会展现一些 Demo 理论案例。

其实不论是 LBM 仿真算法还是 Pipe 仿真算法,实质上都是在计算水体高度场和速度场,如果没有其余的因素烦扰,仿真后果最终会趋势安稳。

然而,如果在计算的过程中退出一些内部影响的话,就能够失去一些交互式的后果,比方角色在河里游泳产生的水波,以及下雨产生的涟漪,海风吹起的海浪等等,这些都是交互仿真,那咱们如何实现这些交互仿真呢?答案就是作用器。

咱们已经想过让作用器直接参与仿真 ComputeShader 的计算,然而这样会导致一个问题:作用器品种是十分多的,每个作用器都有本人的算法实现,这导致很难间接在仿真阶段通过一个对立的函数入口去间接影响仿真后果。这会使得仿真的 shader 变得及其简单,也不不便当前的治理和保护。

于是咱们应用了一个解耦伎俩,就是将所有作用器的后果都输入到作用图中,对于所有的作用器,即使他们的算法实现不同,但他们都输入对立的后果,就是刚体入水深度、速度和体积,而后咱们会把这些后果别离保留在作用图的 RGBA 通道外面。而后咱们就能够在 LBM 仿真或者是 Pipe 仿真的时候去采样这个 RT 作用图,别离将 RGBA 通道数据取出来去影响各个仿真参数,实现交互仿真过程。

这里分享一个比拟重要的内容,就是咱们的作用器零碎框架,该零碎首先会每帧收集以后区域内无效的作用器,而后同时更新这些作用器 Gameplay。而后收集它们产生的 Instance 信息,包含地位、大小、自定义数据等。同时会收集所有作用器可能拜访的 uniform Buffer。包含上一帧的仿真高度场、速度场、场景高度,以及自定义贴图数据等。而后通过 Draw Instance 形式,将每个作用器以雷同材质进行合批渲染到四边形的形式绘制到 RT 上,最终每个作用器对立的输入体积,XY 方向速度和刚体数据四个后果,别离保留到贴图的 RGBA 通道。那么对不同的仿真算法能够拜访这个 RT,对仿真后果加上内部影响。因为 RT 上输入的是对立的物理后果,所以,不管哪种仿真算法都能够应用,而无须关怀作用器的类别,达到解耦的目标。

那如何让使用者疾速开发属于他本人的作用器呢?最后的构想是,让 TA 或者程序能像开发材质一样,在材质编辑器中通过连线的形式就能够实现本人的作用器算法,咱们称之为作用器材质。如图所示,目前凋谢我的项目中曾经通过自定义的形式实现了一部分罕用作用器材质。

同时为了丰盛高度自定义内容,咱们也封装了很多材质函数,以供开发者应用,比方拜访每个作用器自定义的数据,uniform buffer 数据,高度场,速度场贴图数据等。都能在材质编辑器中拖入应用。能够看到上面的截图,它是一个水源作用器的实现过程。

大量不同作用器材质的应用会导致 drawcall 数量减少的问题,那么咱们如何解决这个问题呢?刚刚的分享当中我曾经提到了,咱们这里也会对雷同材质的作用器进行合批处理。咱们能够看到截图外面,在凋谢我的项目中,咱们大量应用了作用器去模仿水源,波浪等,但最终的 drawcall 调用次数只有 3 次。

这里咱们截取了凋谢我的项目中局部的作用器成果示例。能够看到借助作用器框架,能够不便的自定义实现多种 Affector Material 与水体进行交互的 Gameplay 玩法。

3.1 渲染:基于 GPU Driven 的水体渲染

这个主题咱们会向大家分享以往的传统做法,而后咱们如何创造性地利用 CDLOD 来实现 GPU 驱动的水体网格渲染,包含如何在 GPU 上构建四叉树,如何实现遮挡剔除、超高网格密度、顶点变形等。传统的水体网格渲染,咱们都晓得咱们个别会应用一个立体网格,加一个高度图来还原水面的波浪静止。在挪动端甚至不须要实在的波浪,而是仅仅通过法线和 UV 动画来模仿波浪。

实现算法就是咱们去还原波浪的高度以及还原顶点的地位,很简略,咱们只须要在 VertexShader 中采样高度图,来还原顶点的 Z 坐标值。并通过采样邻近高度场数据来从新计算每个顶点的切线空间,最初把顶点的切线空间插值到光栅化阶段。

在实现 GPU 驱动之前,咱们也调研了 UE4.26 的水体插件,其中截帧剖析了陆地的例子,能够看到,Unreal 为了体现不同的陆地网格密度,它应用了 6 种不同的网格拓扑构造。尽管用了同一个水体材质,并且 instance 形式绘制,但也产生了 6DrawCall。而且,UE4.26 的水体分为陆地,河流和湖泊不同的水体 Actor,这也会导致 DrawCall 数量的减少。另外 UE4.26 的水体是以 CPU 的形式驱动,而咱们的目标是做 GPU Driven。

最终咱们在计划的抉择上,咱们采纳了 CDLOD 的技术,CDLOD 全称是基于间断间隔的细节等级。它原本是用来解决大世界地形渲染优化问题的,咱们利用这次机会,将它很好的实际到了大型水体渲染当中。CDLOD 它有很多长处,它具备变动平滑,不会产生裂缝,Lod 之间等级差不超过 1 的特点,并且非常适合做基于四叉树节点的剔除。

在 GPU 下面去构建四叉树的算法原理其实就是一个自顶而下的过程,从根节点开始,每个根节点是 ComputeShader 中的一个线程。如果以后节点在递归的这一级,又在下一级,它就确定了这个 Quad。如果即在以后递归的这一级,又在下一级,那么意味着子节点须要持续递归到更低级别去确定,如此周而复始最终递归到 0 级。在 GPU 上构建去四叉树会遇到比拟多的问题,咱们遇到的第一个问题,首先在 ComputeShader 下面是不能应用递归函数的,它有递归函数应用的限度。

而后,咱们如果想跳过这个限度,比方在 shader 代码中写大量的嵌套循环来解决递归函数问题,又会使得 shader 变得超级简单,也会将寄存器耗尽。而且,个别的构建过程当中,递归复杂度会随着每一级递加,也就是说,根节点那一级他是最简单的,也是最耗时的,0 级是最不耗时的,如果咱们把所有的级别都用对立的 shader 代码,就不能很好的均匀每个线程执行工夫。

为了解决这个问题,咱们采纳了分批次执行 ComputeShader,每一级构建都会有本人对应的 ComputeShader 变种。每次应用上一次构建未确定的节点的输入作为这次的输出,这样使得每一次构建都只关怀它以后这一级,从而大大降低了 shader 复杂度,均匀了线程间的执行工夫。

在构建的过程当中,咱们也会先判断四叉树节点是不是应该被剔除,以缩小 Instance 数量。通过采样水面高度的仿真后果,咱们能够构建一个节点内的 Quad 内的 Min/Max Height 信息,从而构建一个 WorldSpace 上面的立方体。咱们的剔除分为两种,一种是视锥剔除,另外一种是 HZB 剔除。视锥剔除会判断立方体的 8 顶点是否在裁剪空间内,而 HZB 则依据立方体的屏幕空间尺寸抉择适合的 Z -buffer mipmap 做深度判断进行剔除。

对于构建好的节点,如果间接渲染的话,其密度是很低的,那么咱们如何去构建一个高密度的网格呢?如下图所示,色彩雷同的区域具备雷同的网格密度。这里咱们应用了 2 种网格密度,事后生成了 2 种面片 mesh,第二面片的顶点数是第一个的 1 /4。

以全尺寸 32 和半尺寸 16 为例,咱们去拿 32 和 16 去填充非边界和边界的四叉树节点,通过这种办法,咱们仅应用 2 种拓扑构造就能够表白多种网格密度,DrawCall 数量最大为 2。

最终渲染的时候,最要害的就是要确定哪些顶点是须要变形,咱们会为每一级设定一个以后这一级对应的节点尺寸大小的变形区域,处于区域内的顶点将进行变形。因为之前依照 2 的幂次方规定,应用了 2 种不同的面片进行网格密度填充,所以这些面片的顶点地位是奇数的是须要变形的。

另外咱们也总结了构建分辨率和该分辨率下对应下的构建耗费工夫对照表,无论场景多大,咱们的构建四叉树花销的耗费工夫只与构建分辨率无关,这样能够稳定性能下限。构建分辨率和网格密度能够随便搭配,依据理论我的项目须要进行配置,达到性能和成果的均衡。在目前的开源我的项目中,以 512 的构建分辨率 +32 的网格密度作为默认配置,如果想要性能更好,能够抉择 256 +16 甚至更低。然而这样会导致网格精度会降落,可能会呈现锯齿、走样。

最初咱们来看下理论 Demo 中的成果,角色近处始终维持比拟高的网格密度,而远处比拟稠密的网格会随着角色摄像机挪动而进行平滑过渡。

3.2 渲染:基于 Height Blend 的动静地表浅水

接下来介绍如何改良 UE4 原生高度混合算法,并在此基础上实现动态浅水,而后联合仿真后果进一步实现了动静浅水。最初咱们将展现在游戏 Gameplay 过程中的实际效果。

凋谢世界除了陆地,湖泊,河流这些惯例水体。浅水也是一个很重要的能体现环境细节表白,特地是池塘高地,雨后积水的路面。UE4 的地形渲染,咱们晓得都是多层 Layer 进行 Blend 的后果。其中的基于高度的混合,能够让地表体现出缝隙或者低矮处混合。于是,咱们构想在 Landscape 中通过插入一个浅水层,与其余层去做 Height Blend,以表白浅水成果。

先来剖析下 UE 原生的 heightblend 算法,这个算法很简略,它将每层的权重乘以高度值,而后累加,再归一化百分比,最初混合每个层的后果。这个算法很简略,然而它导致不同 layer 在整个权重区间都在 Blend,成果显得很脏。

Layer weight 和 height map value 数值空间在 [0,1] 范畴,这个范畴很小,不是实在物理单位,它会失落计算精度。并且在接缝处无奈实现均值过渡,以及无法控制过渡阈值。

下图右侧是这个算法的原生成果。改良的做法,第一步把高度值映射到实在物理空间,而后乘以对应的权重,接下来咱们会筛选一个权重最大的层,也就是最高的层。而后将每层权重与最高层的差值除以一个过渡阈值,这样就失去一个平滑过渡的权重,最初将这个平滑过渡的权重去做归一化百分比,最初混合每层得后果。这个算法的长处在于,首先将在 [0,1] 的 height map value 映射到理论物理单位下,扩充了计算精度。在接缝的中央会是一个趋向于均匀实现了均值过渡,以及能够管制过渡阈值。咱们能够看到左边的截图是改良后的成果,能够看到根本达到了做浅水的要求。

另外很重要的是,咱们不心愿浅水的 HeightBlend 过程被固定死在 C ++ 代码中,而是凋谢让美术或者 TA 能在材质编辑器中通过连线的形式去进行编辑。UE 默认的地表材质节点,Layer Blend 是无奈解耦浅水和其余层的混合操作的。所以,咱们开发了本人的材质节点叫做 Height Blend。能够看到截图外面,咱们本人开发的这个节点除了输入其余层的混合后果以外,会将浅水层的权重信息独自输入,这样咱们便能在材质中管制最终浅水的混合成果。

咱们把最终的浅水混合操作都封装在了一个材质函数中,之前咱们有提到,咱们开发了一个本人的地表材质节点,它会将浅水层的权重独自输入,咱们拿到这个权重后,就能够别离去混合浅水的色彩,法线和 PBR 信息了。法线和 PBR 的混合比较简单 就用了比拟惯例线性插值,而色彩方面咱们则思考了干燥地表向湿润区域过渡的成果,以及湿润区域向浅水过渡的成果。

方才讲的其实都是动态浅水,进一步减少动静浅水,咱们又退出了 LBM 的仿真后果,下图左侧是退出了 LBM 后果后的成果。咱们把 LBM 的仿真后果去影响高度混合的高度值,能够看到它与周围环境产生了交互成果,并且实在反馈了 HeightBlend。而后,咱们也测试了 Pipe 仿真和 Rain Affector 一起作用的成果,能够看到除了外表涟漪以外,还能够看到下图右侧,积水体积是不一样的,从而实在反馈了仿真对积水体积的影响。

最初,是一些 Gameplay 中的乏味利用。能够看到配角在游戏世界中能够任意开释技能,这个技能能够在地表上残留一层浅水。浅水和地表造成高度混合的侵蚀成果,并且浅水会随工夫缓缓排汇掉。接下来由家铭持续分享。

https://v.qq.com/x/page/k3311…

3.3 仿真后果利用

方才理解到水体根底绘制的原理,当初会进一步解说仿真后果是如何利用到更多水体的细节成果里。首先是水体外表的细节法线,有了细节法线,水流动的感觉更加强烈了;而这个成果也不难去实现的,咱们只须要利用水体的速度场当作是 flowmap 去采样 Detail Normal 的就能够了。因为仿真进去的速度是在世界空间外面定义的,咱们须要进行一个简略的 缩放 和 Clamp 的操作。另外咱们也须要思考到速度为零的时侯把细节法线 Flatten。

还有就是泡沫成果, 跟其余计划通过剖析水体高度图的 Jacobian 不一样。咱们发现水体呈现碰撞的中央通常都会有比拟高的旋度,因为咱们是二维的速度场,所以算进去的旋度是一个纯量。因而咱们能够把旋度视为右边的公式的一个 2 维 旋度的计算方法, 在外面 f 就是速度是 x 坐标 ; 而 g 就是速度的 y 坐标; 而 k 就是一个 c 轴,能够疏忽。咱们再一次把速度场当成是 flow map 去采样泡沫的贴图,再乘以遮罩之后就能失去下图成果。

除了水体外表的成果外,水体周边的物体也会因为沾到水而扭转材质色彩。为了计算沾水的水平,咱们会利用一个 double buffering 的技巧。首先咱们把以后帧 Buffer1 的水体高度拷到 Buffer2 外面, 而后读取上一帧 Buffer2 时候保留的高度,做一个缩减,再跟新的水体高度取 Max 再放回 Buffer 1 外面 所记录的水体高度就能够算出一个沾水权重。在渲染场景物体的时侯,咱们会利用物体的世界坐标的 C 去减去 Buffer 1 所记录的水体高度就能算出一个沾水权重。物体计算光照的时候 PBR 参数,就利用这个权重从干和湿两组 Preset 外面去插值进去。

还有是一个 GPU 粒子与水体交互的例子。在视频外面演示的落叶成果不仅能够沉没在水上,也能够随着波浪而旋转。这个成果的 粒子系统外面,每个粒子能够分为:掉落、沉没还有隐没三个状态。掉落时粒子会以 场景高度 和 水体高度 判断它是否曾经掉落到水外面,如果是就切换到沉没状态。沉没时就简略的以水体高度作为它在世界空间外面的高度;还加上之前提到的旋度来管制叶子旋转。如果粒子是掉落到地上它就会变成一个隐没,就是简略的把它设成一个通明,而后在等好几帧让粒子系统让它回收到池子外面就能够了。

再分享两个与水体高度无关的后处理成果; 首先是 WaterLine,这个成果会呈现在水面跟水底交接的边缘局部。而它的思路就是在屏幕空间去找出靠近水面的像素,再插值成 WaterLine 的色彩。利用投影距阵,咱们能够计算 Near Plane 上每像素的世界坐标, 再用这坐标去采样水体高度, 这样咱们就能够算出 Near Plane 对应像素与水面的间隔,再把这个间隔通过 falloff 的函数 算出一个插值的权重就能得出右边截图的 WaterLine 成果。

延长方才 Water Line 的思路,咱们能够算出在眼帘上不同间隔的世界坐标, WP 利用 WP 咱们就能够通过一些噪声 或者是水体压场模拟出该点的一些 Scattering 的亮度。再以 WP 采样水体的高度,咱们就能够晓得这一点在水下深度,再放到一个 falloff 的函数外面就能够算出因为这个水深而做成的散射的缩减。这样,咱们在眼帘上 Ray March 4 个点,同时算出它散射的一个积分,就能失去视频里的 Light Shafts 成果。

最初要分享的是跟玩法外面有严密关系的浮力成果。有别于之前介绍的,浮力是在 CPU 上计算再给到物理引擎的刚体上。依据公式,浮力跟物体浸在水中的体积无关,所以咱们须要在 CPU 拜访物体地位对应的水深来算出它浸入体积。咱们须要一个无效的机制从 GPU 里读取仿真后果,因而 Aqua 实现了一个提早一帧的 GPU 读取性能。大略的流程是咱们会在每帧都先记录所须要查问的地位,之后合零售给一个 CS 从 Texture Atlas 中读取仿真后果并保留到一张分辨率很低的 Staging RT 里。等 CS Dispatch 实现之后咱们才进行 read back, 最初在通过下一帧 C++ 回调来告诉查问后果。这样就是能够尽量升高 GPU 读取所带来的带宽耗费和提早。有了水深,我就能够计算浮力。在 tech demo 里的浮木就是利用这个 GPU 读取框架来计算木头两端的浮力,再以 AddImpluseAtLocation 利用到木头的刚体上。此外,当木头每一端查看到它是浸在水里,咱们会用读取到的水体速度在这一端在给一道力;这样就能做出刚体随水流沉没的一个成果了。

明天咱们为大家分享了 Aqua 团队所开发的凋谢世界水体计划,外面包含了多层级仿真零碎、作用器零碎、GPU Driven 的 CDLOD 水体渲染;还有浅水成果渲染,以及各样的仿真后果利用。在经验了一年不长也不短的开发,Aqua 反对了不少性能和个性,但当中也有不完满或者能够续继优化的中央。比如说:咱们目前还没有做音效的反对;又或者是计划还没能够反对像地穴这种简单地形的水体模仿。心愿前面咱们有机会再持续优化和打磨 Aqua, 让它变得更尽如人意。

Q & A

Q:水体仿真的网格精度如何管制?
陈家铭:这个问题很好,在咱们开发 tech demo 的过程外面发现,其实以 25 厘米来代表一个格子的精度来仿真,不管在性能或者成果上都能达到一个比拟好的均衡。因为在咱们的 demo 外面,咱们是分了三层 clips 来进行仿真;所以对应的精度就大略是 25cm,50cm 以及是 1m 左右。当然,咱们也能够按着游戏的一个需要来晋升仿真精度,然而当精度每晋升一倍,就代表着 substep 次数要减少一次,次要是为防止超出 CFL 条件导致了数值爆炸的状况呈现。

Q:水体渲染的网格是固定大小还是自适应?
胡有为:方才我的分享当中也提到了,就是说咱们的网格大小,其实是由两个决定的,第一是构建分辨率,第二是网格密度的设置,咱们当初是以 512 的构建分辨率 +32 的设置去决定生成网格大小的,当然您也能够抉择其余的,比如说 256+16。目前这个配置是在运行前在我的项目中设置好的,咱们并没有实现说在运行时 runtime 的时候去扭转这个,当然咱们实践上也是能够做到的。

Q:水体光照渲染是如何防止走样的呢?GPU Driven 如何适配到挪动端?
胡有为:这个问题咱们之前也遇到了,就是在水面很远的中央,咱们的确也呈现了一些锯齿、就是走样。那咱们如何解决这个问题的呢?咱们通过将逐顶点的切线空间的还原变成逐像素的,就是在像素着色器外面去还原切线空间,同时咱们还原切线空间的时候,其实能够不必采样邻近像素的高度场,而是跨多个邻近像素,比如说两个、三个,能够让它的切线空间再次平滑。

目前在挪动端去实现 GPU Driven 还是有点艰难,因为咱们目前大家都晓得挪动端的硬件架构,对于 ComputeShader 来说目前停留在 API 的反对阶段,挪动端的架构并没有做出降级或者扭转,所以说在挪动端去适配 GPU Driven 还是比拟艰难,如果要在挪动端去应用 CDLOD,可能还是要去回滚到 CPU 驱动的形式,咱们把在 GPU 下面去构建的算法,把它移植到 CPU 端,而后像变换大小这些信息能够用 Vertex Streams 顶点流的形式去做到 instance 渲染。自定义的数据就只能通过烘焙到贴图或者更新到贴图的形式,因为挪动端目前是对 struct buff 的反对是不太好的。

正文完
 0