专栏名称: 腾讯游戏学堂
腾讯游戏学院由腾讯互动娱乐发起,致力于打造游戏知识分享和交流平台,专注建设游戏职业培训和发展体系。学院通过提供游戏类专业培训课程、建设游戏职业发展通道、开展丰富的校园活动及行业活动,帮助在校生和游戏从业者提升职业竞争力,成就游戏创想梦。
目录
相关文章推荐
51好读  ›  专栏  ›  腾讯游戏学堂

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

腾讯游戏学堂  · 公众号  ·  · 2025-04-10 15:31

正文

请到「今天看啥」查看全文


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

分享嘉宾:

焦航 腾讯游戏天美J3工作室引擎组负责人

大家好,我是焦航,来自琳琅天上的引擎团队负责人。你们可以叫我Jesse。过去十年,我一直在移动游戏开发领域深耕,参与过《穿越火线手游》《使命召唤手游》等成功项目的开发——这些都是基于Unity引擎实现的。今天,我将以我们使用虚幻引擎4.24开发的《三角洲行动》游戏为例,带大家揭秘游戏中地形与生态群落渲染背后的技术。
首先,我们来聊聊地形贴图技术。
正如上篇中Lichuan之前提到的,我们使用VT技术处理地形贴图。VT本质上是一种纹理缓存算法,能有效节省带宽。同时这项技术支持在地形上叠加大量贴花,让我们能在PC和移动端实现高品质的地形渲染。在虚幻引擎中,我们采用的是adaptive VT方案。
在移动端使用VT时,我们需要在写入VT前,对纹理进行运行时压缩。如果不压缩,每帧都会消耗大量带宽,导致设备发热。因此,我们将其压缩为ASTC 4 * 4或6 * 6格式。我们采用了极简算法,仅支持ASTC的双端点模式,不含ASTC的分区模式。出于兼容性考虑,我们在所有移动设备上使用pixel shader执行压缩。首先通过pixel shader输出到uint4纹理,每个像素128位,对应ASTC的block大小。接着将纹理复制到buffer,最后再复制给压缩纹理。
这是实际效果:带宽方面,每帧带宽减少到原先的四分之一或九分之一。PSNR值稳定在40到50区间,美术团队对最终视觉效果表示认可。单张VT页面的压缩耗时仅需0.2毫秒。
接下来聊聊地形贴图的基础——splat map。我们在双平台使用基本相同的splat map方案,这使移动端也能获得高品质地形。在移动端,我们将PBR纹理的多个通道打包到更少的贴图中。具体来说,我们用了两个texture arrays:一个存储albedo和heightmap,另一个储存normal、roughness及ambient occlusion。
来看看先来看看我们的需求:美术想要至少32层材质混合,以提升场景丰富度。这需要某种形式的ID map方案支持。想要实现基于高度的权重混合,且权重可控——这种混合方式能让两个材质在大范围渐变过渡,使交界更自然。这需要地形上每个点需要为每层材质单独存储权重值。同时受带宽限制,纹理采样次数必须严格控制。但,等等,常规的每层权重方案(比如虚幻引擎默认方案)无法支持如此多层,因为每一层贴图都需要采样做混合。而ID map方案(如《孤岛惊魂》《幽灵行动》采用的方式)又不支持权重混合,每个点只能指定单一材质ID。我们该怎么办呢?
让我们从最基础方案开始,化繁为简。首先为所有材质层设定明确的顺序,采用从底层到顶层的覆盖逻辑。在地形每个采样点存储两个layer ID:底层(bottom)和顶层(top)。禁止同一位置出现三层叠加。这样底层权重始终为1,只需存储顶层(top)的混合权重值。我们采用每米一个采样点的ID map精度存储这些数据。
生成ID map的具体流程是:美术师在编辑器中正常为每层材质绘制权重,我们会在每个地形采样点,筛选出权重最高的两个材质层保留下来。
接下来,地形上的每个像素需要获取周围4个采样点,并进行插值计算。这意味着每个像素需要采样4个ID map点,进而采样8个材质层,在移动端总计产生20次采样。这样的开销显然过大,必须进行优化。
首先引入一个小技巧:我们可以将插值计算范围从方形区域改为在三角形区域进行插值。这样能将材质层采样数从8层降至6层。虽然地面可能出现轻微三角形pattern的感觉,但通过合理的层混合,这种pattern几乎不可察觉。不过6层采样依然偏高——实际开发中美术师很少在同一区域叠加这么多层。如果我们利用这个特性,将不同的ID数量从6个减少到3个呢?这样虽然仍有6个ID编号,但实际只需采样3个材质层。经过实际测试,我们发现3层是保证正常过渡的最低标准,同时足以呈现复杂的地形效果。
那么我们如何从三角形内的6个ID中提取3个独立层?我们通过离线ID修复流程实现这一点。当美术师在编辑器中绘制权重时,每个三角形会包含6个层ID:3个底层ID和3个顶层ID。流程开始时,创建一个容量为3的空集合。首先将3个底层ID全部加入集合。若集合未满(存在重复ID),继续按权重从高到低的顺序逐个添加顶层ID。若集合已满,则丢弃,具体做法是将多余顶层ID设置为与底层ID相同。
由于只是移除多余ID,处理相邻三角形时不会破坏当前三角形数据。现在这6个ID中仅保留3个不同的值。还记得我们为材质层设定了明确顺序吗?因此顶层ID始终大于底层ID。接下来需要在shader中从这6个数值中提取3个ID,分别命名为min、mid和max。
在shader中,每个待渲染像素首先需解码3个ID map采样点,获取6个ID编号及3个权重值。随后通过这段代码,从6个ID中提取出min、mid和max三个核心ID。
最终,我们能在每个三角形顶点获取各材质层的权重值,并完成混合计算。相关PPT稍后会上传至GDC Vault平台。接下来我将继续讲解。
总结来看:相比传统的weight-per-layer方案,我们的方法支持更多材质层叠加。相较《孤岛惊魂》《幽灵行动》采用的ID map方案,我们实现了权重混合功能。传统ID map方案要实现过渡效果,必须创建包含两种材质的混合层,这会额外消耗内存。而我们的方案可以任意两层材质自由混合,无需占用额外内存。此外,这种混合机制还能实现类似《巫师3:狂猎》的法线叠加与衰减等高级效果。
关于悬崖渲染:在VT方案中,UV通常基于世界坐标的XY轴投影,这会导致悬崖面出现纹理拉伸。常规解决方案是采用tri-planar映射——需要额外增加XZ和YZ轴的纹理投影采样,并进行混合。但这会导致每帧的额外性能开销。目前存在tri-planar的优化方案,不过通常仍需在base pass中增加纹理采样次数。
那么为何不直接将tri-planar映射渲染到VT中呢?让我具体说明:可以看到XY轴投影,仅在平坦区域表现良好。在悬崖垂直面上,如果继续使用XY轴投影,UV坐标会产生严重拉伸变形。
在悬崖面使用XZ或YZ轴投影时,UV在模型表面不会拉伸——虽然这可能导致VT页面内的纹理拉伸,但实际渲染时mesh本身不会拉伸。所以一个简单的计算UV的方法是:在生成VT的绘制阶段,对每个像素:检测法线方向(normal direction),基于此自动选择最佳投影平面(XY/XZ/YZ),并使用该UV,绘制这个像素。
要实现这一点,我们需要引入Z轴坐标。必须将实际地形网格输入VT生成流程,而非之前的quad。注意要使用SampleGrad方法——由于每个像素的UV动态变化,必须手动指定正确的mipmap层级。这是优化后的效果:可以看到纹理拉伸消失了,但出现了明显接缝。如何消除接缝?
解决接缝问题,我们采用受《Far Cry》启发的随机分布方案。令人惊喜的是,在VT中这种处理显得非常自然:由于VT缓冲区不会频繁刷新,帧间稳定性得到保证;同时得益于mipmapping机制,避免了aliasing锯齿问题。
最终我们以极低成本实现了类似Tri-planar的效果。我们也尝试过bi-planar方案,虽然混合效果更好但性能开销更大。随机方案在效果与性能间达到平衡,最终成为游戏内的实装方案。不过直接在VT中渲染悬崖存在一个限制——当玩家近距离观察时,最大分辨率会有所下降。我们认为这是一个可接受的取舍。
这里还有另一个提升视觉质量的技巧:使用高度混合时,近处效果良好,但远景会出现块状瑕疵——这是因为远距离混合使用高LOD层级的mipmap,精度不足导致。除了调整远景tiling之外,我们还引入了另一个技巧。我们在远处采用线性混合方案:随着距离增加,逐步过渡到线性混合模式。这种处理使过渡更柔和,有效解决了块状瑕疵。
这里还有另一个运行时出现的问题:美术师在编辑器中精心雕琢的地形细节,在移动端运行时——尤其是远景中——会显得过于平坦,沟壑与侵蚀细节被平滑掉了。
根本原因在于:编辑器中地形使用38万三角面,而运行时版本仅保留8万面。出于性能考量,我们大幅降低了远景区域的网格密度。这意味着通过顶点法线(vertex normal)表现的几何细节也随之减少。
为恢复这些细节,我们引入Streaming Virtual Textures(流式虚拟纹理)解决方案。既然远景区域的网格无法存储足够顶点法线细节,我们就将其烘焙到纹理中。将整个地形的低层级mipmap烘焙成SVT格式。在VT生成阶段,当收到页面请求时,我们会检查是否存有SVT数据。若存在,则直接将数据载入physical texture,而非通过RVT方式实时渲染。SVT与RVT共享同一块physical texture内存,因此不会产生额外内存消耗。
因此,我们将顶点法线(vertex normal)烘焙到SVT中,并在最终渲染时完全忽略地形网格自带的法线信息——所有法线数据都来自SVT。但SVT与近景RVT混合时,必须采用相同的法线生成逻辑才能避免接缝。这意味着在生成RVT时,同样需要通过实际地形网格实时渲染顶点法线——就像处理悬崖渲染时一样。最终效果令人满意:远景地形细节完全保留,不再出现细节丢失。美术团队甚至可以通过手动调整离线生成的SVT数据,自由定制远景视觉效果。
此外,游戏中还有许多细节调优案例。例如在这款快节奏FPS游戏中,开镜时摄像机FOV(视野)会急速变化,导致VT需要立即重绘更高精度内容——这会产生明显的地形突变(pop-in)现象,干扰玩家索敌。因此我们在开镜时引入额外偏移值(bias),使FOV变化时维持当前mip level。该方案显著提升了操作流畅度,代价是开镜时地形会略显模糊。


接下来我们将深入探讨地形几何结构。
在虚幻引擎默认方案中,每块地形tile都需要单独Draw Call,导致渲染整个地形需要大量绘制调用。这在移动设备上会产生巨大开销。因此我们采用CDLOD(Continuous Distance-Dependent Level of Detail)方案渲染地形网格。该技术通过draw instance同时渲染所有LOD层级的地形tile。每个实例对应一个网格单元——离摄像机越近的单元尺寸越小,越远的单元尺寸越大。所有实例保持相同顶点数,因此近处网格更密集,远处更稀疏。通过这种方式,整个地形只需1~2个Draw Call即可完成渲染。
此外,我们还通过一个小技巧进一步降低实例数量。常规情况下,当请求某地形tile的高LOD层级时,会将其细分为四个高LOD子tile,导致需要4个实例渲染。但我们采用顶点着色器裁剪方案:将低LOD网格中需要细分的区域切割出来,仅对该区域进行细分。切割操作通过顶点着色器动态调整顶点位置实现。这使得总实例数降至2个,且每个实例保持相同顶点数,从而减少总顶点绘制量。
关于曲面细分(tessellation)。硬件曲面细分存在许多问题。它存在性能问题,三角形分布模式也不够理想,可能导致更多误差。而且在移动端并不适用。因此,我们实现了软细分方案。这是对CDLOD的自然延伸——通过为地形块引入LOD -1、-2等级别,从VT中采样高度图来调整几何结构。
关于软细分方案——我们希望附近的几何密度非常高,并且密度随着距离增加而快速降低。但CDLOD要求相邻地形块的LOD等级差异只能相差一级。这种限制会导致大量冗余地形块。正如所见,我们不得不切割出额外地形块来满足这个限制。我们放宽了这个限制,允许对LOD进行多次细分,同时保留之前提到的裁剪剔除方法。传统做法是2x2的细分,而我们尝试了4x4和8x8的细分方式。通过之前介绍的裁剪技巧,你会发现实例数量更少了,而且网格密度能以此方式更快降低。我们最终选择4x4方案,因为它在误差率和实例数量之间取得了最佳平衡。
相比硬件曲面细分,这种方案性能更优、三角形分布模式更优,误差也更低。我们已经在PC平台实装了该方案。理论上在移动端也能落地,但最终没有足够时间将其植入正式版本。在中高端机型上值得尝试。


此外,我们还做了多项性能优化——针对时间和内存进行了优化,更重要的是,针对移动端的发热问题。
正如Lichuan之前提到的,我们在多个场景中使用了clipmap技术。这种技术的精髓在于——根据相机位置动态加载周边高精度纹理,远处则加载低精度贴图,具体精度范围取决于你想要保留细节的最远距离。这能节省大量内存。
我们把splat ID map也改造成了clipmap结构——只有近处才需要高精度数据。在实现上,不再为每个区块单独启用VT renderer,而是通过whole scene VT renderer统一管理clipmap流式加载。这样减少了Actor数量,卡顿现象也随之缓解。
如果直接加载全部32层纹理到Texture Array会消耗过多内存。但在渲染近处地形时,实际只会用到其中的部分层。因此我们实现了动态纹理数组——按需加载纹理,并填充到数组的空隙中。渲染中距离地形时则使用完整的32层数组,配合更小的mipmap层级。这样有效控制了需要加载的全分辨率层数。
我们自动监测实际使用的层数,并进行可视化图层检查。在兴趣点(POIs)之间的过渡区域,内存压力较低,因此可以适当放宽限制以获得更佳画质。
还有一个优化思路值得分享。虽然允许每个像素混合3种不同图层,但实际很多区块用不到这么多层——比如大面积单一图层(草地、泥土)或仅混合两种图层的区域占主流。考虑到高频更新的高精度LOD VT页会频繁切换进出physical texture,我们为着色器制作了三种变体,根据即将渲染的VT页实际使用层数动态选择着色器变体。这些数据是离线统计生成的,内存开销极小。
在移动端,我们可以优化VT流程中使用的渲染目标。理想情况下,VT内只需两张纹理,通过通道合并来降低基础通道的采样次数。但由于Decal存在透明度混合,渲染VT页时仍需将透明度通道单独保留。之后通过subpass(或frame buffer fetch)对通道进行重组,使其达到理想布局。这种操作利用移动端的on-chip内存,无需回传到主内存。接着就能进行ASTC格式压缩。更进一步——如果检测到某个Tile没有透明度混合需求,就直接以理想通道布局绘制到渲染目标。这类信息同样是通过离线烘焙获取的。
在设备适配Scaling方面,我们开放了多项参数调节开关。比如说,不同设备可设置不同的最大分辨率——降低分辨率意味着需要更新的VT脏页更少。我们还可以调节VT的mip偏置,配合纹理数组的mip偏置。这在低端机上能节省部分内存。高端设备使用1米精度的地形高度图,低端设备则回退到2米精度——这意味着需要着色的顶点更少。地形网格LOD距离缩放系数提供了另一种优化手段——通过加快远处网格的密度衰减速度来减少顶点数量。每帧允许填充的页面数量也可以设限,从而降低卡顿概率。此外,各向异性纹理采样(aniso-texture sampling)的等级也能通过画质档位灵活控制。
针对极低端移动设备,我们单独设计了一套地形渲染方案。这主要是出于兼容性考虑——部分低端机型存在VT支持问题,因此必须找到不依赖VT的替代方案。具体做法是:在base pass直接渲染splat map。由于缺乏动态纹理数组支持,只能使用完整32层低分辨率纹理数组。将每顶点splat层数压缩到单层,实际上回退到ID贴图方案。移除法线贴图(normalmap)。由于缺乏Decal支持,道路需要作为独立mesh单独渲染。
我们针对不同机型精细调试了所有参数,确保各档次设备都能流畅运行,同时呈现其硬件允许范围内的最佳画质。这是最终各档次设备上的内存占用情况。
这是部分性能测试数据。
我们对地形渲染性能进行了测试,将本方案与移动端主流的4层逐层权重法进行对比。测试数据均基于骁龙855平台,且仅渲染地形。可以看到无论是高画质档还是低画质档,本方案在帧率、功耗、带宽和GPU耗时上均有优势。更关键的是,我们还能支持32层材质混合以及大量Decal效果。
以下是今日分享的核心结论:在地貌与地形渲染方面,我们通过合理控制复杂度实现了跨PC与移动端的兼容适配;运用程序化生成方法加速迭代并提升品质;针对地形贴图与几何结构开创了独特的解决方案;并通过性能与scaling调优确保方案落地。关于未来方向——我们计划完善天气系统的实现方案,开发水下特性,同时加强地形法线混合和衰减的调试以进一步提升视觉表现。
特别感谢所有杰出的同事——你们才是真正的英雄。感谢Delta Force整个团队。感谢GDC组委会提供这个分享平台。感谢在座各位的聆听。 图片







请到「今天看啥」查看全文