Cesium 和 Three.js 集成的官方教程的中文翻译

本文翻译自 Integrating Cesium with Three.js – Cesium

这是一篇由 Wilson Muktar 撰写的特邀文章,介绍如何将 Three.js 与 Cesium 集成。Three.js 是一个轻量级跨浏览器的 JavaScript 库,用于在浏览器中创建和显示三维计算机动画图形。将 Cesium 的行星尺度渲染和 GIS 功能与 Three.js 广泛且易于访问的通用 3D API 相结合,将为全新的 WebGL 体验带来无限可能。您可以查看该演示的实时版本,也可以查看代码本身。- Gary

3D JavaScript 库现已完全成熟并广为人知,使开发人员可以避免在浏览器中使用 3D 时所遇到的麻烦。开发人员可以轻松创建摄像头、对象、灯光、材质和图形,并可以选择渲染器,使用 HTML 5 的画布、WebGL 或 SVG 绘制场景。

由于 Cesium 和 Three.js 都是为 3D 可视化而设计的,并且都是使用 JavaScript 从零开始构建的,因此它们具有相似之处,这使得将这些神奇的库集成到一起成为可能。我将这两个框架整合在一起的方法比想象的要简单:我参照 HTML 画布元素,将这两个框架分隔成不同的视图层,并在同一坐标系中组合了它们的控制器。由于这两个框架都是开源的,因此我可以分享这个演示,其中将介绍一些基础知识。

左图: Cesium 的场景。中间:Three.js 中的场景。右图:组合场景。

Cesium 是为创建数字地球而开发的 3D 库,其渲染效果与真实地球惊人地相似。利用 3D Tiles,开发人员可以在浏览器中将几乎所有内容都重新渲染为数字画布。
指导 Cesium 的基本渲染原理与 Three.js 并无太大区别。Three.js 是一个强大的 3D 库,用于渲染 3D 物体。通过复制 Cesium 的球形坐标系和匹配两个场景中的数字地球,可以很容易地将两个独立的渲染引擎层集成到一个主场景中。下面我将简单介绍一下它的集成方法:

  • 初始化 Cesium 渲染器;
  • 初始化 Three.js 渲染器;
  • 初始化两个库的 3D 对象;
  • 循环渲染器

主要方法

页面上需要两个容器,分别用于包含 Three.js 和 Cesium:

<body>
    <div id="cesiumContainer"></div>
    <div id="ThreeContainer"></div>
</body>
<script> main(); </script>

下面是主函数:

function main() {
    // 用 WGS84 标定边界,以帮助同步渲染器
    var minWGS84 = [115.23, 39.55];
    var maxWGS84 = [116.23, 41.55];
    var cesiumContainer = document.getElementById("cesiumContainer");
    var ThreeContainer = document.getElementById("ThreeContainer");

    var _3Dobjects = []; // 可以是任何 Three.js 对象网格
    var three = {
        renderer: null,
        camera: null,
        scene: null
    };

    var cesium = {
        viewer: null
    };

    initCesium(); // 初始化 Cesium 渲染器
    initThree(); // 初始化 Three.js 渲染器
    init3DObject(); // 使用 Cesium 笛卡尔坐标系初始化 Three.js 对象网格
    loop(); // 循环渲染器
}

初始化 Cesium 的渲染器

首先,我们可以通过添加自定义图像或默认提供的其他部分来定制 Cesium Viewer。通过禁用 Cesium 的默认渲染循环,我们可以将其动画帧与 Three.js 同步。

function initCesium() {
    cesium.viewer = new Cesium.Viewer(cesiumContainer, {
        useDefaultRenderLoop: false,
        selectionIndicator: false,
        homeButton: false,
        sceneModePicker: false,
        navigationHelpButton: false,
        infoBox: false,
        navigationHelpButton: false,
        navigationInstructionsInitiallyVisible: false,
        animation: false,
        timeline: false,
        fullscreenButton: false,
        allowTextureFilterAnisotropic: false,
        contextOptions: {
            webgl: {
                alpha: false,
                antialias: true,
                preserveDrawingBuffer: true,
                failIfMajorPerformanceCaveat: false,
                depth: true,
                stencil: false,
                anialias: false
            },
        },
        targetFrameRate: 60,
        resolutionScale: 0.1,
        orderIndependentTranslucency: true,
        creditContainer: "hidecredit",
        imageryProvider: new Cesium.TileMapServiceImageryProvider({
            url: 'Assets/imagery/NaturalEarthII/',
            maximumLevel: 5
        }),
        baseLayerPicker: false,
        geocoder: false,
        automaticallyTrackDataSourceClocks: false,
        dataSources: null,
        clock: null,
        terrainShadows: Cesium.ShadowMode.DISABLED
    });

    var center = Cesium.Cartesian3.fromDegrees(
        (minWGS84[0] + maxWGS84[0]) / 2,
        ((minWGS84[1] + maxWGS84[1]) / 2) - 1,
        200000
    );
    cesium.viewer.camera.flyTo({
        destination: center,
        orientation: {
            heading: Cesium.Math.toRadians(0),
            pitch: Cesium.Math.toRadians(-60),
            roll: Cesium.Math.toRadians(0)
        },
        duration: 3
    });
}

初始化 Three.js 的渲染器

接下来,我们只需初始化 Three.js 的必经阶段,包括场景、摄像机、渲染器及其 DOM 元素。

function initThree() {
    var fov = 45;
    var width = window.innerWidth;
    var height = window.innerHeight;
    var aspect = width / height;
    var near = 1;
    var far = 10 * 1000 * 1000; // 需要很远才能支持 Cesium 的世界规模渲染

    three.scene = new THREE.Scene();
    three.camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    three.renderer = new THREE.WebGLRenderer({ alpha: true });
    ThreeContainer.appendChild(three.renderer.domElement);
}

在两个库中初始化 3D 对象

Cesium 对象可以通过实体对象简单地添加到其 Viewer 中;例如,可以使用 3D Graphing 类来渲染在 Three.js 中创建的 3D 绘图对象网格,或使用 Three.js 创建的任何其他 3D 对象。所有这些都保存在 _3DObjects 中,以便进一步处理,其中包含用于同步摄像机的额外信息。这里我们将渲染一个“车床几何体”和一个“十二面体”。请注意,Three.js 渲染的是 Z 轴向上,而 Cesium 渲染的是 Y 轴向上。

function init3DObject() {
    // Cesium entity
    var entity = {
        name: 'Polygon',
        polygon: {
            hierarchy: Cesium.Cartesian3.fromDegreesArray([
                minWGS84[0], minWGS84[1],
                maxWGS84[0], minWGS84[1],
                maxWGS84[0], maxWGS84[1],
                minWGS84[0], maxWGS84[1],
            ]),
            material: Cesium.Color.RED.withAlpha(0.2)
        }
    };
    var Polygon = cesium.viewer.entities.add(entity);

    // 车床几何形状
    var doubleSideMaterial = new THREE.MeshNormalMaterial({
        side: THREE.DoubleSide
    });
    var segments = 10;
    var points = [];
    for (var i = 0; i < segments; i++) {
        points.push(new THREE.Vector2(Math.sin(i * 0.2) * segments + 5, (i - 5) * 2));
    }
    var geometry = new THREE.LatheGeometry(points);
    var latheMesh = new THREE.Mesh(geometry, doubleSideMaterial);
    latheMesh.scale.set(1500, 1500, 1500); // 缩放对象,使其在行星比例下可见
    latheMesh.position.z += 15000.0; // translate "up" in Three.js space so the "bottom" of the mesh is the handle
    latheMesh.rotation.x = Math.PI / 2; // rotate mesh for Cesium's Y-up system
    var latheMeshYup = new THREE.Group();
    latheMeshYup.add(latheMesh)
    three.scene.add(latheMeshYup); // 别忘了手动将其添加到 Three.js 场景中

    // 将 Three.js 对象网格指定给我们的对象数组
    var _3DOB = new _3DObject();
    _3DOB.threeMesh = latheMeshYup;
    _3DOB.minWGS84 = minWGS84;
    _3DOB.maxWGS84 = maxWGS84;
    _3Dobjects.push(_3DOB);

    // 十二面体
    geometry = new THREE.DodecahedronGeometry();
    var dodecahedronMesh = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
    dodecahedronMesh.scale.set(5000, 5000, 5000); // 缩放对象,使其在行星比例下可见
    dodecahedronMesh.position.z += 15000.0; // translate "up" in Three.js space so the "bottom" of the mesh is the handle
    dodecahedronMesh.rotation.x = Math.PI / 2; // rotate mesh for Cesium's Y-up system
    var dodecahedronMeshYup = new THREE.Group();
    dodecahedronMeshYup.add(dodecahedronMesh)
    three.scene.add(dodecahedronMeshYup); // 别忘了手动将其添加到 Three.js 场景中

    // 将 Three.js 对象网格指定给我们的对象数组
    _3DOB = new _3DObject();
    _3DOB.threeMesh = dodecahedronMeshYup;
    _3DOB.minWGS84 = minWGS84;
    _3DOB.maxWGS84 = maxWGS84;
    _3Dobjects.push(_3DOB);
}
function _3DObject() {
    this.graphMesh = null; // Three.js 3DObject.mesh
    this.minWGS84 = null; // 位置边界框
    this.maxWGS84 = null;
}

循环渲染器

function loop() {
    requestAnimationFrame(loop);
    renderCesium();
    renderThreeObj();
}
function renderCesium() {
    cesium.viewer.render();
}

我们将克隆 Three.js 摄像头以匹配 Cesium 摄像头,因此无需为 Three.js 分配鼠标控制器,但我们仍需移除它,因为 Three.js DOM 元素位于 Cesium 的上方。我们可以在 Three.js 渲染器中添加 CSS 属性 pointer-events:none,从而移除鼠标控制器。现在,一切都将根据 Cesium 的摄像头投影进行渲染。
要使物体正确显示在地球仪上,还需要进行坐标转换。这包括将大地纬度/经度位置转换为笛卡尔 XYZ 坐标,并使用 WGS84 区域左下角到左上角的方向作为向上矢量,这样物体就会远离地球中心。这也可以通过转换为当地笛卡尔 "东-北-上 "或 "北-东-下 "来计算。

function renderThreeObj() {
    // 使用 Cesium 注册 Three.js 场景
    three.camera.fov = Cesium.Math.toDegrees(cesium.viewer.camera.frustum.fovy) // ThreeJS 的 FOV 是垂直的
    three.camera.updateProjectionMatrix();

    var cartToVec = function (cart) {
        return new THREE.Vector3(cart.x, cart.y, cart.z);
    };

    // 配置 Three.js 网格,使其站在地球仪中心位置的 up 方向
    for (id in _3Dobjects) {
        minWGS84 = _3Dobjects[id].minWGS84;
        maxWGS84 = _3Dobjects[id].maxWGS84;
        // 将纬度/经度中心位置转换为笛卡尔3
        var center = Cesium.Cartesian3.fromDegrees((minWGS84[0] + maxWGS84[0]) / 2, (minWGS84[1] + maxWGS84[1]) / 2);

        // 获取定向的模型的前进方向
        var centerHigh = Cesium.Cartesian3.fromDegrees((minWGS84[0] + maxWGS84[0]) / 2, (minWGS84[1] + maxWGS84[1]) / 2, 1);

        // 使用从左下到左上的方向作为向上矢量
        var bottomLeft = cartToVec(Cesium.Cartesian3.fromDegrees(minWGS84[0], minWGS84[1]));
        var topLeft = cartToVec(Cesium.Cartesian3.fromDegrees(minWGS84[0], maxWGS84[1]));
        var latDir = new THREE.Vector3().subVectors(bottomLeft, topLeft).normalize();

        // 配置实体位置和方向
        _3Dobjects[id].graphMesh.position.copy(center);
        _3Dobjects[id].graphMesh.lookAt(centerHigh);
        _3Dobjects[id].graphMesh.up.copy(latDir);
    }

    // 克隆 Cesium 的相机的投影位置,以便
    // Three.js 对象将出现在与 Cesium 球空间的上方相同的位置
    three.camera.matrixAutoUpdate = false;
    var cvm = cesium.viewer.camera.viewMatrix;
    var civm = cesium.viewer.camera.inverseViewMatrix;
    three.camera.matrixWorld.set(
        civm[0], civm[4], civm[8], civm[12],
        civm[1], civm[5], civm[9], civm[13],
        civm[2], civm[6], civm[10], civm[14],
        civm[3], civm[7], civm[11], civm[15]
    );
    three.camera.matrixWorldInverse.set(
        cvm[0], cvm[4], cvm[8], cvm[12],
        cvm[1], cvm[5], cvm[9], cvm[13],
        cvm[2], cvm[6], cvm[10], cvm[14],
        cvm[3], cvm[7], cvm[11], cvm[15]
    );
    three.camera.lookAt(new THREE.Vector3(0, 0, 0));

    var width = ThreeContainer.clientWidth;
    var height = ThreeContainer.clientHeight;
    var aspect = width / height;
    three.camera.aspect = aspect;
    three.camera.updateProjectionMatrix();

    three.renderer.setSize(width, height);
    three.renderer.render(three.scene, three.camera);
}


 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值