本文探索在Web前端實現AR導航效果的前沿技術和難點。html
加強現實(Augmented Reality,簡稱AR):是一種實時地計算攝影機影像的位置及角度並加上相應圖像、視頻、3D模型的技術,這種技術的目標是在屏幕上把虛擬世界套在現實世界並進行互動。前端
通常在web中實現AR效果的主要步驟以下:html5
以上參考:如何經過 Web 技術實現一個簡單但有趣的 AR 效果web
AR導航比較特殊的地方是,它並不是經過識別marker來肯定虛擬物體的疊加位置,而是經過定位將虛擬和現實聯繫在一塊兒,主要步驟以下:編程
如上文所述AR導航的主要步驟,其中難點在於:瀏覽器
不一樣設備不一樣操做系統以及不一樣瀏覽器帶來的兼容性問題主要體如今對獲取視頻流和獲取設備陀螺儀信息的支持上。安全
Navigator API兼容處理bash
navigator.getUserMedia()
已不推薦使用,目前新標準採用navigator.mediaDevices.getUserMedia()
。但是不一樣瀏覽器對新方法的支持程度不一樣,須要進行判斷和處理。同時,若是採用舊方法,在不一樣瀏覽器中方法名稱也不盡相同,好比webkitGetUserMedia
。微信
//不支持mediaDevices屬性
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
//不支持mediaDevices.getUserMedia
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function(constraints) {
var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
if(!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
}
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
}
複製代碼
參數兼容處理app
getUserMedia
接收一個MediaStreamConstraints
類型的參數,該參數包含兩個成員video
和audio
。
var constraints = {
audio: true,
video: {
width: {
min: 1024,
ideal: 1280,
max: 1920
},
height: 720,
frameRate: {
ideal: 10,
max: 15
},
facingMode: "user" // user/environment,設置先後攝像頭
}
}
複製代碼
在使用WebAR導航時,須要調取後置攝像頭,然而facingMode
參數目前只有Firefox和Chrome部分支持,對於其餘瀏覽器(微信、手Q、QQ瀏覽器)須要另外一個參數optional.sourceId
,傳入設備媒體源的id
。經測試,該方法在不一樣設備不一樣版本號的微信和手Q上表現有差別。
if(MediaStreamTrack.getSources) {
MediaStreamTrack.getSources(function (sourceInfos) {
for (var i = 0; i != sourceInfos.length; ++i) {
var sourceInfo = sourceInfos[i];
//這裏會遍歷audio,video,因此要加以區分
if (sourceInfo.kind === 'video') {
exArray.push(sourceInfo.id);
}
}
constraints = {
video: {
optional: [{
sourceId: exArray[1] //0爲前置攝像頭,1爲後置
}]
}
};
});
} else {
constraints = {
video: {
facingMode: {
exact: 'environment'
}
}
});
}
複製代碼
操做系統的兼容性問題
因爲蘋果的安全機制問題,iOS設備任何瀏覽器都不支持getUserMedia()。因此沒法在iOS系統上實現WebAR導航。
協議
出於安全考慮,Chrome47以後只支持HTTPS頁面獲取視頻源。
設備的轉動角度表明了用戶的視角,也是鏈接虛擬和現實的重要參數。HTML5提供DeviceOrientation API
能夠實時獲取設備的旋轉角度參數。經過監聽deviceorientation
事件,返回DeviceOrientationEvent
對象。
{
absolute: [boolean] 是否爲絕對轉動值
alpha: [0-360]
beta: [-180-180]
gamma: [-90-90]
}
複製代碼
其中alpha、beta、gamma
是咱們想要獲取的角度,它們各自的意義能夠參照下圖和參考文章:
然而iOS系統的webkit內核瀏覽器中,該對象還包括webkitCompassHeading
成員,其值爲設備與正北方向的偏離角度。同時iOS系統的瀏覽器中,alpha
並不是絕對角度,而是以開始監聽事件時的角度爲零點。
Android系統中,咱們可使用-alpha
獲得設備與正北方的角度,可是就目前的測試狀況看來,該值並不穩定。因此在測試Demo中加入了手動校訂alpha
值的過程,在導航開始前將設備朝向正北方來獲取絕對0度,雖然不嚴謹但效果還不錯。
WebGL是在瀏覽器中實現三維效果的一套規範,AR導航須要繪製出不一樣距離不一樣角度的標記點,就須要三維效果以適應真實場景視頻流。然而WebGL原生的接口很是複雜,Three.js是一個基於WebGL的庫,它對一些原生的方法進行了簡化封裝,使咱們可以更方便地進行編程。
Three.js中有三個主要概念:
在AR導航的代碼中,我對Three.js的建立過程進行了封裝,只需傳入DOM元素(通常爲<div>
,做爲容器)和參數,自動建立三大組件,並提供了Three.addObject
和Three.renderThree
等接口方法用於在場景中添加/刪除物體或更新渲染等。
function Three(cSelector, options) {
var container = document.querySelector(cSelector);
// 建立場景
var scene = new THREE.Scene();
// 建立相機
var camera = new THREE.PerspectiveCamera(options.camera.fov, options.camera.aspect, options.camera.near, options.camera.far);
// 建立渲染器
var renderer = new THREE.WebGLRenderer({
alpha: true
});
// 設置相機轉動控制器
var oriControls = new THREE.DeviceOrientationControls(camera);
// 設置場景大小,並添加到頁面中
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setClearColor(0xFFFFFF, 0.0);
container.appendChild(renderer.domElement);
// 暴露在外的成員
this.main = {
scene: scene,
camera: camera,
renderer: renderer,
oriControls: oriControls,
}
this.objects = [];
this.options = options;
}
Three.prototype.addObject = function(type, options) {...} // 向場景中添加物體,type支持sphere/cube/cone
Three.prototype.popObject = function() {...} // 刪除場景中的物體
Three.prototype.setCameraPos = function(position) {...} // 設置相機位置
Three.prototype.renderThree = function(render) {...} // 渲染更新,render爲回調函數
Three.prototype.setAlphaOffset = function(offset) {..} // 設置校訂alpha的偏離角度
複製代碼
在控制相機的轉動上,我使用了DeviceOrientationControls
,它是Three.js官方提供的相機跟隨設備轉動的控制器,實現對deviceorientation
的偵聽和對DeviceOrientationEvent
的歐拉角處理,並控制相機的轉動角度。只需在渲染更新時調用一下update
方法:
three.renderThree(function(objects, main) {
animate();
function animate() {
window.requestAnimationFrame(animate);
main.oriControls.update();
main.renderer.render(main.scene, main.camera);
}
});
複製代碼
咱們的調研中目前有三種獲取定位的方案:原生navigator.geolocation
接口,騰訊前端定位組件,微信JS-SDK地理位置接口:
原生接口
navigator.geolocation
接口提供了getCurrentPosition
和watchPosition
兩個方法用於獲取當前定位和監聽位置改變。通過測試,Android系統中watchPosition
更新頻率低,而iOS中更新頻率高,但抖動嚴重。
前端定位組件
使用前端定位組件須要引入JS模塊(https://3gimg.qq.com/lightmap/components/geolocation/geolocation.min.js
),經過 qq.maps.Geolocation(key, referer)
構造對象,也提供getLocation
和watchPosition
兩個方法。通過測試,在X5內核的瀏覽器(包括微信、手Q)中,定位組件比原生接口定位更加準確,更新頻率較高。
微信JS-SDK地理位置接口
使用微信JS-SDK接口,咱們能夠調用室內定位達到更高的精度,可是須要綁定公衆號,只能在微信中使用,僅提供getLocation
方法,暫時不考慮。
綜上所述,咱們主要考慮在X5內核瀏覽器中的實現,因此選用騰訊前端定位組件獲取定位。可是在測試中仍然暴露出了定位不許確的問題:
針對該問題,我設計了優化軌跡的方法,進行定位去噪、肯定初始中心點、根據路徑吸附等操做,以實現移動時的變化效果更加平穩且準確。
咱們經過getLocation
和watchPosition
方法獲取到的定位數據包含以下信息:
{
accuracy: 65,
lat: 39.98333,
lng: 116.30133
...
}
複製代碼
其中accuracy
表示定位精度,該值越低表示定位越精確。假設定位精度在固定的設備上服從正態分佈(準確來講應該是正偏態分佈),統計整條軌跡點定位精度的均值mean
和標準差stdev
,將軌跡中定位精度大於mean + (1~2) * stdev
的點過濾掉。或者採用箱型圖的方法去除噪聲點。
初始點很是重要,若初始點偏離,則路線不許確、虛擬現實沒法重疊、沒法獲取到正確的移動路線。測試中我發現定位開始時得到的定位點大多不太準確,因此須要一段時間來肯定初始點。
定位開始,設置N
秒用以獲取初始定位。N
秒鐘獲取到的定位去噪以後造成一個序列track_denoise = [ loc0, loc1, loc2...]
,對該序列中的每個點計算其到其餘點的距離之和,並加上自身的定位精度,獲得一箇中心衡量值,而後取衡量值最小的點爲起始點。
基於設備始終跟隨規劃路線進行移動的假設,能夠將定位點吸附到規劃路線上以防止3D圖像的抖動。
以下圖所示,以定位點到線段的映射點做爲校訂點。路線線段的選擇依據以下:
cur = 0; P_cur = P[cur];
N
條線段上移動時,若映射長度(映射點與線段起點的距離)爲負,校訂點取當前線段的起點,線路回退至上一線段,cur = N - 1; P_cur = P[cur];
;若映射長度大於線段長度,則校訂點取當前線段的終點,線路前進至下一線段,cur = N + 1; P_cur = P[cur];
WebGL中的單位長度與現實世界的單位長度並無肯定的映射關係,暫時還沒法準確進行映射。經過測試,暫且選擇1(米):15(WebGL單位長度)。
演示視頻:WebAR技術探索-導航中的應用