Web 全景在之前带宽有限的条件下经常用来做为街景和 360° 全景图片的查看。它能够给用户一种 self-immersive 的体验,经过简单的操做,自由的查看周围的物体。随着一些运营商推出大王卡等免流服务,以及 4G 环境的普及,大流量的应用也逐渐获得推广。好比,咱们是否能够将静态低流量的全景图片,变为动态直播的全景视频呢?在必定网速带宽下,是能够实现的。后面,咱们来了解一下,如何在 Web 端实现全景视频。先看一下实例 gif:html
全景视频是基于 3D 空间,而在 Web 中,可以很是方便触摸到 3D 空间的技术,就是 WebGL。为了简化,这里就直接采用 Three.js 库。具体的工做原理就是将正在播放的 video 元素,映射到纹理(texture) 空间中,经过 UV 映射,直接贴到一个球面上。精简代码为:git
let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100);
// 添加相机
camera.target = new THREE.Vector3(0, 0, 0);
// 设置相机的观察位置,一般在球心
scene = new THREE.Scene();
let geometry = new THREE.SphereBufferGeometry(400, 60, 60);
// 在贴图的时候,让像素点朝内(很是重要)
geometry.scale(-1, 1, 1);
// 传入视频 VideoEle 进行绘制
var texture = new THREE.VideoTexture(videoElement);
var material = new THREE.MeshBasicMaterial({ map: texture });
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio); // canvas 的比例
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
复制代码
具体的过程差很少就是上面的代码。上面代码中有两块须要注意一下,一个是 相机的视野范围值,一个是几何球体的相关参数设置。github
相机视野范围算法
具体代码为:canvas
let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100);
复制代码
这里主要利用透视类型的相机,模拟人眼的效果。设置合适的视野效果,这里的范围还须要根据球体的直径来决定,一般为 2*radius + 100,反正只要比球体直径大就行。bash
几何球体的参数设置网络
let geometry = new THREE.SphereBufferGeometry(400, 60, 60);
// 在贴图的时候,让像素点朝内(很是重要)
geometry.scale(-1, 1, 1);
复制代码
上面其实有两个部分须要讲解一下app
SphereBufferGeometry(radius, widthSegments, heightSegments,...)
。
geometry.scale(-1, 1, 1)
。上面只是简单介绍了一下代码,若是仅仅只是为了应用,那么这也就足够了。可是,若是后面遇到优化的问题,不知道更底层的或者更细节内容的话,就感受很尴尬。在全景视频中,有两个很是重要的点:dom
这里,咱们主要探索一下 UV 映射的细节。UV 映射主要目的就是将 2D 图片映射到三维物体上,最经典的解释就是:ide
盒子是一个三维物体,正如同加到场景中的一个曲面网络("mesh")方块. 若是沿着边缝或折痕剪开盒子,能够把盒子摊开在一个桌面上.当咱们从上往下俯视桌子时,咱们能够认为U是左右方向,V是上下方向.盒子上的图片就在一个二维坐标中.咱们使用U V表明"纹理坐标系"来代替一般在三维空间使用的 X Y. 在盒子从新被组装时,纸板上的特定的UV坐标被对应到盒子的一个空间(X Y Z)位置.这就是将2D图像包裹在3D物体上时计算机所作的.
这里,咱们经过代码来细致讲解一下。咱们须要完成一个贴图,将以下的 sprite,贴到一个正方体上。
from iefreer
这里,咱们先将图片加载到纹理空间:
var material = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/texture-atlas.jpg') } );
复制代码
那么,如今咱们有一个以下的纹理空间区域:
这块内容,就实际涉及到 WebGL 的知识,纹理空间和物理空间并非在一块,WebGL 中的 GLSL 语法,就是将纹理内容经过相关规则,映射到指定的三角形区域的表面。
这里须要注意的是,纹理空间并不存在所谓的最小三角区域,这里适应的只是在物理空间中划分的三角区域。为了简单起见,咱们设置的 boxGeometry 只使用单位为 1 的 Segments,减小须要划分的三角形数量。
这样,就存在 12 块须要贴的三角区域。这里,咱们就须要利用 Vector2
来手动划分一下纹理空间的区域,实际在映射的时候,就是按顺序,将物理空间的定点 和 纹理空间的定点一一映射,这样就实现了将纹理和物理空间联系到一块儿的步骤。
由于,Three.js 中 geometry.faceVertexUvs
在划分物理空间时,定义的面分解三角形的顺序 是 根据逆时针方向,按序号划分,以下图所示:
根据上图的定义,咱们能够获得每一个几何物体的面映射到纹理空间的坐标值能够分为:
left-bottom = [0,1,3]
right-top = [1,2,3]
复制代码
因此,咱们须要定义一下纹理坐标值:
face1_left = [new THREE.Vector2(0, 0),new THREE.Vector2(.5, 0),new THREE.Vector2(0, .333)]
face1_right = [new THREE.Vector2(.5, 0),new THREE.Vector2(.5, .333),new THREE.Vector2(0, .333)]
//... 剩下 10 个面
复制代码
定点 UV 映射 API 具体格式为:
geometry.faceVertexUvs[ 0 ][ faceIndex ][ vertexIndex ]
复制代码
则定义具体面的映射为:
geometry.faceVertexUvs[0][0] = face1_left;
geometry.faceVertexUvs[0][0] = face1_right;
//...剩下 10 个面
复制代码
若是,你写过原生的 WebGL 代码,对于理解 UV 映射原理应该很容易了。
这里须要注意的是 Web 全景不是 WebVR。全景没有 VR 那种沉浸式体验,单单只涉及三个维度上的旋转而没有移动距离这个说法。
上面的描述中,提到了三维,旋转角度 这两个概念,很容易让咱们想到《高中数学》学到的一个坐标系--球坐标系(这里默认都是右手坐标系)。
计算公式为:
如今,若是应用到 Web 全景,咱们能够知道几个已知条件:
p 这个是不变的,而 ∆φ 和 ∆∂ 则是根据用户输入来决定的大小值。这里,就须要一个算法来统一协定。该算法控制的主要内容就是:
用户的手指在 x/y 平面轴上的 ∆x/∆y 经过必定的比例换算成为 ∆φ/∆∂
若是考虑到陀螺仪就是:
用户的手指在 x/y 平面轴上的 ∆x/∆y 经过必定的比例换算成为 ∆φ/∆∂,用户在 x/y 轴上旋转的角度值 ∆φ'/∆∂',分别和视角角度进行合并,算出结果。
为了更宽泛的兼容性,咱们这里根据第二种算法的描述来进行讲解。上面 ∆φ/∆∂ 的变更主要映射的是咱们视野范围的变化。
在 Threejs 中,就是用来控制相机的视野范围。那咱们如何在 ThreeJS 控制视野范围呢?下面是最简代码:
phi = THREE.Math.degToRad(90 - lat);
theta = THREE.Math.degToRad(-lon);
camera.position.x = distance * Math.sin(phi) * Math.cos(theta);
camera.position.y = distance * Math.cos(phi);
camera.position.z = distance * Math.sin(phi) * Math.sin(theta);
复制代码
这里主要模拟地球坐标:
具体内容为:
在一般实践当中,改变全景视角的维度有两种,一种直接经过手滑,一种则根据陀螺仪旋转。
简单来讲,就是监听 touch
和 orientation
事件,根据触发信息来手动改变 lat/lon 的值。不过,这里有一个注意事项:
latitude 方向上最多只能达到 (-90,90),不然会形成屏幕翻转的效果,这种体验很是很差。
咱们分别经过代码来实践一下。
Touch 相关的事件在 Web 中,其实能够讲到你崩溃为止,好比,用户用几个手指触摸屏幕?用户具体在屏幕上的手势是什么(swipe
,zoom
)?
这里,咱们简单起见,只针对一个手指滑动的距离来做为 相机 视角移动的数据。具体代码为:
swipe(e=>{
lat += y * touchYSens;
lon += x * touchXSens;
lat = Math.max(-88, Math.min(88, lat));
})
复制代码
Math.max(-88, Math.min(88, lat))
: 控制 latitude 的移动范围值Web 获取陀螺仪的信息主要是经过 deviceorientation
事件获取的。其会提供相关的陀螺仪参数,alpha、beta、gamma。若是,不了解其内部的原理,光看它的参数来讲,你也基本上是不会用的。具体原理,能够参考一下:orientation 陀螺仪 API。
简单来讲,陀螺仪的参数在标准状况下,手机有两份坐标:
以手机自己为坐标点,地球坐标如图所示:
手机参考点是手机平面,一样也有 3 个坐标系 X/Y/Z。
而后,手机自身在旋转或者移动时,取一下变换值就能够获得 ,alpha、beta、gamma。
其他的内容,直接参考一下 陀螺仪 API 便可。这里,咱们就直接来看一下怎样经过陀螺仪来改变 相机 角度:
lat -= beta * betaSens;
lon += gamma * gammaSens;
lat = Math.max(-88, Math.min(88, lat));
复制代码
beta 是手机上下转动,lon 是手机左右转动。每次经过返回 orientation 的变更值,应用到具体 latitude 和 lontitude 的变化结果。
对于 3D 直播来讲,还有不少点能够说,好比,全景点击,全景切换等等。若是想本身手动打造一个全景直播组件,这个就不必了,这里,Now IVWeb 团队提供了一个 iv-panorama 的组件,里面提供了不少便捷的特性,好比,touch 控制,陀螺仪控制,图片全景,视频全景等功能。
iv-panorama 是 IVWEB 团队,针对于全景直播这个热点专门开发的一个播放器。如今 Web 对 VR 支持度也不是特别友好,可是,对于全景视频来讲,在机器换代更新的前提下,全景在性能方面的瓶颈慢慢消失了。其主要特性为:
项目地址为:iv-panorama。该项目使用很是简单,有两种全景模式,一个是 图片,一个是视频:
import VRPlayer from 'iv-panorama';
new VRPlayer({
player: {
url: '/test/003.mp4'
},
container:document.getElementById('container')
});
// image
let panorama = new VRPlayer({
image: {
url: './banner.png'
},
container:document.getElementById('container')
});
复制代码
全景资源都已经放在 github 仓库了,有兴趣的能够实践观察一下。