写在前面
权力游戏最终季正在热播,本人为了追剧果断买了腾讯会员,跳广告。不过说真的权游的架空世界确实很令人着迷,广袤寒冷的北境,温暖富饶的多恩,面朝黑水湾的君临城。巧了么,前段时间正好看到一个 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}] is undefined..`);
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...