游戏开发论坛

 找回密码
 立即注册
搜索
查看: 313|回复: 0

GDC 2025 | 《三角洲行动》:高性能高品质的端手地形和生态技术

[复制链接]

5万

主题

5万

帖子

8万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
88062
发表于 前天 10:23 | 显示全部楼层 |阅读模式
导语:在今年的游戏开发者大会(GDC 2025)上,腾讯游戏带来20场议题分享,围绕AI、渲染、跨端游戏开发等游戏技术应用及游戏研发经验与全球游戏开发者探讨交流,引发同业关注。此外,腾讯海外工作室拳头、Supercell、Digital Extremes等也带来了超40场分享。本文为“《三角洲行动》:高性能高品质的端手地形和生态技术”分享的图文版干货内容。

分享嘉宾:
王理川 腾讯游戏天美J3工作室技术美术组负责人

大家好,很荣幸能站在这里代表腾讯游戏参加2025年的GDC大会。欢迎大家来到《三角洲行动:高性能高品质的端手地形和生态技术》的分享。首先,请允许我简单介绍一下自己并提供一些背景信息。

我叫王理川,自2005年进入游戏行业,至今已有约20年时间,这对我来说都难以置信。我曾在育碧上海和蒙特利尔工作了大约16年,专注于AAA级射击游戏,主要是汤姆克兰西系列,包括幽灵行动和分裂细胞,相信你们中的一些人可能知道这些IP,还有孤岛惊魂系列,从孤岛惊魂4一直到孤岛惊魂6。

然后在2020年,我加入了腾讯游戏的琳琅天上团队,作为工作室首席技术美术师支持几款游戏,比如《穿越火线》手游和《使命召唤》手游,这两款都非常成功。最近,我花了几年时间在《三角洲行动》项目上,主要专注于渲染、程序化、管线、工具等方面。

微信图片_20250414095932.JPG

首先,我想为今天的分享设定一些期望。

这不是一个纯粹的程序讲座,而是程序与视觉艺术/技术美术管线工具的结合。那么,让我们来看一下今天的《三角洲行动》的目标。

《三角洲行动》是一款超写实主义的FPS游戏,整体渲染解决方案和艺术呈现基于PBR,可以将其视为一个纯粹的基于物理的渲染项目。这也是创造自然开放世界的关键。

我们的技术框架必须够高效地为两个平台提供游戏,尽管时间和人力都非常有限。当然,最后游戏需要高性能,这意味着它需要在PC和各种移动设备上流畅运行。

微信图片_20250414095940.JPG

好的,让我们来看看今天的议程。首先,我们会简要介绍一下《三角洲行动》,以防你们中有人不熟悉这款游戏。然后,我会讲解地形和生物群落中的一些核心着色器技术,以便你们在两个平台上看到今天的内容。接下来,我会谈谈我们如何使用程序化工具来帮助生物群落的生成和管理。

从技术角度来看,我们将介绍适用于PC和移动设备的地形纹理(terrain texturing)和地形几何(terrain geometry)解决方案,这些解决方案具有非常低的消耗和最小的伪影。

接下来,我们将深入探讨各种性能优化技术。最后,我们将总结一些最后的想法和我们已经在进行的未来工作。

这里有一个几个月前刚刚发布的视频片段,应该能为你们提供一些我们正在讨论的内容的背景,那么让我们一起来看看。

好的,现在一起看看我们面临的技术限制。《三角洲行动》在多个平台上发布,包括PC和各种移动设备,从低端到高端。我们旨在PC上达到高达4K 144帧,同时在旗舰移动设备上仍能高达120帧。我们在开放世界中有一个完整的昼夜循环,包括室内外环境。因此,世界构建中的生物群落、地形等都需要与一天中的所有时间兼容。我们有一个100平方公里的主要世界,还有一些相对较小的地图。

微信图片_20250414095942.JPG

说到地图大小,让我简要介绍一下游戏的规模。我们有三种游戏模式:

撤离模式,就像我之前说的,这是主世界,位于一个10公里×10公里的开放世界。在这个巨大的地图上随机事件将玩家持续保持热度。

大规模PVP模式,包含多个4×4和2×2的地图,每张地图的具体尺寸由关卡设计师根据游戏目的进行定制。这种模式允许最多64名玩家同时出现在战场上,同时保持非常高的帧率。并且具有真实的地形,包括军事车辆、坦克和直升机的宏大战争,我们还在这游戏模式中开发了广泛的战争破坏系统和大规模破坏资产。

最后是战役模式,这是一种非常典型的线性任务游戏模式,重启经典的黑鹰坠落。这个模式也发生在一个巨大的地图上。这个模式的地形、环境和游戏体验将带你们回到25年前的经典三角洲部队IP。

我们先来看生物群落,我将重点说说跨平台的框架。

为了让整个三角洲世界更加真实可信,游戏中有大量不同的生态分布,并且除了主世界是在摩洛哥,其他一些玩法模式将会发生在世界各地,不同的气候,环境,以及战损状态。如何在差异巨大的双平台实现这些内容,并且系统化的进行特性管理,甚至内容复用,是今天的跨平台生态shading的主题。

微信图片_20250414095943.JPG

为了增加生态表现得丰富性,我们不希望通过完全的随机shading来实现这件事情,视觉效果丰富但可信度比较低,如果通过材质变体实现,会让DrawCall急剧增加,也不宜与管理和迭代。所以,在开发的早期我们尝试采样了一张大型纹理,来为其提供可控的色彩变化。但是对于10x10的开放世界来说,10k分辨率占用内存约100Mb,这在移动端仍然没有办法落地的,于是乎,我们引入了clipmap。

微信图片_20250414095945.JPG

简而言之,剪切映射是一种动态纹理表示,能够高效地在有限的物理内存中缓存任意大小的纹理,以实现实时渲染速率。大家可以看到,随着相机移动进行滚动更新,相机范围内的纹理使用mip0,随着距离逐级递减,这种方式可以大幅减少内存占用,同时保证了近距离的精度。有着clipmap的加持,大大降低了内存的占用,原本100M的占用率一下子降低到了2.5M这个级别,使得在手游平台的落地变为了可能。

微信图片_20250414095946.JPG

相比常规贴图采样,clipmap本身的更新会有更多指令消耗,所以我们决定不仅仅面数植被变色,而是基于这个基础上进行深挖,整合更多的信息到这唯一一张clipmap中。

其中包含了植被的状态,四季变化,水域数据,地形潮湿等,全部塞到了这张纹理中的各个通道中,它几乎涵盖了当前生态的全部信息。各个生态模块的shader都会采样这些信息。

并且整个框架端游手游统一,接下来我会逐一展开讲解。

微信图片_20250414095948.JPG

首先说一下植被部分,10km的开放世界不同区域、因为生长环境不同,海拔高度等不同植被的健康状态存在着差异。而在不同游戏模式下,关卡有不同四季的设定。我们希望一个植被资产能有不同的四季表现,从而最高程度的复用资产。

那不得不说一下健康度的概念,你也同样可以理解为植被四季变化的概念。我们分别在clipmap的R通道和B通道储存了树和草的健康度信息。之所以树木和草分开,是因为在实际应用中,两者差异明显,不能共用信息。

此外我们认为战损也可以算作健康度的一部份,所以我们将战损状态也编码到这2个通道中。

比如0.1-0.85存储着健康信息或者四季信息,将战损信息压缩到0.9-1。

微信图片_20250414095949.JPG

除了clipmap作为全局健康度或者说四季变化的输入信息外,资产个体则有与之对应的健康变化表现以及战损的表现。为每个植被资产基于季节创建色彩偏移以及烧毁效果,并将其储存在独立的look up table中。通过clipmap中的值采样LUT,得到看似随机但是完全可以掌控的结果。并且双端方案保持一致。

微信图片_20250414095951.JPG

在资产方面,在PC上,我们将一个蒙版打包到法线图中作为色调蒙版,并在上面使用线性插值与BaseColor进行混合。

微信图片_20250414095953.JPG

在移动设备上,为了进一步优化性能,我们放弃了基本颜色,而是完全依赖于我们所谓的“strands mask”(一种灰度图),它被打包到法线图中。因此,输入数据减少了一个纹理,这大大减少了纹理采样。在视觉效果上,我们对此都非常满意。

微信图片_20250414095955.JPG

当植物的不健康程度偏移到极限的时候,就是我们的植物燃烧状态,用0.9到1来定义。它和四季变化共享一个LUT, 这种方式的好处在于可以实现平滑的燃烧过度,同时不需要增加更多的材质变体。

当然由于算力限制。在手游上,我们所呈现的烧焦仅是一个颜色变黑,我们在PC上为其添加了更多的shading细节以及复杂的分布规则。

微信图片_20250414095956.JPG

在PC平台为了营造更好的氛围感和真实度,会按照一定的规则整合弹坑以及燃烧特效等,材质上也会做更多的细节,比如烧掉的叶子,燃烧的火星,这些内容都可以轻易通过材质质量分级进行性能降配,从而保持跨平台风格和效果的一致性。

微信图片_20250414095958.JPG

我们将和水相关的一些信息也都pack在这张clipmap中。

在空间上,河流和陆地生态是互斥的,所以他们的储存并不冲突,换个说法大部分植物不会同时生长在水下和陆地上, 所以我们可以合并储存。

关于河流的全局数据,我们储存了如下信息,其中RG通道储存河流velocity的两个分向量,也就是flow map,B通道储存了河流的吸收率控制颜色,A通道保存了河流的泡沫区域,这些数据在shader 使用上端手会有些差异。

微信图片_20250414100000.JPG

根据flow map进行多次采样即可模拟水波流动的感觉,由于周期内波浪是循环重复的,所以循环采样的次数越多,其流动表现就会更加自然。

对于PC我们对波浪法线进行3次采样,每层之间的过度通过余弦函数作为权重进行混合,此外还有模拟了更锐利的小型波浪。

对于手游高配,只需要减少一次采样,每1/2周期进行混合即可;对于性能较低的机型,直接使用简单的线性周期混合,节省cos指令的负担。

微信图片_20250414100002.JPG

在颜色方面,我们使用剪切映射中的吸收蒙版来定义水的基本浑浊度,将吸收LUT和散射LUT映射到一起,共同描述水的颜色和散射颜色。这使我们无需额外的Draw即可处理自然的水表现,例如区分河流、湖泊以及泥沙被冲走的效果。

微信图片_20250414100004.JPG

手游的水使用半透明渲染,在数据使用上与pc不同的地方在于减少了散射的成分,所以只采样了pc的absorption LUT。

同时映射了LUT的alpha通道用来模拟水的浑浊度,这很好理解,水越浑浊 越深的区域,光线穿透率越低,它会更加的不透明,关于半透明这一点其实我们还做了更多的扩展优化,毕竟手机上的算力实在珍贵。

微信图片_20250414100006.JPG

如前面讲的一样,大部分水比较深的地方几乎倾向于不透明状态,并且我们目前没有水下玩法,所以对于标准地形渲染,半透明衔接处,以及几乎不透明的深水域做了不同程度的性能优化,大大降低了GPU clock的开销。

微信图片_20250414100008.JPG

关于水面反射,在PC端上用了Screen Space reflection和多个IBL相结合的方式。

在移动端上,因为性能考量,目前则是只有使用IBL,不过为了整体视觉上的相对正确性,人为的去修改IBL。

最后,我们将wetness信息打包到clipmap中,来作为湿润地形,湿润岩石,水坑等的输入。

微信图片_20250414100010.JPG

clipmap只有每米一个像素的精度,但是在地形上我们可以采样高度layer,与wet mask阈值混合后就可以获得正确丰富的darkness 区域,对darkness mask灰度裁切能够得到水坑的区域。

这种方式实现潮湿以及水坑,相比decal的方式可以省去额外的drawcall,水坑效果将于地形一同绘制。

微信图片_20250414100011.JPG

同时这些潮湿的区域通过clipmap作为介质储存后,就可以使得其他内容进行采样,比如我们的岩石,这让环境间有了真实互动。

微信图片_20250414100013.gif

对于植物来说,全局光照对其最终表现影响很大,而其中ao是最重要的表现,对于PC我们应用了SSRTAO 作为全局光照遮蔽,同时从纹理中补充一些细微结构的ao。植物预烘培的顶点ao只会作用在subsurface上用来拟合多次散射的遮蔽效果。

对于手游的植物则仅有顶点ao。对于草来说只需要通过额外的uv就能获得ao;除了对环境光进行遮蔽以外,在手游上ao还会参与计算假阴影,这对于手机上的植被表现很重要。

微信图片_20250414100015.JPG

无论是顶点还是像素着色的预算面对画面中大量的植物来说都是非常昂贵的,其中阴影消耗是非常重的一块。

对于树来说我们不仅要减少植物插片的数量和顶点,同时绘制阴影时会使用更低级别的lod模型,但是这同时带来了难以接受的低质量阴影,你可以看到影子精度很低,同时有生硬的边缘。

将顶点ao作用到直接光shading上来近似阴影,可以获得更加平滑的暗部过度,同时减少了采样阴影带来的负担

通过对假阴影的调整,也可以近似模拟出植物多次透射的效果,使暗部不会过于发黑。

微信图片_20250414100017.JPG

最终你能看到更稳定,更稳定,更加统一整体的树冠表现,最重要的是性能方面非常省。

草的像素面积和顶点甚至会比树更多,所以不会投射影子也不会采样影子;

我们通过逐instance的随机和ao混合作为假阴影,这和树是类似的,稍微复杂点的是fake shadow会根据视距和视线角度进行衰减,远处和低头查看草时不会产生生硬的黑色过渡。

微信图片_20250414100019.JPG

此外草同时也不会投射影子,它们数量太多了。

我们在clipmap中烘培了草的位置信息以供地形采样, 为地形做了额外的直接光和高光遮蔽,这很好的缓解了没有投影时地形和草的衔接问题。

微信图片_20250414100021.gif

为了能在各个平台上都有比较好的性能和效果表现,植被远景的方案端游和手游有比较大的不同,因此我们准备了3套不同的方案来应对。

首先,是用impostor方案,它只用在PC端,并且同时作为植被的最后一级LOD。

然后就是Billboard,作为只针对手游用的远景模型,大大降低了渲染压力,并且能保证有不错的效果。

最后就是cluster Card,它是真正意义上的backdrop,适用于地图的边界,以及完全gampplay无法触及的区域。并且在双端上有应用。

微信图片_20250414100023.JPG

首先,让我们来看看Impostor。我们使用了一种相当常见的技术,这在之前的许多演讲和论文中都有提及——它实际上是一种基于mesh card的方法。它的概念非常简单,从不同角度捕捉卡片,得到最适合的面,然后在模型的剩余部分重复这个过程,直到没有剩余部分。

微信图片_20250414100025.JPG

我们发现,这种方法对于团簇状的树冠有不错的生成结果。

但是如果一股脑的对所有树木,或某些种类的整棵树进行card生成和纹理捕获,会存在着card的滥用以及纹理的浪费。

我们有多个地图,不同的地域,植被类型会多种多样,截然不同。为了更好的压榨出性能,我们有自己的处理方式。

微信图片_20250414100027.JPG

我们是这么做的:

我们将树冠和树干进行了拆分生成,然后用尽量少并且更贴合树木模型的card对其分别进行纹理捕获和生成。通常只要3个面也就能表现出令人满意的树干。

两者的分开生成的还有一个好处就是,我们可以对其进行不同处理,比如树冠需要更强调团簇感和体积感,而树干树枝则不需要,最后将2者进行合并。

独立生成这两部分的另一个优点是我们可以不同地对待它们。例如,树冠需要更多地强调簇状和体积,而树干和树枝则不需要。最后,我们将这两部分合并成一个整体。

微信图片_20250414100029.JPG

同时,我们对整棵树进行了dither temporal AA来得到顺滑的过度,通过vertex color存的信息区分,分别对树冠和树枝进行了不一样的调参设置,减轻树干插片感的同时,保证树冠有更高的密度。

微信图片_20250414100031.JPG

相比PC的impostor,billboard比较广泛的应用在性能比较低端的平台,对性能更加友好。在这里,我们手机平台,处理所用的植被远景都用这个方法。

针对不同的树木外貌特性,我们处理billboard的方法也会不同。我们大致有3种方法来处理:

首先是Single-plane billboard,这是最为常见的billboard方式。我们对于场景中大多比较对称植被用采用这种办法,我们会选择一个比较适合的视角进行捕捉,无论玩家在世界中所处什么样的位置,billboard总是朝向玩家。

微信图片_20250414100033.gif

难以避免还有不少植被是非对称的,如果用Single-plane billboard的话,会导致无数远处植被视觉跳变的问题。因此对于这类的植被,我们则用了double planes billboard的方法,捕获外形差异比较大的2个角度,且又相差90度的的纹理。

贴在2片平行的平面上,并且和single plane billboard一样,永远朝向玩家。

微信图片_20250414100035.gif

然后,当视角发生变化后用dither来顺滑过度2者的切换。

最后,通过根据摄像机机位的距离来动态调整2片面的间距,从而防止远处观察的z-fighting问题。

微信图片_20250414100037.JPG

另外一种billboard我们称之为Titled Plane。

通常这种树的轮廓是非常倾倒,极为不对称的。例如椰枣树。

我们抓取相对最对称的角度作为纹理原图,然后在shader中进行操作将其压倒。

微信图片_20250414100039.gif

简单的来说就是,我们区分了树干和树冠部分,对树冠进行偏移,旋转,甚至扭曲。对于树干进行弯曲曲率的调整以及起始点的偏移等等。

这是一个非常主观的操作,力求将造型与实际模型对齐。通过lerp计算,最终在一个面片上还原歪斜树木的结果。

微信图片_20250414100041.JPG

另外要提的一点是,无论是哪种类型的biiboard,我们在创建之初,对于面片的尺寸做了均一化处理,无论树木的实际尺寸是怎样的。

然后我们在shading中进行尺寸的调节来匹配真实性,从而能更好的对其进行合批操作,大大降低渲染性能开销。

微信图片_20250414100043.JPG

对于远景山上,以及无法触及到的树木森林,我们会做更极致的优化。

这是对于billboard的延展,美术会手动组合各种树木的billboard,形成成簇。

从然捕获长条形的群簇作为原始纹理,贴在条状面片上,从而构成我们所谓的cluster card。

各种不同类型的billboard组合,构成了Assembly card的多样性,从而组成为DF世界的远景和非触达区域的森林。

微信图片_20250414100045.JPG

好的,现在我们来谈谈我们在程序化方面的实现。关于程序化世界生成,业界已经有许多成熟的解决方案。

我们依然用的是基于houdini的开发流程, Offline生成结果。获取地形信息,根据特定的生成规律,生成点云,返回到引擎,并且实例化生态数据。由于这种做法比较普遍,我就不多展开了。相反,我将更多地讨论我们如何进行跨平台实现。当然,首先是我们刚才谈到的Clipmap。存储在其中的大部分数据都是由PCG生成和修改的。

微信图片_20250414100047.JPG

在我们的游戏中,几乎所有的地形和生物群落都是结合在一起的,并且基于配方,我们为艺术家和内容创作者提供了画笔工具作为预设。预设包括地形、植被资产、贴花和VFX(例如落叶、漂浮的灰尘、烟雾、雾气等)。艺术家只需选择相应的预设,在世界中他们想要的地方进行绘制,而无需进行任何额外的步骤或设置。

这使我们能够在编辑器中使用一套预设工具,但我们的配方是完全解耦的,这意味着同一个配方在每个平台上都有单独的HDA文件。最后,我们在PC和移动设备上得到不同的结果,主要是为了应对平台限制和性能问题。

微信图片_20250414100049.JPG

尽管PC和移动设备是两个独立的管线,但由于它们共享相同的生物群落类型,我们仍然希望它们具有相似的外观。当然,在创建和迭代过程中,我们也不希望做两次工作。因此,我们寻找两个版本之间的继承关系,并在Houdini中进行降级操作。我们的方法是锁定配方中的高优先级元素,并将其继承到移动设备版本,同时去掉不太重要的元素,如矮树、小灌木和装饰物。这就得到了一个简化的移动设备版本,总体上保持了相似的外观。

微信图片_20250414100051.JPG

而且,如果我们仍然存在一些性能热点,仅减少配方复杂度无法满足要求,我们还设计和创建了专用的配方,在某些区域更激进地降级密度和类别。你可以将其视为仅限移动设备或移动设备专用的工具,仅影响移动设备版本。

微信图片_20250414100054.JPG

我们尽量使生物群落尽可能基于预设,但为了满足非常特定的游戏玩法需求,我们也提供了手动放置树木和大灌木的灵活性。但周围的装饰会进入程序化处理。

最后,细节将在周围生成,并且与整体环境很好地集成。

我们实现了自动修正来处理大块植被的重叠和交叉。像棕榈树总是最好的例子。首先,我们需要找到一种方法来验证它们是否交叉。因此,我们将几何体体素化,3D体素相当于具有值1的3D数组。如果多个3D数组的和的最大值等于2或更大,这意味着模型交叉了。接下来,我们自动更改其中一个模型的方向。然后,再次进行验证和修正。并反复这个过程,直到没有交叉。这样,我们在PC和移动设备上都确保了所有树木都放置得正确美观。

微信图片_20250414100056.JPG

草漂浮是一个普遍的问题,尤其是在移动设备上。由于预算和性能限制,移动设备上草簇模型的边界框通常很大。我们所做的第一件事是向地形投射射线,测量草模型的支点与地形本身的距离。如果结果超过阈值,将漂浮的草稍微向下拉。再进行一次验证,移除漂浮的草,或者如果性能允许,用较小的草替换。

由于一切都由PCG控制,因此我们可以调整密度以减少三角形数量,或者替换某些类型的模型以减少Drawcall,灵活地实现每个质量级别的视觉和性能的最佳平衡。最后,我们得到了在感知上相似的PC和移动设备版本,同时根本上满足了两个平台的质量和性能需求。

微信图片_20250414100058.JPG

最后,道路也是地形的一部分,所以我来展开说一下我们在这方面做出的一些优化。在一些低端移动手机上,由于性能限制,我们无法支持VT(虚拟纹理)。而且,我们的地形精度也从每格1米降低到每格2米。正如你所见,道路和地形几乎到处可见得重叠,Z-fighting非常严重。所以,即使输入数据相同,每个平台的处理方法也需要有所不同。

微信图片_20250414100100.JPG

在程序化处理过程中,我们为低端设备生成额外的道路模型,拓扑结构需要更改为2米以精确匹配地形网格。这样视觉上看起来正确,但这导致了另一个问题,即三角形数量急剧增加,低端设备也无法承受。因此,我们对远处的道路进行减面来作为LOD模型。为了避免重叠再次出现,此外,在顶点着色器中,我们将远处的顶点渐进逐渐向上拉。最后看起来一切都很完美。

微信图片_20250414100103.JPG

在下面内容中,我们的引擎程序员组长焦航,将带大家了解核心技术特性。

分享嘉宾:
焦航 腾讯游戏天美J3工作室引擎组负责人

大家好,我是焦航,来自琳琅天上的引擎团队负责人。你们可以叫我Jesse。过去十年,我一直在移动游戏开发领域深耕,参与过《穿越火线手游》《使命召唤手游》等成功项目的开发——这些都是基于Unity引擎实现的。今天,我将以我们使用虚幻引擎4.24开发的《三角洲行动》游戏为例,带大家揭秘游戏中地形与生态群落渲染背后的技术。

微信图片_20250414100105.JPG

首先,我们来聊聊地形贴图技术。

正如上篇中Lichuan之前提到的,我们使用VT技术处理地形贴图。VT本质上是一种纹理缓存算法,能有效节省带宽。同时这项技术支持在地形上叠加大量贴花,让我们能在PC和移动端实现高品质的地形渲染。在虚幻引擎中,我们采用的是adaptive VT方案。

在移动端使用VT时,我们需要在写入VT前,对纹理进行运行时压缩。如果不压缩,每帧都会消耗大量带宽,导致设备发热。因此,我们将其压缩为ASTC 4 * 4或6 * 6格式。我们采用了极简算法,仅支持ASTC的双端点模式,不含ASTC的分区模式。出于兼容性考虑,我们在所有移动设备上使用pixel shader执行压缩。首先通过pixel shader输出到uint4纹理,每个像素128位,对应ASTC的block大小。接着将纹理复制到buffer,最后再复制给压缩纹理。

微信图片_20250414100108.JPG

这是实际效果:带宽方面,每帧带宽减少到原先的四分之一或九分之一。PSNR值稳定在40到50区间,美术团队对最终视觉效果表示认可。单张VT页面的压缩耗时仅需0.2毫秒。

微信图片_20250414100110.JPG

接下来聊聊地形贴图的基础——splat map。我们在双平台使用基本相同的splat map方案,这使移动端也能获得高品质地形。在移动端,我们将PBR纹理的多个通道打包到更少的贴图中。具体来说,我们用了两个texture arrays:一个存储albedo和heightmap,另一个储存normal、roughness及ambient occlusion。

微信图片_20250414100112.JPG

来看看先来看看我们的需求:美术想要至少32层材质混合,以提升场景丰富度。这需要某种形式的ID map方案支持。想要实现基于高度的权重混合,且权重可控——这种混合方式能让两个材质在大范围渐变过渡,使交界更自然。这需要地形上每个点需要为每层材质单独存储权重值。同时受带宽限制,纹理采样次数必须严格控制。但,等等,常规的每层权重方案(比如虚幻引擎默认方案)无法支持如此多层,因为每一层贴图都需要采样做混合。而ID map方案(如《孤岛惊魂》《幽灵行动》采用的方式)又不支持权重混合,每个点只能指定单一材质ID。我们该怎么办呢?

微信图片_20250414100114.JPG

让我们从最基础方案开始,化繁为简。首先为所有材质层设定明确的顺序,采用从底层到顶层的覆盖逻辑。在地形每个采样点存储两个layer ID:底层(bottom)和顶层(top)。禁止同一位置出现三层叠加。这样底层权重始终为1,只需存储顶层(top)的混合权重值。我们采用每米一个采样点的ID map精度存储这些数据。

微信图片_20250414100116.JPG

生成ID map的具体流程是:美术师在编辑器中正常为每层材质绘制权重,我们会在每个地形采样点,筛选出权重最高的两个材质层保留下来。

微信图片_20250414100118.JPG

接下来,地形上的每个像素需要获取周围4个采样点,并进行插值计算。这意味着每个像素需要采样4个ID map点,进而采样8个材质层,在移动端总计产生20次采样。这样的开销显然过大,必须进行优化。

微信图片_20250414100120.JPG

首先引入一个小技巧:我们可以将插值计算范围从方形区域改为在三角形区域进行插值。这样能将材质层采样数从8层降至6层。虽然地面可能出现轻微三角形pattern的感觉,但通过合理的层混合,这种pattern几乎不可察觉。不过6层采样依然偏高——实际开发中美术师很少在同一区域叠加这么多层。如果我们利用这个特性,将不同的ID数量从6个减少到3个呢?这样虽然仍有6个ID编号,但实际只需采样3个材质层。经过实际测试,我们发现3层是保证正常过渡的最低标准,同时足以呈现复杂的地形效果。

微信图片_20250414100123.JPG

那么我们如何从三角形内的6个ID中提取3个独立层?我们通过离线ID修复流程实现这一点。当美术师在编辑器中绘制权重时,每个三角形会包含6个层ID:3个底层ID和3个顶层ID。流程开始时,创建一个容量为3的空集合。首先将3个底层ID全部加入集合。若集合未满(存在重复ID),继续按权重从高到低的顺序逐个添加顶层ID。若集合已满,则丢弃,具体做法是将多余顶层ID设置为与底层ID相同。

微信图片_20250414100125.JPG

由于只是移除多余ID,处理相邻三角形时不会破坏当前三角形数据。现在这6个ID中仅保留3个不同的值。还记得我们为材质层设定了明确顺序吗?因此顶层ID始终大于底层ID。接下来需要在shader中从这6个数值中提取3个ID,分别命名为min、mid和max。

微信图片_20250414100127.JPG

在shader中,每个待渲染像素首先需解码3个ID map采样点,获取6个ID编号及3个权重值。随后通过这段代码,从6个ID中提取出min、mid和max三个核心ID。

微信图片_20250414100130.JPG

最终,我们能在每个三角形顶点获取各材质层的权重值,并完成混合计算。相关PPT稍后会上传至GDC Vault平台。接下来我将继续讲解。

微信图片_20250414100132.JPG

总结来看:相比传统的weight-per-layer方案,我们的方法支持更多材质层叠加。相较《孤岛惊魂》《幽灵行动》采用的ID map方案,我们实现了权重混合功能。传统ID map方案要实现过渡效果,必须创建包含两种材质的混合层,这会额外消耗内存。而我们的方案可以任意两层材质自由混合,无需占用额外内存。此外,这种混合机制还能实现类似《巫师3:狂猎》的法线叠加与衰减等高级效果。

微信图片_20250414100134.JPG

关于悬崖渲染:在VT方案中,UV通常基于世界坐标的XY轴投影,这会导致悬崖面出现纹理拉伸。常规解决方案是采用tri-planar映射——需要额外增加XZ和YZ轴的纹理投影采样,并进行混合。但这会导致每帧的额外性能开销。目前存在tri-planar的优化方案,不过通常仍需在base pass中增加纹理采样次数。

微信图片_20250414100136.JPG

那么为何不直接将tri-planar映射渲染到VT中呢?让我具体说明:可以看到XY轴投影,仅在平坦区域表现良好。在悬崖垂直面上,如果继续使用XY轴投影,UV坐标会产生严重拉伸变形。

微信图片_20250414100138.JPG

在悬崖面使用XZ或YZ轴投影时,UV在模型表面不会拉伸——虽然这可能导致VT页面内的纹理拉伸,但实际渲染时mesh本身不会拉伸。所以一个简单的计算UV的方法是:在生成VT的绘制阶段,对每个像素:检测法线方向(normal direction),基于此自动选择最佳投影平面(XY/XZ/YZ),并使用该UV,绘制这个像素。

微信图片_20250414100140.JPG

要实现这一点,我们需要引入Z轴坐标。必须将实际地形网格输入VT生成流程,而非之前的quad。注意要使用SampleGrad方法——由于每个像素的UV动态变化,必须手动指定正确的mipmap层级。这是优化后的效果:可以看到纹理拉伸消失了,但出现了明显接缝。如何消除接缝?

微信图片_20250414100142.JPG

解决接缝问题,我们采用受《Far Cry》启发的随机分布方案。令人惊喜的是,在VT中这种处理显得非常自然:由于VT缓冲区不会频繁刷新,帧间稳定性得到保证;同时得益于mipmapping机制,避免了aliasing锯齿问题。

微信图片_20250414100145.JPG

最终我们以极低成本实现了类似Tri-planar的效果。我们也尝试过bi-planar方案,虽然混合效果更好但性能开销更大。随机方案在效果与性能间达到平衡,最终成为游戏内的实装方案。不过直接在VT中渲染悬崖存在一个限制——当玩家近距离观察时,最大分辨率会有所下降。我们认为这是一个可接受的取舍。

微信图片_20250414100147.JPG

这里还有另一个提升视觉质量的技巧:使用高度混合时,近处效果良好,但远景会出现块状瑕疵——这是因为远距离混合使用高LOD层级的mipmap,精度不足导致。除了调整远景tiling之外,我们还引入了另一个技巧。我们在远处采用线性混合方案:随着距离增加,逐步过渡到线性混合模式。这种处理使过渡更柔和,有效解决了块状瑕疵。

微信图片_20250414100150.JPG

这里还有另一个运行时出现的问题:美术师在编辑器中精心雕琢的地形细节,在移动端运行时——尤其是远景中——会显得过于平坦,沟壑与侵蚀细节被平滑掉了。

微信图片_20250414100152.JPG

根本原因在于:编辑器中地形使用38万三角面,而运行时版本仅保留8万面。出于性能考量,我们大幅降低了远景区域的网格密度。这意味着通过顶点法线(vertex normal)表现的几何细节也随之减少。

微信图片_20250414100154.JPG

为恢复这些细节,我们引入Streaming Virtual Textures(流式虚拟纹理)解决方案。既然远景区域的网格无法存储足够顶点法线细节,我们就将其烘焙到纹理中。将整个地形的低层级mipmap烘焙成SVT格式。在VT生成阶段,当收到页面请求时,我们会检查是否存有SVT数据。若存在,则直接将数据载入physical texture,而非通过RVT方式实时渲染。SVT与RVT共享同一块physical texture内存,因此不会产生额外内存消耗。

微信图片_20250414100156.JPG

因此,我们将顶点法线(vertex normal)烘焙到SVT中,并在最终渲染时完全忽略地形网格自带的法线信息——所有法线数据都来自SVT。但SVT与近景RVT混合时,必须采用相同的法线生成逻辑才能避免接缝。这意味着在生成RVT时,同样需要通过实际地形网格实时渲染顶点法线——就像处理悬崖渲染时一样。最终效果令人满意:远景地形细节完全保留,不再出现细节丢失。美术团队甚至可以通过手动调整离线生成的SVT数据,自由定制远景视觉效果。

微信图片_20250414100158.JPG

此外,游戏中还有许多细节调优案例。例如在这款快节奏FPS游戏中,开镜时摄像机FOV(视野)会急速变化,导致VT需要立即重绘更高精度内容——这会产生明显的地形突变(pop-in)现象,干扰玩家索敌。因此我们在开镜时引入额外偏移值(bias),使FOV变化时维持当前mip level。该方案显著提升了操作流畅度,代价是开镜时地形会略显模糊。

微信图片_20250414100200.JPG

接下来我们将深入探讨地形几何结构。

在虚幻引擎默认方案中,每块地形tile都需要单独Draw Call,导致渲染整个地形需要大量绘制调用。这在移动设备上会产生巨大开销。因此我们采用CDLOD(Continuous Distance-Dependent Level of Detail)方案渲染地形网格。该技术通过draw instance同时渲染所有LOD层级的地形tile。每个实例对应一个网格单元——离摄像机越近的单元尺寸越小,越远的单元尺寸越大。所有实例保持相同顶点数,因此近处网格更密集,远处更稀疏。通过这种方式,整个地形只需1——2个Draw Call即可完成渲染。

微信图片_20250414100202.JPG

此外,我们还通过一个小技巧进一步降低实例数量。常规情况下,当请求某地形tile的高LOD层级时,会将其细分为四个高LOD子tile,导致需要4个实例渲染。但我们采用顶点着色器裁剪方案:将低LOD网格中需要细分的区域切割出来,仅对该区域进行细分。切割操作通过顶点着色器动态调整顶点位置实现。这使得总实例数降至2个,且每个实例保持相同顶点数,从而减少总顶点绘制量。

微信图片_20250414100204.JPG

关于曲面细分(tessellation)。硬件曲面细分存在许多问题。它存在性能问题,三角形分布模式也不够理想,可能导致更多误差。而且在移动端并不适用。因此,我们实现了软细分方案。这是对CDLOD的自然延伸——通过为地形块引入LOD -1、-2等级别,从VT中采样高度图来调整几何结构。

微信图片_20250414100206.JPG

关于软细分方案——我们希望附近的几何密度非常高,并且密度随着距离增加而快速降低。但CDLOD要求相邻地形块的LOD等级差异只能相差一级。这种限制会导致大量冗余地形块。正如所见,我们不得不切割出额外地形块来满足这个限制。我们放宽了这个限制,允许对LOD进行多次细分,同时保留之前提到的裁剪剔除方法。传统做法是2x2的细分,而我们尝试了4x4和8x8的细分方式。通过之前介绍的裁剪技巧,你会发现实例数量更少了,而且网格密度能以此方式更快降低。我们最终选择4x4方案,因为它在误差率和实例数量之间取得了最佳平衡。

微信图片_20250414100208.JPG

相比硬件曲面细分,这种方案性能更优、三角形分布模式更优,误差也更低。我们已经在PC平台实装了该方案。理论上在移动端也能落地,但最终没有足够时间将其植入正式版本。在中高端机型上值得尝试。

微信图片_20250414100210.JPG

此外,我们还做了多项性能优化——针对时间和内存进行了优化,更重要的是,针对移动端的发热问题。

正如Lichuan之前提到的,我们在多个场景中使用了clipmap技术。这种技术的精髓在于——根据相机位置动态加载周边高精度纹理,远处则加载低精度贴图,具体精度范围取决于你想要保留细节的最远距离。这能节省大量内存。

微信图片_20250414100212.JPG

我们把splat ID map也改造成了clipmap结构——只有近处才需要高精度数据。在实现上,不再为每个区块单独启用VT renderer,而是通过whole scene VT renderer统一管理clipmap流式加载。这样减少了Actor数量,卡顿现象也随之缓解。

微信图片_20250414100214.JPG

如果直接加载全部32层纹理到Texture Array会消耗过多内存。但在渲染近处地形时,实际只会用到其中的部分层。因此我们实现了动态纹理数组——按需加载纹理,并填充到数组的空隙中。渲染中距离地形时则使用完整的32层数组,配合更小的mipmap层级。这样有效控制了需要加载的全分辨率层数。

微信图片_20250414100217.JPG

我们自动监测实际使用的层数,并进行可视化图层检查。在兴趣点(POIs)之间的过渡区域,内存压力较低,因此可以适当放宽限制以获得更佳画质。

微信图片_20250414100218.JPG

还有一个优化思路值得分享。虽然允许每个像素混合3种不同图层,但实际很多区块用不到这么多层——比如大面积单一图层(草地、泥土)或仅混合两种图层的区域占主流。考虑到高频更新的高精度LOD VT页会频繁切换进出physical texture,我们为着色器制作了三种变体,根据即将渲染的VT页实际使用层数动态选择着色器变体。这些数据是离线统计生成的,内存开销极小。

微信图片_20250414100220.JPG

在移动端,我们可以优化VT流程中使用的渲染目标。理想情况下,VT内只需两张纹理,通过通道合并来降低基础通道的采样次数。但由于Decal存在透明度混合,渲染VT页时仍需将透明度通道单独保留。之后通过subpass(或frame buffer fetch)对通道进行重组,使其达到理想布局。这种操作利用移动端的on-chip内存,无需回传到主内存。接着就能进行ASTC格式压缩。更进一步——如果检测到某个Tile没有透明度混合需求,就直接以理想通道布局绘制到渲染目标。这类信息同样是通过离线烘焙获取的。

微信图片_20250414100222.JPG

在设备适配Scaling方面,我们开放了多项参数调节开关。比如说,不同设备可设置不同的最大分辨率——降低分辨率意味着需要更新的VT脏页更少。我们还可以调节VT的mip偏置,配合纹理数组的mip偏置。这在低端机上能节省部分内存。高端设备使用1米精度的地形高度图,低端设备则回退到2米精度——这意味着需要着色的顶点更少。地形网格LOD距离缩放系数提供了另一种优化手段——通过加快远处网格的密度衰减速度来减少顶点数量。每帧允许填充的页面数量也可以设限,从而降低卡顿概率。此外,各向异性纹理采样(aniso-texture sampling)的等级也能通过画质档位灵活控制。

微信图片_20250414100224.JPG

针对极低端移动设备,我们单独设计了一套地形渲染方案。这主要是出于兼容性考虑——部分低端机型存在VT支持问题,因此必须找到不依赖VT的替代方案。具体做法是:在base pass直接渲染splat map。由于缺乏动态纹理数组支持,只能使用完整32层低分辨率纹理数组。将每顶点splat层数压缩到单层,实际上回退到ID贴图方案。移除法线贴图(normalmap)。由于缺乏Decal支持,道路需要作为独立mesh单独渲染。

微信图片_20250414100226.JPG

我们针对不同机型精细调试了所有参数,确保各档次设备都能流畅运行,同时呈现其硬件允许范围内的最佳画质。这是最终各档次设备上的内存占用情况。

微信图片_20250414100228.JPG

这是部分性能测试数据。

我们对地形渲染性能进行了测试,将本方案与移动端主流的4层逐层权重法进行对比。测试数据均基于骁龙855平台,且仅渲染地形。可以看到无论是高画质档还是低画质档,本方案在帧率、功耗、带宽和GPU耗时上均有优势。更关键的是,我们还能支持32层材质混合以及大量Decal效果。

微信图片_20250414100230.JPG

以下是今日分享的核心结论:在地貌与地形渲染方面,我们通过合理控制复杂度实现了跨PC与移动端的兼容适配;运用程序化生成方法加速迭代并提升品质;针对地形贴图与几何结构开创了独特的解决方案;并通过性能与scaling调优确保方案落地。关于未来方向——我们计划完善天气系统的实现方案,开发水下特性,同时加强地形法线混合和衰减的调试以进一步提升视觉表现。

微信图片_20250414100232.JPG

特别感谢所有杰出的同事——你们才是真正的英雄。感谢Delta Force整个团队。感谢GDC组委会提供这个分享平台。感谢在座各位的聆听。

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-4-16 04:21

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表