Skip to content
目录

天空盒

Markdown 官方教程

下面的例子里的详细的展示了天空盒立方体的六个面

  • left
  • right
  • top
  • bottom
  • back
  • front

如果你不懂WebGL的知识,你可以参考这个效果图来配置每个面的对应的图片,这里已经把每个面的方位和对应的图片都贴出来了, 方便参考

天空盒的功能在 maptalks-gl webgl包里,你需要

sh
npm i maptalks-gl

或者

html
<script
    type="text/javascript"
    src="https://cdn.jsdelivr.net/npm/maptalks-gl/dist/maptalks-gl.min.js"
></script>

天空盒示例

js
var r = "./../assets/image/dawnmountain-";
const map = new maptalks.Map("map", {
    center: [116.45266161, 39.86656647],
    zoom: 17.8,
    bearing: -54,
    pitch: 70.8,
    //灯光配置
    lights: {
        directional: {
            direction: [1, 0, -1],
            color: [1, 1, 1],
        },
        ambient: {
            //配置天空盒的资源
            resource: {
                url: {
                    front: `${r}zpos.png`,
                    back: `${r}zneg.png`,
                    left: `${r}xpos.png`,
                    right: `${r}xneg.png`,
                    top: `${r}ypos.png`,
                    bottom: `${r}yneg.png`,
                },
                prefilterCubeSize: 256,
            },
            exposure: 0.8,
            hsv: [0, 0.34, 0],
            orientation: 1,
        },
    },
});

const baseLayer = new maptalks.TileLayer("base", {
    urlTemplate:
        "https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",
    subdomains: ["a", "b", "c", "d"],

    spatialReference: {
        projection: "EPSG:3857",
    },
});

const groupLayer = new maptalks.GroupGLLayer("group", [baseLayer], {
    sceneConfig: {
        environment: {
            enable: true,
            mode: 1,
            level: 0,
            brightness: 0,
        },
        shadow: {
            type: "esm",
            enable: true,
            quality: "high",
            opacity: 0.11,
            color: [0, 0, 0],
            blurOffset: 1,
        },
        postProcess: {
            enable: true,
            antialias: {
                enable: true,
                taa: true,
                jitterRatio: 0.25,
            },
            ssr: {
                enable: true,
            },
            bloom: {
                enable: true,
                threshold: 0,
                factor: 0.2,
                radius: 0.105,
            },
            ssao: {
                enable: true,
                bias: 0.08,
                radius: 0.08,
                intensity: 1.5,
            },
            sharpen: {
                enable: false,
                factor: 0.2,
            },
        },
        ground: {
            enable: false,
            renderPlugin: {
                type: "fill",
            },
            symbol: {
                polygonFill: [0.517647, 0.517647, 0.517647, 1],
            },
        },
    },
});
groupLayer.addTo(map);

从这个例子里我们看到有几个重要的点:

  • map options里配置了 lights参数了
  • 出现了个新的图层GroupGLLayer,GroupGLLayer是webgl插件包里提供的一个管理图层webgl上下文融合管理的类
  • 关于这些配置参数的信息请参考 maptalks-gl文档

WARNING

注意prefilterCubeSize参数不可太大,否则会导致天空盒初始化时卡顿,页面长时间白屏中

异步加载天空盒

maptalks-gl version>=0.115.0开始,天空盒添加了异步计算和跨域的支持 asynchronous ,开启异步计算 后天空盒的复杂逻辑将交给worker,不会阻塞地图的初始化了

skybox asynchronous demo

js
const map = new maptalks.Map("map", {
    center: [116.45266161, 39.86656647],
    zoom: 17.8,
    bearing: -54,
    pitch: 80.8,
    lights: {
        directional: {
            direction: [1, 0, -1],
            color: [1, 1, 1],
        },
        ambient: {
            resource: {
                url: {
                    front: `${r}zpos.png`,
                    back: `${r}zneg.png`,
                    left: `${r}xneg.png`,
                    right: `${r}xpos.png`,
                    top: `${r}ypos.png`,
                    bottom: `${r}yneg.png`,
                },
                prefilterCubeSize: 512,
                asynchronous: true,
                crossOrigin: "",
            },
            exposure: 0.8,
            hsv: [0, 0.34, 0],
            orientation: 1,
        },
    },
});

GroupGLLayer

GroupGLLayerwebgl包里提供的一个管理图层webgl上下文融合管理的图层,需要注意的是:

  • 一个地图上一般只有一个GroupGLLayer实例
  • 添加到GroupGLLayer里的图层将共享一个webgl上下文
  • 只有webgl图层才能放到GroupGLLayer里,不支持webgl渲染的图层放进去会报错的

不推荐用theejs插件去搞天空盒

理由:

  • maptalks-gl 包里已经自带天空盒的功能,且是基于地图实现的,是个全局的功能
  • three插件只是个图层,一个地图上可能有多个图层的
  • 基于我们开篇的介绍:全局性的功能得有地图实现,而不是某一个图层来实现

自定义天空盒

maptalks核心库从 1.8.0 开始添加了 getGroundExtent方法,可以来拿到地平线的屏幕范围,有了这个方法 我们就可以推导出天空盒的范围了(Extent.ymin)的值,即天空盒的高度

有了这个值,我们就可以对天空盒的区域做各种玩法了,这里我们将尝试:

  • 绘制天空盒的区域
  • 天空盒绘制渐变色
  • 天空盒绘制天空盒的图片

这三个效果由简入繁

WARNING

这里的自定义天空盒只是用来告诉大家,其实简单的天空盒效果我们完全可以自己写一个,适合业务简单的和喜欢 玩花活的同学,如果你的业务是强三维的用官方的方法即可

定义一个SkyLayer图层

为了方便我们绘制天空盒的区域,我们顶一个天空盒的图层,来完成各种效果的绘制

关于maptalks自定义个canvas图层请参考

js
class SkyLayer extends maptalks.Layer {
    //设置天空盒图片
    setSky(skyUrl) {
        this.skyUrl = skyUrl;
        this.loading = false;
        this.skyImage = null;
        this._loadImage();
    }

    getSky() {
        return this.skyUrl;
    }

    //设置天空盒底部偏移量
    setBottomY(y) {
        this.bottomY = y;
        const renderer = this.getRenderer();
        renderer.setToRedraw();
    }

    _loadImage() {
        if (this.loading) {
            return this;
        }
        const image = new Image();
        image.onload = () => {
            this.skyImage = image;
            this.loading = false;
            const renderer = this.getRenderer();
            renderer.setToRedraw();
            this.fire("skyload", { skyUrl: this.skyUrl });
        };
        image.onerror = () => {
            this.fire("skyloaderror", { skyUrl: this.skyUrl });
        };
        image.src = this.skyUrl;
        this.loading = true;
    }
}

// 定义默认的图层配置属性
SkyLayer.mergeOptions({
    zIndex: -Infinity,
    forceRenderOnMoving: true,
    forceRenderOnZooming: true,
    forceRenderOnRotating: true,
});

class SkyLayerRenderer extends maptalks.renderer.CanvasRenderer {
    checkResources() {
        // HelloLayer只是绘制文字, 没有外部图片, 所以返回空数组
        return [];
    }

    draw() {
        this._drawSky();
        this.completeRender();
    }

    drawOnInteracting(evtParam) {
        this._drawSky();
        this.completeRender();
    }

    _drawSky() {
        const layer = this.layer;
        if (!layer) {
            return;
        }
        this.prepareCanvas();
        // this.clearCanvas();
        const ctx = this.context;
        ctx.canvas._drawn = false;
        //do somethings

        return true;
    }
}

SkyLayer.registerRenderer("canvas", SkyLayerRenderer);

绘制天空盒的区域

我们只需要把天空盒的区域框出来即可

_0AQTVJ%7DUGMZANS86%25)1F5T.png

sky-custom-border

js
const layer = this.layer;
if (!layer) {
    return;
}
this.prepareCanvas();
// this.clearCanvas();
const ctx = this.context;
ctx.canvas._drawn = false;
const skyImage = this.layer.skyImage;
const bottomY = this.layer.bottomY || 0;
const map = layer.getMap();
const height = map.getGroundExtent().ymin;
if (height > 0) {
    const width = map.width;
    ctx.strokeStyle = "red";
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.rect(0, 0, width, height);
    ctx.stroke();
}

return true;

绘制渐变色

配置下颜色和利用canvas的 createLinearGradient 绘制下矩形即可

S6%5DJA%7B%60PQ)%5DJGZ%40QKGA%60~6R.png

sky-custom-colors

js
const layer = this.layer;
if (!layer) {
    return;
}
this.prepareCanvas();
const ctx = this.context;
ctx.canvas._drawn = false;
const skyImage = this.layer.skyImage;
const bottomY = this.layer.bottomY || 0;
const map = layer.getMap();
const height = map.getGroundExtent().ymin;
if (height > 0) {
    const width = map.width;
    const colors = layer.colors || layer.options.colors;
    let fillStyle;
    if (colors) {
        const [c1, c2] = colors;
        const grd = ctx.createLinearGradient(width / 2, 0, width / 2, height);
        grd.addColorStop(0, c1);
        grd.addColorStop(1, c2);
        fillStyle = grd;
    }

    if (!fillStyle) {
        return;
    }
    ctx.fillStyle = fillStyle;
    ctx.fillRect(0, 0, width, height + 2);
}

绘制天空盒的图片

这个稍微复杂点,因为天空盒的图片有点特殊:

  • 要求天空盒的四个面(left,back,right,front) 四个面的图片要拼成一个大图片
  • 根据地图旋转的角度,自动计算对应的天空盒图片哪个区域参与绘制
  • 因为天空盒是可以无限循环的,所以绘制时要考虑临界点,让四个面绘制串起来

WARNING

注意例子代码里我们没有考虑天空盒四个面的图片连接顺序问题,这里只是简单的告诉大家怎么玩而已, 自己实际业务里可以根据自己的需要计算对应的left值即可

sky-custom

J%7BMHR0WATDTOF%60Y%24_E%5BDN4F.png

js
const layer = this.layer;
if (!layer) {
    return;
}
this.prepareCanvas();
const ctx = this.context;
ctx.canvas._drawn = false;
const skyImage = this.layer.skyImage;
const bottomY = this.layer.bottomY || 0;
if (this.layer.skyImage) {
    const map = layer.getMap();
    const height = map.getGroundExtent().ymin;
    if (height > 0) {
        const width = map.width;
        //将整个地图的旋转角度变成360
        const bearing = 180 + map.getBearing();
        const imageWidth = skyImage.width;
        const imageHeight = skyImage.height;
        //计算天空盒左边的切割坐标
        const left = (bearing / 360) * imageWidth;
        //天空盒四等分,left,right,front,back
        const w = imageWidth / 4;
        //图片底部坐标
        const y = imageHeight - bottomY - height;
        ctx.drawImage(skyImage, left, y, w, height, 0, 0, width, height + 2);
        //溢出的地方,空白的地方,补起来,即补天空盒最左边的
        if (left + w > imageWidth) {
            //图片最右边实际绘制的区域
            const realwidth = imageWidth - left;
            //需要补充的宽度
            const needwidth = w - realwidth;
            const wscale = needwidth / w;
            const x = (1 - wscale) * width;
            ctx.drawImage(
                skyImage,
                0,
                y,
                w * wscale,
                height,
                x - 1,
                0,
                width - x,
                height + 2,
            );
        }
    }
}
return true;

maptalks教程 document auto generated by mdpress and vitepress