Mapboxgl流动水效果实现

在mapboxgl中改源码相比cesium还是比较繁琐的,mapboxgl中存在着大量的样式配置内容,写一个面的水流效果就要改动很多东西,最近将之前写的水流效果迁移到了最新的mapboxgl版本上,顺便再次梳理下流程。

本文要实现的水体流动效果就是类似于高德地图上水的流动效果,是一种全局效果,移动地图不影响水流动的位置。

新增fill绘制类型

本效果是基于fill数据,fill绘制的program有目前几种,fillPatternfillfillOutlinePatternfillOutline,去结合对应的uniform变量和shader来对应不同的绘制方式。所以我们要新增一种,用来走我们水流特效的绘制。

    // draw_fill.js
    if (!isOutline) {
        if(image){
            programName = 'fillPattern';
        }else{
            if(layer.getPaintProperty('waterTime')){
                programName = 'fillWaterFlow';
            }else{
                programName = 'fill';
            }
        }
        drawMode = gl.TRIANGLES;
    } else {
        programName = image && !layer.getPaintProperty('fill-outline-color') ? 'fillOutlinePattern' : 'fillOutline';
        drawMode = gl.LINES;
    }

然后我们再根据类型判断,去取对应的uniform变量。

// draw_fill.js
            if(image){
                uniformValues = fillPatternUniformValues(tileMatrix, painter, crossfade, tile);
            }else{
                // 此处判断类型,然后去取相应的uniform
                if(layer.getPaintProperty('waterTime')){
                    uniformValues = fillWaterFlowUniformValues(tileMatrix, layer.getPaintProperty('waterTime'));
                }else{
                    uniformValues = fillUniformValues(tileMatrix);
                }
            }
        } else

定义uniform类型

由于mapboxgl是DT语法写的,开源出来的代码应该不是源代码,而是解析后的js文件,所以存在很多类型的定义。
draw_fill.js中我们提到了需要根据类型去取uniform,去对应的uniform值的方法就在fill_program.js里定义。

// 各个uniform的类型
export type FillWaterFlowUniformsType = {|
    'u_matrix': UniformMatrix4f,
    'u_time': Uniform1f
|};

// 构造各个uniform
const fillWaterFlowUniforms = (context: Context): FillWaterFlowUniformsType => ({
    'u_matrix': new UniformMatrix4f(context),
    'u_time': new Uniform1f(context),
});

// 返回各个uniform值
const fillWaterFlowUniformValues = (matrix: Float32Array, time): UniformValues<FillWaterFlowUniformsType> => ({
    'u_matrix': matrix,
    'u_time': time
});

每一个绘制类型都有对应的program,例如line有对应的line_program,fill就有对应的fill_program,每个program里定义了uniform的类型和取值方法,各个类型的program里的uniform变量都要导出到program_uniforms.js里来去统一调用,所以记得在这里做一次导入导出。

编写shader

同样的,我们也要为这个新增的类型去编写新的顶点和片元着色器。因为我们写水流效果,其实只需要在fs里面去写就好了,那么vs的shader直接copy fill.vertex.glsl里的代码即可。

片元着色器里水流效果其实我是从shadertoy里扒下来的,其实原理都一样,就是利用噪声函数来随机生成,然后根据时间去动态移动噪点来达到的效果。

// fill_waterflow.fragment.glsl
#pragma mapbox: define highp vec4 color
#pragma mapbox: define lowp float opacity
uniform float u_time;
float random(float x) {
    return fract(sin(x) * 10000.);
}

float noise(vec2 p) {
    return random(p.x + p.y * 10000.);
}

vec2 sw(vec2 p) { return vec2(floor(p.x), floor(p.y)); }
vec2 se(vec2 p) { return vec2(ceil(p.x), floor(p.y)); }
vec2 nw(vec2 p) { return vec2(floor(p.x), ceil(p.y)); }
vec2 ne(vec2 p) { return vec2(ceil(p.x), ceil(p.y)); }

float smoothNoise(vec2 p) {
    vec2 interp = smoothstep(0., 1., fract(p));
    float s = mix(noise(sw(p)), noise(se(p)), interp.x);
    float n = mix(noise(nw(p)), noise(ne(p)), interp.x);
    return mix(s, n, interp.y);
}

float fractalNoise(vec2 p) {
    float x = 0.;
    x += smoothNoise(p      );
    x += smoothNoise(p * 2. ) / 2.;
    x += smoothNoise(p * 4. ) / 4.;
    x += smoothNoise(p * 8. ) / 8.;
    x += smoothNoise(p * 16.) / 16.;
    x /= 1. + 1./2. + 1./4. + 1./8. + 1./16.;
    return x;
}

float movingNoise(vec2 p) {
    float x = fractalNoise(p + u_time);
    float y = fractalNoise(p - u_time);
    return fractalNoise(p + vec2(x, y));
}

// call this for water noise function
float nestedNoise(vec2 p) {
    float x = movingNoise(p);
    float y = movingNoise(p + 100.);
    return movingNoise(p + vec2(x, y));
}

void main() {
    #pragma mapbox: initialize highp vec4 color
    #pragma mapbox: initialize lowp float opacity

    vec2 iResolution = vec2(1920, 1048);

    vec2 uv = gl_FragCoord.xy / iResolution.xy;
    float n = nestedNoise(uv * 6.);

    gl_FragColor = vec4(mix(vec3(.4, .6, 1), vec3(color.rgb), n), opacity);

    #ifdef OVERDRAW_INSPECTOR
    gl_FragColor = vec4(1.0);
    #endif
}

编写完shader后,记得要在shaders.js里面引入,因为要在此处将glsl代码进行编译。

完善style类型

在mapboxgl中,所有的数据加载效果都是依靠style来进行配置。我们在这个新类型中由于添加了一个waterTime参数(对应在内部的配置uniform变量名称为u_time),所以我们需要将这个新参数进行定义添加,因为在mapboxgl内部要做很多样式类型检查。

首先我们找到src/style-spec/reference/v8.json,将waterTime这个参数进行定义,记得要定义在paint_fill中,因为我们这个是关于fillpaint属性,所以不要加错地方。

然后在src/style-spec/type.js中找到FillLayerSpecification,将waterTime加入到paint属性下面。

最后在src/style/style_layer/fill_style_layer_properties.js中将waterTime加入。

结语

至此源码里的改动就算完成了,然后我们再去写一个水流效果的构造函数即可,这里就不贴了,其实就是添加一个fill_layer,然后配置waterTime参数,唯一注意的地方是,你要让这个变量随时变化。我在这里写了个事件,让waterTime随时变化,在map每次render的时候调用即可。

你可能感兴趣的:(mapboxgl,javascript,前端,webgl,mapboxgl)