如何在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
相关产品推荐
相关产品推荐

