使用ArcGIS API和Three.js在三維場景中實現(xiàn)動態(tài)立體墻效果

使用ArcGIS API和Three.js在三維場景中實現(xiàn)動態(tài)立體墻效果

廢話不多說瓣喊,直接先來看下最終實現(xiàn)的動態(tài)立體墻效果圖榆苞。

動態(tài)立體墻效果圖.gif

如果圖片還不夠直觀,那么點擊鏈接查看在線示例磷斧。

首先我們需要用到ArcGIS API中的externalRenderers類將外部的Three.js渲染器加載到地圖三維場景中贮折,如果不知道怎么使用的可以查看我的這篇文章《ArcGIS API在視圖中渲染Three.js場景》昨凡。那篇文章中加載的是一個三維模型嚣镜,而本示例中只需加載一面“墻”迁匠,也就是一個平面豆同,并增加一個動態(tài)效果番刊。所以重點就是怎么加載一個垂直于地球表面的平面,以及如何實現(xiàn)動態(tài)效果影锈。

1 垂直于地球表面的墻

如圖所示芹务,先確定出兩個“墻角”的坐標。


墻角坐標點.png
let points = [
  [104.06179498614645, 30.659871702738265], // 坐標1
  [104.06494384459816, 30.659931252383917], // 坐標2
];

現(xiàn)在我們有了兩個經緯度坐標的點鸭廷,但是我們需要4個頂點才能構成一個矩形面枣抱,所以我們還需要2個點。假設2個墻角坐標貼近于地面辆床,那么它們的高度就為0佳晶,那就再只需要2個同樣經緯度坐標但高度大于0的點就能構成一個在地面上并且垂直于地面的矩形面了。所以在我們定義的myRenderer對象中添加一個height屬性讼载。

let myRenderer = {
  // ... 其它屬性
  height: 100, // 墻的高度
  // ... 其它屬性轿秧、方法
};

現(xiàn)在我們有4個由經緯度加高度構成的點,如果要在視圖中渲染成一個矩形面咨堤,我們要先將這4個點轉換為渲染坐標系中的點菇篡,再將每3個頂點為一組構成一個三角面,最后由2個三角面構成一個矩形面一喘。這樣做是因為在Three.js中所有的模型都是由頂點加三角面構成的驱还。

1.1 頂點轉換

在頂點轉換之前,我們還需要做一個操作凸克,那就是將我們的經緯度坐標轉換為XY坐標议蟆。這需要用到ArcGIS API中的webMercatorUtils工具中的lngLatToXY方法,該方法將給定的經度和緯度轉換為Web Mercator的XY值萎战。

points.forEach((point) => {
  // 將經緯度坐標轉換為xy值
  let pointXY = webMercatorUtils.lngLatToXY(point[0], point[1]);
});

然后需要用到Three.js的數(shù)學庫中的四維矩陣Matrix4類以及ArcGIS API中externalRenderers對象上的renderCoordinateTransformAt方法將點轉換為渲染坐標系中的點坐標咐容。

let transform = new THREE.Matrix4(); // 變換矩陣
let transformation = new Array(16);
let vector3List = []; // 頂點數(shù)組
points.forEach((point) => {
  // 將經緯度坐標轉換為xy值
  let pointXY = webMercatorUtils.lngLatToXY(point[0], point[1]);
  // 先轉換高度為0的點
  transform.fromArray(
    externalRenderers.renderCoordinateTransformAt(
      this.view,
      [pointXY[0], pointXY[1], 0], // 坐標在地面上的點[x值, y值, 高度值]
      this.view.spatialReference,
      transformation
    )
  );
  vector3List.push(
    new THREE.Vector3(
      transform.elements[12],
      transform.elements[13],
      transform.elements[14]
    )
  );
  // 再轉換距離地面高度為height的點
  transform.fromArray(
    externalRenderers.renderCoordinateTransformAt(
      this.view,
      [pointXY[0], pointXY[1], this.height], // 坐標在空中的點[x值, y值, 高度值]
      this.view.spatialReference,
      transformation
    )
  );
  vector3List.push(
    new THREE.Vector3(
      transform.elements[12],
      transform.elements[13],
      transform.elements[14]
    )
  );
});

renderCoordinateTransformAt方法的作用是計算一個4x4變換矩陣,該矩陣構成從局部笛卡爾坐標系到虛擬世界坐標系的線性坐標變換撞鹉。該方法傳入4個參數(shù):
1 view疟丙,ArcGIS API生成的三維視圖颖侄。
2 origin,局部笛卡爾坐標系中原點的全局坐標享郊,也就是[經緯度轉換后的X坐標, 經緯度轉換后的y坐標, 高度值]览祖。
3 srcSpatialReference,原點坐標的空間參考炊琉。
4 dest展蒂,存儲16個矩陣元素的數(shù)組的引用。生成的矩陣遵循OpenGL約定苔咪,其中轉換組件占據(jù)第13锰悼、14和第15個元素。

現(xiàn)在团赏,vector3List變量中存儲的就是每個頂點轉換后的三維向量箕般,一共為4個頂點。順序是[第一個經緯度的地面頂點, 第一個經緯度的空中頂點, 第二個經緯度的地面頂點, 第二個經緯度的空中頂點]舔清,這個頂點的順序很重要丝里,后面會用到。

1.2 生成三角面以及面的UV隊列

因為Three.js中的面都是由小三角面構成的体谒,所以我們需要根據(jù)頂點列表中的頂點來組成三角面杯聚,每三個頂點構成一個三角面,一定要注意構成三角面的的頂點順序抒痒,因為要和面的UV隊列一一對應起來幌绍,這樣給每個面貼的紋理材質才能正確顯示出來。
紋理貼圖的坐標系統(tǒng)是這樣的:圖片左下角為原點(0, 0)故响,右下角為(1, 0)傀广,右上角為(1, 1),左上角為(0, 1)彩届,這和圖片的大小寬高無關主儡。如下圖所示:

紋理貼圖示意圖.png

將紋理坐標關系轉換為二維向量表示惨缆。

const t0 = new THREE.Vector2(0, 0); // 圖片左下角
const t1 = new THREE.Vector2(1, 0); // 圖片右下角
const t2 = new THREE.Vector2(1, 1); // 圖片右上角
const t3 = new THREE.Vector2(0, 1); // 圖片左上角

一個簡單的矩形面由4個頂點和2個小三角面構成,頂點和三角面關系如下圖所示:

頂點順序示意圖.png

圖中0丰捷、1坯墨、2、3序號代表vector3List變量中頂點的順序病往。按照逆時針規(guī)則畫出2個三角面捣染,下三角面為綠色三角面[0, 2, 1],上三角面為藍色三角面[1, 2, 3]停巷。例如耍攘,要將紋理貼圖和綠色三角面映射起來榕栏,那么綠色三角面對應的UV就是[t0, t1, t3],藍色三角面對應的UV就是[t3, t1, t2]蕾各。
頂點和紋理坐標向量對應圖.png

根據(jù)以上原理生成三角面列表以及UV隊列扒磁。

let faceList = []; // 三角面數(shù)組
let faceVertexUvs = []; // 面的 UV 層的隊列,該隊列用于將紋理和幾何信息進行映射

for (let i = 0; i < vector3List.length - 2; i++) {
  if (i % 2 === 0) { // 下三角面
    faceList.push(new THREE.Face3(i, i + 2, i + 1));
    faceVertexUvs.push([t0, t1, t3]);
  } else { // 上三角面
    faceList.push(new THREE.Face3(i, i + 1, i + 2));
    faceVertexUvs.push([t3, t1, t2]);
  }
}

1.3 生成幾何體

使用Three.js中的Geometry構造函數(shù)來生成自定義幾何體式曲。

const geometry = new THREE.Geometry(); // 生成幾何體
geometry.vertices = vector3List; // 幾何體頂點
geometry.faces = faceList; // 幾何體三角面
geometry.faceVertexUvs[0] = faceVertexUvs; // 面的UV隊列妨托,用于將紋理信息映射到幾何體上

geometry.faceVertexUvs的屬性值為數(shù)組是因為有多組UV。顏色貼圖吝羞、法線貼圖兰伤、高光貼圖、金屬度貼圖等共用一組紋理坐標UV即geometry.faceVertexUvs[0]钧排,設置陰影的光照貼圖lightMap使用另外一組紋理坐標敦腔,也就是geometry.faceVertexUvs[1]。默認情況下恨溜,geometry.faceVertexUvs屬性中會存在一個元素符衔,所以可以直接對geometry.faceVertexUvs[0]進行賦值操作。
注意:對于緩沖區(qū)類型幾何體也就是通過BufferGeometry構造函數(shù)生成的幾何體筒捺,是通過設置.attributes.uv和.attributes.uv2兩個屬性分別定義兩組頂點紋理坐標柏腻。

2 實現(xiàn)墻的動態(tài)效果

動態(tài)效果的原理其實是紋理貼圖實現(xiàn)的,一共兩層貼圖系吭,一層顏色從上到下越來越不透明五嫂,給人一面墻的感覺,另一層從上到下越來越透明肯尺,然后每次渲染都改變第二層紋理在垂直方向上的偏移量沃缘,這樣就有了滾動起來的效果。
因為當?shù)谝粚影胪该骱偷诙影胪该鞯男Ч集B加到一個幾何體上時则吟,這個幾何體就會變得更加的透明槐臀,顯示效果上就不是很好,所以我們把這兩層效果放到兩個幾何體上氓仲,只需要把上面創(chuàng)建好的幾何體克隆一遍水慨。

const geometry2 = geometry.clone();

2.1 利用材質的alphaMap貼圖實現(xiàn)半透明效果

我們選用基礎網絡材質MeshBasicMaterial,該材質不受光照的影響敬扛,所以不需要在場景中再額外的添加光源晰洒,省時省力~。該材質對象上的alphaMap貼圖屬性可以用來控制整個表面的不透明度啥箭,黑色完全透明谍珊,白色完全不透明。如下圖所示急侥,從上到下越來越白砌滞,也就是也來越不透明侮邀。

alphaMap貼圖.png

加載alphaMap的紋理貼圖資源,創(chuàng)建材質贝润,和第一個幾何體生成網格绊茧,然后添加到場景中。

this.alphaMap = new THREE.TextureLoader().load( // 加載alpha貼圖資源
  '../images/texture_1.png'
);
// 創(chuàng)建材質
const material = new THREE.MeshBasicMaterial({
  color: 0xff0000,
  side: THREE.DoubleSide,
  transparent: true, // 必須設置為true,alphaMap才有效果
  depthWrite: false, // 渲染此材質是否對深度緩沖區(qū)有任何影響
  alphaMap: this.alphaMap, // alpha貼圖题暖,控制透明度
});
const mesh = new THREE.Mesh(geometry, material); // 第一個幾何體和第一個材質
this.scene.add(mesh);

注意
side屬性要設置為THREE.DoubleSide按傅,這樣才能兩個面都進行繪制,也就是說從前后兩個方向都能看到幾何體胧卤。
transparent屬性一定要設置為true唯绍,不然alphaMap貼圖是沒有效果的,看不出透明效果枝誊。
depthWrite屬性一定要設置為false况芒,才能正確渲染后方的半透明物體。

效果如圖所示:

第一層透明效果圖.png

2.2 利用材質的map顏色貼圖實現(xiàn)漸變半透明效果

MeshBasicMaterial材質的map屬性為顏色貼圖叶撒【В可以設置為半透明的PNG格式圖片,也就達到了透明的效果祠够。

PNG格式紋理.png

加載PNG格式的紋理貼圖資源压汪,創(chuàng)建材質,和克隆出來的幾何體生成網格古瓤,然后添加到場景中止剖。

this.texture = new THREE.TextureLoader().load(
  '../images/texture_2.png'
);
this.texture.wrapS = THREE.RepeatWrapping; // 水平方向重復
this.texture.wrapT = THREE.RepeatWrapping; // 垂直方向重復
const material2 = new THREE.MeshBasicMaterial({
  side: THREE.DoubleSide,
  transparent: true,
  depthWrite: false, // 渲染此材質是否對深度緩沖區(qū)有任何影響
  map: this.texture, // 顏色貼圖,加載PNG圖片達到透明效果
});
const mesh2 = new THREE.Mesh(geometry2, material2);
this.scene.add(mesh2);

注意
因為需要在垂直方向上存在偏移量落君,形成滾動的效果穿香,所以必須設置紋理的包裹方式為重復,wrapSwrapT屬性設置為THREE.RepeatWrapping绎速。具體可查看文檔皮获。

疊加到場景中的效果如圖所示:

第二層疊加后效果圖.png

2.3 效果動起來

現(xiàn)在大體效果已經差不多了,最后只要動起來就完工了纹冤。要實現(xiàn)動起來的效果只需要在render函數(shù)中添加更新紋理貼圖偏移量的代碼洒宝,每渲染一次就更新一次偏移量。

render() {
  // ... 其它代碼
  if (this.offset <= 0) {
    this.offset = 1;
  } else {
    this.offset -= 0.02; // 每次渲染就向上移動0.02個單位萌京,如果想要速度快就增大該值
  }
  if (this.texture) {
    this.texture.offset.set(0, this.offset); // 水平偏移量0待德,垂直方向偏移量為offset
  }
  // ... 其它代碼
}

texture對象上存在offset屬性,該屬性值類型為二維向量Vector2枫夺,用來設置水平和垂直方向上的偏移量,值的范圍在0.0到1.0之間绘闷。

最終效果如圖所示:

動態(tài)立體墻效果圖.gif

3 總結

至此橡庞,我們的立體動態(tài)墻效果就已經實現(xiàn)了较坛。重要點就是通過頂點向量加三角面構成自定義的平面矩形幾何體,通過設置紋理貼圖以及改變紋理貼圖的偏移量來實現(xiàn)動起來的效果扒最。


點擊鏈接查看完整代碼丑勤。
點擊鏈接查看在線示例

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末吧趣,一起剝皮案震驚了整個濱河市法竞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌强挫,老刑警劉巖岔霸,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異俯渤,居然都是意外死亡呆细,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門八匠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來絮爷,“玉大人,你說我怎么就攤上這事梨树】雍唬” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵抡四,是天一觀的道長柜蜈。 經常有香客問我,道長床嫌,這世上最難降的妖魔是什么跨释? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮厌处,結果婚禮上鳖谈,老公的妹妹穿的比我還像新娘。我一直安慰自己阔涉,他們只是感情好缆娃,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瑰排,像睡著了一般贯要。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上椭住,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天崇渗,我揣著相機與錄音,去河邊找鬼。 笑死宅广,一個胖子當著我的面吹牛葫掉,可吹牛的內容都是我干的。 我是一名探鬼主播跟狱,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼俭厚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了驶臊?” 一聲冷哼從身側響起挪挤,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎关翎,沒想到半個月后扛门,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡笤休,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年尖飞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片店雅。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡政基,死狀恐怖,靈堂內的尸體忽然破棺而出闹啦,到底是詐尸還是另有隱情沮明,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布窍奋,位于F島的核電站荐健,受9級特大地震影響,放射性物質發(fā)生泄漏琳袄。R本人自食惡果不足惜江场,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窖逗。 院中可真熱鬧址否,春花似錦、人聲如沸碎紊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仗考。三九已至音同,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秃嗜,已是汗流浹背权均。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工顿膨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叽赊。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓虽惭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蛇尚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354