三種前端實現VR全景看房的方案!說不定哪天就用得上!
前言
事情是這樣的,前幾天我接到一個外包工頭
的新需求,某品牌要搭建一個在線VR展廳,用戶可以在手機上通過陀螺儀或者拖動來360度全景參觀展廳,這個VR展廳里會有一些信息點,點擊之后可以呈現更多信息(視頻,圖文等)...
我第一反應是用3D引擎,因為我不久前剛用three.js
做過一個BMW
的在線展廳,基本把three.js
摸熟了。
會另寫一篇文章教大家用threejs做這個[BMW在線DIY],感興趣的小伙伴請關注我吧~
方案一:WebGL3D引擎
使用3D引擎先搭一個基本的3D場景,下面的演示使用three.js,同類的3D引擎我還調研過babylon.js,playcanvas,使用都差不太多,學會一個基本都通的
var scene, camera, renderer; function initThree(){ //場景 scene = new THREE.Scene(); //鏡頭 camera = new THREE.PerspectiveCamera(90, document.body.clientWidth / document.body.clientHeight, 0.1, 100); camera.position.set(0, 0, 0.01); //渲染器 renderer = new THREE.WebGLRenderer(); renderer.setSize(document.body.clientWidth, document.body.clientHeight); document.getElementById("container").appendChild(renderer.domElement); //鏡頭控制器 var controls = new THREE.OrbitControls(camera, renderer.domElement); //一會兒在這里添加3D物體 loop(); } //幀同步重繪 function loop() { requestAnimationFrame(loop); renderer.render(scene, camera); } window.onload = initThree; 復制代碼
現在我們能看到一個黑乎乎的世界,因為現在scene
里什么都沒有,接着我們要把三維物體放進去了,使用3D引擎的實現方式無非都是以下幾種
使用立方體(box)實現
這種方式最容易理解,我們在一個房間里,看向天花板,地面,正面,左右兩面,背面共計六面。我們把所有六個視角拍成照片就得到下面六張圖
現在我們直接使用立方體(box)搭出這樣一個房間
var materials = []; //根據左右上下前后的順序構建六個面的材質集 var texture_left = new THREE.TextureLoader().load( './images/scene_left.jpeg' ); materials.push( new THREE.MeshBasicMaterial( { map: texture_left} ) ); var texture_right = new THREE.TextureLoader().load( './images/scene_right.jpeg' ); materials.push( new THREE.MeshBasicMaterial( { map: texture_right} ) ); var texture_top = new THREE.TextureLoader().load( './images/scene_top.jpeg' ); materials.push( new THREE.MeshBasicMaterial( { map: texture_top} ) ); var texture_bottom = new THREE.TextureLoader().load( './images/scene_bottom.jpeg' ); materials.push( new THREE.MeshBasicMaterial( { map: texture_bottom} ) ); var texture_front = new THREE.TextureLoader().load( './images/scene_front.jpeg' ); materials.push( new THREE.MeshBasicMaterial( { map: texture_front} ) ); var texture_back = new THREE.TextureLoader().load( './images/scene_back.jpeg' ); materials.push( new THREE.MeshBasicMaterial( { map: texture_back} ) ); var box = new THREE.Mesh( new THREE.BoxGeometry( 1, 1, 1 ), materials ); scene.add(box); 復制代碼
好,現在我們把鏡頭camera(也就是人的視角),放到box內,並且讓所有貼圖向內翻轉后,VR全景就實現了。
box.geometry.scale( 1, 1, -1 ); 復制代碼
現在我們進入了這個盒子!!
使用球體(sphere)實現
我們將房間360度球形范圍內所有的光捕捉到一個圖片上,再將這張圖片展開為矩形,就能得到這樣一張全景圖片
var sphereGeometry = new THREE.SphereGeometry(/*半徑*/1, /*垂直節點數量*/50, /*水平節點數量*/50);//節點數量越大,需要計算的三角形就越多,影響性能 var sphere = new THREE.Mesh(sphereGeometry); sphere.material.wireframe = true;//用線框模式大家可以看得清楚是個球體而不是圓形 scene.add(sphere); 復制代碼
現在我們把這個全景圖片貼到這個球體上
var texture = new THREE.TextureLoader().load('./images/scene.jpeg'); var sphereMaterial = new THREE.MeshBasicMaterial({map: texture}); var sphere = new THREE.Mesh(sphereGeometry,sphereMaterial); // sphere.material.wireframe = true; 復制代碼
和之前一樣,我們把鏡頭camera(也就是人的視角),放到球體內,並且讓所有貼圖向內翻轉后,VR全景就實現了
現在我們進入了這個球體!!
var sphereGeometry = new THREE.SphereGeometry(/*半徑*/1, 50, 50); sphereGeometry.scale(1, 1, -1); 復制代碼
添加信息點
在VR全景中,我們需要放置一些信息點,用戶點擊之后做一些動作。
現在我們建立這樣一個點的數組
var hotPoints=[ { position:{ x:0, y:0, z:-0.2 }, detail:{ "title":"信息點1" } }, { position:{ x:-0.2, y:-0.05, z:0.2 }, detail:{ "title":"信息點2" } } ]; 復制代碼
遍歷這個數組,並將信息點的指示圖添加到3D場景中
var pointTexture = new THREE.TextureLoader().load('images/hot.png'); var material = new THREE.SpriteMaterial( { map: pointTexture} ); for(var i=0;i<hotPoints.length;i++){ var sprite = new THREE.Sprite( material ); sprite.scale.set( 0.1, 0.1, 0.1 ); sprite.position.set( hotPoints[i].position.x, hotPoints[i].position.y, hotPoints[i].position.z ); scene.add( sprite ); } 復制代碼
看到HOT指示圖了嗎?
添加點擊事件,首先將全部的sprite放到一個數組里
sprite.detail = hotPoints[i].detail;
poiObjects.push(sprite);
復制代碼
然后我們通過射線檢測(raycast),就像是鏡頭中心向鼠標所點擊的方向發射出一顆子彈,去檢查這個子彈最終會打中哪些物體。
document.querySelector("#container").addEventListener("click",function(event){ event.preventDefault(); var raycaster = new THREE.Raycaster(); var mouse = new THREE.Vector2(); mouse.x = ( event.clientX / document.body.clientWidth ) * 2 - 1; mouse.y = - ( event.clientY / document.body.clientHeight ) * 2 + 1; raycaster.setFromCamera( mouse, camera ); var intersects = raycaster.intersectObjects( poiObjects ); if(intersects.length>0){ alert("點擊了熱點"+intersects[0].object.detail.title); } }); 復制代碼
方案二:CSS3D
threejs
等3d引擎太強大了,這些引擎的代碼量都有大幾百K,在今天的網速下顯得無所謂,但在幾年前我接到需求時仍然是重要的考量因素。既然我們只用到3D引擎的一點點功能,那么能否找到一個更加輕量的3D引擎呢。
有!css3d-engine,這個3d引擎只有14kb
,並且在多個大牌商業項目中應用
- 淘寶造物節 shrek.imdevsh.com/show/zwj/
- adidas絕不凋謝 shrek.imdevsh.com/show/drose/
- adidas勝勢全開 shrek.imdevsh.com/show/bbcny/
- adidas絕不跟隨 shrek.imdevsh.com/show/crazyl…
使用skybox實現
window.onload=initCSS3D; function initCSS3D(){ var s = new C3D.Stage(); s.size(window.innerWidth, window.innerHeight).update(); document.getElementById('container').appendChild(s.el); var box = new C3D.Skybox(); box.size(954).position(0, 0, 0).material({ front: {image: "images/scene_front.jpeg"}, back: {image: "images/scene_back.jpeg"}, left: {image: "images/scene_right.jpeg"}, right: {image: "images/scene_left.jpeg"}, up: {image: "images/scene_top.jpeg"}, down: {image: "images/scene_bottom.jpeg"}, }).update(); s.addChild(box); function loop() { angleX += (curMouseX - lastMouseX + lastAngleX - angleX) * 0.3; angleY += (curMouseY - lastMouseY + lastAngleY - angleY) * 0.3; s.camera.rotation(angleY, -angleX, 0).updateT(); requestAnimationFrame(loop); } loop(); var lastMouseX = 0; var lastMouseY = 0; var curMouseX = 0; var curMouseY = 0; var lastAngleX = 0; var lastAngleY = 0; var angleX = 0; var angleY = 0; document.addEventListener("mousedown", mouseDownHandler); document.addEventListener("mouseup", mouseUpHandler); function mouseDownHandler(evt) { lastMouseX = curMouseX = evt.pageX; lastMouseY = curMouseY = evt.pageY; lastAngleX = angleX; lastAngleY = angleY; document.addEventListener("mousemove", mouseMoveHandler); } function mouseMoveHandler(evt) { curMouseX = evt.pageX; curMouseY = evt.pageY; } function mouseUpHandler(evt) { curMouseX = evt.pageX; curMouseY = evt.pageY; document.removeEventListener("mousemove", mouseMoveHandler); } } 復制代碼
方案二的好處除了庫很小以外,還是div+css來搭建三維場景的。但這個庫的作者幾乎不維護,遇到問題必須得自己想辦法解決,比如使用在電腦上會看到明顯的面片邊緣
但是在手機上瀏覽的話表現還是相當完美的
添加信息點
我們繼續為它添加可交互的信息點
var hotPoints=[ { position:{ x:0, y:0, z:-476 }, detail:{ "title":"信息點1" } }, { position:{ x:0, y:0, z:476 }, detail:{ "title":"信息點2" } } ]; 復制代碼
function initPoints(){ var poiObjects = []; for(var i=0;i<hotPoints.length;i++){ var _p = new C3D.Plane(); _p.size(207, 162).position(hotPoints[i].position.x,hotPoints[i].position.y,hotPoints[i].position.z).material({ image: "images/hot.png", repeat: 'no-repeat', bothsides: true,//注意這個兩面貼圖的屬性 }).update(); s.addChild(_p); _p.el.detail = hotPoints[i].detail; _p.on("click",function(e){ console.log(e.target.detail.title); }) } } 復制代碼
這樣就可以顯示信息點了,並且由於是div,我們非常容易添加鼠標點擊交互等效果
不過,bothsides
屬性為true時,背面的信息點圖片是反的。
所以我們這里要做一點處理,根據其與相機的夾角重置一下信息點的旋轉角度。(如果是那種怎么旋轉都無所謂的圖片,比如圓點則無需處理
)
var r = Math.atan2(hotPoints[i].position.z-0,0-0) * 180 / Math.PI+90; _p.size(207, 162).position(hotPoints[i].position.x,hotPoints[i].position.y,hotPoints[i].position.z).material({ image: "images/hot.png", repeat: 'no-repeat', bothsides: false, }).update(); 復制代碼
需求升級了!
以上兩個方案,我以為可以給客戶交差了。但客戶又提出了一些想法
-
全景圖質量需要更高,但加載速度不允許更慢
-
每個場景的信息點挺多的,坐標編輯太麻煩了
當時我心里想,總共才收你萬把塊錢,難不成還得給你定制個引擎,再做個可視化編輯器?
直到客戶發過來一個參考鏈接,我看完驚呆了
,全景圖非常清晰,但首次加載速度極快,像百度地圖一樣,是一塊塊從模糊到清晰被加載出來的。
通過檢查參考鏈接網頁的代碼,發現了方案三
方案三:pano2vr
pano2vr是一款所見即所得的全景VR制作軟件(正版149歐元),功能挺強大的,可以直接輸出成HTML5靜態網頁,體驗非常不錯。
而其核心庫pano2vr_player.js
代碼量也只有238kb
。
我們可以直接使用這個軟件來可視化的添加信息點,輸出成HTML5后,除了靜態圖片以外,所有配置信息都在這個pano.xml
文件里
修改信息點圖片
整體的交互體驗都非常好,但默認的信息點樣式不喜歡,我們可以通過下面的代碼來修改信息點圖片
pano.readConfigUrlAsync("pano.xml",()=>{ var pois=pano.getPointHotspotIds(); var hotScale = 0.2; for(var i=0;i<pois.length;i++){ var ids=pois[i]; var hotsopt=pano.getHotspot(ids); hotsopt.div.firstChild.src="images/hot.png"; hotsopt.div.firstChild.style.width = 207*hotScale+"px"; hotsopt.div.firstChild.style.height = 162*hotScale+"px"; hotsopt.div.onmouseover = null; hotsopt.div.setAttribute("ids",ids); hotsopt.div.onclick=function() { //在這里可以響應信息點的點擊事件啦 console.log(this.getAttribute("ids")); }; } }); 復制代碼
哈哈,沒想到最終的方案不僅極其簡單的就實現了體驗良好的VR全景,還附送了非常方便的信息點編輯。除去第一次開發的耗時,以后再制作新的VR場景也就是花個10分鍾即可搞定。