Web全景圖的原理及實(shí)現(xiàn)
時(shí)間:2023-07-19 23:54:02 | 來源:網(wǎng)站運(yùn)營(yíng)
時(shí)間:2023-07-19 23:54:02 來源:網(wǎng)站運(yùn)營(yíng)
Web全景圖的原理及實(shí)現(xiàn):
全景圖的基本原理全景圖是一種廣角圖。通過全景播放器可以讓觀看者身臨其境地進(jìn)入到全景圖所記錄的場(chǎng)景中去。比如像是這個(gè)。這種看起來很高大上的效果其實(shí)背后的原理并不復(fù)雜。
通常標(biāo)準(zhǔn)的全景圖是一張2:1的圖像,其背后的實(shí)質(zhì)就是等距圓柱投影。等距圓柱投影是一種將球體上的各個(gè)點(diǎn)投影到圓柱體的側(cè)面上的一種投影方式,投影完之后再將它展開就是一張2:1的長(zhǎng)方形的圖像。比較常見的就是應(yīng)用在地圖上的投影。
而在對(duì)全景圖進(jìn)行展示之前就需要得到一張這樣的圖像,這種圖像可以自己用普通相機(jī)拍攝再自己合成,也可以直接使用專門的全景相機(jī)進(jìn)行拍攝。全景照片的拍攝在網(wǎng)上有比較多的教程,由于這不是攝影分享就不詳細(xì)的去說了:P。
在得到了全景圖之后,就是要怎么去展示的問題了。接下來就要說說全景展示的原理。全景展示其實(shí)是等距圓柱投影的逆過程,我們要做的就是將我們得到的全景圖,貼圖到一個(gè)球體上,熟悉webgl的,可以用它畫一個(gè)球體,然后將全景圖作為材質(zhì)貼到這個(gè)球體上進(jìn)行渲染。由于使用webgl來進(jìn)行編程的話,需要自己進(jìn)行比較多的3d運(yùn)算,所以也可以選擇使用api更加友好的3D庫(kù),如THREE.JS來編程。比如下面的這張全景圖,在球面上進(jìn)行貼圖。
這時(shí)我們看到的還跟預(yù)想的全景不一樣,那是因?yàn)槲覀冊(cè)谇虻耐饷妫?dāng)我們?cè)谇虻睦锩鏁r(shí),看到的就是跟一開始的示例一樣的效果了。像是下面的這個(gè)示意圖這樣。
用threejs進(jìn)行編程的話,關(guān)鍵的代碼如下:
//新建一個(gè)球體var geometry = new THREE.SphereGeometry( 500, 60, 40 );//沿x軸進(jìn)行-1的scale,讓球體的面朝內(nèi)(因?yàn)槲覀儗那騼?nèi)進(jìn)行觀看)。geometry.scale( - 1, 1, 1 );//載入一張全景圖生成threejs中可以使用的材質(zhì)var material = new THREE.MeshBasicMaterial( { map: new THREE.TextureLoader().load( 'panoPic.jpg' )} );//將幾何體和材質(zhì)進(jìn)行結(jié)合。mesh = new THREE.Mesh( geometry, material );
兼容性雖然使用webgl可以很容易的就生成一個(gè)全景的場(chǎng)景,但是在web里,兼容性似乎是個(gè)揮之不去的話題。主要是由于webgl不支持Android5.0以下的機(jī)器,所以,用webgl來實(shí)現(xiàn)將會(huì)將很多用戶排除在外。所以只能尋求更好的解決方案。首先想到的就是css 3D transform 和 2D canvas畫布。在threejs里支持在2D的canvas里進(jìn)行繪制,本是一個(gè)比較好的方案,但是經(jīng)過測(cè)試之后,發(fā)現(xiàn)2d畫布來繪制3d的場(chǎng)景,性能上太吃力。所以,也被排除。剩下的就是css變換了。但是,要怎么用css來畫一個(gè)一個(gè)球體呢?答案顯然是不行的。雖然css不能繪制一個(gè)球體,但是css通過3D變換來繪制立方體還是簡(jiǎn)單一些的。那么用立方體可不可以實(shí)現(xiàn)一樣的效果呢?
球體到立方體根據(jù)全景圖的原理,我們是把視角放在球的中心,通過從球心觀看球面上正式場(chǎng)景在球面上的映像從而產(chǎn)生一種空間中全方位的視覺體驗(yàn),同理,對(duì)于立方體,應(yīng)該也可以使用相同的方式來實(shí)現(xiàn)。而我們要做,就是把球面上的像素點(diǎn)映射到立方體上。
說了基本的原理,接下來就是進(jìn)行數(shù)學(xué)建模了。首先我們建立一個(gè)球坐標(biāo)系,坐標(biāo)系描述的變量分別為半徑r,豎直方向上的夾角θ,水平方向上的夾角?,對(duì)于球體,我們可以假定
r=10 < θ < π -π/4 < ? < 7π/4
這樣我們就可以得到球面上的各個(gè)點(diǎn)在直角坐標(biāo)系中的x,y,z
x= r sin θ cos ?y= r sin θ sin ?z= r cos θ
對(duì)于球面到立方體上的投影,我們需要的是角度θ和?相同時(shí),延長(zhǎng)球的半徑r直到和立方體的面相交,假設(shè)這個(gè)長(zhǎng)度是R,由于我們?cè)O(shè)了半徑r是1所以球面上的點(diǎn)為 (sin θ cos ?, sin θ sin ?, cos θ) 對(duì)應(yīng)的立方體上的點(diǎn)是(Rsin θ cos ?, Rsin θ sin ?, Rcos θ)
如果我們要求x=1這個(gè)平面上的點(diǎn),則
1=Rsin θ cos ?
則可以求出來
R= 1/(sin θ cos ?)
所以在x平面上映射的點(diǎn)就是
(1, tan ?, cot θ / cos ?)
在立方體另外的五個(gè)平面上的投影也可以類似地得出。通過上述方法轉(zhuǎn)換一張全景圖,可以得到以下結(jié)果。
離我們想要的效果還有一些差距,圖中似乎多了一些黑色的線。導(dǎo)致這種現(xiàn)象的原因是,由于我們的處理是以像素為單位來進(jìn)行處理的,通過遍歷球面圖上的每個(gè)像素然后投影到立方體上的面來實(shí)現(xiàn)。經(jīng)過這種方式進(jìn)行投射之后,立方體的面上就會(huì)有一些像素被重復(fù)設(shè)置,而一些地方的像素就會(huì)缺失,比如圖中的黑色部分(由于底色的黑色的)。為了解決這個(gè)問題,我們可以通過逆向的方法來解決,也就是遍歷立方體上面上的每個(gè)點(diǎn),求得映射到球面上的位置,然后獲取球面上最接近的位置的像素。
得到了立方體上需要的圖之后,就可以用css 3D變換來實(shí)現(xiàn)全景圖了。
展示更多信息單單地進(jìn)行全景觀看可能還不能滿足我們的需求,也許我們還需要展示更多的信息,而這些信息可能是跟全景中的內(nèi)容相關(guān)的,比如給全景中的物品打標(biāo),給全景中的內(nèi)容添加評(píng)論,像是下圖這樣
對(duì)于這個(gè)實(shí)現(xiàn)的關(guān)鍵在于,屏幕2D坐標(biāo)和空間3D坐標(biāo)之間的相互轉(zhuǎn)換。第一步需要實(shí)現(xiàn)的是記錄,當(dāng)用戶點(diǎn)擊屏幕,要根據(jù)點(diǎn)擊的位置來計(jì)算出和空間中的立方體相交的點(diǎn)并記下這個(gè)點(diǎn)的位置信息。一些3d庫(kù)當(dāng)中會(huì)有一些api來幫助完成這項(xiàng)工作。而在THREEJS中使用的是Raycaster,Raycaster可以生成一條直線,然后可以很方便地得到三維空間中,和這條直線相交的物體和點(diǎn)。由于THREEJS中是以繪制的中心作為原點(diǎn),而鼠標(biāo)的點(diǎn)擊位置是以左上角為原點(diǎn),所以需要進(jìn)行一下轉(zhuǎn)換。
//將鼠標(biāo)點(diǎn)擊事件中的位置信息,轉(zhuǎn)換到位置中心 var mouse = new THREE.Vector2( ( ev.clientX / _this.wrapper.width() ) * 2 - 1, -( ev.clientY / _this.wrapper.height() ) * 2 + 1 )
然后就可以初始化一個(gè)Raycaster獲得需要的內(nèi)容了
//創(chuàng)建一個(gè)Raycaster實(shí)例 var raycaster = new THREE.Raycaster()//根據(jù)點(diǎn)擊的位置,從鏡頭開始初始化一和鏡頭的屏幕垂直的直線 raycaster.setFromCamera(mouse, _this.camera)//獲得和直線相交的物體 var intersects = raycaster.intersectObjects(_this.scene.children)
這樣就在空間中記錄下了一個(gè)目標(biāo)位置。
有了三維中的位置之后,如果我們想要展示這個(gè)位置相關(guān)信息,比如打一個(gè)標(biāo)簽,就是需要將空間中的位置還原到屏幕的二維坐標(biāo)上,然后用傳統(tǒng)的css方法來進(jìn)行展示就可以了。
//根據(jù)上一步中記錄的位置生成一個(gè)向量var vector = new THREE.Vector3(pos.x, pos.y, pos.z)//將這個(gè)向量映射到鏡頭的平面上vector.project(camera)//將位置信息還原成以左上角為原點(diǎn)的位置信息var screenPos = { left: Math.round(( vector.x + 1 ) * wrapper.width() / 2), top: Math.round(( -vector.y + 1 ) * wrapper.height() / 2)}
作者: 阿里聚劃算技術(shù)團(tuán)隊(duì)
鏈接:
http://www.imooc.com/article/14196來源:慕課網(wǎng)
本文原創(chuàng)發(fā)布于慕課網(wǎng) ,轉(zhuǎn)載請(qǐng)注明出處,謝謝合作!
關(guān)鍵詞:原理,實(shí)現(xiàn)