《權力的遊戲》3d地圖-基於Mapbox customlayer

權力遊戲最終季正在熱播,本人為了追劇果斷買了騰訊會員,跳廣告。不過說真的權遊的 架空世界確實很令人著迷,廣袤寒冷的北境,溫暖富饒的多恩,面朝黑水灣的君臨城 。巧了麼,前段時間正好看到一個 github專案 是權遊的地理要素和 mbtile資料,是廣大愛好者和官方合力貢獻且維護的。作為熱心觀眾,必須得添磚加瓦啊。
所以才有了下面的 3d 地形圖層,線上把玩地址:

其實在幾年前,我就藉助Threejs 復現過谷歌一款精美的中土地圖,那個app做得太精緻了,開場動效、音效,都很“中土”,原汁原味的托爾金味道。其實技術也很簡單,就是一個bufferPlane + texture圖 + 高程圖,根據高程圖去修改bufferPlane 對應頂點的 z 值。線上地址
概述技術過程
類似於上一篇 mapbox extrude 文章中描述的類似,我們用到的資料就一張地表影像和一張高程圖(權遊的這個高程圖是我自己先根據影像圖波段運算之後ps 修正過的,具體過程有點意思)

首先我們利用 Threejs 建立一個和影像圖寬高一致的bufferPlaneGeometry,然後拿到這個bufferPlane 的所有頂點,這時候我們要通過一個canvas去讀取高程圖中對應畫素的高度,從紅波段讀取高度,set 給bufferPlane 頂點的position.z,這就可以把平面設定為高低起伏的地形了(如下圖)

// geometry is bufferPlaneGeometry in THREEJS // position flatArray [x,y,z,x1,y1,z1...] in geometry var flatArray = geometry.attributes.position.array; var verticesCount = flatArray.length / 3.0; console.warn('bufferGeom Vertices Array length: '+ verticesCount); for ( var i = 0, j = 0; i < verticesCount; i ++, j += 3 ) { if (data[i] === undefined) { console.warn(`data[${i}] isundefined..`); break; } else { // set each vertice z-depth value with height flatArray[ j-1 ] = data[i] * extrusionRatio; } } 複製程式碼
與 mapbox 整合
為了給三維地形加入文字標註以及興趣點 icon 等要素,我們直接把這個Threejs 圖層整合為 mapbox 的customlayer。customlayer是 mapbox 開放給webgl 開發者的一個重要介面,可以在原有的 圖層列表中插入customlayer 。 構造customlayer最重要的api就倆,可以參考官方文件
- onAdd(map, gl),初始化 webgl
- render(gl, matrix), 每一幀都會call 這個render函式,可以在這裡注入需要在 webgl 上下文中渲染的操作
// configuration of the custom layer for a 3D model per the CustomLayerInterface var customLayer = { id: '3d-terrain', type: 'custom',// 指定是自定義圖層,不然就是 fill,symbol 等圖層. renderingMode: '3d', onAdd: function (map, gl) { this.camera = new THREE.Camera(); this.scene = new THREE.Scene(); this.map = map; // use the Mapbox GL JS map canvas for three.js this.renderer = new THREE.WebGLRenderer({ canvas: map.getCanvas(), context: gl // 用mapbox 的webgl作為threejs 的上下文. }); // 把Threejs 的scene,camera以及renderer 傳入自定義的terrainLoader中,以便add(bufferPlaneMesh) this.terrainLoader = new TerrainLoader({ scene: this.scene, camera: this.camera, renderer: this.renderer }); }, render: function (gl, matrix) { // ..省略部分 以下是將mapbox的matrix 引數同步給threejs 例項 // sync mapbox matrix with THREE camera Matrix. var m = new THREE.Matrix4().fromArray(matrix); var l = new THREE.Matrix4().makeTranslation(modelTransform.translateX, modelTransform.translateY, modelTransform.translateZ) .scale(new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale)) .multiply(rotationX) .multiply(rotationY) .multiply(rotationZ); // sync mapbox matrix with THREE camera. 更新threejs camera的投影矩陣,重新渲染,再強制觸發下mapbox 的repaint,這樣動畫就可以繼續進行了 this.camera.projectionMatrix.elements = matrix; this.camera.projectionMatrix = m.multiply(l); this.renderer.state.reset(); this.renderer.render(this.scene, this.camera); this.map.triggerRepaint(); } } // 把customlayer 加入label 之下,這樣文字標註就可以浮在地形圖層之上 map.on('style.load', function () { map.addLayer(customLayer, 'roads labels'); }); 複製程式碼
github專案地址 後續有空的話,會加上權力遊戲部分文件和 故事線動畫 ,這個比較有趣一點。歡迎繼續完善3d地形的範疇,一定得會photoshop...