使用ArcGIS API和Three.js在三維場景中實現(xiàn)動態(tài)立體墻效果
廢話不多說瓣喊,直接先來看下最終實現(xiàn)的動態(tài)立體墻效果圖榆苞。
如果圖片還不夠直觀,那么點擊鏈接查看在線示例磷斧。
首先我們需要用到ArcGIS API中的externalRenderers
類將外部的Three.js渲染器加載到地圖三維場景中贮折,如果不知道怎么使用的可以查看我的這篇文章《ArcGIS API在視圖中渲染Three.js場景》昨凡。那篇文章中加載的是一個三維模型嚣镜,而本示例中只需加載一面“墻”迁匠,也就是一個平面豆同,并增加一個動態(tài)效果番刊。所以重點就是怎么加載一個垂直于地球表面的平面,以及如何實現(xiàn)動態(tài)效果影锈。
1 垂直于地球表面的墻
如圖所示芹务,先確定出兩個“墻角”的坐標。
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)彩届,這和圖片的大小寬高無關主儡。如下圖所示:
將紋理坐標關系轉換為二維向量表示惨缆。
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個小三角面構成,頂點和三角面關系如下圖所示:
圖中0丰捷、1坯墨、2、3序號代表
vector3List
變量中頂點的順序病往。按照逆時針規(guī)則畫出2個三角面捣染,下三角面為綠色三角面[0, 2, 1],上三角面為藍色三角面[1, 2, 3]停巷。例如耍攘,要將紋理貼圖和綠色三角面映射起來榕栏,那么綠色三角面對應的UV就是[t0, t1, t3],藍色三角面對應的UV就是[t3, t1, t2]蕾各。根據(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的紋理貼圖資源,創(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
况芒,才能正確渲染后方的半透明物體。
效果如圖所示:
2.2 利用材質的map顏色貼圖實現(xiàn)漸變半透明效果
MeshBasicMaterial
材質的map
屬性為顏色貼圖叶撒【В可以設置為半透明的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);
注意:
因為需要在垂直方向上存在偏移量落君,形成滾動的效果穿香,所以必須設置紋理的包裹方式為重復,wrapS
和wrapT
屬性設置為THREE.RepeatWrapping
绎速。具體可查看文檔皮获。
疊加到場景中的效果如圖所示:
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之間绘闷。
最終效果如圖所示:
3 總結
至此橡庞,我們的立體動態(tài)墻效果就已經實現(xiàn)了较坛。重要點就是通過頂點向量加三角面構成自定義的平面矩形幾何體,通過設置紋理貼圖以及改變紋理貼圖的偏移量來實現(xiàn)動起來的效果扒最。