《权力的游戏》3d地图-基于Mapbox customlayer

写在前面

最终季!!转自豆瓣,如侵权请联系... https://movie.douban.com/photos/photo/2549876231/

权力游戏最终季正在热播,本人为了追剧果断买了腾讯会员,跳广告。不过说真的权游的架空世界确实很令人着迷,广袤寒冷的北境,温暖富饶的多恩,面朝黑水湾的君临城。巧了么,前段时间正好看到一个 github项目 是权游的地理要素和 mbtile数据,是广大爱好者和官方合力贡献且维护的。作为热心观众,必须得添砖加瓦啊。

所以才有了下面的 3d 地形图层,在线把玩地址:

image.png

其实在几年前,我就借助Threejs 复现过谷歌一款精美的中土地图,那个app做得太精致了,开场动效、音效,都很“中土”,原汁原味的托尔金味道。其实技术也很简单,就是一个bufferPlane + texture图 + 高程图,根据高程图去修改bufferPlane 对应顶点的 z 值。在线地址

概述技术过程

类似于上一篇 mapbox extrude 文章中描述的类似,我们用到的数据就一张地表影像和一张高程图(权游的这个高程图是我自己ps 的,具体过程有点意思)


左边高程图+右边影像图

首先我们利用 Threejs 建立一个和影像图宽高一致的bufferPlaneGeometry,然后拿到这个bufferPlane 的所有顶点,这时候我们要通过一个canvas去读取高程图中对应像素的高度,从红波段读取高度,set 给bufferPlane 顶点的position.z,这就可以把平面设置为高低起伏的地形了(如下图)

根据高程图设置bufferPlane 的顶点高度
// 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...

你可能感兴趣的:(《权力的游戏》3d地图-基于Mapbox customlayer)