WebGIS 应用 WebGL 在 MapboxGL 上衬着 DEM 三维空间数据

毕业两年,一直在舆图相干的公司事情,虽然不是 GIS 身世,然则也对舆图有些潜移默化;近来在看 WebGl 的东西,就拿 MapboxGL 做了一个关于 WebGL 的三维数据衬着的 DEMO 练手。

起首大抵看了一下 MapboxGL 的 GLGS 到图层的一个构造:

《WebGIS 应用 WebGL 在 MapboxGL 上衬着 DEM 三维空间数据》
大致就是先做 WebGl 的 Shader 代码放进 Painter(WebGL 的 Context 就在这个对象内里) 内里,然后经由历程 Source 层去加载处置惩罚须要的数据(包含矢量和栅格数据),把数据经由历程 Tile 对象传进 Render 内里,去做一些 WebGL 的数据处置惩罚和衬着,然后扔进 Tile 内里传入到 Layer 层,末了就是一些款式和事宜的治理。

MapboxGL 大致就说这么多,下面就是 WebGL 的三维数据处置惩罚和衬着以及增加卫星影象纹理的历程(代码着实太多,只写出部份关键步骤代码):

第一步:拿到须要衬着的数据片(瓦片情势)
// 序列化瓦片地点,将数据瓦片的 xyz 坐标盘算出来
let url = normalizeURL(
    tile.coord.url(this.tiles, null, this.scheme),
    this.url,
    this.tileSize
);
...
// 用 MapboxGl 封装的猎取二进制数据格式的 Ajax 要求拿到二进制数据
tile.request = ajax.getArrayBuffer(url, done.bind(this));
...
// 将数据举行转码处置惩罚成 JS 对象,并传递给 tile
tile.pixelObj = pixelObj;  // 处置惩罚好的数据
...
第二步:在 Render 内里拿到数据和 Painter,去做数据片的衬着:
const divisions = 257;
let vertexPositionData = new Float32Array(divisions * divisions * 3);
const pixels = pixelObj.pixels[0];

if (coord.vertexPositionData) {
    // 做了缓存优化
    console.log('缓存', 'coord');
    vertexPositionData = coord.vertexPositionData;
} else {
    console.time('vertex');
    // 全数据量
    for (let i = 0; i < divisions; ++i) {
        for (let j = 0; j < divisions; ++j) {
            const bufferLength = (i * divisions + j) * 3;
            let dem = parseInt(pixels[bufferLength / 3]);
            if (!dem || dem === -3) {
                // 关于无效数据给一个默认值(PS: DEM 高程数据质量不高 )
                dem = -1000;
            }

            vertexPositionData[bufferLength] = j * SCALE;
            vertexPositionData[bufferLength + 1] = i * SCALE * 1;
            vertexPositionData[bufferLength + 2] = dem;
        }
    }

    // 盘算数据处置惩罚的耗时,优化的时刻要用
    console.timeEnd('vertex');
    coord.vertexPositionData = vertexPositionData;
}

const indexData = getIndex(divisions);
const FSIZE = vertexPositionData.BYTES_PER_ELEMENT;
const positionBuffer = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexPositionData, gl.STATIC_DRAW);
const aPosiLoc = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttribPointer(aPosiLoc, 3, gl.FLOAT, false, FSIZE * 3, 0);
gl.enableVertexAttribArray(aPosiLoc);

// 设置索引
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);

// https://stackoverflow.com/questions/28324162/webgl-element-array-buffers-not-working
gl.getExtension('OES_element_index_uint');
gl.drawElements(gl.TRIANGLES, indexData.length, gl.UNSIGNED_INT, 0);

...
// 天生索引,WebGL 的衬着有两种体式格局,一种是 drawElements,一种是 drawArray,我们这里采纳第一种
function getIndex(divisions) {
    if (drawLerc3D.indexData) {
        return drawLerc3D.indexData;
    }

    console.time('猎取索引');
    const indexData = [];

    // 这个是全数据量衬着
    // for (let row = 0; row < divisions - 1; ++row) {
    //     for (let i = 0; i < divisions; ++i) {
    //         const base = row * divisions + i;

    //         if (i < divisions - 1) {
    //             indexData.push(base);
    //             indexData.push(base + 1);
    //             indexData.push(base + divisions);

    //             indexData.push(base + 1);
    //             indexData.push(base + divisions);
    //             indexData.push(base + divisions + 1);
    //         }
    //     }
    // }

    // 这是一半数据(PS: 这是为了优化,捐躯一些精度)
    for (let row = 0; row < divisions - 2; row += 2) {
        for (let i = 0; i < divisions; i += 2) {
            const base = row * divisions + i;

            if (i < divisions - 2) {
                indexData.push(base);
                indexData.push(base + 2);
                indexData.push(base + divisions * 2);

                indexData.push(base + 2);
                indexData.push(base + divisions * 2);
                indexData.push(base + divisions * 2 + 2);
            }
        }
    }
    console.timeEnd('猎取索引');

    drawLerc3D.indexData = new Uint32Array(indexData);
    return drawLerc3D.indexData;
}
第三步:编写 GLSL,在 GPU 内里处置惩罚差异高度对应衬着的差异色彩值

vertex shader

// 视角矩阵
uniform mat4 u_matrix;
// 极点位置数据
attribute vec3 a_Position;
// 纹理数据,贴图卫星影象
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
// 高程数据
varying float dem;

void main(){
    dem = a_Position.z;
    gl_Position = u_matrix * vec4(a_Position.x, a_Position.y, dem * 32.0, 1.0);
    v_texCoord = a_texCoord;
}

fragment shader

// precision lowp float;

// uniform float u_brightness_low;
// uniform float u_brightness_high;

// 色彩
// varying vec3 v_Color;

varying float dem;

// 纹理
uniform sampler2D u_image;
varying vec2 v_texCoord;

// 依据差异高程取差异色彩
vec4 getColor() {
    // 色彩数组
    const int COLORS_SIZE = 11;
    vec3 colors[COLORS_SIZE];

    // 对 dem 举行归一化
    float n_dem = -2.0 * (dem / 6000.0 - 0.5);

    const float MINDEM = -1.0;
    const float MAXDEM = 1.0;
    const float STEP = (MAXDEM - MINDEM) / float(COLORS_SIZE - 1);
    int index = int(ceil((n_dem - MINDEM) / STEP));

    colors[10] = vec3(0.3686274509803922,0.30980392156862746,0.6352941176470588);
    colors[9] = vec3(0.19607843137254902,0.5333333333333333,0.7411764705882353);
    colors[8] = vec3(0.4,                0.7607843137254902,0.6470588235294118);
    colors[7] = vec3(0.6705882352941176,0.8666666666666667,0.6431372549019608);
    colors[6] = vec3(0.9019607843137255,0.9607843137254902,0.596078431372549);
    colors[5] = vec3(1.0,                1.0,              0.7490196078431373);
    colors[4] = vec3(0.996078431372549,0.8784313725490196,0.5450980392156862);
    colors[3] = vec3(0.9921568627450981,0.6823529411764706,0.3803921568627451);
    colors[2] = vec3(0.9568627450980393,0.42745098039215684,0.2627450980392157);
    colors[1] = vec3(0.8352941176470589,0.24313725490196078,0.30980392156862746);
    colors[0] = vec3(0.6196078431372549,0.00392156862745098,0.25882352941176473);

    if(index > 10){
        return vec4(0.3, 0.3, 0.9, 0.5);
    }

    if(index < 0){
        index = 0;
    }

    for (int i = 0; i < COLORS_SIZE; i++) {
        if (i == index) return vec4(colors[i], 1.0);
    }
}

void main(){
    // 用色彩衬着 DEM 数据,和纹理二选一
    gl_FragColor = getColor();
    // 用纹理(卫星影象)衬着结果
    gl_FragColor = texture2D(u_image, v_texCoord / 256.0 / 32.0);
}
末了:在 MapboxGL 内里运用我们本身定义的 Source 和 Layer
map.addSource('DEMImgSource', {  //高程数据
    "type": "DEM3D",
    "tiles": [
        'http://xxx.xxx.xxx.xxx/{x}/{y}/{z}',
    ],
    "tileSize": 512,
    // 谷歌瓦片地点,用来衬着纹理贴图
    "rasterUrl": 'http://www.google.cn/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}',
    // 高德的
    // "rasterUrl": 'https://webst04.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}'
});

map.addLayer({ // layer
    'id': 'DEMlayer',
    'type': 'DEM3D',
    'source': 'DEMImgSource'
});

终究的衬着结果(色彩衬着):

《WebGIS 应用 WebGL 在 MapboxGL 上衬着 DEM 三维空间数据》
由于数据量着实是太大(平常整张3D屏幕衬着须要40张瓦片,每张都有256*256个数据点),一开始没有做优化的时刻异常卡,基础没法举行舆图拖动和缩放,厥后将数据举行缓存,极点信息举行精简,瓦片大小举行放大(一屏幕只须要20张数据片衬着)获得的结果就很不错了,拖动和缩放基础比较流通,体验和一般舆图差异不大。

纹理衬着结果:

《WebGIS 应用 WebGL 在 MapboxGL 上衬着 DEM 三维空间数据》

《WebGIS 应用 WebGL 在 MapboxGL 上衬着 DEM 三维空间数据》

不得不说彷佛照样色彩衬着的视觉结果更(yao)好(yan)一(jian)些(huo)~

关于 WebGL 方向上的探究一些大公司也有一些效果:
高德 Loca:https://lbs.amap.com/api/java…

百度 Echarts: http://echarts.baidu.com/exam…

UBER: https://deck.gl/

等等,所以关于 WebGL 的远景个人以为在数据可视化、高精舆图(无人驾驶)等方面照样有许多代价的~

第一次写文章,许多处所能够没有诠释清晰,迎接拍砖~

    原文作者:phlicess
    原文地址: https://segmentfault.com/a/1190000016133749
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞