cesium 背景图片设置、远近景天空盒设置
1. 基本架构设计
背景、天空盒设置可细分多个功能,例如:背景颜色设置,背景图片设置,远景天空盒设置,近景天空盒设置。为了维护方便,将这些功能封装在一个类中,供业务方调用,自己组装业务效果
ts
import { Cartographic, Color, SkyBox } from 'cesium';
interface SkyBoxSources {
positiveX: string; // left
negativeX: string; // right
positiveY: string; // back
negativeY: string; // front
positiveZ: string; // up
negativeZ: string; // down
}
class Sky {
private app: any;
private _skyBoxDefault: SkyBox;
private _skyBoxFar: SkyBox | null;
private _skyBoxNear: SkyBox | null;
private _skyBoxNearListenerRemoveCallback: any;
constructor(app: any) {
this.app = app;
this._skyBoxDefault = this.app.viewerCesium.scene.skyBox;
this._skyBoxFar = this.app.viewerCesium.scene.skyBox;
this._skyBoxNear = this.app.viewerCesium.scene.skyBox;
this._skyBoxNearListenerRemoveCallback = null;
}
setBackgroundColor(color: string) {}
recoverBackgroundColor() {}
setBackgroundImage(url: string) {}
recoverBackgroundImage() {}
setSkyBoxFar(sources: SkyBoxSources) {}
recoverSkyBoxFar() {}
setSkyBoxNear(sources: SkyBoxSources) {}
recoverSkyBoxNear() {}
}
export { Sky };
2. 关键代码实现
2.1 设置背景颜色
ts
setBackgroundColor(color: string) {
// 修改背景色
this.app.viewerCesium.scene.backgroundColor = Color.fromCssColorString(color);
// 背景色要生效需要隐藏天空盒
this.app.viewerCesium.scene.skyBox.show = false;
// 我这里为了效果正常一点,隐藏了太阳,月亮
this.app.viewerCesium.scene.sun.show = false;
this.app.viewerCesium.scene.moon.show = false;
}
recoverBackgroundColor() {
this.app.viewerCesium.scene.backgroundColor = Color.BLACK;
this.app.viewerCesium.scene.skyBox.show = true;
this.app.viewerCesium.scene.sun.show = true;
this.app.viewerCesium.scene.moon.show = true;
}
2.2 设置背景图片
cesium没有提供设置背景图片的接口,只有设置背景颜色的,那如何实现设置背景图片呢? 我们可以换一种思路,把渲染背景透明,让其能透过canvas看到后面的html元素,然后设置html元素的背景图片css样式
ts
setBackgroundImage(url: string) {
// 修改背景色透明度0.0
this.app.viewerCesium.scene.backgroundColor = new Color(0.0, 0.0, 0.0, 0.0);
this.app.viewerCesium.scene.skyBox.show = false;
this.app.viewerCesium.scene.sun.show = false;
this.app.viewerCesium.scene.moon.show = false;
// 找到html元素,修改背景css
this.app.viewerCesium.container.style.backgroundImage = 'url(' + url + ')';
this.app.viewerCesium.container.style.backgroundRepeat = 'no-repeat';
this.app.viewerCesium.container.style.backgroundSize = '100% 100%';
}
recoverBackgroundImage() {
this.app.viewerCesium.scene.backgroundColor = Color.BLACK;
this.app.viewerCesium.scene.skyBox.show = true;
this.app.viewerCesium.scene.sun.show = true;
this.app.viewerCesium.scene.moon.show = true;
this.app.viewerCesium.container.style.backgroundImage = null;
this.app.viewerCesium.container.style.backgroundRepeat = null;
this.app.viewerCesium.container.style.backgroundSize = null;
}
但是这样处理还不够,你会发现并没有生效,因为webGL默认没有透明,查看官方文档的Viewer发现可以配置开启
ts
this.viewerCesium = new Viewer(this.optionConfig.dom, {
// ...一些配置项...
contextOptions: {
webgl: {
alpha: true // 开启透明
}
}
});
2.3 设置远景天空盒
很简单,用cesium自带的天空盒接口
ts
setSkyBoxFar(sources: SkyBoxSources) {
this._skyBoxFar = new SkyBox({
sources: sources
});
this.app.viewerCesium.scene.skyBox = this._skyBoxFar;
}
recoverSkyBoxFar() {
this.app.viewerCesium.scene.skyBox = this._skyBoxDefault;
this._skyBoxFar = this._skyBoxDefault;
}
2.4 设置近景天空盒
cesim提供的天空盒就一个接口,他主要应用于地球之外的星空场景,我们把他叫做远景天空盒,当缩放到地球之内,地球表面时,就看不到天空盒效果了,所以我们要搞一个叫近景天空盒
ts
setSkyBoxNear(sources: SkyBoxSources) {
this._skyBoxNear = new SkyBox({
sources: sources
});
this.app.viewerCesium.scene.skyBox = this._skyBoxNear;
if (this._skyBoxNearListenerRemoveCallback) return;
// 绑定事件监听,根据高度动态修改不同的天空盒
this._skyBoxNearListenerRemoveCallback = this.app.viewerCesium.scene.postRender.addEventListener(() => {
let e = this.app.viewerCesium.camera.position;
if (Cartographic.fromCartesian(e).height < 20000) {
this.app.viewerCesium.scene.skyBox = this._skyBoxNear;
this.app.viewerCesium.scene.skyAtmosphere.show = false;
this.app.viewerCesium.scene.sun.show = false;
this.app.viewerCesium.scene.moon.show = false;
} else {
this.app.viewerCesium.scene.skyBox = this._skyBoxFar;
this.app.viewerCesium.scene.skyAtmosphere.show = true;
this.app.viewerCesium.scene.sun.show = true;
this.app.viewerCesium.scene.moon.show = true;
}
});
}
recoverSkyBoxNear() {
this.app.viewerCesium.scene.skyBox = this._skyBoxDefault;
this.app.viewerCesium.scene.skyAtmosphere.show = true;
if (this._skyBoxNearListenerRemoveCallback) {
this._skyBoxNearListenerRemoveCallback();
this._skyBoxNearListenerRemoveCallback = null;
}
this._skyBoxNear = this._skyBoxDefault;
}
只是这样简单的隐藏大气层效果,替换天空盒,自带的天空盒是歪的,所以要模仿SkyBox的实现,重写一个SkyBoxGround来解决近景天空盒
实现SkyBoxGround
js
import {
BoxGeometry,
Cartesian3,
defaultValue,
defined,
destroyObject,
DeveloperError,
GeometryPipeline,
Matrix4,
VertexFormat,
BufferUsage,
CubeMap,
DrawCommand,
loadCubeMap,
RenderState,
ShaderProgram,
ShaderSource,
VertexArray,
BlendingState,
SceneMode,
Transforms,
Matrix3
} from 'cesium';
import SkyBoxFS from '../../../cesium/engine/Source/Shaders/SkyBoxFS.js';
// 重写SkyBoxVS,加入u_rotateMatrix旋转矩阵,让其旋转成正确位置
const SkyBoxVS = `in vec3 position;
out vec3 v_texCoord;
uniform mat3 u_rotateMatrix;
void main()
{
vec3 p = czm_viewRotation * u_rotateMatrix * (czm_temeToPseudoFixed * (czm_entireFrustum.y * position));
gl_Position = czm_projection * vec4(p, 1.0);
v_texCoord = position.xyz;
}`;
function SkyBoxGround(options) {
this.sources = options.sources;
this._sources = undefined;
this.show = defaultValue(options.show, true);
this._command = new DrawCommand({
modelMatrix: Matrix4.clone(Matrix4.IDENTITY),
owner: this
});
this._cubeMap = undefined;
this._attributeLocations = undefined;
this._useHdr = undefined;
}
SkyBoxGround.prototype.update = function (frameState, useHdr) {
const that = this;
if (!this.show) {
return undefined;
}
if (frameState.mode !== SceneMode.SCENE3D && frameState.mode !== SceneMode.MORPHING) {
return undefined;
}
// The sky box is only rendered during the render pass; it is not pickable, it doesn't cast shadows, etc.
if (!frameState.passes.render) {
return undefined;
}
const context = frameState.context;
if (this._sources !== this.sources) {
this._sources = this.sources;
const sources = this.sources;
//>>includeStart('debug', pragmas.debug);
if (
!defined(sources.positiveX) ||
!defined(sources.negativeX) ||
!defined(sources.positiveY) ||
!defined(sources.negativeY) ||
!defined(sources.positiveZ) ||
!defined(sources.negativeZ)
) {
throw new DeveloperError(
'this.sources is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.'
);
}
if (
typeof sources.positiveX !== typeof sources.negativeX ||
typeof sources.positiveX !== typeof sources.positiveY ||
typeof sources.positiveX !== typeof sources.negativeY ||
typeof sources.positiveX !== typeof sources.positiveZ ||
typeof sources.positiveX !== typeof sources.negativeZ
) {
throw new DeveloperError('this.sources properties must all be the same type.');
}
//>>includeEnd('debug');
if (typeof sources.positiveX === 'string') {
// Given urls for cube-map images. Load them.
loadCubeMap(context, this._sources).then(function (cubeMap) {
that._cubeMap = that._cubeMap && that._cubeMap.destroy();
that._cubeMap = cubeMap;
});
} else {
this._cubeMap = this._cubeMap && this._cubeMap.destroy();
this._cubeMap = new CubeMap({
context: context,
source: sources
});
}
}
const command = this._command;
if (!defined(command.vertexArray)) {
command.uniformMap = {
u_cubeMap: function () {
return that._cubeMap;
},
// 获取旋转矩阵
u_rotateMatrix: function () {
const modelMatrix = Transforms.eastNorthUpToFixedFrame(frameState.camera._positionWC);
return Matrix4.getRotation(modelMatrix, new Matrix3());
}
};
const geometry = BoxGeometry.createGeometry(
BoxGeometry.fromDimensions({
dimensions: new Cartesian3(2.0, 2.0, 2.0),
vertexFormat: VertexFormat.POSITION_ONLY
})
);
const attributeLocations = (this._attributeLocations = GeometryPipeline.createAttributeLocations(geometry));
command.vertexArray = VertexArray.fromGeometry({
context: context,
geometry: geometry,
attributeLocations: attributeLocations,
bufferUsage: BufferUsage.STATIC_DRAW
});
command.renderState = RenderState.fromCache({
blending: BlendingState.ALPHA_BLEND
});
}
if (!defined(command.shaderProgram) || this._useHdr !== useHdr) {
const fs = new ShaderSource({
defines: [useHdr ? 'HDR' : ''],
sources: [SkyBoxFS]
});
command.shaderProgram = ShaderProgram.fromCache({
context: context,
vertexShaderSource: SkyBoxVS,
fragmentShaderSource: fs,
attributeLocations: this._attributeLocations
});
this._useHdr = useHdr;
}
if (!defined(this._cubeMap)) {
return undefined;
}
return command;
};
SkyBoxGround.prototype.isDestroyed = function () {
return false;
};
SkyBoxGround.prototype.destroy = function () {
const command = this._command;
command.vertexArray = command.vertexArray && command.vertexArray.destroy();
command.shaderProgram = command.shaderProgram && command.shaderProgram.destroy();
this._cubeMap = this._cubeMap && this._cubeMap.destroy();
return destroyObject(this);
};
export { SkyBoxGround };
然后近景天空盒使用SkyBoxGround替换SkyBox就可以
3. 业务端调用
js
const skyOperate = new Plugins.Sky.SkyOperate(gisApp);
skyOperate.setBackgroundColor('green');
skyOperate.recoverBackgroundColor();
skyOperate.setBackgroundImage('http://mars3d.cn/img/tietu/backGroundImg.jpg');
skyOperate.recoverBackgroundImage();
// 这里为了方便用了火星科技的图片,后续用自己的天空盒图片即可
skyOperate.setSkyBoxFar({
positiveX: 'http://mars3d.cn/img/skybox/1/tycho2t3_80_px.jpg',
negativeX: 'http://mars3d.cn/img/skybox/1/tycho2t3_80_mx.jpg',
positiveY: 'http://mars3d.cn/img/skybox/1/tycho2t3_80_py.jpg',
negativeY: 'http://mars3d.cn/img/skybox/1/tycho2t3_80_my.jpg',
positiveZ: 'http://mars3d.cn/img/skybox/1/tycho2t3_80_pz.jpg',
negativeZ: 'http://mars3d.cn/img/skybox/1/tycho2t3_80_mz.jpg'
});
skyOperate.recoverSkyBoxFar();
// 这里为了方便用了火星科技的图片,后续用自己的天空盒图片即可
skyOperate.setSkyBoxNear({
positiveX: 'http://mars3d.cn/img/skybox_near/wanxia/SunSetRight.png',
negativeX: 'http://mars3d.cn/img/skybox_near/wanxia/SunSetLeft.png',
positiveY: 'http://mars3d.cn/img/skybox_near/wanxia/SunSetFront.png',
negativeY: 'http://mars3d.cn/img/skybox_near/wanxia/SunSetBack.png',
positiveZ: 'http://mars3d.cn/img/skybox_near/wanxia/SunSetUp.png',
negativeZ: 'http://mars3d.cn/img/skybox_near/wanxia/SunSetDown.png'
});
skyOperate.recoverSkyBoxNear();