天空盒
下面的例子里的详细的展示了天空盒立方体的六个面
- left
- right
- top
- bottom
- back
- front
如果你不懂WebGL的知识,你可以参考这个效果图来配置每个面的对应的图片,这里已经把每个面的方位和对应的图片都贴出来了, 方便参考
天空盒的功能在 maptalks-gl webgl包里,你需要
npm i maptalks-gl或者
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/maptalks-gl/dist/maptalks-gl.min.js"
></script>天空盒示例
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,不会阻塞地图的初始化了
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图层
为了方便我们绘制天空盒的区域,我们顶一个天空盒的图层,来完成各种效果的绘制
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);绘制天空盒的区域
我们只需要把天空盒的区域框出来即可
![]()
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 绘制下矩形即可
![]()
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值即可
![]()
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;