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 倉庫了,有興趣的能夠實踐觀察一下。