You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Mapbox GL JS中实现WebGL多边形的模糊/平滑效果?

问题描述

我有一段代码,能在Mapbox GL JS地图上生成两个WebGL三角形(一个绿色、一个蓝色),顶点对应四个欧洲城市的坐标。现在想把这些多边形做成模糊/平滑效果,参考示例模糊图(忽略地图本身的模糊)。作为WebGL新手,想知道能不能通过高斯模糊或着色器实现,求帮助。

附原代码:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Add a custom style layer</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.12.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.12.0/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
    mapboxgl.accessToken = 'pk.eyJ1Ijoic3RlZXBhdHRpY3N0YWlycyIsImEiOiJjbDNvaGFod2EwbXluM2pwZTJiMDYzYjh5In0.J_HeH00ry0tbLmGmTy4z5w';
    const map = new mapboxgl.Map({
        container: 'map',
        zoom: 3,
        center: [7.5, 58],
        style: 'mapbox://styles/mapbox/light-v11',
        antialias: true,
        projection: 'mercator'
    });
    
    const helsinki = mapboxgl.MercatorCoordinate.fromLngLat({
      lng: 25.004,
      lat: 60.239
    });
    const berlin = mapboxgl.MercatorCoordinate.fromLngLat({
      lng: 13.403,
      lat: 52.562
    });
    const kyiv = mapboxgl.MercatorCoordinate.fromLngLat({
      lng: 30.498,
      lat: 50.541
    });
    const moscow = mapboxgl.MercatorCoordinate.fromLngLat({
      lng: 37.6173,
      lat: 55.7558
    });
    
    const vertex_buffer = new Float32Array([
      helsinki.x,
      helsinki.y,
      berlin.x,
      berlin.y,
      kyiv.x,
      kyiv.y,

      kyiv.x,
      kyiv.y,
      helsinki.x,
      helsinki.y,
      moscow.x,
      moscow.y
    ])
    
    const color_buffer = new Float32Array([
      0.0, 1.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 0.0, 1.0, 1.0,
      0.0, 0.0, 1.0, 1.0,
      0.0, 0.0, 1.0, 1.0,
    ])
    
    const highlightLayer = {
        id: 'highlight',
        type: 'custom',
        onAdd: function (map, gl) {
            const vertexSource = `
                uniform mat4 u_matrix;
                attribute vec2 a_pos;
                attribute vec4 color;
                varying vec4 vColor;
                void main() {
                    gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
                    vColor = color;
                }`;
            const fragmentSource = `
                precision lowp float;
                varying vec4 vColor;
                void main() {
                    gl_FragColor = vec4(vColor);
                }`;
            const vertexShader = gl.createShader(gl.VERTEX_SHADER);
            gl.shaderSource(vertexShader, vertexSource);
            gl.compileShader(vertexShader);
            const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
            gl.shaderSource(fragmentShader, fragmentSource);
            gl.compileShader(fragmentShader);
            this.program = gl.createProgram();
            gl.attachShader(this.program, vertexShader);
            gl.attachShader(this.program, fragmentShader);
            gl.linkProgram(this.program);
            this.aPos = gl.getAttribLocation(this.program, 'a_pos');
            this.color = gl.getAttribLocation(this.program, 'color');
            this.vertexBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
            gl.bufferData(
                gl.ARRAY_BUFFER,
                vertex_buffer,
                gl.STATIC_DRAW
            );
            this.colorBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
            gl.bufferData(
                gl.ARRAY_BUFFER,
                color_buffer,
                gl.STATIC_DRAW
            );
        },
        render: function (gl, matrix) {
            gl.useProgram(this.program);
            gl.uniformMatrix4fv(
                gl.getUniformLocation(this.program, 'u_matrix'),
                false,
                matrix
            );
            gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
            gl.enableVertexAttribArray(this.aPos);
            gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);
            gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
            gl.enableVertexAttribArray(this.color);
            gl.vertexAttribPointer(this.color, 4, gl.FLOAT, false, 0, 0);
            gl.enable(gl.BLEND);
            gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
            gl.drawArrays(gl.TRIANGLES, 0, vertex_buffer.length / 2);
        }
    };
    map.on('load', () => {
        map.addLayer(highlightLayer, 'building');
    });
</script>
</body>
</html>
解决方案

方法1:基于距离场的边缘模糊(简单易实现)

这种方法不需要额外的帧缓冲,直接在片段着色器里计算像素到多边形边缘的距离,根据距离调整透明度,实现平滑模糊的边缘效果。

修改后的核心代码如下:

const highlightLayer = {
    id: 'highlight',
    type: 'custom',
    onAdd: function (map, gl) {
        // 顶点着色器新增传递顶点坐标到片段着色器
        const vertexSource = `
            uniform mat4 u_matrix;
            attribute vec2 a_pos;
            attribute vec4 color;
            varying vec4 vColor;
            varying vec2 vPos;
            void main() {
                gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
                vColor = color;
                vPos = a_pos;
            }`;
        
        // 片段着色器添加距离场模糊逻辑
        const fragmentSource = `
            precision lowp float;
            varying vec4 vColor;
            varying vec2 vPos;
            
            // 模糊半径(单位为Mercator坐标,可根据缩放调整)
            const float blurRadius = 0.01;
            
            // 计算点到三角形边缘的最短距离
            float distanceToTriangle(vec2 p, vec2 v0, vec2 v1, vec2 v2) {
                vec2 v0v1 = v1 - v0;
                vec2 v0v2 = v2 - v0;
                vec2 v0p = p - v0;
                float dot00 = dot(v0v2, v0v2);
                float dot01 = dot(v0v2, v0v1);
                float dot02 = dot(v0v2, v0p);
                float dot11 = dot(v0v1, v0v1);
                float dot12 = dot(v0v1, v0p);
                
                float invDenom = 1.0 / (dot00 * dot11 - dot01 * dot01);
                float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
                float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
                
                if (u >= 0.0 && v >= 0.0 && u + v <= 1.0) {
                    vec2 closest = v0 + u * v0v2 + v * v0v1;
                    return length(p - closest);
                } else {
                    float d1 = length(p - v0);
                    float d2 = length(p - v1);
                    float d3 = length(p - v2);
                    return min(min(d1, d2), d3);
                }
            }
            
            void main() {
                // 计算当前像素到两个三角形的距离
                float distGreen = distanceToTriangle(vPos, 
                    vec2(${helsinki.x}, ${helsinki.y}),
                    vec2(${berlin.x}, ${berlin.y}),
                    vec2(${kyiv.x}, ${kyiv.y})
                );
                
                float distBlue = distanceToTriangle(vPos, 
                    vec2(${kyiv.x}, ${kyiv.y}),
                    vec2(${helsinki.x}, ${helsinki.y}),
                    vec2(${moscow.x}, ${moscow.y})
                );
                
                float alpha = 0.0;
                vec4 finalColor = vec4(0.0);
                
                // 绿色三角形的平滑过渡
                if (distGreen <= blurRadius) {
                    alpha += smoothstep(blurRadius, 0.0, distGreen);
                    finalColor += vec4(0.0, 1.0, 0.0, alpha);
                }
                
                // 蓝色三角形的平滑过渡
                if (distBlue <= blurRadius) {
                    alpha += smoothstep(blurRadius, 0.0, distBlue);
                    finalColor += vec4(0.0, 0.0, 1.0, alpha);
                }
                
                // 限制透明度最大值,避免重叠处过亮
                gl_FragColor = vec4(finalColor.rgb, min(alpha, 1.0));
            }`;
        
        // 后续着色器编译、缓冲创建逻辑与原代码一致,此处省略重复部分
        const vertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(vertexShader, vertexSource);
        gl.compileShader(vertexShader);
        const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fragmentShader, fragmentSource);
        gl.compileShader(fragmentShader);
        this.program = gl.createProgram();
        gl.attachShader(this.program, vertexShader);
        gl.attachShader(this.program, fragmentShader);
        gl.linkProgram(this.program);
        this.aPos = gl.getAttribLocation(this.program, 'a_pos');
        this.color = gl.getAttribLocation(this.program, 'color');
        this.vertexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
        gl.bufferData(
            gl.ARRAY_BUFFER,
            vertex_buffer,
            gl.STATIC_DRAW
        );
        this.colorBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
        gl.bufferData(
            gl.ARRAY_BUFFER,
            color_buffer,
            gl.STATIC_DRAW
        );
    },
    // render函数与原代码一致
    render: function (gl, matrix) {
        gl.useProgram(this.program);
        gl.uniformMatrix4fv(
            gl.getUniformLocation(this.program, 'u_matrix'),
            false,
            matrix
        );
        gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
        gl.enableVertexAttribArray(this.aPos);
        gl.vertexAttribPointer(this.aPos, 2, gl.FLOAT, false, 0, 0);
        gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
        gl.enableVertexAttribArray(this.color);
        gl.vertexAttribPointer(this.color, 4, gl.FLOAT, false, 0, 0);
        gl.enable(gl.BLEND);
        gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
        gl.drawArrays(gl.TRIANGLES, 0, vertex_buffer.length / 2);
    }
};

关键说明:

  • 顶点着色器新增vPos变量,将顶点的Mercator坐标传递给片段着色器
  • 片段着色器实现了点到三角形的距离计算,通过smoothstep函数生成平滑的透明度过渡
  • blurRadius控制模糊范围,值越大模糊效果越明显,可根据地图缩放动态调整

方法2:高斯模糊(进阶,真实模糊效果)

如果需要更自然的模糊效果,需要使用帧缓冲(Framebuffer):先将三角形渲染到离屏纹理,再对纹理执行高斯模糊,最后将模糊后的纹理渲染到屏幕。

完整修改代码如下:

const highlightLayer = {
    id: 'highlight',
    type: 'custom',
    onAdd: function (map, gl) {
        // 创建两个帧缓冲和对应纹理,用于模糊过程的中间存储
        this.framebuffers = [];
        this.textures = [];
        const size = [window.innerWidth, window.innerHeight];
        
        for (let i = 0; i < 2; i++) {
            const texture = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, texture);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size[0], size[1], 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            
            const framebuffer = gl.createFramebuffer();
            gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
            
            this.textures.push(texture);
            this.framebuffers.push(framebuffer);
        }
        
        // 三角形渲染着色器(用于渲染到离屏纹理)
        const triangleVertexSource = `
            uniform mat4 u_matrix;
            attribute vec2 a_pos;
            attribute vec4 color;
            varying vec4 vColor;
            void main() {
                gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
                vColor = color;
            }`;
        
        const triangleFragmentSource = `
            precision lowp float;
            varying vec4 vColor;
            void main() {
                gl_FragColor = vColor;
            }`;
        
        // 编译三角形渲染程序
        this.triangleProgram = this.createProgram(gl, triangleVertexSource, triangleFragmentSource);
        this.triangleAPos = gl.getAttribLocation(this.triangleProgram, 'a_pos');
        this.triangleColor = gl.getAttribLocation(this.triangleProgram, 'color');
        
        // 高斯模糊着色器
        const blurVertexSource = `
            attribute vec2 a_pos;
            varying vec2 v_texCoord;
            void main() {
                gl_Position = vec4(a_pos, 0.0, 1.0);
                v_texCoord = (a_pos + 1.0) / 2.0;
            }`;
        
        const blurFragmentSource = `
            precision lowp float;
            varying vec2 v_texCoord;
            uniform sampler2D u_texture;
            uniform vec2 u_blurDirection;
            uniform vec2 u_textureSize;
            
            // 5x5高斯模糊权重
            const float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
            
            void main() {
                vec2 texelSize = 1.0 / u_textureSize;
                vec4 color = vec4(0.0);
                vec2 offset = u_blurDirection * texelSize;
                
                for (int i = -2; i <= 2; i++) {
                    color += texture2D(u_texture, v_texCoord + offset * float(i)) * weights[i + 2];
                }
                
                gl_FragColor = color;
            }`;
        
        // 编译模糊程序
        this.blurProgram = this.create
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 23:55:28