全景封面视频生成技术在淘宝的应用

本文介绍了淘宝双促期间全景视频的业务应用场景,以及为何需要封面视频。全景视频通过360度拍摄提供沉浸式购物体验,封面视频则解决了在列表页展示的难题。在技术实现上,探讨了服务端和前端生成封面视频的方案,最终选择了基于Three.js的前端渲染方案。此外,还详细解析了全景视频的投影方式、3D渲染基础知识以及封面视频的生成流程,展示了如何利用算法和规则动态生成封面视频。
摘要由CSDN通过智能技术生成

9dd198cb6d3e30e0160557ba040642ee.gif

双促期间,淘宝产出了大量的高质量全景视频,并且根据业务配置生成了大量的封面视频。封面视频在双促期间有了大量的曝光和引导观看。在本地化会场,封面视频承接的模块点击率提升了 2 倍

为什么全景视频如此吸引消费者?全景视频在双十一是怎么应用的?又为什么需要封面视频?前端在这方面做了哪些的工作?本文将从业务需求到技术方案进行层层拆解,深入浅出全景封面视频生成这一场景。

淘宝上的全景封面视频

0ad957cedac33fb512d2073948b28b9f.png

  全景视频的应用

行业商品互动创新经过一年时间的沉淀,商品互动表达的核心交互形式基本确定,包含 3D 模型渲染,序列帧渲染方式,还有今年新推进的场景化互动:全景视频。

全景视频是一种用 3D 摄像机进行全方位 360 度拍摄的视频,用户在观看视频的时候可以随意调节视频上下左右动态地进行观看,用户有一种身临其境的感觉,不受空间和地域的限制。

全景视频目前在手淘中主要用于房产、家装等业务场景。在家装场景中,针对软装门店特色商品以全景视频的形式进行展示,用户可体验沉浸式导购,视频所见即门店所展示,规避图片展示的单一性,是一种全新的导购形式。

来看一个全景视频的线上示例:

85bbdb19a74eeae66a1616b1a37630b8.png

  全景视频的业务价值

全景视频在电商的应用,对于消费者、平台和商家的价值在于:

  1. 全景视频提升了消费者的购物体验,场景化互动的形式支持 720 视角旋转,真实的视觉盛宴,增强用户体感,降低用户的决策成本;视频导购形式,用户可以转动视角更好的了解场景中的商品,有更深度的参与感。

  2. 对于平台端,低拍摄成本+低制作成本+轻商品发布体系,将撬动家装行业线下海量优质高客单供给上行;新交互将带给平台更多的内容素材,丰富手淘内容生态

  3. 在商家端,对比全景漫游不需要清场拍摄,拍摄更加灵活;以全景视频为媒介,在商品、内容、店铺(门店)等触点,全面赋能本地化商家提升公域流量获取能力和手段

  为什么需要封面视频

全景视频作为今年全新的互动形式,也需要从零开始建设其在手淘内的展现形式。在双 11 大促期间需要通过会场、主搜、轻店等列表页上对外进行透出:

bcb1ec143251a79eda5278407f3a576e.png

全景视频在列表页上进行透出,由于无法在平面上显示 3D 画面,以及区域有限无法进行交互,因此我们需要设计透出的方案。总结下来,有几种可行的方案:

方案

形式

优点

缺点

视频封面图

只显示一张图片

生产简单

转化吸引力不足

全景视频

展示原视频中心点的画面

不需要再加工

无法显示有效的画面

封面视频*

动态切换视角显示重点画面

有一定的生产成本

点击吸引力高

为了能够最大限度地提升全景视频的在列表页的曝光转化率,产品上最终选择了封面视频的方案。对于封面视频需要以普通平面视频的展示形式进行透出,且:

  1. 消费体验需要越高越好,需体现出全景视频的特点,最好是动态切换视角始终显示重点画面;

  2. 生成成本需要越低越好,最好是自动生成;

  3. 视频时长不宜太长,消费时能够迅速抓住用户眼球,视频的存储成本也更低;

注:全景封面视频,即全景视频的封面视频,以下简称为封面视频。

封面视频的生产流程

  生产封面视频的方案

前面讲到过封面视频的需求的体验越高越好,成本越低越好,时长在可控的范围。对应的,有几个生成封面视频的方案:

方案

描述

优点

缺点

依靠算法

算法自动识别全景视频中讲解商品的时间段和画面位置,视频自动切换到该视角

商家生产成本低

技术开发成本高,视频质量不可控

依靠人工

由商家在端上操作来选择视角和视频内容

商家生产成本高

技术开发成本一般,视频质量完全可控

基于规则

基于一定的视角切换规则来生成

商家生产成本一般

技术开发成本一般,质量可控

当然还有可以采取前期依靠算法加以人工干预的方式,这里不一一展开。考虑到全景视频产品当下是初期状态,结合开发成本、商家生产成本,我们选择了基于规则,规则后台可配置,生产时需商家确认的方式。

默认规则是:

  1. 截取整段视频的前 8s

  1. 视频画面定位到视频中间的位置

  1. 视频播放过程中,视角左右移动 20 度

550e84b917326cbdc89fcd2560c3527c.gif

  全景视频的业务流程

全景视频的业务流程可以分为以下几个部分:

  1. 第一部分是全景视频的采集流程,通过 insta 360 设备进行拍摄,然后导出对应的全景视频文件;

  1. 第二部分是全景视频的素材生成,对应新零售工作台和小二工作台,通过这两个平台,完成商家对全景视频的上传及其业务关联,小二端全景视频的审核;

  1. 第三部分是审核通过的全景视频会在C 端 场景透出,区分具体的业务域和投放渠道

c5e92a1ba824c18642b5b3f01edef8a3.png

  封面视频的业务流程

封面视频的生成和审核,在全景视频业务流程的第二部分,涉及到新零售工作台和小二工作台。

ace23bb234088766a08e489cb27cb05e.png

在新零售工作台上,为了能够让商家的操作成本最低,商家只需要配置在原有的流程上,点击自动生成封面视频,预览确认封面视频,即可完成整个流程:

  • 第一步:生成

5240f2a830d7f06193761b5099913c0f.png

  • 第二步:确认

009c9965f93254030ad629726b263136.png

基于这个业务流程,封面视频生产的系统时序图:

outside_default.png

封面视频生产的技术调研

结合上面的产品交互,将生成全景视频封面视频的能力加入到整个流程中,主要有两个直接的技术调研方向:一个是在服务端对全景视频进行渲染处理,二是在前端对全景视频进行渲染处理。

  服务端方案

服务端可以通过一些媒体处理库(例如FFmpeg)将全景视频按照上诉规则来生成封面视频。其处理过程同下,差别在于需要针对每帧来进行视野移动:

  • 将视频时长截取为 5s

$ ffmpeg -ss 00:00:00 -t 00:00:5 -i 全景视频.mp4 -vcodec copy -acodec copy output.mp4

916fb9db3a5d387195b64c02b4e9f2f4.png

  • 将视频映射到球面或者立方体面

$ ffmpeg -i output.mp4  -vf v360=e:c3x2:cubic:out_pad=0.01 output2.mp4

e0162876172e587f1f3bef25806d200d.gif

  • 选择视角进行截取

2425f19c9cbc75bc3d949952b7561e10.gif

  前端方案

前端可以将全景视频渲染出来,然后进行录制,基于规则参数自动操作视频的视角变化,生成普通视频文件。流程如下:

e9d14906fd9283ee22fdd8ba5b50166b.png

  1. 在浏览器通过 WebGL 渲染全景视频;

  2. 根据规则驱动全景视频的画面变化;

  3. 通过 captureStream 获取 Canvas 上的媒体流数据;

  4. 通过 MediaRecorder 将 Canvas 中的媒体流转化成视频文件;

  5. 将视频文件上传到内容中台,从而进行下发透出。

  方案对比

方案

优点

缺点

服务端方案

质量高,计算速度快

需要较大的计算资源

前端方案

分布式,开发部署成本低

性能一般

相比较而言,前端方案开发成本低,结合商家人工预览确认、调参的过程,做到视频质量可控,功能快速上线的目的。

基于前端技术的全景视频播放

根据上面的方案,要生成封面视频,我们需要在浏览器上打造一个播放器来渲染全景视频,并且提供 API 控制视野画面的能力。要渲染全景视频,我们需要了解全景视频是怎么拍摄的,生成的原始视频展现形式是怎样的,然后再看看如何将 3D 空间渲染到 2D 的平面上。

e5d0fbc4d6b6568552f3f39a176a7d1b.png

  全景视频的拍摄

全景视频的摄影器材可以分面向普通消费者的消费级别;对发烧友或者专业团队初步探索的入门级别;拥有更复杂算法,更好的画面质量和 3D 效果的专业级;还有针对不同导演和客户调性设计的更高要求,使用和后期制作上更复杂的电影级。为了能够实现轻量级拍摄,平台建议采用消费级别的 insta 360 来进行拍摄。

084dd6fc97a126a92aa7583685148097.png

家装场景下使用 insta 360 相机进行拍摄的场景如下:

a7ff7cfb949c6b6be47f197aac4a7614.png


  全景视频的投影方式

全景是将多张图片按照一定的投影方式(projection)方式进行拼接,最终形成一个 360 度(球面)围绕观察者的图像。

那么全景视频的球面投影要解决哪些问题呢?由于球面全景视频与传统编解码方式不兼容,有投影的复杂度(算法效率)、投影后的图像失真程度(视频质量)等问题需要解决。全景视频在与传统视频具有同等清晰度的情况下,其像素总量往往是普通视频的 3-4 倍,因此对传输的带宽消耗巨大,如何减轻传输带宽压力也是全景视频研究中不得不面对的难点之一。

目前主流的投影方式有圆柱型投影立方体投影

  • 圆柱体投影

圆柱体投影不借助中间的投影几何体而直接将球面投影在平面上。现在应用得最广泛的是等距圆柱体投影(Equi-Rectangular Projection, ERP)。它的实现过程如下:

  1. 首先在平面宽长比为 1:2 的矩形区域内按照目标分辨率进行均匀的像素格划分,得到长为 w 等分宽为 h 等分的分割;

  2. 然后按照矩形的长和宽在球面上进行均匀的经线和纬线采样,将经线 w等分,纬线 h 等分,获得球面网格。

28be4cb8347f9e06ee929ac144262df3.png

这一方法的优势是简单且直观,便于直接播放和编辑。缺点也很明显,极低的投影复杂度带来的是投影均匀性的降低,两极处的像素采样密度大于赤道 —— 导致南北极部分内容会被严重拉伸且存在大量像素冗余,赤道部分则在还原时清晰度比较糟糕。

等距柱状投影格式的全景视频原始画面:

97edddacd62abe6766cdf128ccc682c9.png

  • 立方体投影

立方体投影(Cube Map Projection, CMP) 是通过将球面内容投影在立方体模型上后将各个面展开,然后拼接为矩形的一种投影方式。立方体投影通过透视的形式实现从球面到立方体面的映射,具体的操作其实就是简单的坐标比例缩放。由于立方体模型具有极好的对称性,所以在与球面进行相互投影的过程中可以大大降低计算复杂度,并且面与面之间的投影关系是一致的。

7a696f5018e3c94b4942a78885c36fc9.png

相比等距柱状投影,立方体贴图的扭曲更小。将等圆柱映射的 4K 全景视频(3840×1920)转换为相同的正方体映射的分辨率为 2880×1920,文件大小缩小了近 1/3,这也是立方体贴图的优点之一。

但是在球面映射到几何体表面的方法中,放射型投影由于模型每个面的中心位置到球心的距离不同,越靠近边角的地方离球体越远,所以投影的不均匀性无法避免。

在下图的立方体投影方式及其横截面示意图中可以看出,经过圆心和圆周上每一点的射线是以同样的角度向外发射的,但是在投影到正方形的边上时,越靠近中点对应的线段长度越短,越远离中点则对应线段越长,即圆上相同长度的弧映射到正方形上之后长度是不等的。因而球面上相同数量的像素点,投影到立方体边缘区域所分配到的采样像素数量会多于投影到中心区域时所分配的采样像素数量(即边缘区域稀疏,中心区域稠密):

2052afe226ad77a23d06f8929dcbbfa0.png

立方体投影格式的全景视频原始画面(传统立方体投影会按照下图格式将六个面进行排布):

db005bad7d214ef54f86f7fd4245a8fa.png

  • 等角度立方体投影

等角度立方体投影(Equi-Angular Cubemap, EAC) 则是谷歌所提出的一种对立方体投影改进方法,通过调整球面像素点对应的立方体上采样像素点的位置来改善这种不均匀的分布。EAC 在 CMP 的结果之上,额外做一个映射,将原本长度不同的块映射为相同:

77169e8ee1769d417d68013a1b473f91.png

这样做的好处就是在相同的源视频分辨率下可以提高细节部分的清晰度:

06ca7d3330210dd46434e44b404a6cf0.png

等角度立方体投影格式的全景视频原始画面(文字方向都是基于旋转后天空在上、地面在下、画面方向正常的情况标注的):

472e1f0ab1cc6dac89eb7c38617424ef.png

观察截图可以发现,画面上半部分即为面向前方时的横向(左右)扫视图,下半部分逆时针旋转 90° 后即为面向后方时的纵向(上下)扫视图。

从投影质量、投影效率和带宽来进行对比,EAC 是三者中最优的。但由于历史和易于展示/编辑的原因,市面上摄像设备普遍产出的是 ERP 投影的全景视频。

在新零售工作台上,商家上传的是 ERP 投影的全景视频,上传后内容中台将转换为 EAC 投影来供手淘进行渲染播放。我们的封面视频生成环节是在商家的上传流程中,因此需要渲染的是 ERP 投影的全景视频。

  3D 渲染基础知识

了解完投影的方式,接下来看如何实现投影。这部分涉及到一些 3D 渲染的基础知识。

想在屏幕上展示 3D 物体,大体上的思路是这样的:

  1. 创建一个三维空间,称之为场景(Scene)

  1. 确定一个观察点,并设置观察的方向和角度,称之为相机(Camera)

  1. 在场景中添加供观察的物体(Objects),物体有网格(Mesh), 线(Line), 点(Points)等

  1. 最后我们需要把所有的东西渲染到屏幕上

下面来具体看一看这些概念。

  • 场景

场景(Scene)是所有物体的容器,也对应着我们创建的三维世界。

  • 相机

相机(Camera)就相当于我们的眼睛,为了观察这个世界,我们需要描述某个物体的位置。描述物体位置需要用到坐标系。常用的坐标系有左手坐标系和右手坐标系。

bc32dd6668bdbd504fdf7da5ad1d99d9.png

常用的有两种相机,正投影相机(OrthographicCamera)和透视投影相机(PerspectiveCamera):

b5701e3b561c30627aa4fae6fc2c9417.png

上面左图是正交投影,物体反射的光平行投射到屏幕上,其大小始终不变,所以远近的物体大小一样。在渲染一些 2D 效果和 UI 元素的时候会用到;右图是透视投影,符合我们平时看东西的感觉,近大远小,经常用在 3D 场景中。

要理解两者的不同,需要明白「视景体」这个概念。它是指成像景物所在空间的集合。简单点说,视景体是一个几何体,只有在视景体内的物体才会被我们看到,视景体之外的物体将被裁剪掉(所见即所得)。这是为了去除不必要的计算。通过变换视景体,我们就得到不同的相机。

正交投影相机的视景体是一个长方体,它有几个属性:left, right, top, bottom, near, far 。把 Camera 看作一个点,left 则表示视景体左平面在左右方向上与 Camera 的距离,另外几个参数同理。于是六个参数分别定义了视景体六个面的位置。我们可以近似地认为,视景体里的物体平行投影到近平面上,然后近平面上的图像被渲染到屏幕上。


1fad2b0f2935ac51feefaff2f495f41f.png

透视投影相机的视景体是一个四棱台,它有几个属性:fov, aspect, near, far。fov(field of view)即视野,对应着下图图中的视角,是上下两面的夹角;aspect 是近平面的宽高比;再加上近平面距离 near,远平面距离 far,就可以唯一确定这个视景体了。


5ebc1fa04278c4e04fe6b9eb4f6976c2.png

  • 物体

物体(Objects)顾名思义,就是三维空间里的物体。有网格(Mesh), 线(Line), 点(Points)等。这里我们只看 Mesh

我们都知道,计算机的世界里,一条弧线是由有限个点构成的有限条线段连接得到的。线段很多时,看起来就是一条平滑的弧线了。计算机中的三维模型也是类似的,普遍的做法是用三角形组成的网格来描述,我们把这种模型称之为 Mesh 模型。

087820458ee8e62fc989091e4b7c09b5.png

这是在 3D 图形处理中与图像处理领域的 Lena 图齐名的斯坦福兔子。随着三角形数量的增加,它的表面将会越来越平滑。

物体有两个基础的属性,一个是形状(Geometry),另一个是材质(Material)。通俗来说,Geometry 就好像是骨架,材质则类似于皮肤。

  1. 形状(Geometry)在程序中是通过存储模型用到的点集和点间关系(哪些点构成一个三角形)来达到描述物体形状的目的。有立方体、平面、球体、圆形、圆柱、圆台等许多基本形状。也可以通过自己定义每个点的位置来构造形状;

  1. 材质(Material) 是物体表面除了形状以为所有可视属性的集合,例如色彩、纹理、光滑度、透明度、反射率、折射率、发光度等。

  基于 Three.js 实现全景渲染

在 Web 上实现 3D 的渲染,通常使用到 WebGL 技术。WebGL 是一个JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形。

但 WebGL 门槛相对较高,需要相对较多的图形学和数学知识。而 Three.js 则对 WebGL 提供的接口进行了非常好的封装,掩盖了 3D 渲染的细节,大大降低了学习成本,并且几乎没有损失 WebGL 的灵活性。因此我们选择了 Three.js 来实现全景视频的渲染。

  • EAC 全景图片渲染

我们知道,视频是由一张张图像组成的动态画面。因此需要渲染视频,可以先从渲染一张全景照片开始。

前面讲到过,全景视频的投影方式有 ERP 和 EAC 两种,其中 EAC 的的渲染相对易于实现和理解。因此我们从 EAC 投影方式开始实现。EAC 投影的全景图片如下:

701f71b4be2b282e6dcb6a8c44eb6589.png

  1. 创建场景、相机和渲染器,将渲染器挂载到 DOM 上:

    const scene = new THREE.Scene();
    const _width = window.innerWidth > 640 ? 640 : window.innerWidth;
    const _height = window.innerHeight;
    const camera = new THREE.PerspectiveCamera(90, _width / _height, 0.1, 100); // PerspectiveCamera(fov, aspect, near, far);
    
    
    const renderer = new THREE.WebGLRenderer();
    renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比,通常用于避免 HiDPI 设备上绘图模糊
    renderer.setSize(_width, _height); // 将输出 canvas 的大小调整为 (wi
    dth, height) 并考虑设备像素比
    
    
    document.getElementById('container').appendChild(renderer.domElement);
  2. 初始化一个立体几何,并上色:

    const geometry = new THREE.BoxGeometry( 1, 1, 1 ); // BoxGeometry(width, height, depth)
    const material = new THREE.MeshBasicMaterial({ color: 0x156289 });
    const box = new THREE.Mesh(geometry, material);
    scene.add( box ); // 将物体添加到场景
    camera.position.z = 5; // 设置相机的 z 轴位置为正,从外部观察物体
    
    
    // 执行动画渲染
    const animate = () => {
      requestAnimationFrame(animate);
      renderer.render(scene, camera);
    };
    animate();

    c1445655ab07215f3324c1ec0186057e.gif

  3. 给立方体添加图片纹理:

    - const material = new THREE.MeshBasicMaterial({ color: 0x156289 });
    + const texture = new THREE.TextureLoader().load('textures/demol.gif');
    + const material = new THREE.MeshBasicMaterial( { map: texture } );

    a88cc9f3c07449ac4335888e5b7264f0.gif

  4. 将上面的纹理替换为 6 张全景图片,图片加载的顺序是 正X(px.jpg),负X(nx.jpg),正Y(py.jpg),负Y(ny.jpg),正Z(pz.jpg) 和 负Z(nz.jpg),将他们分别赋给 6 个材质的贴图,作为立方体 box 的材质。

    d4769bff1c167d9427bd018cfa297e23.png

    const geometry = new THREE.BoxGeometry( 1, 1, 1 );
    - const texture = new THREE.TextureLoader().load('textures/demol.gif');
    + const textures = [
    + 'https://img.alicdn.com/imgextra/i3/O1CN01LsO1Bk20QbKpFTUQr_!!6000000006844-0-tps-1500-1500.jpg',
    + 'https://img.alicdn.com/imgextra/i3/O1CN01uTWCLc1XOCOuA92H0_!!6000000002913-0-tps-1500-1500.jpg',
    + 'https://img.alicdn.com/imgextra/i4/O1CN016lU3YJ1JdrJuFTcWt_!!6000000001052-0-tps-1500-1500.jpg',
    + 'https://img.alicdn.com/imgextra/i2/O1CN01nYe2Mn1ohkmBVyKpp_!!6000000005257-0-tps-1500-1500.jpg',
    + 'https://img.alicdn.com/imgextra/i4/O1CN014TNffn1nlaTfA98Fg_!!6000000005130-0-tps-1500-1500.jpg',
    + 'https://img.alicdn.com/imgextra/i1/O1CN01sS5m781ya6JgLSaVk_!!6000000006594-0-tps-1500-1500.jpg',
    + ];
    - const material = new THREE.MeshBasicMaterial( { map: texture } );
    + const materials = [];
    + for (let i = 0; i < textures.length; i ++ ) {
    +   const textureLoader = new THREE.TextureLoader();
    +   materials.push( new THREE.MeshBasicMaterial({ map: textureLoader.load(textures[i]) }));
    + }
    const box = new THREE.Mesh(geometry, materials);

    15f80c980c412f9abc0c501caaa11a45.gif

  5. 将相机放入 box 里面,同时将 box 的 X 轴或者 Z 轴的放大倍数变为负数,这样才能看到内部:

    - camera.position.z = 5;
    + camera.position.z = 0.01; // 将相机放在里面
    + box.geometry.scale(1, 1, -1); // 相当于将 Z 轴正向的面移到 Z 轴负方向上

    然后再添加并配置控制器,使得用户可以通过交互来进行观察:

    + const controls = new OrbitControls(camera, renderer.domElement);
    + controls.enableZoom = false; // 禁用放大
    + controls.enablePan = false; // 禁用双指缩放
    + controls.enableDamping = true; // 开启阻尼效果
    + controls.rotateSpeed = -0.25; // 旋转方向取反,使内部拖拽旋转方向一致
    
    
    const animate = () => {
      requestAnimationFrame(animate);
    +  controls.update(); // 开启阻尼效果后必须更新
      renderer.render(scene, camera);
    };

    最终效果

    c1dcc9c7666721eb1efbb29ec5c06880.gif

  • ERP 全景视频渲染

ERP 全景视频的渲染与 EAC 全景图片的渲染主要差别在于:

  1. ERP 使用的是球形投影

  2. 全景视频使用的是视频纹理贴图

  3. 视频的渲染需要媒体流输出

根据这几点不同,只要将上面的代码稍作改造,就可以实现 ERP 全景视频的渲染。

我们要渲染的全景视频如下:

d4cc5f28cfd9d767f19203862f4ad81e.png

改造步骤:

  1. 使用球形替换立方体

    - const geometry = new THREE.BoxGeometry( 1, 1, 1 );
    + const geometry = new THREE.SphereGeometry(1, 32, 16);
  2. 使用视频纹理替换图片纹理

    - const textures = [
    - 'https://img.alicdn.com/imgextra/i3/O1CN01LsO1Bk20QbKpFTUQr_!!6000000006844-0-tps-1500-1500.jpg',
    - 'https://img.alicdn.com/imgextra/i3/O1CN01uTWCLc1XOCOuA92H0_!!6000000002913-0-tps-1500-1500.jpg',
    - 'https://img.alicdn.com/imgextra/i4/O1CN016lU3YJ1JdrJuFTcWt_!!6000000001052-0-tps-1500-1500.jpg',
    - 'https://img.alicdn.com/imgextra/i2/O1CN01nYe2Mn1ohkmBVyKpp_!!6000000005257-0-tps-1500-1500.jpg',
    - 'https://img.alicdn.com/imgextra/i4/O1CN014TNffn1nlaTfA98Fg_!!6000000005130-0-tps-1500-1500.jpg',
    - 'https://img.alicdn.com/imgextra/i1/O1CN01sS5m781ya6JgLSaVk_!!6000000006594-0-tps-1500-1500.jpg',
    - ];
    + const video = document.createElement('video');
    + video.muted = true;
    + video.src = 'https://example.com/panorama.mp4';
    + const texture = new THREE.VideoTexture(video);
    
    
    - const materials = [];
    - for (let i = 0; i < textures.length; i ++ ) {
    -   const textureLoader = new THREE.TextureLoader();
    -   materials.push( new THREE.MeshBasicMaterial({ map: textureLoader.load(textures[i]) }));
    - }
    + const material = new THREE.MeshBasicMaterial( { map: texture } );
  3. 使用球形和视频纹理的材质初始化网格并添加入场景

    - const box = new THREE.Mesh(geometry, materials);
    + const sphere = new THREE.Mesh(geometry, material);
    - scene.add(box);
    + scene.add(sphere);
  4. 启动视频播放,Three.js 将捕获视频的媒体流并渲染:

    + video.play();

    最终效果:

    5a5eec38fdfc7cd8818c65b6e3da959c.gif

基于前端技术的视频封面生成

接下来我们还需要实现「根据参数驱动视野变更」(模拟上面的点击拖动效果)和「将画面生成为新的视频」并上传到后台。

67be3f221299b87021b59660d0e92f15.png

  全景视频的控制

前面讲到,我们的封面视频,需要安装一定的规则来改变视野画面。该规则可以由小二在后台来配置,也允许商家进行调整。回顾一下我们的默认规则:视频画面定位到视频中间的位置,视频播放过程中,视角向左移动 20 度(2 秒),回到中间(2 秒),向右移动 20 度(2 秒),回到中间(2 秒)。总共生成时长为 8 秒的封面视频。

  • 坐标系及视点

在前面的章节中,我们已经将全景视频的画面映射到了一个球体中,并且将相机(我们的眼镜,观察者)放入了这个球体内部中央位置。我们要完成「向左移动 20 度」的指定,但相机现在面向哪里,画面是怎样的?怎么度量「20 度」?

我们示例全景视频使用的是等距柱状投影方式,这种投影方式把球的经线映射成间距相等的垂直线,把球的纬线映射成间距相等的水平线,则可生成一幅横纵比为 2:1 的地图。

e33df95c297867949a90c90198043bdb.png

其中左侧中心点就是 0, 0 的坐标点:

48edb4d2eea75c9d867de7ee7efa4a12.png

进而回顾一下上面 3D 渲染基础中透视相机的知识 —— 我们只能看到视窗内的平面大小。这个视窗是由视点视野决定的:

326134d5f7db88b36c22c479f270db1b.png

结合上面的示例画面,可以确定初始视点坐标是 (90, 0)

因此我们可以提供一个方法去控制相机的视点和视野,就可以得出我们想要的画面。

locate(longitude, latitude, fov)

  1. longitude:经度

  2. latitude:维度

  3. fov: 视野

  • 坐标系的转换

Three.js 里面有没有这样的 API 呢?依据笔者所查,并没有。因为我们忽略了另一个概念:高度。在三维空间中,我们的相机定位「视点」,还需要 Z 轴信息。Three.js 中的 API 是 camera.lookAt(x, y, z),该 API 可以旋转相机使其在世界空间中面朝一个点。

把刚才的经纬度坐标映射为三维空间的某个点,需要应用到一些数学知识:经纬度坐标系与三维笛卡尔坐标系转换。

将经纬度坐标系转换为三维笛卡尔坐标系:

f249e5a5980293a4d0f4db87350a0610.png

将三维笛卡尔坐标系转换为经纬度坐标系:

d8d8cacedd7a91be989b37308075a79e.png

只要理解其数学公式和其推导过程,我们就能通过实现代码该转换算法。文章篇幅有限不再对数学部分展开,其转换算法的代码实现如下:

function locate(longitude = 0, latitude = 0, fov = 90) {
  const modifiedLat = Math.max(-90, Math.min(90, latitude));
  const phi = THREE.MathUtils.degToRad(90 - modifiedLat);
  const theta = THREE.MathUtils.degToRad(longitude + 180);


  const distance = 1; // 球的半径
  const x = distance * Math.sin(phi) * Math.cos(theta);
  const y = distance * Math.cos(phi);
  const z = distance * Math.sin(phi) * Math.sin(theta);
  camera.lookAt(x, y, z);
  camera.fov = fov;
}

向左移动 20 度:

locate(0, 0, 90); // 初始化视点和视野
setTimeout(() => locate(-20), 2000); // 两秒后视点向左移动 20 度

执行效果(观察两秒后视角的变化):

74389f54cc8f310584e7fafff7d420d2.gif

  • 按规则执行动画

按照我们既定的规则「每 2 秒来回转动 20 度」,实现代码如下:

const duration = 2000;
setTimeout(() => locate(-20), duration);
setTimeout(() => locate(0), duration * 2);
setTimeout(() => locate(20), duration * 3);
setTimeout(() => locate(0), duration * 4);

执行效果:

f7d58d0a773e4944c586fd1f95ac7446.gif

可以看到移动的效果非常地生硬,不像是正常的人眼睛转动的感觉。因为我们的程序缺少了对执行过程的描述。可以引入动画来进行优化,让「 x 时间转动到 x 位置」的执行过程化:

import animejs from 'animejs';


function runAnimate( update) {
  const anim = {
    x: 0,
  };
  animejs.timeline({
    targets: anim,
    duration: 2000, // 间隔 2 秒
    easing: 'linear', // 线性执行
    update: () => update(anim),
  })
    .add({ x: -20, })
    .add({ x: 0, })
    .add({ x: 20, })
    .add({ x: 0, });
}


// 视频播放即开始执行动画
video.addEventListener('play', () => {
  runAnimate(({ x }) => locate(x));
});

最终效果:

6f43b440ca73473dad45c9a306189789.gif

自此我们已经完成了全景视频控制 API 的提供,后面只需要规范一种数据格式来让程序生成上面的执行代码,即可完成根据配置驱动全景视频视野的目的。

在新零售工作台我们通过默认参数来生成封面视频,也提供了表单的方式允许商家重设这些参数:

abf7da00d892a45eebf79b943b6bd389.png

  媒体流的捕获和上传

最后将 Three.js 渲染用的 Canvas 通过 captureStream 捕获其媒体流(Stream),使用 MediaRecorder API 将其转换为 Blob 文件对象,即可用于上传。

捕获文件对象:

let recordedBlobs = [];
let mediaRecorder;
function startRecording() {
  mediaRecorder = new MediaRecorder(
    renderer.domElement.captureStream()
  );
  mediaRecorder.ondataavailable = function(event) {
    if (event.data && event.data.size > 0) {
      recordedBlobs.push(event.data);
    }
  };
  mediaRecorder.start();
}
function stopRecording() {
  mediaRecorder.stop();
}

上传文件对象:

import { createUploader } from '@ali/speedster-media-upload'; // 内容中台视频上传库


const file = new File(
  new Blob(recordedBlobs),
  'cover.webm',
);


const uploader = await createUploader();
const fileInfo = await uploader.startUpload(file);

线上效果

商家上传的全景视频原格式:

04febc32a4929cc12486a9c44cd9c8b3.gif

生成的封面视频:

558feec183ec05cee5b2f3f0aee847e1.gif

未来展望

借助封面视频生成的场景,我们在 Web 端探索了全景视频这种新的视频形态的播放。随着视频制作成本的降低、生产和消费链路的完善,全景视频技术的发展将为家装行业的数字化升级提供新的推动力,加速其进程。在技术侧,现有的硬件条件下,仍需进一步升级客户端技术解决消费侧 App 的流量和性能的问题;升级前端技术解决生产侧审核平台的播放能力问题。例如:

  1. 多种投影方式的支持:拍摄设备的多样性以及业界标准的不一导致产出的全景视频投影方式多种多样;在进行视频审核时播放器对主流投影方式的支持可以减少服务端转格式的计算成本,同时转码和审核的并行将能提高视频生产时效;

  2. 音频播放的支持:封面视频生成的场景只需要渲染画面,但在审核场景下播放器需要对音频进行播放来让工作人员进行审核;

  3. 交互能力的支持:封面视频生成的场景不需要人工进行交互,因此播放器并未提供控件。但在审核场景下需要有诸如进度条拖拽、全屏和倍速等交互能力。

参考资料

本文内容参考或引用了以下资料:

  1. 常见的 360° 全景视频格式介绍及播放方式(地址:https://www.bilibili.com/read/cv788511)

  2. 谈谈全景视频投影方式(地址:https://zhuanlan.zhihu.com/p/106922217)

  3. 初识 Three.js(地址:https://zhuanlan.zhihu.com/p/27296011)

✿  拓展阅读

19c8dbc6699d67eb3040be937888a561.png

9448dd61139cc572830eb13736c8ea74.png

作者|梧忌、云墨

编辑|橙子君

出品|阿里巴巴新零售淘系技术

a97dfa064f04e71ecc5eead1f3dce4f9.png

2f164e26b57bdce0fc7d4b1bf92968ce.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值