背景
酷家乐全景图启动后会加载全景图的六面图的缩略图,同时开始加载 6 个面的完整图片,每一面加载完后去除对应面的预览图,这在全景图分辨率较高时会使得整体加载速度显著变慢,长时间处于显示预览图状态。 同时因为图片加载速度不一,加载过程中已加载的面和未加载的面交界很明显。随着图片分辨率的提升,这些问题将越来越明显。
之前线上的做法是加载 WebP 格式(默认质量)的图片,但存在显著的画质损失,需要调研如何更快的加载高质量全景图。
经过调研发现,使用 krpano 的 Multires
功能可以做到将全景图的图片进行分块懒加载。
Multires 概述
从 krpano 1.17 开始,加入了 HTML5 Multiresolution 功能(简称 Multires),用于加载超高分辨率的全景图。
和一般网页图片的懒加载类似,Multires 通过自动分层和分块来推迟不可见范围内的内容的加载,基本规则如下:
- 将全景图分为多个不同分辨率的层级(Level)
- 将每个 Level 划分为多个大小都为 tilesize 的 Tile
- 从第一级开始,逐层加载
- 优先加载当前可见的 Tile
- 加载完当前视野内可见的 Tile 后,预加载一部分不可见的 Tile
- 当不可见区域进入视野中时,重复上述几步进行加载
其加载顺序大致为 L1落在可见范围内的tile -> L2落在可见范围内的tile -> L3落在可见范围内的tile -> ... -> 部分不可见的 Tile
。具体顺序与 FOV
及 aspreview
等设置有一定关系。
Multires 基于 HTML5 实现,其兼容性和 HTML5 基本保持一致,不支持 IE8、9。 兼容性参考
XML 配置
想要开启 Multires 需要在 XML 中进行对应的配置:
<image
tilesize="512" // 每个 tile 的大小
baseindex="0" // 下标从0开始(默认会从1开始)
>
<level tiledimagewidth="512" tiledimageheight="512" aspreview="true">
<cube url="./l1/2000x2000_%s.jpg_%h_%v" />
</level>
<level tiledimagewidth="1024" tiledimageheight="1024">
<cube url="./l2/2000x2000_%s.jpg_%h_%v" />
</level>
<level tiledimagewidth="2000" tiledimageheight="2000">
<cube url="./l3/2000x2000_%s.jpg_%h_%v" />
</level>
...
</image>
全景图加载过程中会将 url 内的占位符进行替换:
%s
: 六面体的面%h
: 水平索引%v
: 垂直索引
最新版本的 Krpano(1.20.9)还支持更简单的写法,不需要写分为多个 level:
<image>
<cube url="pano_%s_%l_%v_%h.jpg"
multires="TILESIZE,LEVELSIZE1,LEVELSIZE2,..."
/>
</image>
为什么 tilesize 取 512px?
- 取 2 的指数的 tilesize 可以自动触发 mipmapping,mipmapping 会在渲染时带来视觉和性能的提升(可能耗费更多的内存),但是在 Chrome 57,58 中对 mipmap 的处理存在 bug,需要关闭
- 过小的尺寸会导致出现大量的图片请求,过大的尺寸又会导致单个 tile 加载缓慢。该大小经过测试相对平衡且在视觉显示上较合理。
图片分级策略
目前设定的分级最高三级,如果单面图片尺寸小于 1024x1024,则跳过 L2 的加载。
- L1:单面 512x512,30Q
- L2:单面 1024x1024,50Q
- L3:全尺寸,100Q
L2 的设定需要多次测试确定,不一定是这个值。
使用阿里云 OSS 图片 API 进行切割
Multires 需要将图片进行切割分块,这个步骤如果由后端服务进行则会有较大的性能和传输存储压力,不过还好阿里云 OSS 提供了切割 API,可以便捷的进行图片的分块。
使用阿里云图片处理 API 提供的索引切割功能实现。 通过管道操作,将图片沿 x 轴每 512 像素裁剪,然后将裁剪的图沿 y 轴每 512 像素裁剪。从而将原图按下图类似的形式划分,完整的每一小块尺寸为 512*512。
可以用这样的图片操作取出划分后的索引(0,0)位置的裁剪图(索引从 0 开始):
https://oss.domain.com/some_pic.jpg_f?x-oss-process=image/resize,w_512|image/indexcrop,x_512,i_0|image/indexcrop,y_512,i_0
这样就得到了用在全景图 XML 中的 url:
https://oss.domain.com/some_pic.jpg_%s?x-oss-process=image/resize%2Cw_512|image/indexcrop%2Cx_512%2Ci_%h|image/indexcrop%2Cy_512%2Ci_%v
由于阿里云裁剪算法的问题,操作后的图片质量会有略有下降。如果对画质有较高要求,需要后端预处理裁剪出图组成最后一层。
对比测试
为了使差异更加明显,在对比过程主要在限速情况下进行,同时附上不限速的情况。
主要进行以下几个版本之间的对比:
- 无损的原始全景图,不进行任何压缩(以下简称 origin 或原始图)
- 使用 webp 格式的全景图,默认质量(以下简称 webp 或 webp 格式),存在显著质量损失。
- oss 裁剪得到的全景图,绝对质量 100Q(以下简称 Multires)
限制条件:
- 下载速度:1Mb/s
- 上传速度:750kb/s
- 延迟:40ms RTT
- 只加载基本代码和全景图,不加载其他任何功能
主要关注指标:
- 首屏加载图片大小:该指标仅供参考,使用多级加载不会在首屏加载当前场景的所有图片,原始方式会加载所有图片。
- 首屏加载时间:从打开到首屏完全加载完成所用的时间(静止情况下)。
- 缩略图 (preview) 存在时间(第一面、最后一面):指最模糊的缩略图元素存在的时间。从缩略图的存在到消失过程会有巨大的视觉差异。缩略图存在的时间能大致反映加载时的用户体验。
对比
PANO-1
指标 | origin | webp | webp 100Q | multires |
---|---|---|---|---|
首屏加载图片大小 | 7.7M | 740k | 3.7M | 1.8M |
加载时间 | 46.28s | 7.76s | 30.74s | 18.66s |
加载时间(无限制) | 14.43s | 2.18s | 6.12s | 3.80s |
PANO-2
指标 | origin | webp | multires |
---|---|---|---|
首屏加载图片大小 | 7.1M | 543k | 1.6M |
加载时间 | 58.87s | 6.23s | 16.82s |
加载时间(无限制) | 11.1s | 1.86s | 3.48s |
PANO-3
指标 | origin | webp | multires |
---|---|---|---|
首屏加载图片大小 | 8.6M | 875k | 2M |
加载时间 | 1.2min | 8.76s | 20.39s |
加载时间(无限制) | 13.33s | 2.41s | 4.22s |
总结
- 多级加载的首屏速度显著快于原图加载,虽然比会 webp 压缩版本略慢,但能保证图片质量,同时用户体验上反而更好
- 多级加载为后续 8k 以上分辨率全景图的投入使用提供支持,使加载速度不会因为图片分辨率提升而有太大的变化。