cesium 背景图片设置、远近景天空盒设置

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();

4. 效果

相关推荐
web1350858863527 分钟前
前端node.js
前端·node.js·vim
m0_5127446428 分钟前
极客大挑战2024-web-wp(详细)
android·前端
若川37 分钟前
Taro 源码揭秘:10. Taro 到底是怎样转换成小程序文件的?
前端·javascript·react.js
潜意识起点1 小时前
精通 CSS 阴影效果:从基础到高级应用
前端·css
奋斗吧程序媛1 小时前
删除VSCode上 origin/分支名,但GitLab上实际上不存在的分支
前端·vscode
IT女孩儿1 小时前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
m0_748256563 小时前
如何解决前端发送数据到后端为空的问题
前端
请叫我飞哥@3 小时前
HTML5适配手机
前端·html·html5
@解忧杂货铺5 小时前
前端vue如何实现数字框中通过鼠标滚轮上下滚动增减数字
前端·javascript·vue.js
F-2H7 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++