![](https://image.gcores.com/5d454a1e1bb6f677a195c147d6185f14-960-540.png)
前言
前言
最近刚好看到消息说《赛博朋克2077》这款游戏正式结束开发了。在通过DLC《往日之影》最终为这个游戏挽回声望并且扬长避短之后,这款游戏话题之作的官方故事就算告一段落了。
近期我更新的思路就是有什么想说的话题,就先去GDC和SIG的历史记录里翻找权威性的分享;例如想说明《赛博朋克2077》这款游戏开发的困难,这次找到的这篇UI开发分享我觉得就很有代表性——这里的UI更多的是广义上的UI,即游戏中各类平面表达和交互界面都可以认为是UI。
![分享人介绍——CDPR的资深开发人员,项目中的UI开发负责人](https://image.gcores.com/d04c49d6cbce0c4715c7f2e03b027630-960-540.png)
在看到这篇2023年的GDC Showcase上的分享后,我觉得其中最突出点是:分享者事无巨细地列举了整个UI开发中面临的各种课题,既有时间上的视角,也有阶段性的问题意识。
相比来说,这篇分享可能不是那种讲细节干货的类型,但是能让大家窥见世界上为数不多的3A项目在开发中面临的实际挑战与人们的努力。虽然可能产品上线时整体仍然还有不少BUG,不过相对于他们面对的整个巨大难题来说,实现得算很不错了。
*后面的内容还是以翻译PPT页的内容为主,对于信息不足的部分会补充一些我自己的分析。顺便一说,这家公司的PPT都做得很有精美,能看出这个项目的艺术气质;另外,由于他们母语不是英文,部分地方的英文无论是读起来还是翻译过来都会比较别扭——不过在原文基本通顺的情况下,我还是尽量保留了原文的词语内容。
1 已有框架的不足与新框架的预期
1 已有框架的不足与新框架的预期
*随着3A游戏的表达方式越来越丰富,UI的内容早已不局限于老老实实停在屏幕角落中的一些按钮、文字和图片了。从3D空间中的指示层UI,到融入3D物体的UI,“用户界面”的这一概念内涵的表现形式早已不局限于动态的图片与文字,特效、动态物体等都可能组成UI。这篇分享的后面也能看到UI内涵不断丰富的这一过程。
![为什么定制化UI框架](https://image.gcores.com/de3bea92a662080ad3d158069f50a303-960-540.png)
![对UI框架的期望](https://image.gcores.com/c57aec54e059761c5a354c5e33426631-960-540.png)
——技术侧:
- 引擎内创建
- 更少的迭代时间
- 使用引擎内的子系统
- 更高的密度和复杂度
——创意侧:
- 更多动态元素
- 支持多语言
- 在3D空间显示与交互的可行性
- 可包含嵌入的视频
- 使用自定义材质和特效的可行性
![研究现有解决方案中的缺乏项](https://image.gcores.com/06799b20ca866563122a8376ac7251f2-960-540.png)
——对现有解决方案(基于《巫师3》)的评估:
- 不支持Scaleform中间件
- 不是单一完整解决方案(需要一堆中间件组合)
- 部分中间件或方案——不够高效、不够可伸缩、集成到Red引擎中比较困难
2 原型阶段与初版开发
2 原型阶段与初版开发
![决定定制开发UI框架的时间点](https://image.gcores.com/9303b6cb00352854eeb3328a3337dc48-960-540.png)
![UI框架开发——步骤1/3](https://image.gcores.com/eb7871573c71b2c0a6c71426faeda357-960-540.png)
——引擎内多目标并行开发( Multi-Variable Programming ):
- 简单的widget类型(widget就不翻了 这个是UI系统控件的常用词语)
- 2D输入传播(有些引擎里用dispatch这个词,类似的意思)
- 排版布局构建
- 集成到Red引擎的系统中——渲染、输入系统、文件系统
![MVP开发时间节点](https://image.gcores.com/a41337379d04ca1ab52faa59c26d96cd-960-540.png)
![原型框架示意](https://image.gcores.com/5539df9c824f00a7d441ea8d637284ac-960-540.png)
![UI框架开发——步骤2/3](https://image.gcores.com/93e801e3de94e74fd779cc4213c8118d-960-540.png)
——游戏内UI(大约半年):
- 包含完整功能的UI系统
- 简单的3D功能实现
- 无编辑器(所有布局通过C++硬编码)
- 支持嵌入视频
![首个游戏内UI和内部DEMO的时间点](https://image.gcores.com/2887ae058458f413c00a44bf0a90fb3d-960-540.png)
![DEMO的UI功能示意](https://image.gcores.com/0e481a70e66d17e6b11535fe5c37339d-960-540.png)
![UI框架开发——步骤3/3](https://image.gcores.com/2052e48a75e2aaa61c698cc800c72ab4-960-540.png)
- 合适的实现——引擎、UI、游玩层的连接
- 合适的管线——编辑器和资源导入工具
- 合适的游戏内UI——不是通过硬编码
- 和更多...
*CDPR这个公司的产品是大量使用中间件的,这意味这一旦想要深入做定制化的需求,就需要从头打造一个UI系统,从设计、逻辑到工具都是零基础。从后面的时间节点也能看出,每年除了优化以外又不停引入新的特性,这个节奏持续到项目上线。
3 性能指标与整体优化方向分析
3 性能指标与整体优化方向分析
![UI的性能指标](https://image.gcores.com/8abe538f8a1a261f610753cbe1271fa4-960-540.png)
![UI的内存指标](https://image.gcores.com/27ef862205dfd567f84ad45f5e03c9bc-960-540.png)
*Budge 指标,预算。Starting Point是他们当时的状态,即优化的起点(可以看出差距挺大)。
![UI时间指标](https://image.gcores.com/c764062b689016078a257aca67912281-960-540.png)
- 指标:在高同步性的线程上3-5毫秒执行;需要做到多线程执行
- 现状:单线程10-15毫秒
![引入指标并开始优化的时间点](https://image.gcores.com/6cc800ec61dd6a510694a8801000a983-960-540.png)
![为什么UI系统性能开销那么高](https://image.gcores.com/7f08b399c028a2ec443d351f78c3a524-960-540.png)
- 解答1:UI系统没有经过代码优化
- 解答2:太多的UI实例(内容)了
- 解决方案——在内存、刷新和绘制方面优化UI系统。(在运行时)只保留某一特定时刻真正需要的UI实例。
![复杂度的来源](https://image.gcores.com/a083de15c21222c52e9c833d15612584-960-540.png)
屏幕空间UI——小地图、地图图钉、3D地图、道具栏、Perks、HUD元素。
![高密度的来源](https://image.gcores.com/40256cabb7c81844fd53aaf392683fc6-960-540.png)
世界空间UI——武器UI、车内UI、设备系统、公告牌、本地化的街边告示牌、全局的TV系统、任意地方的本地化文字。
![一些运行时的状态统计](https://image.gcores.com/6cce0b49bea91dc09141ff1e4be228d9-960-540.png)
![UI术语定义——第一部分 层级](https://image.gcores.com/3439b3add60a4cc216a3cec198118015-960-540.png)
——UI实例:独立的UI层级结构,包含起所有依赖的资源和物体,例如纹理、动画、渲染对象,被一起实例化到内容中。产生自Widget库中的一个物体预设。
——Widget库元件。可被实例化的简单的UI层级(模板),存在于Widget资源库中。
——Widget库资源文件。定义了一组Widget库物体资源或依赖资源(的对应关系)的文件。
*Unity和UE中都有类似的结构,区分模板和实例是可编辑的UI框架的基本内容。另外几处术语定义由于他们母语不是英文,所以有些用的名词的准确性是存疑的。
*后面的内容包含了分享者提到的优化项的6个方面。
4 UI实例分类——Group UI Instances
4 UI实例分类——Group UI Instances
![层概念(1/3):定义](https://image.gcores.com/4a88b5b5792542e81a4576c911716fb4-960-540.png)
——一个简单的UI层包含:
- 4个主要元件:事件委托、刷新处理器、控制器、动画处理器
- 独立的资源管理系统
- 定制化的独立的绘制逻辑
![层概念(1/3):类型](https://image.gcores.com/13d072449109b9193e91b8debe5e5d74-960-540.png)
——层类型:
- 全屏(水印、系统体型、加载、游戏提醒、菜单、视频、HUD、相片模式、编辑器)
- 游戏世界中(世界物体、广告、街边告示牌)
- 杂项(画外项、Debug)
*这里的offscreen从后文来看是一套预加载层
![层概念(1/3):一些设想](https://image.gcores.com/a6f9826c8e2c4189a17ae232ba52a7f5-960-540.png)
- 异步的层刷新:异步游戏控制刷新、异步执行生成请求(同步化的关联处理)、异步的动画刷新(同步化的参数应用处理)
- 异步的层绘制:同步化的最终合并处理
- 每层有独立的多线程调度链
![第一批UI层解耦完毕的时间点](https://image.gcores.com/b99500670fe099b891482c1fcbf973d1-960-540.png)
![多线程UI框架方面的Take Away](https://image.gcores.com/1f109c4d92c45f3aaa288aa112e7a413-960-540.png)
- 提取所有独立的计算项,并使它们异步进行
- 使用独立的渲染目标来异步绘制UI
- 缓存一切必要的东西
- 一帧中尽早开始UI的执行过程
*这里可以看到CDPR在自研开发中也是从同步执行的原型到面向对象多线程逐步解耦的设计模式。任何3A项目放下中间件从零自研一个大模块,一定是想寻求独创特点的,这一点UE引擎给不了他们。
5 减少UI刷新——Reduce UI Updates
5 减少UI刷新——Reduce UI Updates
*这部分主要是关于视觉表现和逻辑执行拆分的。
![激活与未激活的模式](https://image.gcores.com/62eb93e44fefc57ecc660627a90bcf3b-960-540.png)
- 两类逻辑执行模式:激活——UI倾向于是可见的、所有逻辑都被执行;未激活(Passive 被动的)——UI不可见、只执行关键逻辑。
- 单独层可以有不同的定制化。
- 非常有弹性的机制。
![UI术语定义——第二部分 控制器](https://image.gcores.com/087882366802ce7b2d316aeddf71bb15-960-540.png)
——游戏控制器:
- 可以被帧调度执行(默认是关闭的。这里tick是逻辑上执行一帧代码的概念,我翻译成帧调度)
- 可以访问所有游戏系统
- 被控制处理器管理
- 只能被添加到UI实例上
- 被一个中央系统控制
- 例如:车辆控制器、电梯控制器、小地图控制器、纸娃娃控制器
——逻辑控制器:
- 基于事件的(没有帧调度执行的功能)
- 仅包含UI逻辑
- 只能访问下属的widget和UI层级
- 可以被添加到任意widget上
- 被用来功能性地扩展widget系统
- 例如:按钮逻辑控制器、审查逻辑控制器、滑块逻辑控制器
*这里censorship 审查、检查,从后文来看这个是指需要基于审查标准动态调整的控件。
![控制器的处理器](https://image.gcores.com/f3c8be043f77b7df394206b4f81eac80-960-540.png)
- 包含一个特定层的所有控制器
- 中心化的帧调度执行
- 可以为每个控制器决定不同的调度模式
- 传递一个游戏内系统的context(上下文,指相关数据和状态等)给游戏控制器
- 多线程执行
- 决定所有游戏控制器的生命周期
![生成处理器](https://image.gcores.com/37b127db506bf823c0c10b9b849f3889-960-540.png)
- 生成UI实例默认是一个异步的过程
- 每一帧有一个激活生成处理器的数量上限
- 可以延迟或取消生成过程
- 管理不同资源的加载过程
- 可以通过对象池来重用同样层级结构的UI
- 队列化关联新实例(这里指虽然生成加载是异步的,但是逻辑需要保证时序正确)
![独立的事件调度逻辑Take Away](https://image.gcores.com/391845f5320b95ff2f323604833854af-960-540.png)
- 基础的事件逻辑是相对轻量级的
- 严格控制基础逻辑的帧调度
- 如果可能就关闭帧调度
- 避免大规模的数据拉取
6 减少UI动画刷新——Reduce UI Animation Updates
6 减少UI动画刷新——Reduce UI Animation Updates
![动画方面的挑战](https://image.gcores.com/d77364d50cd514246bd926209d87608e-960-540.png)
- 全部(UI控件)可动画化的
- 包含视频
- 几乎所有文字都是需要本地化的
- 基于玩家的选择动画可能有分支表现
- 任何部分可能在任何时候被播放
![UI术语定义——第三部分 动画](https://image.gcores.com/74cb3a29b6a3ae1e7d32d5a52773f15e-960-540.png)
——动画定义(模板):将特定的动画插值系统及事件定义,以特定顺序配置在时间轴上的元件。顺序和属性在运行时不会改变。
——动画插值系统:控制如何进行特定动画插值的信息的元件。
——动画实例:查找(对应)到动画定义和储存的插值参数,基于一些发布的播放选项来播放动画时间轴。可在运行时修改。
*这里的插值系统可以理解成基于关键帧的动画系统。
![动画处理器](https://image.gcores.com/ba463a5380c5a6163f2f8dae07dabeee-960-540.png)
——激活模式:
- 递增的动画时间
- 异步的插值计算
- 异步的参数提交处理流程(保持其基于依赖关系的顺序)
- 发出所有(帧上定义的)事件
——未激活模式:
- 递增的动画时间
- 发出有意义的事件
![优化UI动画的TakeAway](https://image.gcores.com/5e013e65eaa8ee6c0ad8d72d338536eb-960-540.png)
- 内存中仅保持同一动画的一份模板数据
- 对每个实例使用轻量化的参数定义数据(metadata)
- 只计算并运行对玩家可见的动画效果
- 对不可见的动画只更新时间
7 UI实例剔除——UI Instance Culling
7 UI实例剔除——UI Instance Culling
*这部分主要介绍了关于广告物体层的处理,一般游戏开发这部分内容不会划归到UI系统中,不过由于其介于场景物体和UI物体之间,他们这么考虑也是有道理的。
![引入广告物体层概念的时间点](https://image.gcores.com/e0475a66458012bfdfcf124edcb9b885-960-540.png)
![广告物体设计](https://image.gcores.com/3f4d6b0fe97ed9f9ec1ad130190b9e40-960-540.png)
- 减少纹理内存:使用一张纹理图集、多种不同的广告布局、重用渲染对象的内存(生成并绘制可见的物体)
- 动画广告的可能性
- 内容审查过滤器
- 运行时的随机化
- 定制化的光照支持
![图集和布局示意](https://image.gcores.com/641f794e97884fe4db09a8c9e0afdd88-960-540.png)
![每个广告的可动版本](https://image.gcores.com/7c7c05a920c2e6187f316fad730bea9e-960-540.png)
![右边是和谐版 懂的都懂](https://image.gcores.com/1f29d89cbd23b7f212fc9672db03071e-960-540.png)
![有多少这种广告牌? 图中框出的都是](https://image.gcores.com/9aa0bd041fc9a7e51fe1f19b70ccd352-960-540.png)
![实际运行画面示意](https://image.gcores.com/9f667bf5f0318846ffadf0d6a58886c5-960-540.png)
![玩家可视范围优化](https://image.gcores.com/68a195c2c6a012b0a339e1ef6a3a5322-960-540.png)
- 距离检测
- 视锥体剔除:旋转、运动预测,惯性机制
- 遮挡剔除:定制化的软件实现
- 屏幕覆盖:“weapon plane”的问题。
- 静态纹理替换
- “车内”情况:延后流式加载,调过更行或绘制
*由于没有讲解原文的文案,这里实在不明白“weapon plane”是个啥问题。一般提到屏幕覆盖大致就是基于其占屏尺寸比例来进行刷新率、精度等各种维度的质量调整,但这里不确定是不是这个意思。
![可视范围优化的TakeAway](https://image.gcores.com/d94dad277ffb14907d5d5c924f04d0c8-960-540.png)
- 相对于普通3D几何体,使用优化后的管线
- 如果UI实例不可见,则设置为未激活状态
- 基于UI实例占的屏幕比例调整渲染质量
*我能理解他们不把广告牌作为环境物体而是UI的原因是其行为模式更接近他们定义的UI。What ever...
8 延迟刷新与绘制——Deffered Update & Draw
8 延迟刷新与绘制——Deffered Update & Draw
*这里不是延迟渲染管线的那个延迟,而是指时间上的延后(其本来的意思)。基本上说的是 一套预加载机制,但是用的词语都比较绕。
![越来越多的UI实例](https://image.gcores.com/de2a89d2d1e2ff7819ad8cb8c6aa0ea6-960-540.png)
- 5个全局的TV频道,每个包含3个绘制pass
- 车内过多UI实例
- 巨量的图标
——最终会导致视觉故障(显示Bug)。
![引入画外层的时间](https://image.gcores.com/8e31315b53712ce389f074c513cb7f08-960-540.png)
![画外层](https://image.gcores.com/877459e1fe8f58b636815b4ed970e9ca-960-540.png)
- 混合在游戏世界中和全屏幕的(渲染)方案中
- 延迟处理
- 不阻塞(线程)
- 成对生效(UI资源与渲染对象)
- 依赖于帧的状态
- 实例列举:道具栏图标、全局TV的叠加层、复杂特效的动态遮罩
![画外处理流程示例](https://image.gcores.com/8bc55480d8b28e1a0b4034cd747072a5-960-540.png)
*从左到右分别是:UI模板控件、预渲染的视频、UI叠加层,它们共同构成了一个简单的电视频道显示。
![画外渲染结果示例](https://image.gcores.com/3ab5f7245c14f72b34049e0a2bf08833-960-540.png)
*这部分其实看图看不出,但是用性能较低的设备玩过这游戏的应该更有体会。
![画外层的一些TakeAway](https://image.gcores.com/c7b31ba8c26a1590d16a7fe3a1576faa-960-540.png)
- 尽量缓存和重用
- 仅在一帧有空闲时间时使用它(作为画外层处理)
- 使用不同的独立渲染对象做即发即弃式的管理(fire and forget是一个既有词组)
9 3D空间中UI的HLOD——HLOD for UI in 3D-World
9 3D空间中UI的HLOD——HLOD for UI in 3D-World
*这一节用路牌系统的渲染讲了类似Mipmap的机制,只不过他们做成了一套LOD系统。
![街边路牌被提取到单独层的时间](https://image.gcores.com/593db2c72163e33e5caf24e4eaa432c2-960-540.png)
![街边路牌层的设想](https://image.gcores.com/08126168c74e307100ab9e6fde93d8b8-960-540.png)
- 类似广告牌
- 需要本地化,但不是随机的
- 在运行时进行集成
- 在夜之城有成百上千这样的路牌
- 渲染目标快速的片元着色
![渲染对象管理](https://image.gcores.com/eb6235d1ba2930ff27bdd68107a4248b-960-540.png)
- 渲染目标片元着色的方案
- 作为图集渲染
- 对渲染对象使用包围层(wrapper 一般是一种加载结构或对象管理结构)
- 复杂的匹配机制:支持各种边缘情况、对图标有特定渲染规则
- 独立的渲染对象池(3DUI和特效)
![HLOD渲染对象的示例](https://image.gcores.com/d0e07d240d77e3520b77ecab77162001-960-540.png)
![渲染对象管理的TakeAway](https://image.gcores.com/762993908fbe569b421fffa8da4dd1f0-960-540.png)
- 在世界空间中UI常常是缩小后的
- UI占屏幕范围是一个很好的衡量(需要)质量的指标
- 在渲染对象的范围内绘制到较小的区域,而不是缩小渲染对象本身
10 作者的总结
10 作者的总结
![全部任务都搞定了(么)](https://image.gcores.com/a4e7f5231196f3602723dba8c1a3ee17-960-540.png)
![其它未提到但是也很耗时的工作(可以粗略看看 就不翻译了)](https://image.gcores.com/c9f9801ecdc4cefc9f8f872c96a3bed3-960-540.png)
![项目上线时间](https://image.gcores.com/cff68dbce5d13b724440de5c08520412-960-540.png)
![整体性的TakeAway](https://image.gcores.com/b495499ee72911aaad5357e9841c3ae3-960-540.png)
- 截止时间和指标预算是你的朋友(高情商环节)
- 代码解耦和并行执行是非常重要的解决方案
- 你需要有一个惊人的团队来做惊人的事(高情商环节)
![UI组的构成](https://image.gcores.com/879277af121db7d88986509897749582-960-540.png)
- 最初包含3个开发人员
- 最终包含了3个UI组(艺术、设计、编程)
- UI编程人员的峰值:11个
*下面的感谢与特殊感谢就不翻了。最终这套UI系统虽然也在性能较低的设备上由于异步加载策略之类会有一些Bug,但比起游戏主循环的Bug来说症状还是轻很多了。
结语
结语
读了这篇分享我最大的感受是,他们对UI系统的拆分和理解和其它主流的商业引擎有着一定程度上的不同,这其中既有其优势也有其实现别扭的很多地方。但无论如何,分享人提出的这些课题是真切存在的,也正是这些内容极大地丰富了《赛博朋克2077》这款游戏的视觉表现。
如果有一定开发经验的读者可能会感觉,他们总结的take away都是很基础的思考,但是实现到这个巨型3A项目中还是非常困难的。一方面他们在工业化管线的设计上确实不如那些商业引擎公司,但另一方面他们也确实能开发出完全符合内部需求的UI模块。
这里可以Callback到持续会被讨论的“是用自研引擎还是用UE之类商业引擎”这个辩论上来。显然作为一个前作大量使用中间件搭起来的开发团队,这一作他们付出巨大努力搭建自己的UI系统,又何尝不是一种还技术债的过程呢;如果使用UE,或许这里面大部分的事项他们可以更高效的完成,但可能其中很少的一部分特性就几乎无法实现。
或许得益于人力成本上的优势,或许是这个产品作为光追展示最强游戏的地位,又或许是DLC《往日之影》赢回的极大声誉,总之得益于最终还不错的商业成绩,这群思路清奇的波兰人在这款游戏中完成了他们RedEngine的极大进化,应该可以在下个项目轻装上阵、有备而战了。
最后是资料链接: