CesiumJS 全景影像显示——官方教程翻译

CesiumJS 全景影像显示——官方教程翻译

CesiumJS 支持全景和 360° 球面影像,让你能够在 CesiumJS 场景中直接渲染等距柱状投影(equirectangular)和立方体贴图(cube map)全景图。这为在 3D 地理空间环境中流式加载 Google Street View 全景图(以及其他全景影像服务)提供了支持。

法国拉昂大教堂内部的 360° 全景影像。

法国拉昂大教堂内部的 360° 全景影像。照片由 David Iliff 拍摄,基于 CC BY-SA 3.0 许可。

你将学习如何:

  • 等距柱状投影全景图添加到 CesiumJS 场景,并将相机放置到其”内部”
  • 添加立方体贴图全景图(六个面)并正确朝向
  • 配置相机控制以获得沉浸式体验,包括可选的视场角”缩放”

先决条件

  • 一个基础的 CesiumJS 应用。如果从零开始,请先遵循 CesiumJS 快速入门教程 并确保应用正常运行
  • 一张全景影像:
    • 等距柱状投影:最好为 2:1 宽高比,代表水平 360° / 垂直 180°
    • 立方体贴图:六个面(每面 90° × 90° 透视投影)

本教程中的代码示例会提供示例数据供你上手。

信息:关于图片托管和 CORS 的说明:如果你的全景图片托管在不同的域名上,服务器必须允许跨域使用(CORS),否则 WebGL 无法将其作为纹理采样。如果来源不支持 CORS,你需要一个同源代理。


步骤 1:设置查看器用于全景浏览

创建一个查看器并关闭地球,使全景图成为场景背景。为了获得最佳的全景浏览体验,建议通过设置 globe.show = false 来隐藏地球。

import * as Cesium from "cesium";

const viewer = new Cesium.Viewer("cesiumContainer");

// 为获得最佳全景浏览效果,关闭地球
viewer.scene.globe.show = false;
const controller = viewer.scene.screenSpaceCameraController;

为什么要隐藏地球?

  • 全景影像通常作为背景上下文层使用,而其他图元(3D Tiles、模型、实体)在前方渲染
  • 实用提示:退出全景模式时,可以重新启用地球(例如 viewer.scene.globe.show = true
  • 性能提示:在许多应用中,这还能在全景浏览期间避免地球渲染,从而减少绘制开销

步骤 2:添加等距柱状投影全景图

等距柱状投影全景图是一张包裹在相机周围球体上的单一图像。准备好影像后,定位并朝向你的全景图:你需要在地球上确定一个位置,并在可用时使用航向/俯仰/翻滚元数据来正确朝向。

const equirectangularFromFile = () => {
    const position = Cesium.Cartesian3.fromDegrees(-122.4175, 37.655, 100);

    // 如果你有朝向元数据(例如来自 EXIF/XMP 或影像服务),
    // 将其转换为弧度并在此使用。
    // 以下为演示用的示例值。
    const heading = Cesium.Math.toRadians(10.0); // 绕本地向上轴旋转
    const pitch = Cesium.Math.toRadians(-5.0);   // 负值表示向下看
    const roll = Cesium.Math.toRadians(2.0);     // 绕前向轴翻滚
    const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);

    // 创建一个变换矩阵来定位并朝向全景图。
    // 这在全景图的世界位置处融合了航向/俯仰/翻滚。
    const transform = Cesium.Transforms.headingPitchRollToFixedFrame(
        position,
        hpr,
        Cesium.Ellipsoid.WGS84,
        Cesium.Transforms.eastNorthUpToFixedFrame,
    );

    const image =
        "https://upload.wikimedia.org/wikipedia/commons/0/08/Laon_Cathedral_Interior_360x180%2C_Picardy%2C_France_-_Diliff.jpg";

    const credit = new Cesium.Credit(
        "Photo by DAVID ILIFF. Interior of Laon Cathedral, France. Licensed under " +
            "https://creativecommons.org/licenses/by-sa/3.0/.",
    );

    const panorama = new Cesium.EquirectangularPanorama({
        transform,
        image,
        credit,
    });

    viewer.scene.primitives.add(panorama);

    // 将相机放置在全景图中心
    viewer.scene.camera.lookAt(
        position,
        new Cesium.HeadingPitchRange(0, 0, 2), // 微小偏移以允许旋转
    );

    // 将用户"固定"在全景图内部(可选,但对许多体验推荐)
    controller.enableZoom = false;
    controller.enableTranslate = false;
};

信息:为什么要用航向/俯仰/翻滚?许多拍摄流程和影像服务会提供朝向元数据。将其纳入全景图的变换矩阵中,可以确保全景图与实际场景正确对齐。

这里发生了什么?

  • 你计算了一个本地坐标系变换矩阵并将其传递给全景图,使其在全局场景中正确定位/朝向
  • 你使用 camera.lookAt 将相机放置在全景图中心
  • 你可以选择性地限制相机控制,使相机保持固定,获得清晰的”环顾四周”体验
  • 你将航向/俯仰/翻滚融入了变换矩阵中

一个可直接在 CesiumJS 场景中渲染的沉浸式全景图。

步骤 3:添加立方体贴图全景图(六张图片)

立方体贴图是六张图片(前/后/左/右/上/下),包裹在相机周围的立方体上。与等距柱状投影全景图类似,你需要创建一个变换矩阵来在场景中正确朝向。

const cubeMapFromFiles = () => {
    const position = Cesium.Cartesian3.fromDegrees(104.923323, 11.569967, 0);

    // 创建一个变换矩阵来朝向全景图
    const matrix4 = Cesium.Transforms.localFrameToFixedFrameGenerator(
        "north",
        "down",
    )(position, Cesium.Ellipsoid.default);

    const transform = Cesium.Matrix4.getMatrix3(matrix4, new Cesium.Matrix3());

    const credit = new Cesium.Credit(
        "Image by Kiensvay via Wikimedia Commons " +
            "Licensed under " +
            '<a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a>.',
    );

    const cubeMapPanorama = new Cesium.CubeMapPanorama({
        sources: {
            positiveZ:
                "https://upload.wikimedia.org/wikipedia/commons/3/37/360%C2%B0_Phnom_Penh_%28Central_Market_2022%29_%28left%29.jpg",
            negativeZ:
                "https://upload.wikimedia.org/wikipedia/commons/1/1b/360%C2%B0_Phnom_Penh_%28Central_Market_2022%29_%28right%29.jpg",
            positiveY:
                "https://upload.wikimedia.org/wikipedia/commons/7/73/360%C2%B0_Phnom_Penh_%28Central_Market_2022%29_%28down%29.jpg",
            negativeY:
                "https://upload.wikimedia.org/wikipedia/commons/d/de/360%C2%B0_Phnom_Penh_%28Central_Market_2022%29_%28up%29.jpg",
            negativeX:
                "https://upload.wikimedia.org/wikipedia/commons/2/2e/360%C2%B0_Phnom_Penh_%28Central_Market_2022%29_%28back%29.jpg",
            positiveX:
                "https://upload.wikimedia.org/wikipedia/commons/d/db/360%C2%B0_Phnom_Penh_%28Central_Market_2022%29_%28front%29.jpg",
        },
        transform,
        credit,
    });

    viewer.scene.camera.lookAt(position, new Cesium.HeadingPitchRange(0, 0, 2));

    removeAndDestroyPanoramas();
    viewer.scene.primitives.add(cubeMapPanorama);

    // 可选:为此模式恢复默认导航
    controller.enableZoom = true;
    controller.enableTranslate = true;
};

信息:要将航向/俯仰/翻滚元数据纳入变换矩阵,请参考步骤 2 中的示例。

提示: 如果立方体面出现交换/旋转的情况,请检查:

  • 面的命名约定(positiveX、negativeX 等)
  • 本地坐标系轴的选择(此处为 north/down)

金边的立方体贴图全景图。创建一个变换矩阵来在场景中正确朝向立方体贴图。

步骤 4:移除(并可选销毁)全景图元以清理

当你使用完一个全景图后,可能需要将其移除(例如,切换回”世界视图”,或在新的位置加载不同的全景图)。

function removeAndDestroyPanoramas() {
    const primitives = viewer.scene.primitives;

    for (let i = primitives.length - 1; i >= 0; i--) {
        const primitive = primitives.get(i);

        const isPanorama =
            primitive instanceof Cesium.CubeMapPanorama ||
            primitive instanceof Cesium.EquirectangularPanorama;

        if (isPanorama) {
            // 从场景中移除
            primitives.remove(primitive);

            // 可选:如果你的 CesiumJS 版本暴露了 destroy() 方法,
            // 调用它以释放 GPU 资源。
            // if (typeof primitive.destroy === "function") {
            //   primitive.destroy();
            // }
        }
    }
}

如果要切换回”世界视图”,重置相机和地球默认设置:

function exitPanoramaMode() {
    // 重新启用地球/世界上下文
    viewer.scene.globe.show = true;

    controller.enableZoom = true;
    controller.enableTranslate = true;

    viewer.scene.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
}

步骤 5:沉浸式全景体验的相机控制

要在场景中查看全景图,使用 camera.lookAt 将相机放置在全景图中心。为了获得沉浸式体验,通常建议允许旋转和倾斜,但禁用平移和缩放,使用户固定在全景图原点。

信息: 等距柱状投影设置禁用了缩放和平移以将用户”固定”在全景图内部,而立方体贴图设置恢复了默认导航(缩放/平移)以便更自由地探索。请选择最适合你应用用户体验的行为。

推荐的控制器设置

你已经在步骤 2 和 3 中设置了 camera.lookAt,以下代码片段明确了控制器行为

const controller = viewer.scene.screenSpaceCameraController;

controller.enableRotate = true;
controller.enableTilt = true;
controller.enableTranslate = false;
controller.enableZoom = false;

可选:通过调整视场角(FOV)实现”缩放”

在全景模式下,鼠标滚轮自然地缩放/扩大相机的 FOV(而不是平移相机)。如果你想在保持 enableZoom = false 的情况下使用滚轮缩放,可以调整相机的视场角:

const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);

const minFov = Cesium.Math.toRadians(20.0);
const maxFov = Cesium.Math.toRadians(100.0);
const zoomSpeed = 0.05;

function enableFieldOfViewAdjustment() {
    handler.setInputAction(
        function (movement) {
            const camera = viewer.camera;
            const frustum = camera.frustum;

            let fov = frustum.fov;
            const delta = movement;

            if (delta < 0) {
                fov *= 1.0 + zoomSpeed; // 放大(更窄的视野)
            } else {
                fov *= 1.0 - zoomSpeed; // 缩小(更宽的视野)
            }

            fov = Cesium.Math.clamp(fov, minFov, maxFov);
            frustum.fov = fov;
        },
        Cesium.ScreenSpaceEventType.WHEEL,
    );
}

enableFieldOfViewAdjustment();

完整代码(Sandcastle)

点击 + 拖动进行交互

探索并尝试 全景图 Sandcastle


故障排除

全景图为黑色,不渲染?

  • 检查图片主机的 CORS 头信息;WebGL 在没有正确 CORS 的情况下无法采样跨域纹理

全景图旋转了或朝向错误?

  • 确认在构建变换矩阵时使用了正确的本地坐标系和朝向元数据(航向/俯仰/翻滚)

想知道为什么可以”离开”全景图?

  • 禁用平移/缩放控制(或将相机固定在全景图中心),使交互保持沉浸感

资源

其他全景数据源

除 Google Street View 外,还有多个公共和开放数据源提供可用于 CesiumJS 全景图的全景影像。

地理定位的街景影像 API

这些平台提供地理参考的街景影像,可按位置或空间范围查询,并集成到自定义全景工作流中:

  • Google Street View Cesium 文档:通过 GoogleStreetViewCubeMapPanoramaProvider 加载 Google Street View 影像到 CesiumJS 全景图的 API 参考
  • Mapillary:大量众包、地理定位的街景影像集合,提供基于位置、边界框和元数据发现图像的 API
  • Panoramax:一个开放的、联邦式平台,用于托管和访问地理定位的街景照片。Panoramax 提供基于标准(STAC)的 API,用于在公共和自托管实例中发现影像及关联元数据
非地理定位或松散地理参考的全景图

这些来源适用于室内场景或不需要精确定位的一般环境上下文:

  • Wikimedia Commons(360° 全景图):大量自由许可的 360° 全景影像集合,包括等距柱状投影和立方体贴图资源,适用于演示、示例和教育用途
  • Poly Haven HDRIs:高质量等距柱状投影 HDR 全景图,常用于背景上下文。虽未地理定位,但适用于室内场景、天空盒和非特定位置的全景体验

总结

全景影像实现了沉浸式的街景和场地级别可视化,同时保留了完整 3D 地理空间场景的空间上下文。使用 CesiumJS,你可以无缝集成等距柱状投影和立方体贴图全景图,在地球上精确定位,并将用户直接置于体验之中。

这一能力对于受益于人类尺度上下文的工作流尤其强大,例如基础设施巡检、资产状况评估、场地规划和态势感知。通过将全景图与 CesiumJS 的其他图元(如地形、3D Tiles、模型和分析覆盖层)结合使用,你可以弥合地理空间数据与实地视觉理解之间的鸿沟。


cesium.com/learn 上的内容和代码示例根据 Apache 2.0 许可证 提供。你可以在商业或非商业应用中使用这些代码示例。


专业服务

奇小狐工作室 – 3D 数据处理专家

  • 高斯溅射(Gaussian Splat)PLY 数据处理
  • 大批量点云数据处理
  • 3D Tiles 格式转换与优化

联系方式:微信 Elusive57


发表评论