查看原文
其他

内含源码 | 微信、QQ、王者荣耀等背后的动效方案揭秘

当你开开心心的在视频号直播中送出一份打赏;

当你刷完一局王者,获得一份高光时刻的战报;

当你拿起手机,想要制作一段精美的视频,记录美好一刻;

你……是否会想到这背后的技术到底是如何实现的呢?

今天,这篇文章你值得拥有。

由于能够显著提升用户交互的体验,动效素材在全行业,尤其是短视频和游戏等场景已经被广泛使用,但传统动效方案一直存在视觉动效弱、研发成本高、开发周期长等痛点。为了彻底解决这些痛点,腾讯从2016年开始就着手研发一套PAG动效方案,经过5年多的打磨,目前该方案已经对接服务了包括微信视频号、QQ、王者荣耀,小红书,知乎,B站,京东等在内的600个产品业务

应用场景


PAG 的目标是降低或消除动效相关的研发成本,打通设计师创作到素材上线的自动化流程。整套工作流主要分为左边的 AE 导出插件和桌面预览工具部分,以及右边的SDK 部分。AE 导出插件能够一键将设计师制作好的动效导出成PAG文件,经过桌面预览工具的确认,再上线到终端由PAG SDK渲染成动效内容。可以发现左边的部分实际上是不需要研发介入的,我们将之前需要研发介入的工作都变成了自动化的工具。整套工作流完美解决了传统工作流的上述三个核心痛点。

快速了解PAG使用流程

目前为止, PAG 的 5 个典型应用场景包括直播礼物,UI 动画,贴纸花字,视频模板,和游戏战报。从左到右对运行时编辑性的要求也依次提高:

目前微信视频号的直播就在采用PAG 方案实现所有的礼物动效,也用在了之前西城男孩的线上演唱会里。UI 动画这里演示的 Pick 按钮是一个 PAG 的可交互动效,支持编程控制进度、文本内容。关键是整个动效文件体积非常小,仅 2 KB左右。中间的贴纸花字和视频模板相信大家在短视频产品中都已经非常熟悉了。其中 PAG 的视频模板现在也大规模的应用在了广告视频的生成中。最后一个游戏战报场景,使用了 PAG 提供的图层组合能力,可以从多个PAG文件动态组合出一个自适应的模板,相当于活字印刷的功能。这块目前的已经在王者荣耀以及和平精英的高光时刻战报功能里得到了充分的应用。

同类方案对比


行业里跟 PAG 相似的动效解决方案还有 Lottie 和 SVGA:

这三个方案背后其实存在一个比较有意思的共同点:它们的作者都具有 Flash 相关的研发背景。Flash 是历史上把研发和设计师的工作流打通的最为完善的平台,但是从 PC 时代过渡到移动端后,这里就出现了彻底的断层。这三个方案其实都是在将原先繁荣的Flash 工作流复刻到移动端上,一起在推动行业动效工作流的持续完善。除了诞生场景的不同,目前 PAG 相比其他方案在文件格式,渲染架构,AE特性支持,运行时编辑以及平台支持等各方面都更具有优势。

PAG 架构演进

相信经过前面的讲解,大家对 PAG 方案解决的具体问题和应用场景应该已经有一个大致的了解。在接下来的内容中,我们将带领大家深入到这个方案的技术细节中,展示如何从头打造一个 PAG 动效方案的全过程,以及其中涉及到的重点技术挑战。PAG 方案到目前为止已经迭代了 5 年多,共经历 4 个大版本:


// 1.0 高效文件格式

在 1.0 版本中,我们从最底层开始实现了一套类似游戏引擎的渲染架构,能够无缝地跟视频渲染管线整合,并通过三级缓存等架构的设计满足了极高的实时性需求。但我们花费最多的时间的部分还是在 PAG 文件格式的设计上,最终实现了相同动效内容,只有其他方案一半左右的大小,而且解码还快 12 倍。那具体是如何做到的呢?

首先采用了二进制的数据结构来存储动效内容。因为二进制数据结构能够非常方便的单文件集成任何资源,并且由于不需要像 JSON 一样处理字符串匹配问题,解码速度可以快几十倍。在压缩率方面,二进制数据结构可以跳过 Key 的内容,只存储 Value,这样也能节省大量空间。另外还引入了 TAG 数据块的结构,来解决二进制数据的向后兼容性问题。每个 PAG 文件都由无数个互相独立的 TAG 组成,扩展格式只需新增 TAG 即可。高版本 SDK 可以识别低版本所有的 TAG,低版本 SDK 遇到无法识别的 TAG 会跳过读取,如果不是关键的信息也可以正常解码不影响渲染。

但做到这个程度其实也只是对齐了行业里通用序列化方案的上限,而在压缩率方面我们还有很大的空间,原理就是根据动效文件本身的特点实现跨越字节边界的压缩。具体有两大压缩策略:

等于默认值可无需存储:时间轴属性是AE动效的最基本组成单元,但通常情况下设计师并不会修改所有的属性,大部分时间轴属性都等于默认值。这里可以用一个标志位就跳过默认值的存储。例如一个Point类型的时间轴属性,默认值最少需要8个字节存储。而我们如果使用一个字节的标志位来跳过默存储,单个属性就可以节省7个字节的空间。这是非常可观的。

尽量聚合相似数据类型:在文件的每个属性组里,都会尽可能地把相似的数据重新排列,让他们聚合到一起。这样就可以绕开字节对齐的问题,使用比特位来紧凑存储。例如中间这一组的属性,经过重新排列后,符号位的区域可从5字节降到5比特。内容的区域还可以利用右边的连续数组编码压缩,平均减少一半以上的文件大小。其原理主要是利用了表示屏幕坐标的数据值通常不会太大,在存储数组时我们可以按照最大的那个数需要的比特位来存储所有数据即可。

经过以上的压缩策略,就可以把一个动效文件尽可能地压缩到最小。这里是一些 PAG 和 Lottie 的动效文件大小的对比数据:

两者都进行了 zip 压缩之后再进行对比,可以看到相同的动效内容平均 PAG 只有Lottie 文件的56% 大小。如果不进行 zip 压缩,差距还会更大。

// 2.0 AE 全特性支持

我们在 2.0 版本里重点引入了BMP预合成的解决方案,并且比较创新的实现了矢量和序列帧的混合导出能力,从而在保留编辑性的前提下又实现了所有的 AE 特性的导出,从这个版本开始才真正意义上彻底释放了设计师的生产力。

矢量导出方式优势就是文件极小,并且可以运行时编辑动效的内容。但这种方式注定无法支持所有AE特性,因为有很多的AE效果在有桌面显卡的情况下,都要走一个进度条才能预览,在移动端是没有可能做到实时渲染的。这也导致在实际的生产过程中,设计师有很多的复杂动效,都无法用矢量模式导出出来,这样会极大限制设计师的创意发挥。而传统的序列帧导出方式,运行时又无法编辑,文件也相对较大。PAG方案在这里的创新点就是将两者的进行了完美整合,支持矢量和序列帧的混合导出。设计师可以主动标记哪些图层使用序列帧导出,例如不需要编辑并且有复杂的动效,而需要编辑的图层继续用简单的矢量方式导出。从而实现支持所有的AE特性又能保持运行时的编辑性。

在实现混合导出后,剩下的挑战就是怎么尽可能压缩序列帧的大小。我们在PAG内部设计了视频序列帧的格式,充分利用了视频的极限帧间压缩能力。另外视频的格式还可以在运行时利用硬件加速解码,从而获得更高的渲染性能。但它也有一个明显缺点,就是不支持透明通道。我们会在导出时,会将一张 RGBA 的截图扩展成两倍大小的不透明图片,左边放置 RGB 的内容,右边放置表示 Alpha 的灰度图。最后渲染时再合并回 RGBA 的图片,从而实现透明通道的支持。在渲染的过程中,我们没有像常规做法一样先转换 YUV 到 RGB,再叠加 Alpha通道的合并,而是直接实现了各种 YUV 硬解格式的一次性上屏,利用自定义 Shader 脚本,在一次绘制中同时完成 YUV 到 RGB的转换以及与Alpha通道的合并,让视频序列帧也实现了接近普通图片一样的绘制性能

最后可以看一下各种序列帧方案文件大小的对比。相比传统的图片序列帧,视频序列帧可以轻松压缩到百分之一点几的大小。

// 3.0 模板拼装组合

运行时编辑性在 PAG 里一直是项非常重要的能力,是让设计师的素材创意跟用户的个性化元素能够快速融合的关键。我们在 PAG 的前两个版本的迭代过程中,已经分别实现了文本编辑以及占位图的编辑能力,让业务可以轻松实现贴纸花字以及视频模板等功能。再到 3.0 版本的时,又引入了图层渲染树的编辑架构,让素材的最小控制单元由文件变成了图层。不仅很好满足了一键出片和游戏战报相关场景下模板动态拼装的需求,也让运行时编辑的灵活性提升到了一个新的高度

在PAG 1.0版本时,我们的主要需求是在视频上叠加各种带动画的贴纸花字,因此需要文本的运行时编辑能力。想象一个滚字的动画从屏幕旁边一路滚过来,我们在PAG里提供了接口可以修改文本内容,字体,颜色,字号等十几项属性,并保留设计师预设的动画效果。基本原理就是运行时对占位的文本内容进行修改,但保留所有预设的动画属性再进行渲染。到PAG 2.0时,同样基于方式这个又引入了占位图的概念来解决视频模板的需求。核心原理就是运行时将视频逐帧替换到指定的占位图上,由PAG文件来控制视频的画面的动效和层级关系,输出完整的内容。设计师在制作视频模板时,只要添加一个占位图并当成视频处理就行,对它应用的任何变换和特效最终都会作用到替换后的视频上。一个 PAG 文件就是一个完整的模板,可以包含一个或多个占位图。我们可以使用单个占位图来实现简单添加效果的视频模板,也可以用两个占位图实现视频片段切换的转场特效,或者多个占位图来实现画面复制的多格视频。这样可以把模板的创意生产完全交给设计师发挥,最终让照片或视频模板等应用场景进入了工业化批量生产的时代

而到 3.0 版本时,我们的编辑需求进入了智能模板的阶段。会根据用户传的视频内容,自动生成一个自适应的模板。3.0的智能模板存在无限种可能性,设计师没法靠穷举每种可能性去生产素材。最佳方式是生产一个个小的PAG效果组件,然后进行拼装组合。于是在 3.0 里引入了图层渲染树的编辑架构,把素材的最小控制单元由文件下沉到了图层级别。一个文件就是一棵图层渲染树,内部每个图层都可以自由增删改。并且文件本身也是一个图层,因此可以把多个文件添加到一个空的 PAGComposition 里组合播放,并且通过 setMatrix 对文件的空间相对位置进行任意修改。而在时间轴的组合上。PAGFile 增加了时间伸缩的能力,提供循环,变速,定格等多种自适应模式,可以灵活适配用户的视频时长。每个图层又提供了起始时间的调整能力,能够自由组合每个图层在时间轴上的相对位置。经过这些改造,新的接口不仅很好地满足了类似王者战报这样的需求场景,也为海量素材播放提供了新的优化可能,不再需要给每个动效创建独立的上下文,而是可以组合在一起共享同一个上下文渲染,实现性能的大幅提升。

// 4.0 全新渲染引擎

在性能和包体方面,到 4.0 版本时上层能做的工作已经差不多到极限了,要继续突破只能深入到渲染引擎底层替换掉 Skia。因此我们花了将近一年半的时间从头实现了一套全新的纯 GPU 绘图引擎 TGFX,最终将PAG 包体直线降低了 65% 左右,并将PAG 的完整能力扩展到了 Web 端

首先看一下为什么不继续使用Skia?目前谷歌开源的 Skia 2D 绘图库是行业里事实标准,多年来几乎没有任何实质性的可替代方案出现,Chrome,Firefox,Flutter,Adobe 系列软件,乃至 Android 系统都在基于 Skia 做文本和矢量的绘制。但 Skia 本身是一个维护了近 20 年的方案,也存在很多的历史包袱,很难满足 PAG 对包体和性能的进一步优化需求。在包体方面,我们虽然已经针对 Skia 做了非常多的定制和裁剪,但是它依然占据了 PAG 3.0 版本 80% 左右的包体,也无法再进一步进行裁剪。而在性能方面,由于 Skia 需要兼容历史遗留的 CPU 绘制模式,在 API 上暴露会比较保守,很多针对现代 GPU 绘制管线可以进一步优化的接口都没暴露出来。为了彻底突破包体和性能的限制,我们花了将近一年半的时间自研实现了一套轻量的纯 GPU 绘图引擎 TGFX,完成了对Skia 的替换。下面看一下 TGFX 具体做了哪些优化:

在包体方面,最终以 400K 左右的大小覆盖了 Skia 近 2M 包体的绝大部分功能。核心优化策略主要有两点:

彻底抛弃传统的 CPU 渲染管线:因为现代的硬件已经几乎不存在没有 GPU 的设备了,即使像服务器端这种特殊的场景,通过 Swiftshader 来模拟 GPU 得到的性能也会让你很意外。但 Skia 由于历史原因一直同时包含了 CPU 和 GPU 的两条渲染管线,并且由于它的 GPU 渲染管线重度依赖 CPU 的部分,导致没法单独使用它的 GPU 渲染管线。我们在 TGFX 中彻底解决了这个耦合的问题,打造出了一个纯 GPU 的绘图引擎,这里就节省了大概一半的包体。

最大化的利用平台端内置的所有能力:例如图片解码,字体解析,矢量栅格化等等,这些都会优先使用系统原生的接口替代内置第三方库的策略。以文本和矢量的栅格化为例,在 iOS 上直接使用了系统提供的 CoreGraphics,文本方面则利用起 CoreText 等。而在其他平台才嵌入了 Freetype。虽然增加了不同平台适配的工作量,但是包体确实也获得了极致的优化。

在易用性方面,TGFX 也做了不少改进,尤其是这个 Skia 没有的 Device & Window 系统。Skia 虽然提供了 GPU 的渲染管线,但要用起来门槛还是比较高的。主要存在两个问题:

第一点是 Skia 只实现了跨平台渲染的部分,所有跟平台相关的视图桥接以及上下文的初始化都需要用户自己处理。这会导致用户正常用起来 Skia 的 GPU 模式需要对每个平台写大量的适配代码。除了工作量大外这部分还是兼容性的重灾区,要处理很多类似 iOS 中退到后台执行 OpenGL 的特殊情况。

第二点是 Skia 并不帮你保证线程安全或者上下文状态的切换,默认都由调用方自己处理。但绝大部分刚刚接触 Skia 的用户并不清楚这里的坑点,Skia 也没有显式说明过,按普通的方式接入使用,很容易就造成大量的显存泄露以及难以排查的随机 Crash。

但 TGFX 提供了完善的 Device & Window 系统,可以帮你把这些问题一次性都彻底解决,只要按照统一的模式进行调用,所有平台相关的复杂度都可以不用关心,并且从 API 上限制了你必须以线程安全的方式进行调用。即使没有非常资深的 GPU 渲染经验也可以很容易上手使用

以上这些还只是 TGFX 做的优化的一部分,更多的细节欢迎大家到 Github(https://github.com/Tencent/libpag) 源代码中参考研究。

最后来看一下成果,在把Skia 彻底替换为 TGFX 绘图引擎后,PAG 4.0版本的包体整体平均都下降了65%左右,并且矢量渲染性能平均还提升了 60% 左右。整体的优化非常显著。后续我们也正在推动 TGFX 作为一个独立仓库开源,持续完善并把它打造为一个通用的 2D 绘图引擎,为行业提供 Skia 之外的另一个轻量化的选择。

PAG 总结与展望


PAG方案诞生在最复杂的视频编辑下,也能够很好的满足其他各种场景下的动效需求。它提供所见即所得的桌面工具和AE插件,能够一键将设计师的创意导出成PAG文件,并通过SDK快速渲染到几乎所有的主流平台上。

整套工作流给业务带来了三方面的核心价值:

去研发成本:素材生产环节无需研发介入,节省大量研发人力和调试返工成本。研发只需要接入一次SDK的成本,后续设计师可以独立完成素材的生产上线,也避免了最耗时的研发和设计的联调环节,最终将素材生产相关的研发成本大幅降低。

工业化生产:由于不再受到研发人力瓶颈的限制,素材生产可以扩大到更多的设计师进行批量化生产。再加上桌面效率工具在效果预览和性能检测上的易用性,设计师可以所见即所得地生产素材,最终让视频模板平均生产耗时从一周降低到四个小时,实现快速响应运营热点。

无限AE动效:PAG的SDK已经完全还原了AE的整个动效渲染系统,并支持矢量和序列帧混合导出,接入一次,设计师就可以复用PAG经过5年积累的AE动效原子能力,组合出无限的视觉动效,不用因为代码还原成本的问题而对效果打折扣。

PAG 已于2022年 1 月 14 日正式开源至Github,到目前已获得 2500+ star数,官网累计上线 46 篇文档教程,团队日常对接2400+ 持续增长的设计师和研发用户群,SDK 也已接入服务了腾讯内外 600+ 产品业务,包括微信,手机 QQ,王者荣耀等腾讯系头部 App,也有大量的外部产品,比如小红书,B站,京东,知乎等,稳定性经过了海量用户的持续验证

过去一年多,PAG 团队的主要精力都投入到了 PAG 4.0 全新渲染引擎的升级完善上,目的也是为了后续整个 PAG 方案能有更高的天花板。接下来重心会在AE特性的补全、工具链完善以及更多可交互能力上持续深耕,为开发者提供更完善的动效技术。

PAG 相关资源


感兴趣的朋友可以通过以下方式进一步了解PAG:

—END—

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存