Cesium | Camera类介绍及其业务场景的简单实现

3,061 阅读7分钟

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

前言

在二维地图中,我们只需要提供经纬度坐标即可定位到指定地点,而在三维世界中,经纬度(即二维坐标系)是无法描述一个要素的位置,所以需要使用笛卡尔坐标系来描述空间要素的位置。具体介绍这里不啰嗦,在之前的文章中已经大致介绍过,这篇主要来讲解一下 camera 相关参数及利用camera能够做出的效果及其实现思路。

camera

image.png 我在 blender 中截了一张图模拟我们的相机对象,球体便是 cesium 的地球 🌏,个人觉得是很生动形象的。有助于我们了解相机的视角,方便后续讲解调整相机的相关参数。

官网的 api 中是这么介绍的:相机类由位置、方向和视锥体定义。 视锥体(viewing frustum)由6个(上、下、左、右、近、远)平面限定,每个平面可 由Cartesian4对象表示,其中x,y和z分量定义垂直于平面的单位向量,w分量是平面距原点/相机位置的距离。

//创建相机对象。
var camera = new Cesium.Camera(scene);
camera.position = new Cesium.Cartesian3();
camera.direction = Cesium.Cartesian3.negate(Cesium.Cartesian3.UNIT_Z, new Cesium.Cartesian3());
camera.up = Cesium.Cartesian3.clone(Cesium.Cartesian3.UNIT_Y);
camera.frustum.fov = Cesium.Math.PI_OVER_THREE;
camera.frustum.near = 1.0;
camera.frustum.far = 2.0;

说的什么鸟语,目前我们不需要了解这么复杂的自定义实现,只需要专注于它给我们提供的变换视角方法即可!

操作 camera

cesium 中提供了多种方式对相机进行操作:setViewflyTolookAt,主要介绍一下这三种方式的使用及异同

setView(options)

viewer.camera.setView(options)

设置相机位置、方向和变换。

opitons<Object>参数列表:

名称类型描述
destinationCartesian3 | Rectangle相机在 wgs84 坐标系中的最终位置,或自顶向下视图可见的矩形区域
orientationObject包含方位(direction),上方向(up)及方位角(heading),仰角(pitch),滚动角(roll)属性的对象
endTransformMatrix4相机参考坐标系的变换矩阵
convertBooleanWhether to convert the destination from world cool。(这参数我也不知道干嘛的……)

用法:

// 1. 通过top-down视图设置位置。
viewer.camera.setView({
    destination : Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0)
});

// 2 通过方位角、俯仰角、滚动角设置视图。
viewer.camera.setView({
    destination : cartesianPosition,
    orientation: {
        heading : Cesium.Math.toRadians(90.0), // east, default value is 0.0 (north)
        pitch : Cesium.Math.toRadians(-90),    // default value (looking down)
        roll : 0.0                             // default value
    }
});

// 3. 相机空间位置不变,改变方位角、俯仰角和滚动角。.
viewer.camera.setView({
    orientation: {
        heading : Cesium.Math.toRadians(90.0), // east, default value is 0.0 (north)
        pitch : Cesium.Math.toRadians(-90),    // default value (looking down)
        roll : 0.0                             // default value
    }
});


// 4. 通过自顶向下视图查看矩形。
viewer.camera.setView({
    destination : Cesium.Rectangle.fromDegrees(west, south, east, north)
});

// 5. 通过使用单位向量的方向(orientation)设置相机位置。
viewer.camera.setView({
    destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),
    orientation : {
        direction : new Cesium.Cartesian3(-0.04231243104240401, -0.20123236049443421, -0.97862924300734),
        up : new Cesium.Cartesian3(-0.47934589305293746, -0.8553216253114552, 0.1966022179118339)
    }
});

flyTo(options)

viewer.camera.flyTo(options)

相机从当前位置飞行到新的空间位置。

opitons<Object>参数列表(其余参数请查阅官方文档):

名称类型描述
destinationCartesian3 | Rectangle相机在 wgs84 坐标系中的最终位置,或自顶向下视图可见的矩形区域
orientationObject包含方位(direction),上方向(up)及方位角(heading),仰角(pitch),滚动角(roll)属性的对象
durationNumber飞行持续时间(以秒为单位)。如果省略,由飞行距离计算合理的持续时间。
completeCamera~FlightCompleteCallback飞行完成时执行的功能。
cancelCamera~FlightCompleteCallback飞行取消时执行的功能。
endTransformMatrix4飞行完成时相机将处于的参考系的变换矩阵。
maximumHeightNumber飞行中的最大高度。
easingFunctionEasingFunction | EasingFunction~Callback释放时调用功能。
………………

用法:

// 1. 飞向通过top-down视图表示的位置
viewer.camera.flyTo({
    destination : Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0)
});

// 2. 飞向通过top-down视图表示的矩形
viewer.camera.flyTo({
    destination : Cesium.Rectangle.fromDegrees(west, south, east, north)
});

// 3. 飞向利用单位向量表示方向(orientatin)的位置
viewer.camera.flyTo({
    destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),
    orientation : {
        direction : new Cesium.Cartesian3(-0.04231243104240401, -0.20123236049443421, -0.97862924300734),
        up : new Cesium.Cartesian3(-0.47934589305293746, -0.8553216253114552, 0.1966022179118339)
    }
});

// 4. 飞向利用方位角(heading)、俯仰角(pitch)、滚动角(roll)表示方向(orientatin)的位置
viewer.camera.flyTo({
    destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),
    orientation : {
        heading : Cesium.Math.toRadians(175.0),
        pitch : Cesium.Math.toRadians(-35.0),
        roll : 0.0
    }
});

lookAt(target,offset)

通过目标(target)和偏移(offset)设置相机位置、方向。 目标(target)以世界坐标方式表示;偏移(offset)是在以目标为中心的本地“东-北-上”参考系中的笛卡尔坐标或“方位角/俯仰角/范围(heading/pitch/range)”。 如果偏移(offset)由笛卡尔坐标表示,它表示距离由变换矩阵定义的参考系中心的偏移量。 如果偏移(offset)由“heading/pitch/range”表示,方位角(heading)和俯仰角(pitch)的角度由根据变换矩阵定义的参考系确定,方位角(heading)从 y 轴开始朝向 x 轴增加,俯仰角(pitch)为从 xy 平面的旋转角度,正的俯仰角位于平面下方,负的俯仰角位于平面上方。 范围(range)为距中心点的距离。在 2D 中必须为自上而下视图,相机位于俯视目标的上方,目标上方的高度值是偏移量的大小。方位角根据偏移确定,如果不能从偏移确定方位角,则方位角为北方向。

viewer.camera.lookAt(target,offset)

设置相机位置、方向和变换。

opitons<Object>参数列表:

名称类型描述
targetCartesian3目标在世界坐标中的空间位置。
offsetCartesian3 | HeadingPitchRange距以目标为中心的本地“东-北-上”参考系的偏移。

用法:

// 1. 利用笛卡尔坐标设置偏移
var center = Cesium.Cartesian3.fromDegrees(-98.0, 40.0);
viewer.camera.lookAt(center, new Cesium.Cartesian3(0.0, -4790000.0, 3930000.0));

// 2. 利用HeadingPitchRange设置偏移
var center = Cesium.Cartesian3.fromDegrees(-72.0, 40.0);
var heading = Cesium.Math.toRadians(50.0);
var pitch = Cesium.Math.toRadians(-20.0);
var range = 5000.0;
viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, range));

三者异同

首先三者基本都是通过调整相机对象的destinationorientation达到定位到指定位置的目的,不同点在于flyTo是带有动画效果的飞入目的地,其余二者是直接定位到对应位置(如图)接下来我们详细说明一下参数中的方位角/俯仰角/滚动角,因为这和我们想要实现的效果息息相关。

setViewflyTolookAt

Heading/Pitch/Roll

上文中已经对这三个角进行了中文注释

  • Heading:方位角

  • Pitch:俯仰角

  • Roll:滚动角

现在以你聪明的小脑袋瓜为中心,heading类似于左右环顾也就是左右方向的改变,pitch相当于低头抬头也就是上下方向的改变,而roll可以想象你在侧翻,而你的头始终朝前看,滚动一圈也就相当于roll了360°。

下图直观的展现了视角与三个角的关系:

image.png

HeadingPitchRange与HeadingPitchRoll

这两者都是用来定义一个局部使用的角对象,二者区别在HeadingPitchRoll主要用在模型变换上,而HeadingPitchRange更多用于相机、视角相关操作的时候。下边简单将二者有区别的参数进行展示。

  • range:代表一个半径值,也就是锁定了相机距离目标的距离。

  • roll:滚动分量(以弧度为单位)

实战案例

相机能做的效果非常多,而最经典的因该就是绕点飞行和漫游了,接下俩就两个实现效果来简单实现。

绕点飞行

绕点飞行.gif

我们首先创建一个点将绕点飞行的中心点可视化出来,不多介绍了就是基本的创建实体:

var entity = this.GLOBAL.Viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(116.391935, 39.907123),
    point: {
        color: Cesium.Color.YELLOW,
        pixelSize: 10,
    },
})

接下来利用zoomTo将视角定位到我们创建的点,在then中利用onTick绑定一个每帧运行的方法,在方法中调整我们的heading(方位角),实现绕着点位旋转:

var heading = 0 // 朝向
var offset = new Cesium.HeadingPitchRange(
    Cesium.Math.toRadians(heading),
    -Cesium.Math.toRadians(30),
    1000
)
this.GLOBAL.Viewer.zoomTo(entity, offset).then(() => {
    this.GLOBAL.Viewer.clock.onTick.addEventListener(() => {
        heading += 0.1
        offset = new Cesium.HeadingPitchRange(
        Cesium.Math.toRadians(heading),
        -Cesium.Math.toRadians(30),
        1000
        )
        this.GLOBAL.Viewer.zoomTo(entity, offset)
        this.GLOBAL.Viewer.scene.screenSpaceCameraController.enableInputs = false
    })
})

第一人称视角漫游

在之前的文章【Cesium | 利用Property机制实现轨迹回放】中我们已经实现了轨迹回放,现在我们可以利用相机和变换来实现固定视角。

addRoamLine方法中,我们添加固定视角方法:

// 添加漫游路线
addRoamLine(positions, speed) {
    // ……
    this.viewer.trackedEntity = this.animateEntity;
    if (this.isFirstPerson) {
        this.clockEvent = this.viewer.clock.onTick.addEventListener(
        this.firstPersonEvent()
        );
    }
    // ……
}
firstPersonEvent() {
    return () => {
        if (!this.animateEntity) return;
        if (!this.viewer.trackedEntity) return;

        let center = this.animateEntity.position.getValue(
            this.viewer.clock.currentTime
        );
        let orientation = this.animateEntity.orientation.getValue(
            this.viewer.clock.currentTime
        );
        if (center) {
        let transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
        transform = Cesium.Matrix4.fromRotationTranslation(
            Cesium.Matrix3.fromQuaternion(orientation),
            center
        );
        this.viewer.camera.lookAtTransform(
            transform,
            new Cesium.Cartesian3(-100, 0, 50)
        );
        }
    };
}

关于transform相关的知识等我统一梳理一下在分享吧,本来想在这篇文章里写,写着写着感觉自己的理解还不够到位……太弟弟了/(ㄒoㄒ)/~~

第一人称视角漫游.gif