現在做 Web 全景合適嗎?

villainhr發表於2018-08-01

Web 全景在以前頻寬有限的條件下常常用來作為街景和 360° 全景圖片的檢視。它可以給使用者一種 self-immersive 的體驗,通過簡單的操作,自由的檢視周圍的物體。隨著一些運營商推出大王卡等免流服務,以及 4G 環境的普及,大流量的應用也逐漸得到推廣。比如,我們是否可以將靜態低流量的全景圖片,變為動態直播的全景視訊呢?在一定網速頻寬下,是可以實現的。後面,我們來了解一下,如何在 Web 端實現全景視訊。先看一下例項 gif:

gif

tl;dr;

  • 使用 three.js 實現全景技術
  • UV 對映原理簡介
  • 3D 座標原理和移動控制
  • Web 陀螺儀簡介
  • iv-panorama 簡單庫介紹

基於 Three.js

全景視訊是基於 3D 空間,而在 Web 中,能夠非常方便觸控到 3D 空間的技術,就是 WebGL。為了簡化,這裡就直接採用 Three.js 庫。具體的工作原理就是將正在播放的 video 元素,對映到紋理(texture) 空間中,通過 UV 對映,直接貼到一個球面上。精簡程式碼為:

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);
複製程式碼

具體的過程差不多就是上面的程式碼。上面程式碼中有兩塊需要注意一下,一個是 相機的視野範圍值,一個是幾何球體的相關引數設定。

相機視野範圍

具體程式碼為:

let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100); 
複製程式碼

這裡主要利用透視型別的相機,模擬人眼的效果。設定合適的視野效果,這裡的範圍還需要根據球體的直徑來決定,通常為 2*radius + 100,反正只要比球體直徑大就行。

幾何球體的引數設定

let  geometry = new THREE.SphereBufferGeometry(400, 60, 60);
// 在貼圖的時候,讓畫素點朝內(非常重要)
geometry.scale(-1, 1, 1);
複製程式碼

上面其實有兩個部分需要講解一下

  • 球體引數設定裡面有三個屬性值比較重要,該 API 格式為:SphereBufferGeometry(radius, widthSegments, heightSegments,...)
    • raidus: 設定球體的半徑,半徑越大,視訊在 canvas 上繪製的內容也會被放大,該設定值合適就行。
    • width/height Segments: 切片數,主要用來控制球體在寬高兩個維度上最多細分為多少個三角切片數量,越高紋理拼接的邊角越清晰。不過,並不是無限制高的,高的同時效能損耗也是有的。
  • 在幾何繪製時,通過座標變換使 X 軸的畫素點朝內,讓使用者看起來不會存在 凸出放大的效果。具體程式碼為:geometry.scale(-1, 1, 1)

UV 對映

上面只是簡單介紹了一下程式碼,如果僅僅只是為了應用,那麼這也就足夠了。但是,如果後面遇到優化的問題,不知道更底層的或者更細節內容的話,就感覺很尷尬。在全景視訊中,有兩個非常重要的點:

  • UV 對映
  • 3D 移動

這裡,我們主要探索一下 UV 對映的細節。UV 對映主要目的就是將 2D 圖片對映到三維物體上,最經典的解釋就是:

盒子是一個三維物體,正如同加到場景中的一個曲面網路("mesh")方塊. 如果沿著邊縫或摺痕剪開盒子,可以把盒子攤開在一個桌面上.當我們從上往下俯視桌子時,我們可以認為U是左右方向,V是上下方向.盒子上的圖片就在一個二維座標中.我們使用U V代表"紋理座標系"來代替通常在三維空間使用的 X Y. 在盒子重新被組裝時,紙板上的特定的UV座標被對應到盒子的一個空間(X Y Z)位置.這就是將2D影象包裹在3D物體上時計算機所做的.

image.png-544.6kB
from 浙江研報

這裡,我們通過程式碼來細緻講解一下。我們需要完成一個貼圖,將如下的 sprite,貼到一個正方體上。

sprite

from iefreer

cube

這裡,我們先將圖片載入到紋理空間:

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 對映原理應該很容易了。

3D 移動原理

這裡需要注意的是 Web 全景不是 WebVR。全景沒有 VR 那種沉浸式體驗,單單隻涉及三個維度上的旋轉而沒有移動距離這個說法。

上面的描述中,提到了三維,旋轉角度 這兩個概念,很容易讓我們想到《高中數學》學到的一個座標系--球座標系(這裡預設都是右手座標系)。

球座標

  • φ 是和 z 軸正方向 <=180°的夾角
  • ∂ 是和 x 軸正方向 <=180°的夾角
  • p 是空間點距離原點的直線距離

計算公式為:

formula

現在,如果應用到 Web 全景,我們可以知道幾個已知條件:

  • p:定義的球體(SphereBufferGeometry)的半徑大小
  • ∆φ:使用者在 y 軸上移動的距離
  • ∆∂:使用者在 x 軸上移動的距離

p 這個是不變的,而 ∆φ 和 ∆∂ 則是根據使用者輸入來決定的大小值。這裡,就需要一個演算法來統一協定。該演算法控制的主要內容就是:

使用者的手指在 x/y 平面軸上的 ∆x/∆y 通過一定的比例換算成為 ∆φ/∆∂

如果考慮到陀螺儀就是:

使用者的手指在 x/y 平面軸上的 ∆x/∆y 通過一定的比例換算成為 ∆φ/∆∂,使用者在 x/y 軸上旋轉的角度值 ∆φ'/∆∂',分別和視角角度進行合併,算出結果。

為了更寬泛的相容性,我們這裡根據第二種演算法的描述來進行講解。上面 ∆φ/∆∂ 的變動主要對映的是我們視野範圍的變化。

gif

在 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);
複製程式碼

這裡主要模擬地球座標:

  • lat 代表維度(latitude): 使用者上下滑動改變的值,或者手機上下旋轉
  • lon 代表經度(lontitude): 使用者左右滑動改變的值,或者手機左右旋轉

具體內容為:

image.png-17.9kB

在通常實踐當中,改變全景視角的維度有兩種,一種直接通過手滑,一種則根據陀螺儀旋轉。

簡單來說,就是監聽 touchorientation 事件,根據觸發資訊來手動改變 lat/lon 的值。不過,這裡有一個注意事項:

latitude 方向上最多隻能達到 (-90,90),否則會造成螢幕翻轉的效果,這種體驗非常不好。

我們分別通過程式碼來實踐一下。

新增 touch 控制

Touch 相關的事件在 Web 中,其實可以講到你崩潰為止,比如,使用者用幾個手指觸控螢幕?使用者具體在螢幕上的手勢是什麼(swipezoom)?

這裡,我們簡單起見,只針對一個手指滑動的距離來作為 相機 視角移動的資料。具體程式碼為:

swipe(e=>{
	lat += y * touchYSens;
	lon += x * touchXSens;
	
	lat = Math.max(-88, Math.min(88, lat));
})
複製程式碼
  • touchYSens/touchXSens 用來控制靈敏度,這可以自行除錯,比如 0.5。
  • x/y: 手指單次移動的距離
  • Math.max(-88, Math.min(88, lat)): 控制 latitude 的移動範圍值

新增陀螺儀控制

Web 獲取陀螺儀的資訊主要是通過 deviceorientation 事件獲取的。其會提供相關的陀螺儀引數,alpha、beta、gamma。如果,不瞭解其內部的原理,光看它的引數來說,你也基本上是不會用的。具體原理,可以參考一下:orientation 陀螺儀 API

簡單來說,陀螺儀的引數在標準情況下,手機有兩份座標:

  • 地球座標 x/y/z:在任何情況下,都是恆定方向
  • 手機平面座標 x/y/z:相當於手機螢幕定義的方向

以手機本身為座標點,地球座標如圖所示:

此處輸入圖片的描述

  • x:表示東西朝向,X 正向指向東
  • y:表示南北朝向,Y 正向指向北
  • z:垂直於地心,Z 正向指向上

手機參考點是手機平面,同樣也有 3 個座標系 X/Y/Z。

  • 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 簡介

iv-panorama 是 IVWEB 團隊,針對於全景直播這個熱點專門開發的一個播放器。現在 Web 對 VR 支援度也不是特別友好,但是,對於全景視訊來說,在機器換代更新的前提下,全景在效能方面的瓶頸慢慢消失了。其主要特性為:

  • 依賴於 Three.js,需要預先掛載到 window 物件上
  • 靈活配置,內建支援陀螺儀和 touch 控制。
  • 支援靈敏度引數的動態調整
  • 使用 ES6 語法
  • 相容 React,jQuery(簡單湊數的)

專案地址為: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 倉庫了,有興趣的可以實踐觀察一下。

相關文章