福满环球:
可以看到,压缩纹理极大地优化了内存开销,基本彻底解决了内存问题。
条件和考虑
当然,这个世界上没有免费的午餐。当我们接受压缩纹理的优点时,我们就必须付出相对的代价并接受它的约束:
压缩纹理属于有损压缩,会对图像质量产生一定的影响。这取决于项目。
压缩纹理的传输量可能比 JPG/PNG 方案高 1 到 4 倍。
压缩纹理需要POT,即长度和宽度都是2的幂。
对于iOS,也要求长度和宽度相等。
由于压缩纹理格式无法在不同平台上通用,并且降级需要三个资源,因此对离线加速技术不友好。
对于一些成本,比如视觉质量损失、传输量等,我们可以自己调整。不是原则性问题,但是这个POT对于很多前端应用来说确实是原则性问题,比如福满全球的地标、红包。贴纸不是来自 POT,那么我该怎么办?有一个方法——使用图集。
纹理标准化 -
图集是纹理标准化的一种方法。常用于游戏领域,处理UI、2D精灵等。简单来说,图集就是把很多张图片放到一张里。就是我们常说的精灵图片( )。 ):
如图所示,我们将四张地标图片放入一张图集中,以满足压缩纹理的需要。那么我们如何使用这个图集呢?这很简单。我们的引擎是内置的,让您使用起来非常简单。在引擎的标准开发流程中,依靠+工作流程,可以非常方便地引入这个能力——直接在里面编辑图集,然后就会讲到。
还有其他优点,就是减少内存碎片、减少数据提交次数,在某些情况下还可以减少资源请求。
精确控制内存
目前我们有两种策略:模型缩减和纹理压缩,大大减少了内存开销,减少了部分传输量。不过,从上面的讨论中我们不难发现,我们其实还可以更进一步——我们不难发现,在整个过程中,相同的数据可能会同时存在于CPU和GPU端,尤其是在移动设备上CPU和GPU共享内存。所以我们必须有一个更进一步的方法来解决这个问题。
这是我选择压缩纹理的另一个原因——压缩纹理本质上是好的,而且我们可以通过控制引用来帮助GC。这就是为什么上面的数据分析可以保证稳定的开销是峰值的一半。 。
在我们引擎的设计中,这个功能是可选的,可以通过纹理来打开。如果遵循标准工作流程,这一切都是自动的,不需要开发人员担心。
当然,这也是有代价的,就是GL上下文丢失后无法恢复,所以请酌情使用。
进一步减少传输量
到目前为止,内存已经得到了很好的控制,但在传输量方面还有更大的优化空间。对此,我首先考虑的是内部团队提供的模型压缩方案。
模型压缩
我们采用的模型压缩方案的原理非常简单。对于移动端使用的模型,每个顶点数据不需要是同一类型。一般来说可能就够用了,所以还有很大的压缩空间。事实上经过测试发现确实是这样,但是当然这也是有代价的,通过模型压缩:
也就是说,模型压缩后,首页所有资源的大小达到5.8M,iOS达到5.2M。但代价是增加了解压时间和1.5M峰值内存。相对于收益而言,开销是可以接受的。
不过,即便如此,5M的资源规模对于上亿个UV来说还是有点大了。我们还有更多的解决方案吗?是的,是时候邀请我们的老朋友 GZIP 了。
广州邮政编码
众所周知的GZIP在很多情况下其实可以起到意想不到的作用。在我们的工作链接下,模型压缩将改善GZIP的效果,压缩纹理也能获得好处。 GZIP 之后:
可以看到,我们对资源量进一步减半,比开始减少了六倍。
通用图片资源
当然,除了3D相关资源之外,我们还提供了压缩普通图像的方法,主要是将PNG图像编码压缩为索引颜色。这是一种有损压缩,是常用的策略。当然,这并没有什么神奇之处。我们把这个算法作为插件集成到工具链中,可以通过工作流程直接无缝集成。最终一般会带来2到4倍的体积压缩。
减少资源请求
至此,我们已经解决了大部分主要问题,但仍有一些次要问题会影响最终体验。这是资源请求的数量。不难发现,对于这两个场景,3D场景的资源请求数量都接近20个,这个问题也不是无法解决。
对3D领域有一定了解的读者一定知道glTF格式,我们自研引擎的场景序列化也采用了这种格式。为了应对某些场合,glTF有其二进制形式GLB,可以将索引、纹理、元数据等打包成二进制文件,大大减少请求次数。在两个新年场景中,请求数下降到了1倍。
我们还将封装GLB的功能集成到了链接中,开发者可以零成本引入。
剩余的性能问题
上述问题解决后,项目的稳定性基本可以得到保证。关于福满世界大量透明物体和高清屏幕的问题,经过业务层面的优化,最终发现在可控范围内。这是由业务性质决定的,否则我们当然可以使用强制最大画布大小来减少开销。
另外,还有一点需要注意,我们很可能会忽略——运行时的GPU资源提交。由于引擎的设计采用了提交原则(当然这是符合规范的),但是对于这两个项目来说,保证用户操作时不卡顿的优先级非常高,同时,经过上面的内存优化我们已经保证了即使所有资源都提交了也是可控的,所以需要一个策略,先将所有资源提交给GPU,然后全部预编译。
为了做到这一点,我们采用了一个简单的策略:渲染第一帧中的所有对象,然后结束它。这会稍微增加加载时间,但可以确保整个过程不会出现延迟。
对于首页的3D显示,为了达到最终的效果,我们设计了渐进式的显示策略。
渐进式显示
这个策略是基于项目用户数量巨大,网络情况各异,不可能等到结束才显示页面,否则首次性能会很差,所以我们最终确定了plan - 始终首先显示静态图像,然后加载 3D 资源。 、解析并成功提交给 GPU,然后无缝切换到 3D 动画。
这种首页3D动画的策略值得很多展示项目借鉴。这里需要注意的一点是:如果模型比较复杂,首帧渲染会阻塞用户的操作。因此,针对本项目的场景,我们采用了时间切片策略,将五个模型切分成五个效果图,每个间隔留出时间给用户操作:
loadOne = (index: number, total: number) => {
const { state, event } = this.getGame();
const actor = this.actors[state.typeList[index]];
actor.visible = true;
event.addOnce('MainRendererIsFinished', () => {
actor.visible = false;
if (index === total - 1) {
event.trigger('Ready');
} else {
setTimeout(() => this.loadOne(index + 1, total), 200);
}
});
}
并且我们还保证静态图片和3D场景的姿势完全一致,从而达到视觉上无缝切换的目的。
酷炫且易于使用 - 粒子效果
在大型促销活动中,我们都需要炫酷的页面来吸引用户,但动画通常是开发的噩梦。通常我们在制作动画时会遇到以下三个问题:
这次的新年红包项目大量使用了3D场景,并在3D的基础上加入了大量的粒子特效。那么这些特效是如何产生并解决上述三个问题的呢?
让动画设计更加美观
我们在切换主页时添加了旋转粒子效果。效果如下:
这是一位设计同学的手稿。由于技术的普及,设计生大多采用AE制作的动画(只使用、、、修改)导出播放,大大降低了开发成本。 AE本身是一个视频后期制作软件。除了制作简单的动画之外,还可以启用3D渲染、图像跟踪、添加滤镜等。这种粒子效果是使用AE中的插件制作的,所以AE的上限就是设计师设计的上限。
设计师的设计工具将直接决定设计产品的质量。如果没有插件,那么我们设计的产品永远都是动画,很多影视级别的特效也不会出现在产品页面上。因此,设计工具能力的提高将直接决定动画输出的质量。当然,还有一个问题值得担心。我们的产品开发不知道插件是如何实现的,所以很大概率无法恢复。因此,我们不仅要提高设计工具的质量,还要限制设计师随意使用设计工具。无法实现。
新年红包项目的粒子特效设计全部在该工具中实现:
如果是手写代码还原设计稿,恐怕最重要的就是还原功能曲线。为了让动画更加流畅,会添加很多曲线来控制。比如刚才旋转升起的恒星,会有一个先加速后减速的过程:
复杂的粒子系统具有 60 多个属性。如果开发者用肉眼恢复数据,即使复制粘贴属性值,也可能会出现问题。
恢复它的最好方法就是不写代码。编辑器直接导出动画数据并在手机上播放。开发完全不需要担心各种参数。该产品易于使用。您可以直接保存项目并加载它,就像使用图片一样简单。
import myAnimation from '../assets/my-ani.vfx'; // 网页编辑项目工程文件
const player = new Player({
container:document.getElementById('displayObject')
});
player.loadSceneAsync(myAnimation).then(scene=>player.play(scene));
新年红包项目中的3D场景是由引擎构建和渲染的。它的使用方式类似。可以将编辑器项目作为资源引入,直接播放。动画播放完之后,就是开发最重要的问题了。
保证动画性能
事实上,任务首页的粒子效果很少,不存在性能瓶颈。不过,福满环球使用了大量的粒子,尤其是烟花作为永久特效,需要特殊优化。这里我们参考了游戏领域粒子系统的很多优化策略,并将其应用到本次优化中。
优化1:粒子运动完全由GPU计算
对于粒子系统来说,由于粒子数量较多,采用曲线控制后的运动计算比较复杂。如果通过CPU来计算粒子的运动,网页就会不堪重负。因此,粒子的运动旋转和颜色变化计算都放在GPU中,通过定制完成,计算中的曲线是一件比较复杂的事情(这里省略3千字)。
优化2:优化粒子发射器
可以看到进度条的粒子不断生成。因为粒子有生命周期,旧的粒子会消亡,新的粒子会诞生,无限繁衍。首先,我们在内存中开辟一个固定地址,并有一个按照粒子生命周期排序的双向列表。当每帧需要生成新粒子时,检查列表中最先死亡的粒子。如果这个粒子已经死亡,那么就会将该粒子的地址写入到新粒子的数据中,并从末尾插入这个列表元素。该过程大致类似于以下内容:
这样的列表可以保证粒子插入的速度。假设一个粒子系统有200个粒子,每帧只插入3-4个新粒子,CPU中的计算量很小。
优化3:合并启动器
烟花由两个发射器组成,形成双层烟花效果。同时,每个烟花都增加了拖尾效果。烟花飞舞的地方有一条小尾巴。编辑后效果如下:
可以看到同一个模式下烟花爆炸了6次,只是每次爆炸的位置不同。通常我们会在编辑器中制作一次爆炸,然后复制6次。时间线类似于以下内容:
然而,这会导致绘图元素的频繁创建和销毁,从而消耗大量的性能。因此,编辑器提供了合并粒子爆炸的选项,并且每次爆炸的位置都可以修改。对于习惯复制粘贴的设计者来说,很容易复制很多相同的元素,造成过多的性能开销。将六个绘图元素合并为一个元素可以在重用内存的同时大大减少开销。
优化4:减少尾随的使用
尾部是一条飞线,在粒子移动过的地方生成一个顶点,在绘制时将其连成一条线,这样就有流星划过的感觉。但由于粒子的计算是在GPU中完成的,如果每帧都要生成新的顶点,则还必须在CPU中重新计算粒子的位置,这需要大量的计算量。如果烟花只进入场景并爆炸一次,则开销可以忽略不计。然而,有些烟花是永久性的,每隔一段时间就会燃放一次。因此,对于永久动画,请避免使用拖尾。这次我们选择使用纹理缩放而不是拖尾。
如果不使用纹理,它看起来就像一圈延伸的小菊花。这是通过增加矩形的长度来实现的。如果我们用尖角纹理替换它,它看起来会非常像一条尾巴。最终,永久烟花没有使用尾巴,但视觉效果仍然像流星。这确保了所有计算都在 GPU 中执行,从而提高了动画性能。
成为最好的——工程自动化
当然,作为一名引擎开发者,除了将这些技术应用到新年项目中之外,让更多开发者更方便地使用这些功能也很重要,所以我把所有内容都封装在了引擎的标准工作流程中:
引擎工作流程
该引擎的工作流程集成了上面讨论的所有优化策略,主要包括两部分:和链接。
它是一个插件,用于导出各种功能供引擎使用。整个流程高度集成,目前支持大量功能,包括但不限于模型、材质、纹理、动画、光源、相机、天空等。导出和导入盒子、图集、精灵、物理、音频、环境反射、环境光照、光照图,支持自定义扩展组件,支持脚本逻辑绑定等。
关联
然后是链接。我对链接进行了深度定制,以满足引擎工作流程的需求。上面提到的压缩纹理、模型压缩、资源预处理、自动资源释放等都集成到了里面,包括多平台适配,也是通过插件来实现的。
下面简单介绍一下本项目中用到的最重要的链接:gltf-
这是整个链接中非常核心的一致性,它提供了加载gltf文件并进行复杂预处理的能力。使用它我们可以做到:
模型压缩。
纹理压缩。
打包 GLB。
资源预处理:对gltf文件引用的资源进行预处理。通过定制的接口,您可以实现任何您想要的预处理。
资源发布者:自定义发布者,在生成 gltf 文件时拦截并自动处理由 gltf 文件引用的资源(包括其本身)。
在这两个项目中,都用到了这些功能,这也为最终项目的稳定性和可靠性提供了重要的保证。
关于团队
本次分享来自支付宝,致力于让前端变得更有创意,通过交互和图形技术为用户带来更好的体验。如果您有兴趣加入我们,请联系我的邮箱:。
推荐活动
4月9日,在线观看“出海|今日聊”系列活动第一期——对话网易游戏:出海背后的技术代码。 将帮助您了解游戏发行商在选择海外云平台时的考量因素。 ?在游戏部署中的应用? MOBA 游戏风靡全球。此类游戏在海外面临哪些技术难点?扫描下方二维码或点击阅读原文即可免费报名哦~