前言
分享人介绍——CDPR的资深开发人员,项目中的UI开发负责人
1 已有框架的不足与新框架的预期
为什么定制化UI框架
对UI框架的期望
- 引擎内创建
- 更少的迭代时间
- 使用引擎内的子系统
- 更高的密度和复杂度
- 更多动态元素
- 支持多语言
- 在3D空间显示与交互的可行性
- 可包含嵌入的视频
- 使用自定义材质和特效的可行性
研究现有解决方案中的缺乏项
- 不支持Scaleform中间件
- 不是单一完整解决方案(需要一堆中间件组合)
- 部分中间件或方案——不够高效、不够可伸缩、集成到Red引擎中比较困难
2 原型阶段与初版开发
决定定制开发UI框架的时间点
UI框架开发——步骤1/3
- 简单的widget类型(widget就不翻了 这个是UI系统控件的常用词语)
- 2D输入传播(有些引擎里用dispatch这个词,类似的意思)
- 排版布局构建
- 集成到Red引擎的系统中——渲染、输入系统、文件系统
MVP开发时间节点
原型框架示意
UI框架开发——步骤2/3
- 包含完整功能的UI系统
- 简单的3D功能实现
- 无编辑器(所有布局通过C++硬编码)
- 支持嵌入视频
首个游戏内UI和内部DEMO的时间点
DEMO的UI功能示意
UI框架开发——步骤3/3
- 合适的实现——引擎、UI、游玩层的连接
- 合适的管线——编辑器和资源导入工具
- 合适的游戏内UI——不是通过硬编码
- 和更多...
3 性能指标与整体优化方向分析
UI的性能指标
UI的内存指标
UI时间指标
- 指标:在高同步性的线程上3-5毫秒执行;需要做到多线程执行
- 现状:单线程10-15毫秒
引入指标并开始优化的时间点
为什么UI系统性能开销那么高
- 解答1:UI系统没有经过代码优化
- 解答2:太多的UI实例(内容)了
- 解决方案——在内存、刷新和绘制方面优化UI系统。(在运行时)只保留某一特定时刻真正需要的UI实例。
复杂度的来源
高密度的来源
一些运行时的状态统计
UI术语定义——第一部分 层级
4 UI实例分类——Group UI Instances
层概念(1/3):定义
- 4个主要元件:事件委托、刷新处理器、控制器、动画处理器
- 独立的资源管理系统
- 定制化的独立的绘制逻辑
层概念(1/3):类型
- 全屏(水印、系统体型、加载、游戏提醒、菜单、视频、HUD、相片模式、编辑器)
- 游戏世界中(世界物体、广告、街边告示牌)
- 杂项(画外项、Debug)
层概念(1/3):一些设想
- 异步的层刷新:异步游戏控制刷新、异步执行生成请求(同步化的关联处理)、异步的动画刷新(同步化的参数应用处理)
- 异步的层绘制:同步化的最终合并处理
- 每层有独立的多线程调度链
第一批UI层解耦完毕的时间点
多线程UI框架方面的Take Away
- 提取所有独立的计算项,并使它们异步进行
- 使用独立的渲染目标来异步绘制UI
- 缓存一切必要的东西
- 一帧中尽早开始UI的执行过程
5 减少UI刷新——Reduce UI Updates
激活与未激活的模式
- 两类逻辑执行模式:激活——UI倾向于是可见的、所有逻辑都被执行;未激活(Passive 被动的)——UI不可见、只执行关键逻辑。
- 单独层可以有不同的定制化。
- 非常有弹性的机制。
UI术语定义——第二部分 控制器
- 可以被帧调度执行(默认是关闭的。这里tick是逻辑上执行一帧代码的概念,我翻译成帧调度)
- 可以访问所有游戏系统
- 被控制处理器管理
- 只能被添加到UI实例上
- 被一个中央系统控制
- 例如:车辆控制器、电梯控制器、小地图控制器、纸娃娃控制器
- 基于事件的(没有帧调度执行的功能)
- 仅包含UI逻辑
- 只能访问下属的widget和UI层级
- 可以被添加到任意widget上
- 被用来功能性地扩展widget系统
- 例如:按钮逻辑控制器、审查逻辑控制器、滑块逻辑控制器
控制器的处理器
- 包含一个特定层的所有控制器
- 中心化的帧调度执行
- 可以为每个控制器决定不同的调度模式
- 传递一个游戏内系统的context(上下文,指相关数据和状态等)给游戏控制器
- 多线程执行
- 决定所有游戏控制器的生命周期
生成处理器
- 生成UI实例默认是一个异步的过程
- 每一帧有一个激活生成处理器的数量上限
- 可以延迟或取消生成过程
- 管理不同资源的加载过程
- 可以通过对象池来重用同样层级结构的UI
- 队列化关联新实例(这里指虽然生成加载是异步的,但是逻辑需要保证时序正确)
独立的事件调度逻辑Take Away
- 基础的事件逻辑是相对轻量级的
- 严格控制基础逻辑的帧调度
- 如果可能就关闭帧调度
- 避免大规模的数据拉取
6 减少UI动画刷新——Reduce UI Animation Updates
动画方面的挑战
- 全部(UI控件)可动画化的
- 包含视频
- 几乎所有文字都是需要本地化的
- 基于玩家的选择动画可能有分支表现
- 任何部分可能在任何时候被播放
UI术语定义——第三部分 动画
动画处理器
- 递增的动画时间
- 异步的插值计算
- 异步的参数提交处理流程(保持其基于依赖关系的顺序)
- 发出所有(帧上定义的)事件
- 递增的动画时间
- 发出有意义的事件
优化UI动画的TakeAway
- 内存中仅保持同一动画的一份模板数据
- 对每个实例使用轻量化的参数定义数据(metadata)
- 只计算并运行对玩家可见的动画效果
- 对不可见的动画只更新时间
7 UI实例剔除——UI Instance Culling
引入广告物体层概念的时间点
广告物体设计
- 减少纹理内存:使用一张纹理图集、多种不同的广告布局、重用渲染对象的内存(生成并绘制可见的物体)
- 动画广告的可能性
- 内容审查过滤器
- 运行时的随机化
- 定制化的光照支持
图集和布局示意
每个广告的可动版本
右边是和谐版 懂的都懂
有多少这种广告牌? 图中框出的都是
实际运行画面示意
玩家可视范围优化
- 距离检测
- 视锥体剔除:旋转、运动预测,惯性机制
- 遮挡剔除:定制化的软件实现
- 屏幕覆盖:“weapon plane”的问题。
- 静态纹理替换
- “车内”情况:延后流式加载,跳过刷新或绘制
可视范围优化的TakeAway
- 相对于普通3D几何体,使用优化后的管线
- 如果UI实例不可见,则设置为未激活状态
- 基于UI实例占的屏幕比例调整渲染质量
8 延迟刷新与绘制——Deffered Update & Draw
越来越多的UI实例
- 5个全局的TV频道,每个包含3个绘制pass
- 车内过多UI实例
- 巨量的图标
引入画外层的时间
画外层
- 混合在游戏世界中和全屏幕的(渲染)方案中
- 延迟处理
- 不阻塞(线程)
- 成对生效(UI资源与渲染对象)
- 依赖于帧的状态
- 实例列举:道具栏图标、全局TV的叠加层、复杂特效的动态遮罩
画外处理流程示例
画外渲染结果示例
画外层的一些TakeAway
- 尽量缓存和重用
- 仅在一帧有空闲时间时使用它(作为画外层处理)
- 使用不同的独立渲染对象做即发即弃式的管理(fire and forget是一个既有词组)
9 3D空间中UI的HLOD——HLOD for UI in 3D-World
街边路牌被提取到单独层的时间
街边路牌层的设想
- 类似广告牌
- 需要本地化,但不是随机的
- 在运行时进行集成
- 在夜之城有成百上千这样的路牌
- 渲染目标快速的片元着色
渲染对象管理
- 渲染目标片元着色的方案
- 作为图集渲染
- 对渲染对象使用包围层(wrapper 一般是一种加载结构或对象管理结构)
- 复杂的匹配机制:支持各种边缘情况、对图标有特定渲染规则
- 独立的渲染对象池(3DUI和特效)
HLOD渲染对象的示例
渲染对象管理的TakeAway
- 在世界空间中UI常常是缩小后的
- UI占屏幕范围是一个很好的衡量(需要)质量的指标
- 在渲染对象的范围内绘制到较小的区域,而不是缩小渲染对象本身
10 作者的总结
全部任务都搞定了(么)
其它未提到但是也很耗时的工作(可以粗略看看 就不翻译了)
项目上线时间
整体性的TakeAway
- 截止时间和指标预算是你的朋友(高情商环节)
- 代码解耦和并行执行是非常重要的解决方案
- 你需要有一个惊人的团队来做惊人的事(高情商环节)
UI组的构成
- 最初包含3个开发人员
- 最终包含了3个UI组(艺术、设计、编程)
- UI编程人员的峰值:11个