mapbox / mapbox-gl-js

Interactive, thoroughly customizable maps in the browser, powered by vector tiles and WebGL
https://docs.mapbox.com/mapbox-gl-js/
Other
11.17k stars 2.22k forks source link

Cannot set the renderer.autoClear to true when using customLayer #8936

Closed AmeliaWang93 closed 4 years ago

AmeliaWang93 commented 4 years ago

I change the code from https://docs.mapbox.com/mapbox-gl-js/example/add-3d-model/ and use THREE.EffectComposer to add some effect, but the map doesn't show. What should I do if it's necessary to set the renderer.autoClear to true when using customLayer?

<!DOCTYPE html>
<html lang="en">
<head>
    <title>three.js webgl - postprocessing - unreal bloom</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.4.1/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.4.1/mapbox-gl.css' rel='stylesheet'/>
    <script src='https://unpkg.com/three@0.106.2/build/three.min.js'></script>

    <script src="https://unpkg.com/three@0.106.2/examples/js/controls/OrbitControls"></script>
    <script src="https://unpkg.com/three@0.106.2/examples/js/loaders/GLTFLoader"></script>
    <script src="https://unpkg.com/three@0.106.2/examples/js/shaders/LuminosityHighPassShader"></script>
    <script src="https://unpkg.com/three@0.106.2/examples/js/shaders/CopyShader"></script>
    <script src="https://unpkg.com/three@0.106.2/examples/js/shaders/AfterimageShader"></script>
    <script src="https://unpkg.com/three@0.106.2/examples/js/postprocessing/EffectComposer"></script>
    <script src="https://unpkg.com/three@0.106.2/examples/js/postprocessing/ShaderPass"></script>

    <script src="https://unpkg.com/three@0.106.2/examples/js/postprocessing/RenderPass"></script>
    <script src="https://unpkg.com/three@0.106.2/examples/js/postprocessing/AfterimagePass"></script>
    <style>
        body {
            margin: 0;
            padding: 0;
        }

        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }
    </style>
</head>
<body>

<div id="map"></div>

<script type="module">

    // add custom layer
    mapboxgl.accessToken = '<your token>';
    let map = new mapboxgl.Map({
        container: 'map',
        style: 'mapbox://styles/mapbox/light-v10',
        zoom: 16,
        center: [120.75298105777887, 31.289765064205724],
        pitch: 60,
        antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
    });

    let modelOrigin = [120.75298105777887, 31.289765064205724];
    let modelAltitude = 100;
    let modelRotate = [Math.PI / 2, 0, 0];

    let modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(modelOrigin, modelAltitude);

    // transformation parameters to position, rotate and scale the 3D model onto the map
    let modelTransform = {
        translateX: modelAsMercatorCoordinate.x,
        translateY: modelAsMercatorCoordinate.y,
        translateZ: modelAsMercatorCoordinate.z,
        rotateX: modelRotate[0],
        rotateY: modelRotate[1],
        rotateZ: modelRotate[2],
        /* Since our 3D model is in real world meters, a scale transform needs to be
        * applied since the CustomLayerInterface expects units in MercatorCoordinates.
        */
        scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
    };

    let composer, cube;
    let params = {
        enable: true
    };

    let THREE = window.THREE;

    let customLayer = {
        id: '3d-model',
        type: 'custom',
        renderingMode: '3d',
        onAdd: function (map, gl) {
            this.camera = new THREE.Camera();
            this.scene = new THREE.Scene();
            this.map = map;

            let ambientLight = new THREE.AmbientLight(0xffffff);
            this.scene.add(ambientLight);

            let geometry = new THREE.BoxBufferGeometry(50, 50, 50, 2, 2, 2);
            let material = new THREE.MeshNormalMaterial();
            cube = new THREE.Mesh(geometry, material);

            this.scene.add(cube);

            this.map = map;

            // use the Mapbox GL JS map canvas for three.js
            this.renderer = new THREE.WebGLRenderer({
                canvas: map.getCanvas(),
                context: gl,
                antialias: true,
                preserveDrawingBuffer: false
            });

            // change value from false to true
            this.renderer.autoClear = true;

            // add EffectComposer
            composer = new THREE.EffectComposer(this.renderer);
            composer.addPass(new THREE.RenderPass(this.scene, this.camera));

            let afterimagePass = new THREE.AfterimagePass();
            composer.addPass(afterimagePass);

        },
        render: function (gl, matrix) {
            let rotationX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), modelTransform.rotateX);
            let rotationY = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), modelTransform.rotateY);
            let rotationZ = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), modelTransform.rotateZ);

            let m = new THREE.Matrix4().fromArray(matrix);
            let l = new THREE.Matrix4().makeTranslation(modelTransform.translateX, modelTransform.translateY, modelTransform.translateZ)
                .scale(new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale))
                .multiply(rotationX)
                .multiply(rotationY)
                .multiply(rotationZ);

            this.camera.projectionMatrix = m.multiply(l);
            this.renderer.state.reset();

            cube.rotation.x += 0.005;
            cube.rotation.y += 0.01;

            // use composer
            composer.render();

            // this.renderer.render(this.scene, this.camera);

            this.map.triggerRepaint();
        }
    };

    map.on('style.load', function () {
        map.addLayer(customLayer);
    });

</script>

</body>

</html>
ansis commented 4 years ago

Hey, thanks for trying this. Support for integrating threejs is still fairly rough.

I would have guessed that setting .clear on the AfterimagePass would clear the underlying buffers but it looks like it's only clearing those buffers if autoClear is set. This may be a bug in threejs but I'm not familiar enough with it to know for sure. I think a if (this.clear) renderer.clear(); here would work: https://github.com/mrdoob/three.js/blob/a8b0cd49939d140baf864183b238dfe5adbdd310/examples/js/postprocessing/AfterimagePass.js#L58

It looks like it's possible to hack around this by wrapping the passes' render function to add the clear:

const ap = new THREE.AfterimagePass();
ap._realRender = ap.render;
ap.render = function(renderer) {
            renderer.setRenderTarget( this.textureComp );
            renderer.clear();
            this._realRender.apply(this, arguments);
}
composer.addPass(ap);
AmeliaWang93 commented 4 years ago

Thanks a lot. The problem above seems to be solved. But when I add buildings by mapbox and put the cube inside, the cube is always on the top as the picture shows, where the cube should be behind the building. This problem also appears when I use other pass like BloomPass.

image

ansis commented 4 years ago

the cube is always on the top as the picture shows, where the cube should be behind the building

This is probably because we are using the depth buffer associated with the main framebuffer to store information about how deep each pixel is, when threejs uses a different framebuffer when using these passes. Unfortunately I don't see a clear way for us to fix this.

AmeliaWang93 commented 4 years ago

@ansis Thanks for your reply.

likeswinds commented 4 years ago

Thanks a lot. The problem above seems to be solved. But when I add buildings by mapbox and put the cube inside, the cube is always on the top as the picture shows, where the cube should be behind the building. This problem also appears when I use other pass like BloomPass.

image

Could you update the demo? thanks