简体   繁体   中英

How to avoid transparency overlap using the latest Mapbox-gl-js version?

I have been playing around with the shadows from building geometries example at ( https://bl.ocks.org/andrewharvey/9490afae78301c047adddfb06523f6f1 ) and have been able to blend the transparent layers to become one uniform alpha value with an older version of mapbox-gl-js.

However when I change the mapbox-gl-js version to v0.54.0 or higher it no longer blends the shadows to be a uniform value. I have experimented with gl.blendFunc() and gl.blendFuncSeparate() but still seem to get a mixture of weird anti-aliasing issues or overlapping opacities.

How could I avoid this transparency issue and get a similar result to the first example provided.

0.53.1: 0.53.1: 1.6.1: 1.6.1:

Code using version 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>

Code using version 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>

There are various variations but

  1. draw all the shadows into the stencil buffer then draw one quad with the stencil test on so that it only draws where the stencil is set?

  2. draw all the shadows opaquely into a texture. draw the texture as a quad over the map.

  3. draw all the shadows into the depth buffer. draw one quad with the depth test set so it only draws where there were shadows.

also

  1. draw the shadows with the depth test or stencil test on such that no pixel can be drawn more than once.

Various related examples:

Stencil buffer in WebGL

WebGL – Use mesh as mask for background image

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM