自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Threejs開發(fā)3D地圖實(shí)踐總結(jié)

開發(fā) 前端
前段時(shí)間連續(xù)上了一個(gè)月班,加班加點(diǎn)完成了一個(gè)3D攻堅(jiān)項(xiàng)目。也算是由傳統(tǒng)web轉(zhuǎn)型到webgl圖形學(xué)開發(fā)中,坑不少,做了一下總結(jié)分享。

前段時(shí)間連續(xù)上了一個(gè)月班,加班加點(diǎn)完成了一個(gè)3D攻堅(jiān)項(xiàng)目。也算是由傳統(tǒng)web轉(zhuǎn)型到webgl圖形學(xué)開發(fā)中,坑不少,做了一下總結(jié)分享。

1、法向量問題

法線是垂直于我們想要照亮的物體表面的向量。法線代表表面的方向因此他們?yōu)楣庠春臀矬w的交互建模中具有決定性作用。每一個(gè)頂點(diǎn)都有一個(gè)關(guān)聯(lián)的法向量。

 

如果一個(gè)頂點(diǎn)被多個(gè)三角形共享,共享頂點(diǎn)的法向量等于共享頂點(diǎn)在不同的三角形中的法向量的和。N=N1+N2; 

 

所以如果不做任何處理,直接將3維物體的點(diǎn)傳遞給BufferGeometry,那么由于法向量被合成,經(jīng)過片元著色器插值后,就會(huì)得到這個(gè)黑不溜秋的效果  

 

我的處理方式使頂點(diǎn)的法向量保持唯一,那么就需要在共享頂點(diǎn)處,拷貝一份頂點(diǎn),并重新計(jì)算索引,是的每個(gè)被多個(gè)面共享的頂點(diǎn)都有多份,每一份有一個(gè)單獨(dú)的法向量,這樣就可以使得每個(gè)面都有一個(gè)相同的顏色

 

2、光源與面塊顏色

開發(fā)過程中設(shè)計(jì)給了一套配色,然而一旦有光源,面塊的最終顏色就會(huì)與光源混合,顏色自然與最終設(shè)計(jì)的顏色大相徑庭。下面是Lambert光照模型的混合算法。

 

而且產(chǎn)品的要求是頂面保持設(shè)計(jì)的顏色,側(cè)面需要加入光源變化效果,當(dāng)對(duì)地圖做操作時(shí),側(cè)面顏色需要根據(jù)視角發(fā)生變化。那么我的處理方式是將頂面與側(cè)面分別繪制(創(chuàng)建兩個(gè)Mesh),頂面使用MeshLambertMaterial的emssive屬性設(shè)置自發(fā)光顏色與設(shè)計(jì)顏色保持一致,也就不會(huì)有光照效果,側(cè)面綜合使用Emssive與color來應(yīng)用光源效果。

 

3、POI標(biāo)注

Three中創(chuàng)建始終朝向相機(jī)的POI可以使用Sprite類,同時(shí)可以將文字和圖片繪制在canvas上,將canvas作為紋理貼圖放到Sprite上。但這里的一個(gè)問題是canvas圖像將會(huì)失真,原因是沒有合理的設(shè)置sprite的scale,導(dǎo)致圖片被拉伸或縮放失真。

 

問題的解決思路是要保證在3d世界中的縮放尺寸,經(jīng)過一系列變換投影到相機(jī)屏幕后仍然與canvas在屏幕上的大小保持一致。這需要我們計(jì)算出屏幕像素與3d世界中的長度單位的比值,然后將sprite縮放到合適的3d長度。

  

 

4、點(diǎn)擊拾取問題

webgl中3D物體繪制到屏幕將經(jīng)過以下幾個(gè)階段

 

所以要在3D應(yīng)用做點(diǎn)擊拾取,首先要將屏幕坐標(biāo)系轉(zhuǎn)化成ndc坐標(biāo)系,這時(shí)候得到ndc的xy坐標(biāo),由于2d屏幕并沒有z值所以,屏幕點(diǎn)轉(zhuǎn)化成3d坐標(biāo)的z可以隨意取值,一般取0.5(z在-1到1之間)。

  1. function fromSreenToNdc(x, y, container) { 
  2.  
  3.   return { 
  4.  
  5.     x: x / container.offsetWidth * 2 - 1, 
  6.  
  7.     y: -y / container.offsetHeight * 2 + 1, 
  8.  
  9.     z: 1 
  10.  
  11.   }; 
  12.  
  13.  
  14. function fromNdcToScreen(x, y, container) { 
  15.  
  16.   return { 
  17.  
  18.     x: (x + 1) / 2 * container.offsetWidth, 
  19.  
  20.     y: (1 - y) / 2 * container.offsetHeight 
  21.  
  22.   }; 
  23.  
  24.  

然后將ndc坐標(biāo)轉(zhuǎn)化成3D坐標(biāo):

  1. ndc = P * MV * Vec4 
  2.  
  3. Vec4 = MV-1 * P -1 * ndc  

這個(gè)過程在Three中的Vector3類中已經(jīng)有實(shí)現(xiàn):

  1. unproject: function () { 
  2.  
  3.   
  4.  
  5.         var matrix = new Matrix4(); 
  6.  
  7.   
  8.  
  9.         return function unproject( camera ) { 
  10.  
  11.   
  12.  
  13.             matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) ); 
  14.  
  15.             return this.applyMatrix4( matrix ); 
  16.  
  17.   
  18.  
  19.         }; 
  20.  
  21.   
  22.  
  23.     }(),  

將得到的3d點(diǎn)與相機(jī)位置結(jié)合起來做一條射線,分別與場(chǎng)景中的物體進(jìn)行碰撞檢測(cè)。首先與物體的外包球進(jìn)行相交性檢測(cè),與球不相交的排除,與球相交的保存進(jìn)入下一步處理。將所有外包球與射線相交的物體按照距離相機(jī)遠(yuǎn)近進(jìn)行排序,然后將射線與組成物體的三角形做相交性檢測(cè)。求出相交物體。當(dāng)然這個(gè)過程也由Three中的RayCaster做了封裝,使用起來很簡單:

  1. mouse.x = ndcPos.x; 
  2.  
  3. mouse.y = ndcPos.y; 
  4.  
  5. this.raycaster.setFromCamera(mouse, camera); 
  6.  
  7. var intersects = this.raycaster.intersectObjects(this._getIntersectMeshes(floor, zoom), true);  

5、性能優(yōu)化

隨著場(chǎng)景中的物體越來越多,繪制過程越來越耗時(shí),導(dǎo)致手機(jī)端幾乎無法使用。

 

在圖形學(xué)里面有個(gè)很重要的概念叫“one draw all”一次繪制,也就是說調(diào)用繪圖api的次數(shù)越少,性能越高。比如canvas中的fillRect、fillText等,webgl中的drawElements、drawArrays;所以這里的解決方案是對(duì)相同樣式的物體,把它們的側(cè)面和頂面統(tǒng)一放到一個(gè)BufferGeometry中。這樣可以大大降低繪圖api的調(diào)用次數(shù),極大的提升渲染性能。

 

這樣解決了渲染性能問題,然而帶來了另一個(gè)問題,現(xiàn)在是吧所有樣式相同的面放在一個(gè)BufferGeometry中(我們稱為樣式圖形),那么在面點(diǎn)擊時(shí)候就無法單獨(dú)判斷出到底是哪個(gè)物體(我們稱為物體圖形)被選中,也就無法對(duì)這個(gè)物體進(jìn)行高亮縮放處理。我的處理方式是,把所有的物體單獨(dú)生成物體圖形保存在內(nèi)存中,做面點(diǎn)擊的時(shí)候用這部分?jǐn)?shù)據(jù)來做相交性檢測(cè)。對(duì)于選中物體后的高亮縮放處理,首先把樣式面中相應(yīng)部分裁減掉,然后把選中的物體圖形加入到場(chǎng)景中,對(duì)它進(jìn)行縮放高亮處理。裁剪方法是,記錄每個(gè)物體在樣式圖形中的其實(shí)索引位置,在需要裁切時(shí)候?qū)⑦@部分索引制零。在需要恢復(fù)的地方在把這部分索引恢復(fù)成原狀。

6、面點(diǎn)擊移動(dòng)到屏幕中央

這部分也是遇到了不少坑,首先的想法是:

面中心點(diǎn)目前是在世界坐標(biāo)系內(nèi)的坐標(biāo),先用center.project(camera)得到歸一化設(shè)備坐標(biāo),在根據(jù)ndc得到屏幕坐標(biāo),而后根據(jù)面中心點(diǎn)屏幕坐標(biāo)與屏幕中心點(diǎn)坐標(biāo)做插值,得到偏移量,在根據(jù)OribitControls中的pan方法來更新相機(jī)位置。這種方式最終以失敗告終,因?yàn)橄鄼C(jī)可能做各種變換,所以屏幕坐標(biāo)的偏移與3d世界坐標(biāo)系中的位置關(guān)系并不是線性對(duì)應(yīng)的。

最終的想法是:

我們現(xiàn)在想將點(diǎn)擊面的中心點(diǎn)移到屏幕中心,屏幕中心的ndc坐標(biāo)永遠(yuǎn)都是(0,0)我們的觀察視線與近景面的焦點(diǎn)的ndc坐標(biāo)也是0,0;也就是說我們要將面中心點(diǎn)作為我們的觀察點(diǎn)(屏幕的中心永遠(yuǎn)都是相機(jī)的觀察視線),這里我們可以直接將面中心所謂視線的觀察點(diǎn),利用lookAt方法求取相機(jī)矩陣,但如果這樣簡單處理后的效果就會(huì)給人感覺相機(jī)的姿態(tài)變化了,也就是會(huì)感覺并不是平移過去的,所以我們要做的是保持相機(jī)當(dāng)前姿態(tài)將面中心作為相機(jī)觀察點(diǎn)。

回想平移時(shí)我們將屏幕移動(dòng)轉(zhuǎn)化為相機(jī)變化的過程是知道屏幕偏移求target,這里我們要做的就是知道target反推屏幕偏移的過程。首先根據(jù)當(dāng)前target與面中心求出相機(jī)的偏移向量,根據(jù)相機(jī)偏移向量求出在相機(jī)x軸和up軸的投影長度,根據(jù)投影長度就能返推出應(yīng)該在屏幕上的平移量。

  1. this.unprojectPan = function(deltaVector, moveDown) { 
  2.  
  3.     // var getProjectLength() 
  4.  
  5.     var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 
  6.  
  7.   
  8.  
  9.     var cxv = new Vector3(0, 0, 0).setFromMatrixColumn(scope.object.matrix, 0);// 相機(jī)x軸 
  10.  
  11.     var cyv = new Vector3(0, 0, 0).setFromMatrixColumn(scope.object.matrix, 1);// 相機(jī)y軸 
  12.  
  13.     // 相機(jī)軸都是單位向量 
  14.  
  15.     var pxl = deltaVector.dot(cxv)/* / cxv.length()*/; // 向量在相機(jī)x軸的投影 
  16.  
  17.     var pyl = deltaVector.dot(cyv)/* / cyv.length()*/; // 向量在相機(jī)y軸的投影 
  18.  
  19.   
  20.  
  21.     // offset=dx * vector(cx) + dy * vector(cy.project(xoz).normalize) 
  22.  
  23.     // offset由相機(jī)x軸方向向量+相機(jī)y軸向量在xoz平面的投影組成 
  24.  
  25.     var dv = deltaVector.clone(); 
  26.  
  27.     dv.sub(cxv.multiplyScalar(pxl)); 
  28.  
  29.     pyl = dv.length(); 
  30.  
  31.   
  32.  
  33.     if ( scope.object instanceof PerspectiveCamera ) { 
  34.  
  35.       // perspective 
  36.  
  37.   
  38.  
  39.       var position = scope.object.position; 
  40.  
  41.       var offset = new Vector3(0, 0, 0); 
  42.  
  43.       offset.copy(position).sub(scope.target); 
  44.  
  45.       var distance = offset.length(); 
  46.  
  47.       distance *= Math.tan(scope.object.fov / 2 * Math.PI / 180); 
  48.  
  49.   
  50.  
  51.       // var xd = 2 * distance * deltaX / element.clientHeight; 
  52.  
  53.       // var yd = 2 * distance * deltaY / element.clientHeight; 
  54.  
  55.       // panLeft( xd, scope.object.matrix ); 
  56.  
  57.       // panUp( yd, scope.object.matrix ); 
  58.  
  59.   
  60.  
  61.       var deltaX = pxl * element.clientHeight / (2 * distance); 
  62.  
  63.       var deltaY = pyl * element.clientHeight / (2 * distance) * (moveDown ? -1 : 1); 
  64.  
  65.   
  66.  
  67.       return [deltaX, deltaY]; 
  68.  
  69.     } else if ( scope.object instanceof OrthographicCamera ) { 
  70.  
  71.   
  72.  
  73.       // orthographic 
  74.  
  75.       // panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 
  76.  
  77.       // panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 
  78.  
  79.       var deltaX = pxl * element.clientWidth * scope.object.zoom / (scope.object.right - scope.object.left); 
  80.  
  81.       var deltaY = pyl * element.clientHeight * scope.object.zoom / (scope.object.top - scope.object.bottom); 
  82.  
  83.   
  84.  
  85.       return [deltaX, deltaY]; 
  86.  
  87.     } else { 
  88.  
  89.   
  90.  
  91.       // camera neither orthographic nor perspective 
  92.  
  93.       console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 
  94.  
  95.   
  96.  
  97.     } 
  98.  
  99.   }  

7、2/3D切換

23D切換的主要內(nèi)容就是當(dāng)相機(jī)的視線軸與場(chǎng)景的平面垂直時(shí),使用平行投影,這樣用戶只能看到頂面給人的感覺就是2D視圖。所以要根據(jù)透視的視錐體計(jì)算出平行投影的世景體。

 

因?yàn)橛脩魰?huì)在2D、3D場(chǎng)景下做很多操作,比如平移、縮放、旋轉(zhuǎn),要想無縫切換,這個(gè)關(guān)鍵在于將平行投影與視錐體相機(jī)的位置、lookAt方式保持一致;以及將他們放大縮小的關(guān)鍵點(diǎn):distance的比例與zoom來保持一致。

平行投影中,zoom越大代表六面體的首尾兩個(gè)面面積越小,放大越大。

 

8、3D中地理級(jí)別

地理級(jí)別實(shí)際是像素跟墨卡托坐標(biāo)系下米的對(duì)應(yīng)關(guān)系,這個(gè)有通用的標(biāo)準(zhǔn)以及計(jì)算公式:

  1. r=6378137 
  2.  
  3. resolution=2*PI*r/(2^zoom*256)  

各個(gè)級(jí)別中像素與米的對(duì)應(yīng)關(guān)系如下:

  1. resolution zoom 2048 blocksize 256 blocksize scale(dpi=160) 
  2.  
  3. 156543.0339 0 320600133.5 40075016.69 986097851.5 
  4.  
  5. 78271.51696 1 160300066.7 20037508.34 493048925.8 
  6.  
  7. 39135.75848 2 80150033.37 10018754.17 246524462.9 
  8.  
  9. 19567.87924 3 40075016.69 5009377.086 123262231.4 
  10.  
  11. 9783.939621 4 20037508.34 2504688.543 61631115.72 
  12.  
  13. 4891.96981 5 10018754.17 1252344.271 30815557.86 
  14.  
  15. 2445.984905 6 5009377.086 626172.1357 15407778.93 
  16.  
  17. 1222.992453 7 2504688.543 313086.0679 7703889.465 
  18.  
  19. 611.4962263 8 1252344.271 156543.0339 3851944.732 
  20.  
  21. 305.7481131 9 626172.1357 78271.51696 1925972.366 
  22.  
  23. 152.8740566 10 313086.0679 39135.75848 962986.1831 
  24.  
  25. 76.4370283 11 156543.0339 19567.87924 481493.0916 
  26.  
  27. 38.2185141 12 78271.51696 9783.939621 240746.5458 
  28.  
  29. 19.1092571 13 39135.75848 4891.96981 120373.2729 
  30.  
  31. 9.5546285 14 19567.87924 2445.984905 60186.63645 
  32.  
  33. 4.7773143 15 9783.939621 1222.992453 30093.31822 
  34.  
  35. 2.3886571 16 4891.96981 611.4962263 15046.65911 
  36.  
  37. 1.1943286 17 2445.984905 305.7481131 7523.329556 
  38.  
  39. 0.5971643 18 1222.992453 152.8740566 3761.664778 
  40.  
  41. 0.2985821 19 611.4962263 76.43702829 1880.832389 
  42.  
  43. 0.1492911 20 305.7481131 38.21851414 940.4161945 
  44.  
  45. 0.0746455 21 
  46.  
  47. 0.0373227 22  

3D中的計(jì)算策略是,首先需要將3D世界中的坐標(biāo)與墨卡托單位的對(duì)應(yīng)關(guān)系搞清楚,如果已經(jīng)是以mi來做單位,那么就可以直接將相機(jī)的投影屏幕的高度與屏幕的像素?cái)?shù)目做比值,得出的結(jié)果跟上面的ranking做比較,選擇不用的級(jí)別數(shù)據(jù)以及比例尺。注意3D地圖中的比例尺并不是在所有屏幕上的所有位置與現(xiàn)實(shí)世界都滿足這個(gè)比例尺,只能說是相機(jī)中心點(diǎn)在屏幕位置處的像素是滿足這個(gè)關(guān)系的,因?yàn)槠叫型队坝薪筮h(yuǎn)小的效果。

9、poi碰撞

由于標(biāo)注是永遠(yuǎn)朝著相機(jī)的,所以標(biāo)注的碰撞就是把標(biāo)注點(diǎn)轉(zhuǎn)換到屏幕坐標(biāo)系用寬高來計(jì)算矩形相交問題。至于具體的碰撞算法,大家可以在網(wǎng)上找到,這里不展開。下面是計(jì)算poi矩形的代碼 

責(zé)任編輯:龐桂玉 來源: 前端大全
相關(guān)推薦

2023-06-03 08:06:20

項(xiàng)目開發(fā)客戶端

2011-12-21 12:46:43

2012-06-07 09:57:13

Android版Goo

2012-11-26 12:51:44

木材3D打

2023-08-18 08:00:00

游戲開發(fā)3D模型

2014-04-28 17:30:53

2012-04-03 12:53:47

諾基亞

2011-10-06 13:30:45

宏碁投影儀

2012-05-13 12:54:33

iOS

2011-05-26 10:05:07

優(yōu)派投影機(jī)

2013-08-23 10:51:52

蘋果3D手勢(shì)

2010-09-08 11:26:26

Windows PhoXNA 4.0 3D游戲開發(fā)

2011-05-25 16:07:17

2015-09-09 11:05:52

3d視差引導(dǎo)頁

2021-12-28 10:52:10

鴻蒙HarmonyOS應(yīng)用

2011-08-26 14:50:23

2011-04-26 14:21:20

3DJVC投影機(jī)

2023-03-03 21:42:18

鴻蒙

2012-08-13 17:11:37

Silverlight

2011-09-22 10:07:52

奧圖碼投影儀
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)