如何使用最新的 Mapbox-gl-js 版本避免透明度重叠?

Posted

技术标签:

【中文标题】如何使用最新的 Mapbox-gl-js 版本避免透明度重叠?【英文标题】:How to avoid transparency overlap using the latest Mapbox-gl-js version? 【发布时间】:2020-01-23 23:56:47 【问题描述】:

我一直在使用 (https://bl.ocks.org/andrewharvey/9490afae78301c047adddfb06523f6f1) 构建几何示例中的阴影,并且能够将透明层混合成一个统一的 alpha 值与旧版本的 mapbox-gl-js。

但是,当我将 mapbox-gl-js 版本更改为 v0.54.0 或更高版本时,它不再将阴影混合为统一值。我已经尝试过 gl.blendFunc() 和 gl.blendFuncSeparate() 但似乎仍然混合了奇怪的抗锯齿问题或重叠的不透明度。

我怎样才能避免这种透明度问题并获得与提供的第一个示例类似的结果。

0.53.1: 1.6.1:

使用版本 0.53.1 的代码:

<!DOCTYPE html>
<html>
<head>
    <title>Mapbox GL JS debug page</title>
    <meta charset='utf-8'>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.css' rel='stylesheet' />
    <style>
        body  margin: 0; padding: 0; 
        html, body, #map  height: 100%; 
        #time  position: absolute; width: 90%; top: 10px; left: 10px; 
    </style>
</head>

<body>
<div id='map'></div>
<input id='time' type='range' min="0" max="86400" />

<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.js'></script>
<script src='https://unpkg.com/suncalc@1.8.0/suncalc.js'></script>
<!-- <script src='/debug/access_token_generated.js'></script> -->
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiYWxhbnRnZW8tcHJlc2FsZXMiLCJhIjoiY2pzcTA4NjRiMTMxczQzcDFqa29maXk3bSJ9.pVYNTFKfcOXA_U_5TUwDWw';
var map = window.map = new mapboxgl.Map(
    container: 'map',
    zoom: 15,
    center: [-74.0059, 40.7064],
    style: 'mapbox://styles/mapbox/streets-v11',
    hash: true
);
var date = new Date();
var time = date.getHours() * 60 * 60 + date.getMinutes() * 60 + date.getSeconds();
var timeInput = document.getElementById('time');
timeInput.value = time;
timeInput.oninput = () => 
    time = +timeInput.value;
    date.setHours(Math.floor(time / 60 / 60));
    date.setMinutes(Math.floor(time / 60) % 60);
    date.setSeconds(time % 60);
    map.triggerRepaint();
;
map.addControl(new mapboxgl.NavigationControl());
class BuildingShadows 
    constructor() 
        this.id = 'building-shadows';
        this.type = 'custom';
        this.renderingMode = '3d';
        this.opacity = 0.5;
    
    onAdd(map, gl) 
        this.map = map;
        const vertexSource = `
        uniform mat4 u_matrix;
        uniform float u_height_factor;
        uniform float u_altitude;
        uniform float u_azimuth;
        attribute vec2 a_pos;
        attribute vec4 a_normal_ed;
        attribute lowp vec2 a_base;
        attribute lowp vec2 a_height;
        void main() 
            float base = max(0.0, a_base.x);
            float height = max(0.0, a_height.x);
            float t = mod(a_normal_ed.x, 2.0);
            vec4 pos = vec4(a_pos, t > 0.0 ? height : base, 1);
            float len = pos.z * u_height_factor / tan(u_altitude);
            pos.x += cos(u_azimuth) * len;
            pos.y += sin(u_azimuth) * len;
            pos.z = 0.0;
            gl_Position = u_matrix * pos;
        
        `;
        const fragmentSource = `
        void main() 
            gl_FragColor = vec4(0.0, 0.0, 0.0, 0.5);
        
        `;


        const vertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(vertexShader, vertexSource);
        gl.compileShader(vertexShader);
        const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        console.log(gl.FRAGMENT_SHADER)
        console.log(fragmentShader)
        console.log(fragmentSource)
        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);
        gl.validateProgram(this.program);
        this.uMatrix = gl.getUniformLocation(this.program, "u_matrix");
        this.uHeightFactor = gl.getUniformLocation(this.program, "u_height_factor");
        this.uAltitude = gl.getUniformLocation(this.program, "u_altitude");
        this.uAzimuth = gl.getUniformLocation(this.program, "u_azimuth");
        this.aPos = gl.getAttribLocation(this.program, "a_pos");
        this.aNormal = gl.getAttribLocation(this.program, "a_normal_ed");
        this.aBase = gl.getAttribLocation(this.program, "a_base");
        this.aHeight = gl.getAttribLocation(this.program, "a_height");
    
    render(gl, matrix) 
        gl.useProgram(this.program);
        const source = this.map.style.sourceCaches['composite'];
        const coords = source.getVisibleCoordinates().reverse();
        const buildingsLayer = map.getLayer('3d-buildings');
        const context = this.map.painter.context;
        const lng, lat = this.map.getCenter();
        const pos = SunCalc.getPosition(date, lat, lng);
        gl.uniform1f(this.uAltitude, pos.altitude);
        gl.uniform1f(this.uAzimuth, pos.azimuth + 3 * Math.PI / 2);
        map.setLight(
            anchor: 'map',
            position: [1.5, 180 + pos.azimuth * 180 / Math.PI, 90 - pos.altitude * 180 / Math.PI],
            'position-transition': duration: 0,
            color: '#fdb'
            // color: `hsl(20, $50 * Math.cos(pos.altitude)%, $ 200 * Math.sin(pos.altitude) %)`
        , duration: 0);
        this.opacity = Math.sin(Math.max(pos.altitude, 0)) * 0.9;

        // ADDED: normalises the colour of the shadows
        gl.blendFunc(gl.SRC_COLOR, gl.CONSTANT_COLOR)
        gl.enable(gl.BLEND)

        for (const coord of coords) 
            const tile = source.getTile(coord);
            const bucket = tile.getBucket(buildingsLayer);
            if (!bucket) continue;
            const [heightBuffer, baseBuffer] = bucket.programConfigurations.programConfigurations['3d-buildings']._buffers;
            gl.uniformMatrix4fv(this.uMatrix, false, coord.posMatrix);
            gl.uniform1f(this.uHeightFactor, Math.pow(2, coord.overscaledZ) / tile.tileSize / 8);
            for (const segment of bucket.segments.get()) 
                const numPrevAttrib = context.currentNumAttributes || 0;
                const numNextAttrib = 2;
                for (let i = numNextAttrib; i < numPrevAttrib; i++) gl.disableVertexAttribArray(i);
                const vertexOffset = segment.vertexOffset || 0;
                gl.enableVertexAttribArray(this.aPos);
                gl.enableVertexAttribArray(this.aNormal);
                gl.enableVertexAttribArray(this.aHeight);
                gl.enableVertexAttribArray(this.aBase);
                bucket.layoutVertexBuffer.bind();
                gl.vertexAttribPointer(this.aPos, 2, gl.SHORT, false, 12, 12 * vertexOffset);
                gl.vertexAttribPointer(this.aNormal, 4, gl.SHORT, false, 12, 4 + 12 * vertexOffset);
                heightBuffer.bind();
                gl.vertexAttribPointer(this.aHeight, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
                baseBuffer.bind();
                gl.vertexAttribPointer(this.aBase, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
                bucket.indexBuffer.bind();
                context.currentNumAttributes = numNextAttrib;
                gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2);
            
        
    

map.on('load', () => 
    map.removeLayer('building');
    map.addLayer(
        'id': '3d-buildings',
        'source': 'composite',
        'source-layer': 'building',
        'type': 'fill-extrusion',
        'minzoom': 14,
        'paint': 
            'fill-extrusion-color': '#ddd',
            'fill-extrusion-height': ["number", ["get", "height"], 5],
            'fill-extrusion-base': ["number", ["get", "min_height"], 0],
            'fill-extrusion-opacity': 1
        
    , 'road-label');
    map.addLayer(new BuildingShadows(), '3d-buildings');
);
</script>
</body>
</html>

使用版本 1.6.1 的代码:

<!DOCTYPE html>
<html>
<head>
    <title>Mapbox GL JS debug page</title>
    <meta charset='utf-8'>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.css' rel='stylesheet' />
    <style>
        body  margin: 0; padding: 0; 
        html, body, #map  height: 100%; 
        #time  position: absolute; width: 90%; top: 10px; left: 10px; 
    </style>
</head>

<body>
<div id='map'></div>
<input id='time' type='range' min="0" max="86400" />

<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.js'></script>
<script src='https://unpkg.com/suncalc@1.8.0/suncalc.js'></script>
<!-- <script src='/debug/access_token_generated.js'></script> -->
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiYWxhbnRnZW8tcHJlc2FsZXMiLCJhIjoiY2pzcTA4NjRiMTMxczQzcDFqa29maXk3bSJ9.pVYNTFKfcOXA_U_5TUwDWw';
var map = window.map = new mapboxgl.Map(
    container: 'map',
    zoom: 15,
    center: [-74.0059, 40.7064],
    style: 'mapbox://styles/mapbox/streets-v11',
    hash: true
);
var date = new Date();
var time = date.getHours() * 60 * 60 + date.getMinutes() * 60 + date.getSeconds();
var timeInput = document.getElementById('time');
timeInput.value = time;
timeInput.oninput = () => 
    time = +timeInput.value;
    date.setHours(Math.floor(time / 60 / 60));
    date.setMinutes(Math.floor(time / 60) % 60);
    date.setSeconds(time % 60);
    map.triggerRepaint();
;
map.addControl(new mapboxgl.NavigationControl());
class BuildingShadows 
    constructor() 
        this.id = 'building-shadows';
        this.type = 'custom';
        this.renderingMode = '3d';
        this.opacity = 0.5;
    
    onAdd(map, gl) 

        this.map = map;
        const vertexSource = `
        uniform mat4 u_matrix;
        uniform float u_height_factor;
        uniform float u_altitude;
        uniform float u_azimuth;
        attribute vec2 a_pos;
        attribute vec4 a_normal_ed;
        attribute lowp vec2 a_base;
        attribute lowp vec2 a_height;
        void main() 
            float base = max(0.0, a_base.x);
            float height = max(0.0, a_height.x);
            float t = mod(a_normal_ed.x, 2.0);
            vec4 pos = vec4(a_pos, t > 0.0 ? height : base, 1);
            float len = pos.z * u_height_factor / tan(u_altitude);
            pos.x += cos(u_azimuth) * len;
            pos.y += sin(u_azimuth) * len;
            pos.z = 0.0;
            gl_Position = u_matrix * pos;
        
        `;
        const fragmentSource = `
        void main() 
            gl_FragColor = vec4(5.0, 0.0, 0.0, 0.1);

        
        `;


        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);
        gl.validateProgram(this.program);
        this.uMatrix = gl.getUniformLocation(this.program, "u_matrix");
        this.uHeightFactor = gl.getUniformLocation(this.program, "u_height_factor");
        this.uAltitude = gl.getUniformLocation(this.program, "u_altitude");
        this.uAzimuth = gl.getUniformLocation(this.program, "u_azimuth");
        this.aPos = gl.getAttribLocation(this.program, "a_pos");
        this.aNormal = gl.getAttribLocation(this.program, "a_normal_ed");
        this.aBase = gl.getAttribLocation(this.program, "a_base");
        this.aHeight = gl.getAttribLocation(this.program, "a_height");
    
    render(gl, matrix) 
        gl.useProgram(this.program);     
        const source = this.map.style.sourceCaches['composite'];
        const coords = source.getVisibleCoordinates().reverse();
        const buildingsLayer = map.getLayer('3d-buildings');
        const context = this.map.painter.context;
        const lng, lat = this.map.getCenter();
        const pos = SunCalc.getPosition(date, lat, lng);
        gl.uniform1f(this.uAltitude, pos.altitude);
        gl.uniform1f(this.uAzimuth, pos.azimuth + 3 * Math.PI / 2);
        map.setLight(
            anchor: 'map',
            position: [1.5, 180 + pos.azimuth * 180 / Math.PI, 90 - pos.altitude * 180 / Math.PI],
            'position-transition': duration: 0,
            color: '#fdb'
            // color: `hsl(20, $50 * Math.cos(pos.altitude)%, $ 200 * Math.sin(pos.altitude) %)`
        , duration: 0);
        this.opacity = Math.sin(Math.max(pos.altitude, 0)) * 0.9;

        // ADDED: New Attempt to normalise the colour of the shadows
        gl.depthMask(false);
        gl.enable(gl.BLEND)
        gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);


        for (const coord of coords) 
            const tile = source.getTile(coord);
            const bucket = tile.getBucket(buildingsLayer);
            if (!bucket) continue;
            const [heightBuffer, baseBuffer] = bucket.programConfigurations.programConfigurations['3d-buildings']._buffers;
            gl.uniformMatrix4fv(this.uMatrix, false, coord.posMatrix);
            gl.uniform1f(this.uHeightFactor, Math.pow(2, coord.overscaledZ) / tile.tileSize / 8);

            for (const segment of bucket.segments.get()) 
                const numPrevAttrib = context.currentNumAttributes || 0;
                const numNextAttrib = 2;
                for (let i = numNextAttrib; i < numPrevAttrib; i++) gl.disableVertexAttribArray(i);
                const vertexOffset = segment.vertexOffset || 0;
                gl.enableVertexAttribArray(this.aPos);
                gl.enableVertexAttribArray(this.aNormal);
                gl.enableVertexAttribArray(this.aHeight);
                gl.enableVertexAttribArray(this.aBase);
                bucket.layoutVertexBuffer.bind();
                gl.vertexAttribPointer(this.aPos, 2, gl.SHORT, false, 12, 12 * vertexOffset);
                gl.vertexAttribPointer(this.aNormal, 4, gl.SHORT, false, 12, 4 + 12 * vertexOffset);
                heightBuffer.bind();
                gl.vertexAttribPointer(this.aHeight, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
                baseBuffer.bind();
                gl.vertexAttribPointer(this.aBase, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
                bucket.indexBuffer.bind();
                context.currentNumAttributes = numNextAttrib;     
                gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2);    
            

        
    

map.on('load', () => 
    map.removeLayer('building');
    map.addLayer(
        'id': '3d-buildings',
        'source': 'composite',
        'source-layer': 'building',
        'type': 'fill-extrusion',
        'minzoom': 14,
        'paint': 
            'fill-extrusion-color': '#ddd',
            'fill-extrusion-height': ["number", ["get", "height"], 5],
            'fill-extrusion-base': ["number", ["get", "min_height"], 0],
            'fill-extrusion-opacity': 1
        
    , 'road-label');
    map.addLayer(new BuildingShadows(), '3d-buildings');
);
</script>
</body>
</html>

【问题讨论】:

【参考方案1】:

有各种各样的变化,但是

    将所有阴影绘制到模板缓冲区中,然后在模板测试打开的情况下绘制一个四边形,以便它只在设置模板的位置绘制?

    将所有阴影不透明地绘制到纹理中。将纹理绘制为地图上的四边形。

    将所有阴影绘制到深度缓冲区中。使用深度测试集绘制一个四边形,因此它只绘制有阴影的地方。

还有

    使用深度测试或模板测试绘制阴影,这样就不会多次绘制像素。

各种相关示例:

Stencil buffer in WebGL

WebGL – Use mesh as mask for background image

Is there a way in WebGL to quickly invert the stencil buffer?

【讨论】:

以上是关于如何使用最新的 Mapbox-gl-js 版本避免透明度重叠?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 mapbox-gl-js 中为源指定授权标头?

如何在 mapbox-gl-js 中以某些缩放级别隐藏点标签?

如何在 mapbox-gl-js 的标记上添加“点击”侦听器

mapbox-gl-js 围绕 lat/lng 创建一个扇区?

我可以像在(mapbox-gl-js 文档)中那样使用 react-map-gl 添加 GeoJSON 行吗?

在 Mapbox-GL-JS 中 minzoom 和 maxzoom 到底做了啥?