• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

arc3dlab

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

第五篇:给地球加点“魔法”——帧率、截图、底图控制,统统安排!

第五篇:给地球加点“魔法”——帧率、截图、底图控制,统统安排! 本专栏旨在手把手带你从零开始,基于开源三维地球引擎 **Cesium** 封装一套功能完善、可复用的 **WebGIS 增强型 SDK**。内容涵盖核心封装思路、关键代码实现、常用 GIS 功能抽象,以及基于该 SDK 构建的 UI 组件库开发。

📘 专栏说明

本专栏旨在手把手带你从零开始,基于开源三维地球引擎 Cesium 封装一套功能完善、可复用的 WebGIS 增强型 SDK。内容涵盖核心封装思路、关键代码实现、常用 GIS 功能抽象,以及基于该 SDK 构建的 UI 组件库开发。如果你更关注结果而非实现过程,也可直接使用已发布的成果:

🌟 GitHub仓库 📦 NPM 包 ✨ 公众号:经纬码客(欢迎关注)

💡 建议:即便你打算直接使用 SDK,也推荐订阅本专栏 -- 理解设计思路,才能更灵活地扩展属于你自己的专属 GIS 能力!

由于作者需兼顾全职工作,更新主要安排在晚间或节假日,无法保证高频发布,但会持续迭代,直至 SDK 达到实际项目落地标准。届时将完整开源所有源码,供学习与商用(遵循许可证协议)。

大家好,我是 Cesium 酱(也可以叫我“本猿”),一名在 WebGIS 领域摸爬滚打多年的前端开发者。上一篇,我们一起封装了 Arc3DLab.Viewer,实现了“一行代码创建三维地球”。

但一个真正好用的 SDK,不能只停留在“能跑”,还要让开发者用得爽。

这一期,我们就来给 Viewer 类加几个超实用的小功能:

  • ✅ 动态开关帧率显示(FPS)
  • ✅ 一键开启/关闭地球光照与阴影
  • ✅ 获取当前地图尺寸(width / height)
  • ✅ 导出当前地图场景为 Base64 图片
  • ✅ 灵活替换底图(ImageryLayer)

这些功能看似简单,但原生 Cesium 要么藏得深,要么写法啰嗦。

我们造轮子的目标:把复杂留给自己,把简洁留给用户。

准备好了吗?打开你的 Viewer.ts,我们继续敲代码!


🔧 第一步:添加帧率控制 —— fps 属性

很多Cesium开发者调试时需要看帧率,肯定每次都要写:

viewer.scene.debugShowFramesPerSecond = true

太麻烦!

我们在 Viewer 类中添加 getter/setter:

// src/core/Viewer.ts
/**
 * 控制帧率显示
 * @type {Boolean}
 */
get fps() {
  return this.scene.debugShowFramesPerSecond
}
set fps(show: boolean) {
  this.scene.debugShowFramesPerSecond = show
}

✅ 使用方式:

viewer.fps = true   // 开启帧率
console.log(viewer.fps) // 读取当前状态

是不是清爽多了?


💡 第二步:一键控制地球光影 —— lightShadow 属性

想让地球有明暗变化?需要同时开启两个设置:

  • globe.enableLighting
  • shadows

我们把它合并成一个开关:

/**
 * 地球光源阴影
 * @type {Boolean}
 */
set lightShadow(bool: boolean) {
  this.scene.globe.enableLighting = bool
  this.shadows = bool
}

✅ 使用方式:

viewer.lightShadow = true  // 开启真实光照 + 阴影
viewer.lightShadow = false // 关闭,性能更优

🌍 小知识:开启光照后,地形起伏会更真实,但对 GPU 要求更高。本猿一般是关闭的...没办法,小破本...


📏 第三步:获取地图尺寸 —— size 只读属性

有时候我们需要知道当前画布大小(比如做截图适配),原生要写:

const { width, height } = viewer.canvas

我们封装成一个只读属性,并提取为独立函数(便于复用):

先创建工具函数 src/utils/Scene.ts(后续场景相关的方法都将在此文件扩充):

// src/utils/Scene.ts
import { Viewer } from "../core/Viewer"
/**
 * 获取地图尺寸
 */
export function mapSize(viewer: Viewer) {
  const { width, height } = viewer.canvas
  return { width, height }
}

然后在 Viewer.ts 中引用:

import { mapSize } from "../utils/Scene"
/**
 * 地图画布大小,例如:{width:1920,height:1080}
 * @type {Object}
 * @readonly
 */
get size() {
  return mapSize(this)
}

✅ 使用方式:

console.log(viewer.size) // { width: 1920, height: 1080 }

📸 第四步:一键导出场景图片 —— image 只读属性

想把当前地球画面保存为图片?原生 toDataURL() 有时会导出黑屏(因为未渲染完成)。

我们加个保险,同样是在 src/utils/Scene.ts:

// src/utils/Scene.ts
import { Viewer } from "../core/Viewer"
/**
 * 获取地图图片(Base64)
 */
export function mapImg(viewer: Viewer) {
  viewer.render() // 强制渲染一帧,避免黑屏
  return viewer.scene.canvas.toDataURL("image/png")
}

在 Viewer.ts 中添加:

import { mapImg } from "../utils/mapImg"
/**
 * 当前地图场景图片,base64格式
 * @type {String}
 * @readonly
 */
get image() {
  return mapImg(this)
}

✅ 使用方式:

const base64 = viewer.image
const img = document.createElement('img')
img.src = base64
document.body.appendChild(img)

💡 提示:功能不大,但常用,用于生成报告、分享截图、自动化测试等场景。


🌐 第五步:灵活控制底图 —— baseImagery 属性

Cesium 的底图机制有点“隐晦”,没有明确的定义:

  • 最底层的图层会被标记为 _isBaseLayer
  • 替换它需要先移除旧图层,再用 lowerToBottom() 放到底部,反正挺烦人的设置

我们把它封装成一个属性:

/**
 * 场景底图
 * Cesium机制是最底层的图层为_isBaseLayer,通过lowerToBottom来控制
 * @type {Cesium.ImageryLayer}
 */
get baseImagery() {
  //@ts-ignore
  const layers = this.imageryLayers._layers
  const baseLayer = layers.find((layer: any) => layer._isBaseLayer)
  return baseLayer
}
set baseImagery(imagery: Cesium.ImageryLayer) {
  //@ts-ignore
  const baseLayer = this.imageryLayers._layers.find(
    (layer: any) => layer._isBaseLayer
  )
  if (baseLayer) {
    this.imageryLayers.remove(baseLayer)
  }
  this.imageryLayers.lowerToBottom(imagery)
}

✅ 使用方式:

// 创建一个天地图影像图层
const tianditu = new Cesium.WebMapTileServiceImageryProvider({
  url: "http://t0.tianditu.gov.cn/img_w/wmts?tk=你的密钥",
  layer: "img",
  style: "default",
  format: "tiles",
  tileMatrixSetID: "w",
  maximumLevel: 18,
})
const imageryLayer = new Cesium.ImageryLayer(tianditu)
// 一键替换底图!
viewer.baseImagery = imageryLayer

⚠️ 注意:使用第三方地图服务请遵守其许可协议。


🧪 本地测试:试试这些新能力!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>004-场景设置</title>
    <script src="./Cesium/Cesium.js"></script>
    <link rel="stylesheet" href="./Cesium/Widgets/widgets.css" />
    <script src="./lib/arc3dlab.umd.js"></script>
    <script src="./assests/dat.gui.min.js"></script>
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
      }
      #cesiumContainer {
        width: 100%;
        height: 100%;
        position: relative;
      }
      #gui-box {
        position: absolute;
        left: 5px;
        top: 5px;
        z-index: 99;
        display: flex;
      }
    </style>
  </head>
  <body>
    <div id="cesiumContainer">
      <div id="gui-box"></div>
    </div>
    <script>
      const viewer = new Arc3DLab.Viewer("cesiumContainer")
      initGui()
      function initGui() {
        const params = {
          message: "GUI面板-场景设置",
          fps: false,
          mapSize: "width:--,height:--",
          获取尺寸: function () {
            const { width, height } = viewer.size
            params.mapSize = `width:${width},height:${height}`
            mapSizeController.updateDisplay()
          },
          场景截图: function () {
            const guiBox = document.getElementById("gui-box")
            const existingScreenshots = guiBox.querySelector("#screenshot")
            if (existingScreenshots) existingScreenshots.remove()
            const base64 = viewer.image
            const img = document.createElement("img")
            img.id = "screenshot"
            img.src = base64
            img.style.width = "300px"
            img.style.border = "1px solid #ccc"
            guiBox.appendChild(img)
          },
        }
        const gui = new dat.GUI({ autoPlace: false })
        const customContainer = document.getElementById("gui-box")
        customContainer.appendChild(gui.domElement)
        gui.add(params, "message")
        const boolFps = gui.add(params, "fps")
        boolFps.onFinishChange((val) => {
          viewer.fps = val
        })
        const mapSizeController = gui.add(params, "mapSize")
        gui.add(params, "获取尺寸")
        gui.add(params, "场景截图")
      }
    </script>
  </body>
</html>

刷新页面,你会看到:

  • 右上角显示 FPS
  • 地球出现明暗变化
  • 点击显示尺寸,点击插入截图!

🌟 项目开源,欢迎Star✨!

GitHub:https://github.com/jianlei-wang/Arc3DLab_SDK

NPM:https://www.npmjs.com/package/arc3dlab

Cesium 酱の百宝箱 · 第 5 篇

功能不在多,而在刚刚好。

2026 年伊始,与你共添一行魔法代码。

posted on 2026-01-17 11:52  Arc3DLab  阅读(0)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3