导读:本文是淘宝前端技术专家——徐乾伟(烧鹅)分享的淘宝 Web 3D 应用与游戏开发实战,这个话题在业界被谈及得比较少。今天将会从移动、3D、游戏三种交叉的话题来和大家探讨。接下来和小编一起从初试 Web 3D、使用 WebGL、工作流相关的游戏编辑器三个部分来了解吧~
讲师介绍
徐乾伟(烧鹅)-淘宝前端技术专家,来自淘宝虚拟互动团队,这个团队主攻 3D /游戏/ VR / AR 。其中,我们有一个小团队叫斜杠实验室,主攻 Web 方向上的动画和 3D 技术。
为什么我们会在这样交叉领域去发力做一些事情?去年的双十一淘宝去年交易额多少?一千多亿,其中有 80% 的 GMV 是来自移动端的,简单地理解就是说我们公司在电商领域 80% 的钱是通过手机客户端赚取的,而不是 PC 。这就是为什么在我们要在移动端做 3D/VR/AR 的应用。
初试Web 3D
有一句话叫:给我一个支点,我就能撬动地球。
很多人都做过 2D 游戏, 3D 最大的区别就是多了一根 Z 轴,而给我一个 Z 轴我就能创造 3D 世界。很多做前端的同学对 3D 这个事情是有误解的,比如说 HTML5 中的 Canvas 有两个上下文,大家认为 2d Context 只能画 2D,WebGL context 才能画 3D ,这是一种误解。
其实 3D 和 2D 并不是由绘图引擎来决定的,而是由数学家决定的。假如我们要画这样的曲面会怎么画呢?
首先有描述这个面的公式,这个公式根据 X、Y 入参算出 Z 的坐标值,假设 Z 越大颜色越红,Z 越小颜色越绿,画出来是这样的。如果 X、Y、Z 乘以一种神奇的东西叫矩阵(矩阵是数学家发明的),这是 3×3 的旋转矩阵,把每个点都乘一下,然后画到屏幕上得到的结果就是这样的。
大家是不是一脸懵逼呀~
关于如何用 Canvas 2d 绘制 3D 曲面,以后再详细讲解,我有一段时间写过 CSS3D 的库,就是用 glMatrix 数学库做出非常酷炫的效果。
使用 Web GL
2016 年双十一我们做过一个小游戏,不知道大家有没有玩过?
这个游戏是用 Canvas 2d 绘制,就是用的 glMatrix 数学库画实现 3D 效果。当时为什么用 Canvas 2d 呢?我们淘宝市场部的同学说我们要做 3D ,因为 pokemongo 做了一个 3D 的,但是你这个东西最后要给我搞到 iPhone 4 上去。大家知道 iPhone 4是不支持 WebGL 的,而当时开发时间非常紧张,我只能用 Canvas 2d 的方案。
如果点了主场景中的猫,就会进入一个 AR 捉猫的环境。这个不是 web 渲染,因为当时移动端的 web 还不具备获取摄像头数据的能力,所以当时 AR 只能用 Native 的 3D 引擎渲染,叫 T3D,顾名思义 Taobao 3D 。另外还有一个比较有趣的 AR 场景,叫“黄金猫”。黄金猫在双十一前后三天会出现在银泰或者苏宁的商场上方,你只要抢到了这只猫至少有一百块钱的红包奖励。
难点一:建筑模型的制作,我们的设计师是个平面设计师,不会做 3D ,他当时给我的图是这样的,你看着办吧,我当时花了整整一天时间做模型。
**
难点二:地面算法**,这个地面是六边型的结构,要把地面从地球坐标系转换成 3D 世界里的场景,会分几步。我们小时候都看过世界地图,怎样把一个球形的面投射到平面上呢?
这种投影叫做墨卡托投影(Mercator projection)。这个投影算法的代码是服务端拷给我的,因为要保持前后端算法一致,我复制了后端的投影算法。相比墨卡托投影,这是简化的算法,因为要求看到周围的猫是在五十米左右,所以精度并不是特别高,简单的算法就能够满足了。
当时的视角是这样的,以用户当前的位置经纬度为中心,辐射一圈就可以看到周围有多少只猫。
这里的六边型地面如果用 X、Y 两个轴的算法去计算其实是比较慢的,我当时看了一篇论文,这是一个斯坦福的同学花了二十年研究六边型的算法,他本质上是以夹角为 120 度的 X、Y、Z 三个轴为坐标轴算,相比算 X、Y 两个轴的算法快很多。上面还有很多基于这个基础算法拓展的算法如寻路等。
好不容易跨过了双十一的坎,我们已经看到 Canvas 2d 的方案在模型输入和绘制性能方面都是非常弱的。
如何继续开发 3D 类的游戏呢?可能大家会问,WebGL 在 PC 上都不行,在手机上行不行呀?我跟大家说,现在完全没问题,我们在上亿台同时在线的设备上都试过了,前提是要做一下 WebGL 能力检测。 PC 还有一些古董浏览器不支持 WebGL ,反而手机比 PC 发展快得太多。
大家之前理解了 3D 的概念, 3D 不是绘图引擎的功能, 3D 是数学的概念。那 CPU 绘图与 GPU 绘图有什么区别呢?GPU 是并行处理每一个像素的。
我们刚开始尝试 WebGL 小心翼翼,因为怕给手淘带来影响,事实上也造成比较大的 Crash 。
2016 年的圣诞节,市场部同学说要不在手淘里下一场雪吧,那就下了。后面我会和大家介绍下这场雪的代价。我们还尝试做类似于右边这种模型粒子动画,这是一只天猫的模型。这两个都是粒子系统,因为我们刚开始不知道怎么做复杂的 3D 渲染,我们只能从最基础的绘制“点”出发去尝试。
我们团队有一种叫 PopLayer 的技术,可以在当前 Native View 上面随时弹出一层 Web View。比如之前搜一下鹿晗出弹幕,还有明星打电话,都是通过 PopLayer 技术实现的。
上文提到,在淘宝首页的 Poplayer 里 下一场雪导致了大面积的客户端Crash 。原因是 iOS 下的 UIWebView 使用 webgl 渲染时,WebCore 会调用到 OpenGL ES 进行渲染,而苹果发现有在后台调用 OpenGL ES,就会直接结束 App。
知道 RequestAnimationFrame API 吗?解法就是监测当前用户退出后台或当前页面不可见时,会把 RequestAnimationFrame 停止。
小倩也提到过 Page Visibility 方面的 API ,我们发现安卓是支持这个 API 的, 但 IOS 还是需要调 Js Bridge接口来监听 App 的是否退后台的状态。接着,我把游戏主循环(或者动画主循环)停下来之后还发现一些用户会 Crash 。最后我发现一件非常神奇的事情~这个代码大家都知道,它是用来获取Canvas的WebGL context,这行代码为什么Crash呢?我们翻了 Webkit 的源码发现它有一个 reshape 函数,reshape 会通过 GPU 获取当前画布的高宽,所以它还是会 Crash 。
接下来将会分享 3D 之旅我们的心情,以及我的思维是如何进化的。
2017 年的造物节时我们做了真正意义上的 3D 应用,当时跟英国一家设计公司合作叫 FRAMESTORE ,这个电影(《奇异博士》)大家知道吧,特效就是他们设计制作的。
FRAMESTORE 当时给我的东西是这样的,俯视图是这样的,灯光是这样打的。虽然他们在影视特效领域非常牛逼,但是他们也没做过 Web 应用。而我当时也不知道怎么和设计团队合作,还是我的老方法手写代码。他们给我的模型,我当时也不知道其他高级的格式,只知道 Obj + Mtl 。如果发现 WebGL 渲染有问题,我们就去代码里找原因,模型引用的材质对不对,贴图对不对。我们要翻代码看一下是不是引用错了。工作流的问题在这个项目中没有解决,但是促使我开始寻找问题的解法。
这个项目还有一个性能问题,广告牌发光效果,我第一个想到的是后处理(Post Processing),大家不理解的话,可以把它当作实时滤镜,如果在手机屏幕这么大的 Bloom 滤镜是会卡死的。我当时的方案是在每块广告牌上写一个独立的 Shader ,这样在iphone6上至少是可以流畅渲染的。
游戏编辑器
上面讲了这么多,痛苦和迷茫。其实我之前做的东西也不能称之为真正的游戏,只能算是营销互动类游戏。
我们还是觉得做游戏要向业界规范的方案靠拢,所以 游戏编辑器是必须要做的。虽然我今天并没有做出一款游戏编辑器,我会跟大家分享为什么我要做游戏编辑器(现在已经正在做了),这中间的坎坷是今天要讲的重点。
和英国团队合作之后我非常难过,他们的设计做得那么酷,而我只能实现成这样。我在中国环顾一圈,没有看到 Web 3D 游戏方面比较好的方案,因为在中国做 WebGL 的都凤毛麟角。
2017 年我去澳洲参加了 Web 3D 大会,他们当时用了 X3dom 像 HTML 一样用标签地描述 3D 世界。
这是一种非常陈旧的技术,虽然也是基于 WebGL 渲染。这个方案已经推了十几年了,老外也不知道为什么这么执着,有几十个 Paper 都是讲这个的。他们讲的东西都非常学术,我觉得对我们的帮助并不是很大。
然后我又去工业界寻找解决方案。这是前索尼 PlayStation 的一位同学做的应用,他用的技术大家可能会大吃一惊,他用了Unity。第一次看到 Unity 和 Web 嫁接起来是非常令我震惊的。我当时用的是 iphone6 ,运行这个 demo 都是 60 fps 满帧,他是怎么做到的呢?我去查了一下它的代码,虽然代码是压缩过的,但是为了突破这个技术难关,我阅读了压缩后的代码并且理解了它背后的实现。
我发现里面有各种各样的新颖的技术。比如,Unity 可以合并 3D 模型的贴图。
合并贴图这件事情是很重要的。做前端的同学都知道雪碧图,为什么做雪碧图?大家都知道是为了减少网络请求数,但是其实合并贴图对运行时的性能有很大影响。
GPU 读一张图快还是读十张图快?计算机资源是非常宝贵的,图片要适度合并尽量压缩。一张 200K 的图片,可能占用 3-4 倍的显存。 JS 优化半年减少 30K ,图片批量压缩减少个几兆都是有可能的,所以要把时间花在能够快速见效的事情上面。
下图的 Texture Baker 就是用来烘培并且合并图片。这个是 ITween Path 是 Unity 做路径动画的插件。
Unity还有一个插件叫 Collada Exporter 。Collada 是标准的 3D 模型格式,看到这里我们已经抛弃了之前 Obj+Mtl 的老方案。而Runtime根据我之前的开发经验封装了一套 MVC 的方案。
基于这套工作流,我们做了 2017 年双十一切红包项目。我们经常调侃:腾讯做游戏和阿里做有戏有什么区别?腾讯做游戏是收钱,我们做游戏是发钱。用 Unity 带来的好处是能够直接导入设计师给的源文件,如 Maya 源文件、 Photoshop 源文件。这里我们看到,红包模型是预先切开的,大家知道切水果也是这样做的,即使你竖着切菠萝它还是横着裂开的。
至于红包的特效,我会经常逛一些国外的网站,这是某个游戏开发者写的 Shader 特效,我就照着他的思路来写了一个类似的。
大家看到一个红包在天上飞,上面有光在流动,其实整个场景中一盏灯都没有打。光照计算,特别是点光源的计算是非常耗性能的。所以大家做 3D 应用的时候尽量要少放光源。这种效果其实只要在像素着色器中写一行代码就解决了。
红包是怎么切中的呢?Picking 这个话题对没有开发过游戏的人也许比较陌生,切红包的游戏里我当时做了两种方案:一种叫做 CPU Picker ,另一种是 GPU Picker 。
CPU Picker:在每个红包上面套上一个包围盒,计算射线有没有击中这个包围盒,因为 CPU Picker 的计算成本和场景的复杂度正相关, 用包围盒会比较快;
GPU Picker:通过拾取离屏画布上面的颜色值就行了。
虽然感觉 GPU Picker 性能会特别好,但在移动端性能表现却不佳,因为拾取颜色的过程实际上是 CPU 和 GPU 通信的过程,这个过程会比较慢。所以,CPU Picker 性能会更好一点。
还有一点就是 Dom 操作,在 Web 游戏开发中,Dom 操作就是魔鬼。我抓了比较慢的一帧花了 25 毫秒(约 40 帧)。
游戏逻辑加上 Web GL 渲染就花了那么几毫秒,而 DOM 操作却耗掉很长的时间,而且还引发了重绘(紫色部分)。
所以 Dom在游戏里是不合适的,GUI 部分需要用 2D 的 Canvas 或者 Web GL 渲染去解决。
最后讲一下音效,我个人非常喜欢游戏中声音给我带来的奖励。我做切红包的时候注意到了上面几点,这也是我上周去北京 Unity 大会上听到关于 CRIWare 的声音中间件的内容:
- 背景音乐要有渐有渐出,这样用户体验比较好;
- 用户做一些操作或者比较重要操作的时候,当前声音要强调一下,背景音乐减弱一下;
- 声音要有变化,比如说很多射击的游戏,如果枪声都一样,用户听觉会疲劳的,我们切红包时左切和右切都是不一样的。
这个软件叫 bfxr,是一款制作游戏音效的小软件,在线和客户端版本都有,人人都可以设计音效。
讲了那么多技术点,我们总要看一下业界真正做游戏的人是怎么做的。我大概探索了一两年,发现 Playcanvas 引擎是 Web 世界上最健全的游戏引擎。它的引擎代码是开源的,但是编辑器不开源。我分析了一下它的引擎源码,大概有几部分组成:
- ECS 的架构,Unity 也是采用这样的设计模式。
- PBR,基于物理的渲染模型,看起来更像真实世界的渲染。物理引擎也是很重要的,还有输入设备,比如说你的游戏手柄、手机都是输入设备。
Playcanvas 和 Threejs 有什么区别?
Threejs 只是一个 3D 渲染库。游戏还有一个非常重要的东西叫编辑器,这是 Playcanvas 在线的编辑器,我看了这个游戏之后就觉得一定要做编辑器,因为编辑器是引擎的载体。如果没有编辑器,我们每次开发游戏要注意的工程和技术问题太多。
编辑器架构
最后讲一下我们团队思考的编辑器的架构,现在只是一张工程架构图。
游戏最后发布的内容是什么?就是一堆资源,图片、模型、音频、脚本,在 Web 开发环境中最后都要发上 CDN 。
游戏里的大部分资源如音频、全景图、模型这些都是第三方软件输入的,模型资源的序列化、减面、合并、烘培等操作我们暂时可能不会去做(还是交给 Unity 做),中间 GUI 部分就是编辑器的面板操作,最后 Script 组件和 Shader 可以通过 Vscode 来编辑。这张图是我一两年的心得,大家可以留言区交流~
作者:徐乾伟(烧鹅)