前端内存分析之图片篇

背景

二零年年末,我所在如视的前端团队针对核心 C 端项目 VR 3D 看房 做了次从 2.0 到 3.0 的系统重构——交互风格、前端架构等等都重新整了遍。灰度阶段前,通过 PerfDog 性能狗 性能分析发现:我们一个 VR 3D 页面在 PC 端占用 120MB 左右内存,在 iPhone 12 上竟然高达 360MB。

在加上业务能力的升级——除了传统实景 VR 之外,我们还新增了虚拟 VR 用以展示房源装修前后的效果对比,这又新增了一个 VR 实例,内存占用已超 700MB。

图一:2.0和3.0 内存占用情况

此外,随着用户的交互(开启地图、逐帧动画等),内存还在不断递增,高峰期已经超过 1G。而 iOS 系统 WebView 内存溢出的阈值最高也才 1.5G,VR 页面已经濒临崩溃。

很好奇为啥会占用了那么多的内存?让我们来简单探究一下吧。

图片内存占用

三维模型一般由面片数据(顶点、线)和贴图组成,内存占用的大头是图片。那一张图片渲染至浏览器占用的内存该怎么计算呢?

一般浏览器渲染图片BitMap选用的是 ARGB_8888:颜色信息由透明度 A(Alpha)与 R(Red),G(Green),B(Blue)四部分组成,每个部分都占 8 位,总共占 32 位。即一个像素:

  • A - alpha 透明 8bit(位)
  • R - Red 8bit(位)
  • G - Green 8bit(位)
  • B - Blue 8bit(位)

"1Byte(字节)=8bit(位)" 因此,一个像素会占用四个字节
所以一张 2048*2048 的图片占用的内存有:2048*2048*4 Byte,换算成 MB 单位 2048*2048*4/ (1024*1024) Byte = 16MB

图片占用的内存跟图片文件体积大小无关,仅跟其分辨率相关。压缩图片目的是为了 CDN 下载速度更快、节省存储空间,但无法节省浏览器占用内存。

"一个像素会占用四个字节" 这个结论适用于绝大部分 PC、macOS 等终端设备,但在移动端并不完全适用,详细内容请往下看。

终端设备

以 iPhone 为例,先统计下历代 iPhone 屏幕信息:

机型 逻辑像素 渲染像素 物理像素 设备像素比 DPR 一个像素用几个字节
iPhone 3G/3Gs 320*480 320*480 320*480 1 4 个字节
iPhone 4/4s 320*480 640*960 640*960 2 4 * 2 个字节
iPhone 5/5C/5s/SE 320*568 640*1136 640*1136 2 4 * 2 个字节
iPhone 6/6s/7/8/SE2 375*667 750*1334 750*1334 2 4 * 2 个字节
iPhone XR/11 414*896 828*1792 828*1792 2 4 * 2 个字节
iPhone X/Xs/11 Pro 375*812 1125*2436 1125*2436 3 4 * 3 个字节
iPhone 12 mini 375*812 1125*2436 1080*2340 3 约 4 * 2.88 个字节
iPhone 12/12 Pro 390*844 1170*2532 1170*2532 3 4 * 3 个字节
iPhone 6/6s/7/8/ Plus 414*736 1242*2208 1080*1920 3 约 4 * 2.61 个字节
iPhone Xs Max / 11 Pro Max 414*896 1242*2688 1242*2688 3 4 * 3 个字节
iPhone 12 Pro Max 428*926 1284*2778 1284*2778 3 4 * 3 个字节
  • 物理像素:硬件真实的像素,即屏幕分辨率。
  • 逻辑像素:前端使用的像素,即 px
  • 渲染像素:操作系统抽象的像素。

从 iPhone 4 代开始,iPhone 屏幕的物理分辨率是很高的,除了 "iPhone 6/6s/7/8/ Plus" 和 "iPhone 12 mini" 设备之外,iOS 系统基本是把 2 个或 3 个物理像素当作 1 个逻辑像素来使用的(放大倍数了)。

Android 系统则比较凌乱,但本质还是将多个物理像素当作一个逻辑像素来渲染使用。因此,一张2048*2048图片内存占用换算公式是: (物理分辨率/逻辑像素)*2048*2048*4/ (1024*1024) MB

这基本解释了移动端设备图片占用的内存要比 PC 上统计的要多出 2 倍、3 倍甚至 4 倍以上。这也解释了明明是旗舰机型崩溃率反而增加了,比如 iOS 系统 WebView 内存崩溃的阈值固定在 1.5G 以下,旗舰机型 iPhone 12 Pro Max 更加容易达到这个阈值。

介于设备屏幕 LCD、OLED 等材质差异,实际统计会有些许偏差,但是数量级不会有太多出入。

Five 实例内存占用

@realsee/five 是如视基于 Three.js 实现的在浏览器环境中运行的三维空间渲染引擎。创建 Five 实例并渲染一个三维空间需要耗费多少内存呢?

常态情况下,Five 渲染依赖的图片是三维模型的 UV 贴图和一个立方体全景贴图(立方体六个面六张图),如图二、三所示。

图二:立方体全景贴图(2048*2048)
图三:UV 贴图及网格数据组成模型(512*512)

因此,我们以贝壳·VR 看房 | 常楹公元 2 室 1 厅 房源为例,其实景 VR 的 UV 贴图有 12 张。

所以,此看房 VR 图片所占用的内存有:

① 常态情况

  • PC 端:2048*2048*4/ (1024*1024) *6 + 512*512*4/ (1024*1024)*12= 108MB
  • iPhone 8:(2048*2048*4/ (1024*1024) *6 + 512*512*4/ (1024*1024)*12) * 2= 216MB
  • iPhone 12:(2048*2048*4/ (1024*1024) *6 + 512*512*4/ (1024*1024)*12) * 3= 324MB

此处分析的这还仅仅是一个实景 VR 依赖图片占用的内存。

② 走点 moveToPano

由于走点为了过渡动画效果,一般会出现两个立方体全景,所以全景图片由 6 张图片变成 12 张。

  • PC 端:2048*2048*4/(1024*1024)*6*2 + 512*512*4/ (1024*1024)*12= 204MB
  • iPhone 8:(2048*2048*4/(1024*1024)*6*2 + 512*512*4/ (1024*1024)*12)*2= 408MB
  • iPhone 12:(2048*2048*4/(1024*1024)*6*2 + 512*512*4/ (1024*1024)*12)*3= 612MB

看此数据,基本解释:

  • 高端 iOS 设备比低端 iOS 设备更容易出现黑白屏内存溢出问题。(iOS 端 WebView 内存崩溃的阈值在 1.5G 以下)。
  • 全景走点时更加容易内存溢出。
  • 除了图片占用内存之外,Five 涉及的其他部分其实并没有占用过多内存。(也就意味着图片之外的优化空间不多)。

序列帧动画

如图五所示,这是一个如视 Logo 组成的循环关键帧动画:

图四:关键帧Sprite图和逐帧动画

这张帧动画雪碧图分辨率是14065*265,占用内存:

  • PC 端:14065*265*4/(1024*1024)=14.21823501586914MB
  • iPhone 8:14065*265*4/(1024*1024)*2= 28.43647003173828MB
  • iPhone 12:14065*265*4/(1024*1024)*3=42.65470504760742MB

将这张雪碧图放在<image>标签中确实是这样的内存占用。但是,一旦套用 CSS 帧动画实现之后:

@keyframes logo-sprites-animation {
0% {
background-position: 0 0;
}
100% {
background-position: 13800px 0;
}
}

animation: logo-sprites-animation 2.208s 0s steps(53) infinite normal;

通过 PerfDog 统计的内存占用却是图片内存的三倍:

  • PC 端:14065*265*4/(1024*1024)*3=42.65470504760742MB
  • iPhone 8 端:14065*265*4/(1024*1024)*2*3= 85.30941009521484MB
  • iPhone 12 端:14065*265*4/(1024*1024)*3*3=127.96411514282227MB

这个三倍是怎么来的,目前尚未找到相关资料,个人猜测的逻辑是:
此处的逐帧动画本质上是个补间动画,用在帧动画中,需要上一帧、当前帧、下一帧 来计算补间动画,同时需要三张图片,所以可能会同时存在三张图片实例。

这个目前尚属猜测逻辑。但需要关注的经验是:逐帧动画慎用,帧数最好限制在 24 帧以内,且占用内存不要超过 20MB。

最后

有兴趣的同学,可以安装 PerfDog 性能狗 工具自己实践一下本文的数据是否存在偏差。

VR 及 3D 技术在 Web 端架构设计与实践

本文基于 2021 年 GMTC 全球大前端技术大会"移动技术新趋势"专题下主题分享《VR 及 3D 技术在 Web 端架构设计与实践》整理而来。内容与当日分享基本无异,仅以文字的形式重新整理一遍。

GMTC_全球大前端技术大会-InfoQ
"GMTC是由极客邦科技和InfoQ中国主办的顶级技术盛会,关注移动、前端、AI应用等多个技术领域,促进全球技术交流,推动国内技术升级。GMTC为期4天,包括两天的会议和两天的培训课,主要面向各行业对移动开发、前端、AI技术感兴趣的中高端技术人员,大会聚焦前沿技术及实践经验,旨在帮助参会者了解移动开发&前端领域最新的技术趋势与最佳实践。"

VR 看房是 VR 及 3D 技术落地的场景之一,其特点是通过手机终端就能让人真正的置身其中,用自己直觉的空间感去感受整个房屋特征。本次分享将介绍贝壳如视前端团队是如何基于 VR 3D 模型进行前端架构设计的。除此之外,还将分享我们团队是如何基于 VR 看房能力探索新的业务形式以及面临的技术挑战。

基于 VR 3D 模型前端架构设计

在讲前端架构设计之前,先详细介绍下看房场景下的 VR 3D 模型的组成及形态。

看房 VR 3D 模型的组成及形态

房源的 VR 3D 模型的形态有多种,但在用户层面直观感受到的主要有三个形态:3D 模型形态、点位全景形态及 VR 眼镜视角形态。下面对这三个形态做详细介绍。

3D 模型

首先,我们简单思考一下三维模型是如何在二维平面抽象建模的?目前主流的三维模型抽象建模是基于多边形网格(Polygon Mesh),如图一所示。整体感知就是多边形面片愈多(面片密度)还原的三维立体效果愈真实。最精简的多边形自然是三角形(大部分场景下说的面片即三角面片),三维物体的每个细节可以通过三角面片的顶点、边及面等几何数学概念来描述。微观上来看,基于面片建模的三维模型本质上都是密度及其复杂的几何体。

多边形网格模拟立体效果
图一:多边形网格模拟立体效果

因此,依赖一些专业 3D 扫描仪(比如如视自研的黎曼、伽罗华等扫描仪)或全景相机等设备采集数据后,再通过算法加工可以获取这些描述三维立体结构的三角面片数据。前端再利用 WebGL/Three.js 等技术将其渲染至浏览器上,此时我们能得到房源的三维立体轮廓,效果如图二(左)所示的网格模型。当然,图二(右)才是我们期望的效果,仅仅有三维"骨架"轮廓是不够的,我们需要在此基础上贴一层"皮肤",而这层"皮肤"则是通过 UV 纹理贴图添加上的。

图二:三角面片描述的三维效果

对于三维模型有两个比较重要的坐标系统,一个是顶点的位置(x,y,z)坐标,另一个则是 UV 坐标。什么是 UV 呢?简言之,就是二维平面贴图映射到三维模型表面的依据。比如典型的 UV 贴图效果如图三所示,刚刚前文提到三维结构是通过顶点、边及面组成的三角面片组成的,这个三角面是二维的,通过一些数据依赖映射关系从 UV 贴图中抠出一个相同边、面的三角形贴到三角面片上。所以,此处的 UV 即指定义了二维平面图片每个点的位置与三维结构三角面片位置的映射关系信息。作为前端工程师,这个跟前端雪碧图(Sprite)概念将多个图标合并成一张图的原理是一致的。

房源UV贴图
图三:房源UV贴图

至此,基于三角面片和 UV 贴图数据我们成功渲染出了房源的 3D 模型。当然,出于性能考虑我们的三角面片密度不是特别高的,纯粹依靠 3D 模型在终端设备(iOS\Android 等)还原房源的真实细节现阶段并不现实。三角面片少,数据量低,内存占用低,我们可以通过 3D 模型还原房源的整体结构。至于细节,则通过点位立方体全景的方式去实现。

点位全景

前文提到房源的整体结构通过 3D 模型体现,至于细节则通过全景的形式来表现。我们会在房源选择多个合适的点位拍摄全景图片,然后以立方体全景的方式渲染以实现 720 º 环顾的效果,如图四(左)所示。

全景效果
全景贴图展开
图四:立方体全景效果及其展开

全景的实现是比较成熟的技术,主流的实现方式有立方体全景和球型全景。两种方式各有优缺点,由于立方体全景二次加工成本低如视目前以立方体全景技术实现为主。立方体全景的原理是渲染一个立方体盒子,给其上、下、前、后、左和右六个面各贴上一张图。需要注意的是,这六张图从中选择连续的四张图拼接在一起是一张连贯的全景图,如图四(右)所示 T 字形立方体贴图展开。此时,当人眼放置在立方体中心点观望四周是连贯的全景效果。

全景的效果完全依赖贴图的清晰度,所以我们可以拍摄高清 2048 分辨率的全景图片去体现房源某个位置的细节信息。这也是看房 VR 3D 模型的第二个核心形态点位全景形态。

VR 眼镜全景

前文提到的 3D 模型和点位全景形态都是基于二维显示屏展现的(裸眼体验),如果想让用户具备身临其境的感觉往往需要依赖 VR 眼镜设备。针对这类设备我们需要适配WebXR Device API,我们现阶段的适配策略是渲染两个相同的点位立方体全景,分别供左右眼感知。最终适配的效果如图五所示。

眼镜全景
图五:VR 眼镜全景

限于大部分用户的设备还是 iOS\Android,目前的裸眼 VR 3D 体验是主流。随着硬件设备的推广,等到 VR 眼镜走向普通用户时,这种更具身临其境的体验会慢慢更多用户接触到。

当然,除了本文提到 3D 模型形态、点位全景和 VR 眼镜全景三种形态之外,我们内部还有多种其他形态,如模型垂直视角、深度图渲染的全景视角等形态,但是偏技术领域且与普通用户感知不深,此处不详细介绍了。

最后,基于这三种形态外加一个房源的二维户型图就组成了我们看房 VR 3D 模型的核心结构,在此基础不断完善各种交互(比如形态间切换补间 Tween 动画)、产品功能逐步演变成大家所熟悉的贝壳如视 VR 看房。

演讲问答环节及后续的反馈情况来看,大家对分享提到的形态间切换的 Tween 动画实现比较感兴趣,且部分同行表示自己实现的效果达不到如视的移动真实感。此处细节较多,准备后续单独出文章分享,本文暂不花费篇幅详细介绍。

前端架构分层设计

前端架构分层设计
图六:前端架构分层设计

前文提到房源的 VR 3D 模型的组成及三个核心形态,我们实现了通过 3D 技术真实还原房源信息。经过多轮的产品需求迭代,我们在 VR 3D 模型的基础上不断地完善整个前端的架构分层设计。现阶段,整个 VR 用户端前端设计中我们抽象了三层:Web 服务层、前端数据层和 View 层。

我们将 View 层划分成四个方向进行抽象,第一个方向是纯 DOM 层的,比如首屏内容、控制面板、信息面板等,这层我们通常以 React/Vue 组件进行抽象服用。第二个方向是基于 Canvas/WebGL 渲染的三维视图,其功能即前文提到的房源 VR 3D 模型交互。第三个方向是我们维护的 3D 插件生态,以 VR 3D 模型为基础且以插件的形式派生出新的交互、能力(比如,模型中的指南针、电视视频等均以插件的形式集成)。最后一个方向是协议层抽象,我们 VR 是通过 Web 前端技术渲染实现的,以 WebView 作为容器集成在终端 App 里面,通过 jsBridge 的方式实现双向通信。为了保障业务代码的统一性,我们将第三方依赖(jsBridge/RTC/WebSocket 等)进行一层协议抽象,以达到面向协议开发以抹平不同终端差异性的目的。

数据序列帧抽象
图七:数据序列帧抽象

第二层是数据层的抽象。此处的数据并不是面向后端服务的数据层,而是前端 UI 交互的数据层抽象。我们将 UI 交互的状态以全局帧数据的形式抽象出来,当 UI 发生变化则同步至帧数据;当然,如果帧数据被发生改动(修改帧数据对象)则也会驱动 UI 发生相同变化。这个过程通过 JavaSciprt 中 Proxy 拦截数据对象实现的,如图七。换言之,UI 交互能产生新的帧数据,通过帧数据也能还原对应的 UI 状态。至于,为什么要花费大量精力做这个工作后文讲解业务部分时会有详细介绍。

第三层 Web 层有两个方向的核心服务,其中基于 Node.js/Go 实现的 HTTP 服务主要提供 VR 页面的 HTML"壳子"和首屏数据,而基于 WebSocket 服务的全双工数据通道则保障了 VR 体验过程与后台服务的实时通讯。WebSocket 长链接技术有传统 HTTP 方式无可比拟的优势(协议私有、实时性高、性能优异等),对我们业务的智能化、性能体验提升等无可替代,下文描述业务探索和性能体验部分大家会有更深切的感知。

贝壳如视用户端的前端设计大致如此,我们大部分核心业务如 VR 语言导览、VR 实时带看和 AR 讲房等都是基于此设计研发的。

基于 3D 模型与传统 DOM 开发的差异性对比

作为一名工作频繁接触 3D 相关技术的研发工程师,经常被咨询基于 3D 模型研发与传统 DOM 开发的区别。与传统前端开发差异性是存在的,但是适应如下三点基本就迈入前端 3D 开发的门槛。

三维坐标系 vs DOM 树

前端 DOM 树布局是基于 CSS 盒子模型和 Flex 布局,页面大部分布局都是基于此实现的,此外还有圣杯、双飞翼等经典布局体系。在二维层面依托强大的 CSS,前端布局是随心所欲的。但是放在三维空间,我们大部分时间都在跟坐标系及坐标系间切换打交道。

三维建模坐标体系
图八:三维建模坐标体系

三维研发的首个门槛就是跟各种坐标系打交道,比如三维物体本身的坐标系(一般称呼为本地坐标系),一个三维空间会存在多个三维物体,如何放置这些三维物体则需要一个三维世界坐标系来定位。此外,三维空间的三维物体通常都是静止的,其移动、旋转等操作都是控制相机的移动来实现的(当然,相机也是一种特殊的三维物体),如图八所示。然而,我们终端设备的屏幕是二维的,相机作为一个"眼睛"将三维物体投影到二维屏幕上又涉及到平面坐标系、齐次坐标系等等。所以,如何理清这些坐标系的概念和坐标系间的相互转换是 3D 研发的首个门槛,搞清这些在日后的研发中就能做到"游刃有余"。

面向异步 Hooks 事件

在处理三维模型行为交互体验时与传统前端还有个很明显的差异就是面临的异步细节要多得多。在 DOM 层面前端开发时,我们接触的异步事件主要集中在点击、触摸、滚动和 Ajax 异步请求等。但是在三维交互中,除此之外我们还频繁接触放大缩小、拖拽位移、模式切换等各类异步行为。

全景走点效果
涉及异步hooks事件
图九:点位全景切换走点

在如视内部的底层渲染引擎中,我们维护了比较完善的异步 Hooks 事件集来应对各种场景的交互行为。比如,如图九(左)效果是我们常见的 VR 房源点位全景交互走点移动,整个过程触发了九个异步事件回调,如图九(右)所列。这些回调将整个过程的细节全部暴露出来,方便研发人员更精准地把控体验。一般的终端工程师很难体验这种交互层面细维度精准把控的开发体验,初次接触需要适应。

碰撞检测

最后一个比较明显的差异性是三维空间里面的碰撞监测。

物体间遮挡与重叠
图十:物体间遮挡与重叠

如图十所示,在三维空间中摆置新物体难免会涉及遮盖、重叠的情况。在实际开发中,我们尽量规避这种现象的发生。碰撞监测常规的做法是针对物体创建一个规则的立体几何外形将其包围然后分析是否有重叠的部分;还有种思路是建立一条射线,获取此射线与两个物体间的焦点然后分析是否重合。
碰撞监测在不同的场景一般会采用合适的方式,对于移动的物体,有时候我们还需要在建模体系中添加物理引擎的支持。碰撞检测在不同的业务场景下,检测的策略是不同的,这个比较考验研发对整个三维空间的理解能力,本文就不展开更细节的内容了。

新型业务场景探索与实践

前文涉及的都是偏技术领域的,下面向大家分享下在已有的技术储备下,如视是如何在业务上做的一些探索与实践的。

三维空间分析计算与二次加工

物体(家具)识别
图十一:物体(家具)识别

三维模型是来源于现实真实的房源(通过专业设备拍摄及算法分析获取),我们可以对三维模型进行分析并将里面的家具物体识别出来(如图十一所示)。识别出这些物体后我们就能做些有趣的事情了,比如识别出显示器或电视,可以在此处添加一个视频播放广告或节目来营造更加真实的 3D 场景,效果如图十二(左)。识别平滑地面,我们可以放置一个扫地机器人或 3D 宝箱来做些营销活动等等,效果如图十二(中)、(右)。

电视视频
扫地机器人
宝箱营销
图十二:根据物体识别添加动态内容

除了空间内的物体识别之外,户型图也是我们二次加工的重点方向。比如,我们将二手房源里面家具及装修物体全部清理掉,然后就得到一个及其"纯净"的白模模型;在基于原有的户型结构重新规划将一个两室一厅的房源改造成一个三室一厅的房源,然后再重新加工装修风格和摆置家居物体等。

整个过程,如图十三(左)所示,经历了从真实复杂的普通房源到简洁的白模再到复杂的新装修家居风格过程,给潜在的购房用户提前示例这套房源的改造空间。

加工过程
一键切换
图十三:真实房源的二次加工

此外,我们在技术体验上也做了些突破,在终端层面实现真实房源与设计房源一键切换和同屏对比的交互体验,最终效果如图十三(右)所示。

VR 实时带看:同屏连线,高效看房

另外一个业务场景探索则是线上 VR 实时带看能力的落地。首先,解释下为什么要往这个方向探索?大家有过买房或租房体验的都知道,大部分场景都是经纪人开车载着你去实地看房,一天下来也就看几套房源可能还要爬楼梯、等红绿灯或被太阳曝晒等意外情况。

3D 交互与二维交互对比
VR 同屏1
VR 同屏2
图十四:3D 交互与二维交互对比及 VR 同屏

尽管 VR 房源虽然还原了房源的真实场景,但是三维空间交互还是比较复杂的,需要用户去探索细节。如图十四(左)是经典的信息流布局:搜索 ➙ 导航 ➙ 推荐 ➙ 筛选 ➙ 列表,这是二维最高效的信息展示布局,国内绝大部分提供数据服务的 App(电商京东、餐饮美团、房产贝壳等)均是这类布局。

但是三维空间交互就没有这么明确了,全景只能查看当前点位且全景游走大部分用户并不知晓。此外,诸如房源的小区信息和附近学校、医院等信息也无法在 VR 3D 模型中明确体现。因此,我们实现了由用户无目的的在 VR 3D 模型中漫游、探索信息转向专业由经纪人带领画面同步、实时语言讲解。

前文提到我们将前端所有的交互以序列帧数据的形式进行了抽象,用户交互会产生帧数据然后通过 WebSocket 将生成的帧数据同步给另外一个用户来驱动另外一个用户画面的更新。语音的话目前 RTC 技术比较成熟,我们落地即可,效果如图十四(右)所示。

终端App与微信小程序VR 实时带看通道链路
图十五:终端App与微信小程序VR 实时带看通道链路

除了端与端 VR 带看之外,我们还实现终端 App(iOS/Android)与微信小程序的 VR 实时语音带看的业务能力,整个链路通道如图十五所示。

线上 VR 实时带看能力在 2018 年底我们就已经初步实现落地,由于 2020 年新冠疫情影响造成大批潜在购房用户和经纪人居家隔离,线上 VR 实时带看目前已经成为了看房业务的核心场景。

VR 智能讲房:智能解说,身临其境

前面提到 VR 带看是通过专业的经纪人陪同去了解房源解决 VR 3D 看房获取信息的方式不高效问题。但这个业务场景也存在些许缺陷:

  • 人力成本:经纪人不一定能及时响应,比如深夜休息时段。
  • 专业水平:不能保障经纪人对所有的房源都了解,又诸如方言等沟通效率。
社交恐惧症
图十六:“社交恐惧症”:客户不愿跟陌生人沟通
  • 顾客“社交恐惧症”:不是人人都愿意跟陌生人沟通等。

鉴于此,我们尝试把 VR 3D 交互做得更智能些。怎么做才更智能呢?首先,我们得不完全依赖真实的经纪人。我们将真实的经纪人形象和音色采集出来然后通过视频拼接和语言 TTS 服务来抽象出一个虚拟经纪人,并将此虚拟经纪人形象搬到用户的终端屏幕上,如图十七所示。

虚拟数字经纪人
图十七:虚拟数字经纪人

有了虚拟的经纪人,那么该讲解什么样的内容呢?VR 带看语音来自于经纪人,画面行为帧数据也来源于经纪人行为。此时,就需要通过算法层面去合成讲稿并生产对应的音频和序列帧数据。整体的架构如图十八所示,前端所需要支持的就是定义画面行为的序列帧数据格式规范,由 AI 团队的剧本服务和 NLG 服务去计算 LRC 文本讲稿和行为序列。然后,通过主控服务生成带讲稿音频虚拟经纪人视频并附带行为序列帧数据给前端"翻译"。

AR 讲房架构
图十八:AR 讲房架构

因为涉及的点过多,更多的细节本文就不再详细讲解了。大家可以扫描图十九的二维码或访问 珠江罗马嘉园东区 2 室 1 厅 这套房源进行体验。总之,由于 WebSocket 双工实时性和前端序列帧数据抽象,VR 的整体体验变得更加智能化。

体验二维码
入口位置
图十九:AR 讲房体验二维码

面临的性能挑战及应对方案

在过去三年的 VR 看房及衍生业务研发中我们主要面临的性能瓶颈有两个:加载耗时和内存溢出。

加载耗时

在 2019 年 8 月份前,贝壳如视 VR 首屏加载平均耗时 7.6s,截至 2021 年 7 月份已经降至 1.92s,正常网络情况下用户基本无需等待过多时间去体验 VR 房源。如此巨大的提升我们究竟做了些什么呢?首先我们先分析之前慢的原因,然后"对症下药"。而且首屏的性能提升也不是一蹴而就的事情,我们内部成立了个性能体验专项虚拟团队持续了近一年才达到最终 1.92s 的效果。

问题出在哪儿呢?主要在三个方面:

密集的 HTTP 请求

前文提到 VR 3D 模型依赖大量的模型 UV 贴图和全景图片;除此之外,还有大量的地图、讲房音视频等资源。在浏览器的限制下同个域下的 CDN 请求限制在 3~6 个(不同浏览器会有差异)。大量的网络请求只能排队等待。

实时计算

前端存在大量的实时计算,比如 3D 模型文件的解压缩、户型图数据解析、三维空间分析及碰撞监测等。由于 JavaScript 的单进程,这些计算依赖也阻塞一些核心逻辑。

模块渲染加载策略不合理

由于 VR 开发初期考虑不周全,我们的异步渲染加载策略设计并不合理,优先级策略划分错乱。

分析原因后,优化策略就很明确了。针对密集的 HTTP 请求我们先添加更多 CDN 域名支持,保障同时刻的请求限制在五个以内并增加 HTTP2 协议支持。实时计算带来的耗时采取的策略是充分利用缓存(离线计算缓存、浏览器缓存以及服务端计算缓存等);同时,我们对模块渲染加载策略进行了重新设计,每个模块都规划好权重,按照权重来加载。此外,部分非核心交互则由用户触发后再加载渲染。由于历史包袱过重,真个过程持续了近一年,最终有了 7.6s 到 2.55s 的首屏加载的性能提升,过程如图二十(左)所示。

耗时变化
加载效果
图二十:VR 首屏性能提升过程

除上文提到的优化之外,我们还充分挖掘了部分客户端的能力。第一个能力是客户端 HTTP 请求拦截代理和缓存,通常情况下 WebView 缓存池"阈值"很低,而客户端缓存池则大得多;此外,分析对比来看客户端的 HTTP 请求效率要比 WebView 的 HTTP 请求高很多。支持 HTTP 请求代理和缓存之后,整个加载耗时降低了近 500ms。

另外一个核心能力则是增加了客户端首屏渲染:即进入 VR 页面前客户端提前预载好首屏内容,在加载阶段展示客户端内容,等前端完成首屏渲染之后再换成前端的渲染效果。整个过程是无缝的,用户甚至感知不到加载过程,最终的效果如图二十(右)所示。

内存溢出

加载耗时现阶段已经取得比较好的效果,我们目前遭遇的最大的瓶颈是内存溢出。

VR 内存占用
图二十一:VR 内存占用

在前文首屏优化中提到我们耗费大量的时间完善了模块加载渲染策略,因此在 VR 交互过程中,随着各个模块不断完成渲染,内存占用是逐步递增的,如图二十一(左)所示。在图二十一(右)扇形图中也列举了不同模块的内存占用情况。目前,iOS 设备的 WebView 内存崩溃的阈值大约在 1.5G 左右,Android 设备则不同机型阈值不完全一致,高端 Android 设备普遍比 iOS 设备高很多,但低端机阈值远低于 1.5G 内存。

规避内存溢出问题我们从两个方向入手:

增加内存池

目前我们测试过 iOS/Android 设备各类 WebView 控件,除了实现 WebView 独立进程之外并没有找到突破 WebView 内存限制的方式。这个属于 WebView 容器瓶颈。

降低内存占用

我们做了些突破,比如按需渲染,非可视区域销毁模块等等,但仅仅降低了崩溃率,成效并不明显。

而且,随着业务的不断迭代,VR 能力愈来愈丰富,内存占用还在不断提升。依赖 WebView+WebGL+jsBridge 技术栈落地的 VR 体验现阶段有很明显的局限性,虽然纯原生技术栈已经提上日程但短期来看还是很难落地的。为了弱化内存溢出带来的影响,我们目前采取的策略是根据用户的使用场景以动态降级的方式给予用户最合适的交互体验。

VR 性能瓶颈影响因素鱼骨图
图二十二:VR 性能瓶颈影响因素鱼骨图

性能优化的本质是渐进增强和优雅降级,把握每个细节把自己该做的部分做好一般都会有比较好的性能表现。我们系统分析了造成性能瓶颈各个因素,如图二十二所示。事实上,我们很难做些突破然后彻底解决内存问题,只能降级保障体验。

如何做到更"智能"地渐进增强和优雅降级?首先需要的是前端支持模块的"热插拔"能力,即能动态的销毁某个模块以将内存空间给其他模块使用。此外,我们维护一个关于内存瓶颈的数据仓库,依托 WebSocket 的双工能力,VR 交互时会收集用户的终端设备信息及部分 VR 用户行为,并在实时分析该用户的终端的最大承受能力,推送给前端再动态地加载或卸载前端模块,从而达到加强体验或降级的效果。

总结

前面给大家讲述了贝壳如视前端团队如何基于 VR 及 3D 技术在 Web 领域架构设计,并分享了在这个领域上的一些业务探索、实践及应对性能瓶颈的具体措施。本次的演讲的专题是"移动技术新趋势",最后站在技术的角度上做如下四个方面的经验(或趋势)总结来结束本次的演讲内容吧。

可玩性

三维领域研发比传统基于 DOM 前端研发有趣得多,比如团队就有产品说过三维空间二次加工装修设计是更高阶的"乐高"式游戏,欢迎大家加入这个领域。

序列帧抽象及数据驱动

过往的前端交互都是用户主动触发的,但是在 3D 方向的交互模型更需要自动播放,提高信息获取的方式。前端数据层序列帧抽象,支持数据驱动、序列化和反序列化将是不可或缺的一环。

"热插拔"

3D 领域开发内存占用是远大于传统前端页面的,尤其在终端设备 WebView 容器下内存限制更明显。模块、组件及插件等封装都需要支持"热插拔",从而做到动态加强体验或降级的效果。

WebSocket

我们已经逐步在抛弃主动式 Ajax,数据的实时性和智能化都依赖 WebSocket 的双工能力。目前,WebSocket 服务已经是核心基础建设。

那些年我关注过的NBA球星

我不喜欢用"那些年"这类很直白缅怀过去的字眼做标题,但是折腾许久才慢慢意识到现在的我已经找不着更好一点儿的词汇了。其实这就是一篇缅怀过去的一篇文章,并且是关于 NBA 球星的。因为我意识到自己已经很难和过去一样关注 NBA 赛事的输赢,NBA 于我渐行渐远。

第一次接触

首次接触 NBA 是在 2007 年,同学的一份体育类报纸被我当作隔离水泥的墙纸贴在宿舍的墙上。

因为家距离学校不足一公里,我成为班级里极少数的"走读生"。但是学校还是给我预留了一个床位,或许嫌弃家附近的塑料工厂太吵,也可能是觉得晚自习十点后的夜太黑,亦或是思索着换种方式能与同学走得更近些,毕竟居住在一室共同的话题才多。总之,我在学校宿舍待了一段时间,每夜入睡前都会不经意间瞧见墙上的那图、那文字。

科比・布莱恩特 vs. 拉沙德・刘易斯
科比・布莱恩特 vs. 拉沙德・刘易斯

报纸封面的新闻是关于拉沙德・刘易斯跟魔术队签了一份价值超亿的转会合同。虽然玩篮球有很长一段时间了,体验过扔进框的喜悦,享受过拼抢的激动;但那时我不知道世界上有 NBA 这类东西、这个联盟。我很好奇这个人有多厉害,一个运动员能值这么大价值?

刚刚步入高中的我后来也发现,了解 NBA 资讯越多就能与同学有更多的共同话题,特别是体魄强壮的男生。往后周六日,我也愈来愈关注 CCTV5 频道、慢慢准时追着 NBA 球赛。

球赛是无聊的,远没有自己在球场扔球来得好玩。追比赛的动力只为了上学第一天的周一能与同学有更多的谈资:谁赢了?哪个球星得了多少分?哪只队伍还有进季后赛的机会。

球赛是无聊的,我坚持了一段时间就再也没接触 NBA 球赛了。因为懂得少,因为自己打篮球只是扔个球,因为比赛的输赢好像跟我无关。大家都喜欢科比、麦迪、詹姆斯、加内特,可是他们距离我好远。但是了解到身边存在着许多热衷篮球的同学,不论高矮、性别、成绩,喜欢的球队赢了关注的球员表现亮眼了都会很开心。

球赛是无聊的,某个周六上午最后一节课刚刚结束,同桌迫不及待地悄悄跟我说"阳儿,下午打篮球不?我借到了一个篮球下午可以来"。果然,我居然没有注意到在他课桌的死角内放置着一个起着毛刺的篮球,我们课堂上时常喊着饥饿其实就是为了能够在操场上流汗。

这种邀请我是不会拒绝的,从下午两点玩到六点,要不是快到饭点我们是不会离开的。
同桌是真心爱篮球的,一直嫌弃自己一米七八的身高还不够高,因为喜欢加内特能够在一个夏天把自己晒得黝黑黝黑的。
于我而言,这比看篮球比赛有意思多了,挣着球一群人流着汗...

中国队

08 年是中国多灾多难的一年,也是我存储许多记忆的一年。
年初的南方大雪,寒假在家的我担心父亲还能不能回家过年,给我准备新衣裳、发压岁钱。
刚开学不久,新闻里播着西藏拉萨打砸抢烧暴力事件,文科老师偶尔也在课堂上义愤填膺。

当我紧张忙着午间休憩时,又听闻四川汶川地震。幸好在四川的外婆没啥事。趁着暑假,和妹妹二人勇敢地去了一趟四川德阳。经历过几次余震,却无意识畏惧,日常就是和妹妹在德阳街道图书馆看书,在家追着动漫,对川式水煮五花肉也终身难忘,刺激味蕾的记忆是挥之不去的。

北京奥运
2008・北京奥运

最重要的还是北京奥运啦,"北京欢迎你"的调调随处都存在着。

那个阶段我关注的是刘翔的 110 米栏、中国足球、中国篮球。等待了一上午,刘翔没有完成比赛,心中难以释怀——或许1356就不应该押付到一个人的身上。
足球是对阵美丽遥远的新西兰,拿下奥运首个进球的中国队没有拿下比赛;中国队的实力怎么这么不济,这是我难以接受的事实。还好,篮球比赛是很精彩的。

印象最深刻的是中国对战美国和中国对战西班牙——对战世界第一、第二。
第一例进球是姚明的三分,赛前我构思了第一个球的各种场景,却没猜测到居然会是姚明的三分。
朱芳雨、王仕鹏的手感好好,各种进;看着矮矮的刘炜,运起球来各种潇洒;易建联、王治郅、孙悦与美国全明星对位起来一点儿也不虚。
双方有来有回不可开交,虽然半场之后中国队慢慢落入下风。
确实我们都知道这是一场结果赤裸裸的比赛,但还是为中国队的球员骄傲、发自内心的。更何况,对阵西班牙的比赛中国队差点还赢了。

原来篮球这么好看,哦不原来打篮球的那一群人是如此优秀。

季后赛

学业的压力徒增,生活中面临的诱惑也多了许多,流汗的疯狂却越来越少;不过,看 NBA 球赛却成为学习之外的日常。

季后赛姚明迎接湖人队防守
季后赛姚明迎接湖人队防守

〇九上半年,班级中最具人气的球员麦迪被交易走了,没有麦迪的火箭队其实更强大。季后赛与湖人挣抢七让我见识到竞技体育的韧性,记忆最深刻的是那一场比赛好多同学凌晨就逃离校园去看比赛了,期待奇迹却没有奇迹。

勒布朗・詹姆斯 vs. 拉沙德・刘易斯
勒布朗・詹姆斯 vs. 拉沙德・刘易斯

另外一边则是詹姆斯的骑士和魔术鏖战着,我知道有许多人不喜欢詹姆斯,但是看过那一轮系列赛的我对他的评价始终是无与伦比的优秀。有第二场绝杀转身举指的怒吼,也有第六场的黯然离去,上演着我以为只有电影里面才有的剧情。

季后赛科比:湖人 vs. 魔术
季后赛科比:湖人 vs. 魔术

〇九总决赛的主角是科比,第一场科比求胜的眼神告诉我"第二名才是真正的输家"不是简单说说而已。熬过巴蒂尔和阿泰斯特防守,那个时期的联盟还有什么组合能够阻止科比夺冠的心。第二年依旧是科比与凯尔特人大战七场,科比上演了自己曼巴式复仇,不过看着他们这群球员比赛真的好可爱。

一一年是是神奇的逆转,当小牛夺冠诺维茨基第一时间离开球场;拉起球衣捂住脸掩盖自己失控的情绪。〇六年总决赛被翻盘,〇七年被勇士"黑八",一〇年又被马刺"黑七",没有被打入谷底的耻辱,怎会有攀上顶峰的泪水。

一二年在热火的詹姆斯终于拿到自己想要的荣誉,虽然我不认同"无冠之王不是王"的价值观。

一三年,雷・阿伦的神奇三分与吉诺比利神一般的表现以及邓肯最后一场最后一刻不甘地捶地板。马刺太老了、GDP 时代结束了 …

一四年,去年马刺的失利让我耿耿于怀了许久,想不到今年能够卷土重来,我好害怕马刺再输。但总决赛阵容与去年一般,我告诉我自己这个系列赛我一场都不愿意错过。
可惜,最后一次比赛却发生在我毕业答辩时刻...

马刺夺冠
马刺夺冠:"每一次的失败都是下一次的卷土重来"

马刺夺冠时刻就是我毕业答辩的时刻。总决赛的最后一场,我在答辩室盯着 3G 网络手机中的文字直播,可是答辩的次序被安排在了上午。我的内心是紊乱的,毕业设计做得不够好,害怕被老师刁难,又担心马刺不能赢。想不到大学的最后时刻是如此纠结的体验,庆幸结局都是美好的。

最后

未来很长的一段时间我可能会很难再关注 NBA 联赛,吸引我的是某些球员,让我不舍的是那碎片一样的岁月,而非 NBA 联赛,无关篮球。追求自己想要、喜欢的路途感觉真好,虽然会一路波折、忐忑、耻辱。

版权声明:文中涉及图片素材均来源于Zimbio,版权归属于Zimbio