Cesium和Three.js结合的5个方案

CesiumJS是一个开源、免费的三维地图开发框架,Three.js是一个也开源、免费的3D渲染框架,两者都是基于WebGL技术、使用JavaScript开发的Web前端三维可视化框架,在国内外应用非常广泛。本篇我们来聊聊Cesium+Three.js的几种方案,结合实际应用效果来分析各个方案的优点和缺点,以便项目中快速做出技术路线和方案选择。

1、Cesium和Three.js简单对比

Cesium可视化内容以地理空间数据为主,如卫星影像、地形、城市级三维模型等,数据量和空间范围都非常大,而对精度要求更高,所以其核心优势是高性能、高精度、规范标准,规范标准体现在遵循行业标准和主导制定3D Tiles标准上。

在这里插入图片描述
cesium官网

Three.js的目标则是构建一个易于使用、轻量级、跨浏览器的通用3D渲染框架,核心数据是3D对象和三维模型,视觉呈现、场景组织、3D对象管理、动画、可扩展性等方面有独特的优势,且可以于物理引擎很好的结合使用,在大屏三维展示、游戏、仿真等方面很有应用价值。
官网

相比之下,Cesium更像是一个高级餐厅,下单即可坐享其成;而Three.js更像是一个厨房,厨具、食材甚至菜谱都备齐,就等你来发挥厨艺。

2、探索融合之路

写到这里,不禁要回望一下自己这几年执着地探索两个引擎融合的历程。

2.1、将Three.js场景当作模型加载

早在2017年初,接触Cesium近两年,又写了一段时间Three.js,对Cesium只支持gltf格式模型这一点不满,看到Three.js社区有很多模型格式的插件,便萌生了CesiumThree.js结合的想法。

折腾大概两周,实现了第一个方案,先使用GLTFExporterThree.js场景导出为gltf格式,然后通过加载gltf模型的方式,总算是把Three.js解析的模型加载到Cesium场景中了。

很快就发现这种做法的致命缺点:加载极慢,且不支持动画!

2.2、实现基于Cesium的Three.js渲染器

没过多久,开始读Three.js渲染器的源码,再加上对Cesium底层的一知半解,心里似乎很有底气了,开始了新的探索,参照Three.jsWebGLRendererCanvasRendererSVGRenderer,动手写一个CesiumRenderer,还将源码传到了GitHub,还起了一个很牛的名字Cesium3js( https://github.com/MikesWei/Cesium3js ),这也是我上传的第一个开源项目。

想法很大,名头也很大,也实现了当时预期的效果,前面一个方案的缺点一定程度上已经弥补了,但是和真正渲染器差点不是一星半点,绝大部分的特性,都没有支持。

几个月后,Cesium官方博客上那篇介绍CesiumThree.js集成方法的文章( 《Integrating Cesium with Three.js》https://cesium.com/blog/2017/10/23/integrating-cesium-with-threejs/ ) 发表了。

2.3、在Cesium实现Three.js部分接口

2017年底,换了新工作,有个项目需要将天气雷达三维数据展示在三维地球中,最好是实现体积渲染。寻寻觅觅,最终还是在Three.js社区找到了资源。有了项目需求驱动,我更清楚自己做CesiumThree.js结合的目的,于是将Cesium3js代码重新整理,放弃了在Cesium上使用Three.js所有功能的想法,甚至直接不用依赖Three.js,而是在Cesium中实现类似Three.js的Mesh和Material的一套接口和渲染机制,支持部分Three.js对象。半年后将其开源,这便是我的第三个开源项目CesiumMeshVisualizer ( https://github.com/MikesWei/CesiumMeshVisualizer )。承蒙同行不吝点赞,目前收获了454个Star,156次Fork。

2.4、将融合进行到底

2020年夏,GIS平台和游戏引擎融呈现不可挡的趋势,超图、Cesium等都相继推出虚幻引擎(Unreal Engine)插件。我们接到的需求,除了“静止”的海量数据调度外,场景中“运动”的物体也呈数量级增长,数据更新频率更高,更新的部件粒度也更细,用户三维场景的视觉效果要求更高,Cesium在这方面的能力明显不足。我们看到Three.js在解决这类需求具有极大的潜力,并在2020年底实现了CesiumThree.js深度融合方案,真正实现了基于CesiumThree.js渲染器,让CesiumThree.js两个场景浑然一体!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ld1g34qF-1638780791860)(./案例一(封面)].jpg)

3、方案效果对比及分析

从我们的探索历程可以看出,融合的方案很多,可以分为两类:

  • 两个场景叠加,使用两个相互隔离的绘图上下文(context),WebGL层也没有任何交集,这类我们称之为“两个画布”方案;
  • 两个场景共用一个绘图上下文(context),WebGL层有交集,这类我们称之为“一个画布”方案。

两类方案中又有很多具体的实现方案,下面基于我们现有的技术,通过实际代码,看看几个方案的效果。

3.1、小窗叠加显示three.js场景

此方案以Cesium创建的三维地图为主,展示宏观的地理空间场景,点击到地图上某个要素时,弹出小窗使用three.js展示诸如室内、设备详细结构、零部件等。两个场景只在业务逻辑上有关联,完全可以独立开发,最后进行集成,属于“两个画布”类。

下面通过一个简单的示例演示这个方案的主要流程。

HTML代码很简单,创建两个场景的容器,然后引入Cesium的js和css文件,Three.js通过import导入。

<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1.小窗叠加显示three.js场景</title>
    <style>
        html,body{
            margin: 0;
            padding: 0;
        }
        #cesiumContainer {
            width: 100%;
            height: 100%;
        }
        #threeContainer{
            width: 20%;
            height: 30%;
            position: absolute;
            z-index: 2;
            top: 0%;
        }
    </style>
</head>
<body>
    <div id="cesiumContainer"></div>
    <div id="threeContainer"></div>

    <link rel="stylesheet" href="../node_modules/@mesh-3d/cesium/Build/`Cesium`/Widgets/widgets.css">
    <script src="../node_modules/@mesh-3d/cesium/Build/`Cesium`/Cesium.js"></script>
    <script src="./index.js" type="module"></script>
</body>
</html>

JavaScript代码主要做几件事:

  • 创建两个场景显示器
  • Cesium场景添加一个球体;
  • Three.js场景添加一个圆环节;
  • 点击Cesium场景的球体,弹窗显示Three.js场景。

import * as THREE from "@mesh-3d/three";
import MeBaseViewer from "@mesh-3d/core/Source/MeBaseViewer";
 
//创建three.js显示器
var viewer3js = new MeBaseViewer({
    container: 'threeContainer',
    background: 'black',
    //默认不显示,点击到球体再显示
    visible: false
})
//改变相机的视锥体远端截面与视点间的距离,避免坐标范围太大而被裁剪掉
viewer3js.camera.far = 1000000000

var geometry = new THREE.TorusKnotBufferGeometry(1000, 320, 128);
var material = new THREE.MeshStandardMaterial({
    color: 'gray'
});
geometry.computeBoundingSphere()
var mesh = new THREE.Mesh(geometry, material)
//添加到three.js场景
viewer3js.add(mesh)

//将相机定位到3D网格
viewer3js.setView(mesh, {
    heading: 45,
    pitch: 45
})

//添加坐标轴辅助线
viewer3js.scene.add(new THREE.AxesHelper(10000));

//添加光源
var light = new THREE.PointLight()
viewer3js.camera.add(light)
viewer3js.add(viewer3js.camera)


//地图默认视野调整到中国
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(
    70, -1, 140, 60
);
//创建cesium显示器(地球组件)
var viewer = new Cesium.Viewer('cesiumContainer', {
    creditContainer: document.createElement('div')
})
//Cesium添加球体
var entity=viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(106.643932721321, 26.623618804165, 0),
    ellipsoid: {
        radii: new Cesium.Cartesian3(100, 100, 100)
    }
})
viewer.flyTo(entity)
//点击球体时显示Three.js场景
viewer.screenSpaceEventHandler.setInputAction(e => {
    var picked = viewer.scene.pick(e.position)
    if (picked && picked.id) {
        viewer3js.visible = true
    } else {
        viewer3js.visible = false
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)

效果视频:

1.小窗叠加three.js场景

3.2、在Three.js中使用Cesium

如果只是需要使用Cesium的部分GIS功能,比如坐标,几何体,太阳位置及照射方向等,那么可以只创建Three.js场景,场景中的几何体的顶点位置坐标、3D对象位置等由Cesium计算,使得Three.js的场景具备了地理空间表达的能力。

下面演示用Three.js展示一个地球,并加载海陆模板geojson,几何体由Cesium创建。

<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2.在three.js中使用cesium</title>
    <style>
        html,body{
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
    <script src="../node_modules/@mesh-3d/cesium/Build/`Cesium`/Cesium.js"></script>
    <script src="./index.js" type="module"></script>
</body>
</html>

JavaScript代码关键点:

  • 使用Cesium创建基于WGS84坐标的几何体;
  • Cesium几何体转为Three.js几何体;
  • 创建材质和3D网格;
  • 3D网格绕x轴旋转90度,完成从y-up到z-up变换。
import * as THREE from "@mesh-3d/three";
import GeometryUtils from "@mesh-3d/core/Source/GeometryUtils";
import MeBaseViewer from "@mesh-3d/core/Source/MeBaseViewer";
window.THREE = THREE;

//创建three.js显示器
var viewer3js = new MeBaseViewer({
    container: document.body,
    stats: true,
    background: 'black'
})
//改变相机的视锥体远端截面与视点间的距离,避免坐标范围太大而被裁剪掉
viewer3js.camera.far = 1000000000

/**
 * 使用`Cesium`创建基于WGS84坐标的几何体
 * @param {geojson.Polygon} polygon 
 * @returns {Cesium.PolygonGeometry}
 */
function createPolygonGeometry(polygon) {
    var positions = polygon.coordinates[0].map(coord => {
        return Cesium.Cartesian3.fromDegrees(coord[0], coord[1])
    })
    var holes = [];
    for (let i = 1; i < polygon.coordinates.length; i++) {
        var hole = polygon.coordinates[i].map(coord => {
            return Cesium.Cartesian3.fromDegrees(coord[0], coord[1])
        })
        holes.push(new Cesium.PolygonHierarchy(hole))
    }
    var polygonHierarchy = new Cesium.PolygonHierarchy(positions, holes)
    var geom = new Cesium.PolygonGeometry({
        polygonHierarchy: polygonHierarchy,
        granularity: Cesium.Math.toRadians(1)
    })
    geom = Cesium.PolygonGeometry.createGeometry(geom);

    return geom;
}

//加载海陆模板
Cesium.Resource.fetchJson('./sealand.geojson').then(json => {
    var root = new THREE.Object3D();

    json.features.forEach(feature => {
        //使用`Cesium`创建基于WGS84坐标的几何体
        var geometry = createPolygonGeometry(feature.geometry);
        //将`Cesium`几何体转为three.js几何体
        var geometry3js = GeometryUtils.toBufferGeometry3js(geometry);
        //创建材质和3D网格
        var material = new THREE.MeshBasicMaterial({
            color: 'gray'
        })
        var mesh = new THREE.Mesh(geometry3js, material);
        //绕x轴旋转90度,从y-up到z-up
        mesh.rotation.x = -Math.PI / 2;
        root.add(mesh)
    })

    //添加到three.js场景
    viewer3js.add(root)
 
})

//使用Cesium创建基于WGS84坐标的几何体
var geometry = new Cesium.EllipsoidGeometry({
    radii: Cesium.Ellipsoid.WGS84.radii,
    stackPartitions: 64,
    slicePartitions: 64
})
geometry = Cesium.EllipsoidGeometry.createGeometry(geometry)
//将Cesium几何体转为three.js几何体
geometry = GeometryUtils.toBufferGeometry3js(geometry)
//创建材质和3D网格
var mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
    wireframe: true,
    color: 'gray'
}));
//绕x轴旋转90度,从y-up到z-up
mesh.rotation.x = -Math.PI / 2
//添加到three.js场景
viewer3js.add(mesh)

//将视野调整到中国
viewer3js.setView(mesh,  {
    heading: 200,
    pitch: 105
});

//添加坐标轴辅助线
viewer3js.scene.add(new THREE.AxesHelper(10000000));

效果视频:

2.在three.js中使用cesium

这个方案以Three.js为主,属于“一个画布”类,相当于基于Three.js构建一个简单的三维地图应用了,Cesium在这个方案中没有那么重要,完全可以用其他计算库替代,进而完全从头构建一个基于Three.js的三维地图框架,iTowns( https://github.com/iTowns/itowns )就是这么做的。

3.3、将Three.js场景当作模型加载

Three.js组织场景,完成3D对象创建,gltf之外的其他格式模型解析,然后整体导出为gltf,最后使用Cesium.Model展示模型,属于“一个画布”类。

HTML代码也只是引用js、css文件,场景容器直接用body。

<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3.将three.js场景当作模型加载</title>
    <style>
        html,body{
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
    <link rel="stylesheet" href="../node_modules/@mesh-3d/cesium/Build/`Cesium`/Widgets/widgets.css">
    <script src="../node_modules/@mesh-3d/cesium/Build/`Cesium`/Cesium.js"></script>
    <script src="./index.js" type="module"></script>
</body>
</html>

JavaScript代码也很简单,为了和其他方案对比,我们禁用碰撞检测,允许Cesium相机穿透物体和进入地下,开启地形深度测试,看看地形是不是正常的遮挡Three.js导出的gltf模型。


import * as THREE from "@mesh-3d/three";
import { GLTFExporter } from '@mesh-3d/three/examples/jsm/exporters/GLTFExporter'

Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(
    70, -1, 140, 60
);
//创建`Cesium`显示器(地球组件)
var viewer = new Cesium.Viewer(document.body, {
    creditContainer: document.createElement('div')
});
//禁用碰撞检测,允许`Cesium`相机穿透物体和进入地下
viewer.scene.screenSpaceCameraController.enableCollisionDetection = false
//开启地形深度测试
viewer.scene.globe.depthTestAgainstTerrain=true

//搭建three.js场景

var scene = new THREE.Scene()
var geometry = new THREE.TorusKnotBufferGeometry(1000, 320, 128);
var material = new THREE.MeshStandardMaterial({
    color: 'gray'
});
geometry.computeBoundingSphere()
var box = new THREE.Mesh(geometry, material)
box.position.set(0, geometry.boundingSphere.radius, 0)
scene.add(box)

//将three.js场景导出为gltf
var exporter = new GLTFExporter()
exporter.parse(scene, gltf => {

    //加载导出的gltf模型

    //创建模型变换矩阵,设置模型在地球上的位置、旋转、缩放等参数
    var origin = Cesium.Cartesian3.fromDegrees(106.643932721321, 26.623618804165, 0);
    var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);

    var model = new Cesium.Model({
        gltf: gltf,
        modelMatrix: modelMatrix
    });
    viewer.scene.primitives.add(model);

    //加载完成后,将相机定位到该模型
    model.readyPromise.then(model => {
        var boundingSphere = model.boundingSphere.clone();
        Cesium.Matrix4.multiplyByPoint(model.modelMatrix, boundingSphere.center, boundingSphere.center);
        viewer.camera.flyToBoundingSphere(boundingSphere)
    })
})

3.将three.js场景当作模型加载

其实毫无疑问,导出的gltf和普通的模型一样,遮挡关系必然时正常的,但是这个方案的限制很多,Three.js在这里相当于模型格式转换工具。

3.4、Three.jsCesium相机同步

这是Cesium官方博客文章介绍的方案,属于“两个画布”类,Cesium创建的地球作为底图,以地图上一个点原点建立局部坐标系,Three.js场景基于此坐标系叠加到Cesium场景中,在Cesium帧渲染前同步两个场景的相机,之后Three.js场景搭建就和一般的Three.js应用搭建一样了。

HTML创建两个场景的容器,引入Cesium的js、css文件。

<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>4.three.js和cesium相机同步</title>
    <style>
        html,body{
            margin: 0;
            padding: 0;
        }
        #cesiumContainer,
        #threeContainer{
            width: 100%;
            height: 100%;
        }
        #threeContainer{
            position: absolute;
            z-index: 2;
            top: 0%;
            pointer-events: none;
        }
    </style>
</head>
<body>
    <div id="cesiumContainer"></div>
    <div id="threeContainer"></div>

    <link rel="stylesheet" href="../node_modules/@mesh-3d/cesium/Build/`Cesium`/Widgets/widgets.css">
    <script src="../node_modules/@mesh-3d/cesium/Build/`Cesium`/Cesium.js"></script>
    <script src="./index.js" type="module"></script>
</body>
</html>

JavaScript代码关键点:

  • 创建模型变换矩阵,设置整个Three.js场景在地球上的位置、旋转、缩放等参数;
  • 使用模型变换矩阵构建局部坐标系;
  • 监听Cesium场景preRender事件,将Cesium相机转为Three.js相机,并渲染Three.js场景。
import * as THREE from "@mesh-3d/three";
import MeBaseViewer from "@mesh-3d/core/Source/MeBaseViewer";
import CameraUtils from "@mesh-3d/core/Source/CameraUtils";
window.THREE = THREE

Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(
    70, -1, 140, 60
);

//创建`Cesium`显示器(地球组件)
var viewer = new Cesium.Viewer('cesiumContainer', {
    creditContainer: document.createElement('div')
});
//禁用碰撞检测,允许`Cesium`相机穿透物体和进入地下
viewer.scene.screenSpaceCameraController.enableCollisionDetection = false
//开启地形深度测试
viewer.scene.globe.depthTestAgainstTerrain = true

//创建three.js显示器
var viewer3js = new MeBaseViewer({
    container: 'threeContainer',
    autoRender: false
})

//搭建three.js场景

var geometry = new THREE.TorusKnotBufferGeometry(1000, 320, 128);
var material = new THREE.MeshStandardMaterial({
    color: 'gray'
});
geometry.computeBoundingSphere()
var mesh = new THREE.Mesh(geometry, material)
viewer3js.scene.add(mesh)

//添加坐标轴辅助线
viewer3js.scene.add(new THREE.AxesHelper(10000));

//添加光源
var light = new THREE.PointLight()
viewer3js.camera.add(light)
viewer3js.add(viewer3js.camera)

//同步相机

//创建模型变换矩阵,设置整个three.js场景在地球上的位置、旋转、缩放等参数。
var origin = Cesium.Cartesian3.fromDegrees(106.643932721321, 26.623618804165, 0);
//这里不用eastNorthUpToFixedFrame是因为three.js场景默认y轴朝上
var modelMatrix = Cesium.Transforms.northUpEastToFixedFrame(origin);

//使用模型变换矩阵构建局部坐标系
var referenceFrame = {
    /**
     * 从three.js相机到`Cesium`相机转换时使用
     */
    matrix: modelMatrix,
    /**
     * 从`Cesium`相机到three.js相机转换时使用。
     * 如果modelMatrix不变,则逆矩阵可以不给定;modelMatrix变化时需要同时更新逆矩阵
     */
    inverseMatrix: Cesium.Matrix4.inverse(modelMatrix, new Cesium.Matrix4())
};

viewer.scene.preRender.addEventListener(() => {
    //同步相机:将`Cesium`相机转为three.js相机
    CameraUtils.toPerspectiveCamera3js(viewer.camera, viewer3js.camera, referenceFrame);

    //执行three.js场景渲染
    viewer3js.render()
})

//将`Cesium`相机定位到three.js场景
var boundingSphere = Cesium.BoundingSphere.clone(geometry.boundingSphere)
Cesium.Matrix4.multiplyByPoint(modelMatrix, boundingSphere.center, boundingSphere.center);
viewer.camera.flyToBoundingSphere(boundingSphere)

效果视频:

4.three.js和cesium相机同步

这个方案和第一个方案很像,唯一不同在于是否相机同步。

最大优点是可以完全使用所有Three.js的特性,而且看上去很有融合的效果,也因为没有更好的其他方案,所以目前在网上流传甚广。

缺点主要是两个场景绘图上下文(context)完全分离的,遮挡关系、光照、阴影、后期特效、交互等等都是两套东西,并没有真正的融合。

3.5、Cesium和Three.js深度融合

CesiumThree.js都是经过10年打磨的引擎,渲染器这层已经相对稳定了,所以这个方案便是从渲染器层着手,其基本原理是,用Cesium的DrawCommand来接替Three.js渲染器的部分工作;具体做法就是实现一个基于Cesium.DrawCommand的Three.js渲染器,让Three.js真正融入Cesium的渲染流程和上下文,和Cesium的其他对象一起进入GPU完成绘制。

我们先看一个简单的例子。

<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>5.cesium和three.js深度融合</title>
    <style>
        html,body{
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
    <link rel="stylesheet" href="../node_modules/@mesh-3d/cesium/Build/`Cesium`/Widgets/widgets.css">
    <script src="../node_modules/@mesh-3d/cesium/Build/`Cesium`Unminified/Cesium.js"></script>
    <script src="./index.js" type="module"></script>
</body>
</html>

JavaScript主要做几件事:

  • 创建Cesium场景,开启阴影和地形深度测试,禁用碰撞检测;
  • 使用Cesium.Entity创建两个实体,颜色为天蓝色,开启阴影;
  • 搭建Three.js场景,创建一个圆环节,颜色为灰色,开启阴影;
  • 创建基于CesiumThree.js显示器,加入Cesium场景的primitives;
  • 设置Three.js显示器,使用Cesium太阳光作为Three.js场景光源。
import * as THREE from "@mesh-3d/three";
import { MeshVisualizer } from "@mesh-3d/core";
window.THREE = THREE

Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(
    70, -1, 140, 60
);

//创建`Cesium`显示器(地球组件)
var viewer = new Cesium.Viewer(document.body, {
    creditContainer: document.createElement('div')
});
//禁用碰撞检测,允许`Cesium`相机穿透物体和进入地下
viewer.scene.screenSpaceCameraController.enableCollisionDetection = false
//开启地形深度测试
viewer.scene.globe.depthTestAgainstTerrain = true

//用Cesium.Entity方式添加球体,并接收/投射阴影
viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(106.643932, 26.6238, 5),
    ellipsoid: {
        radii: new Cesium.Cartesian3(5, 5, 5),
        shadows: Cesium.ShadowMode.ENABLED,
        material:Cesium.Color.SKYBLUE
    }
})
//用Cesium.Entity方式添加立方体,并接收/投射阴影
viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(106.64445, 26.6232, 25),
    box: {
        dimensions: new Cesium.Cartesian3(5, 5, 50),
        shadows: Cesium.ShadowMode.ENABLED,
        material:Cesium.Color.SKYBLUE
    }
})

//搭建three.js场景
var scene3js = new THREE.Scene()

var geometry = new THREE.TorusKnotBufferGeometry(10, 3, 128);
var material = new THREE.MeshStandardMaterial({
    color: 'gray'
});
geometry.computeBoundingSphere()
var mesh = new THREE.Mesh(geometry, material)
scene3js.add(mesh)
//接收/投射阴影
mesh.castShadow = true
mesh.receiveShadow = true

//添加坐标轴辅助线
scene3js.add(new THREE.AxesHelper(10000));

//创建基于cesium的three.js显示器

//创建模型变换矩阵,设置整个three.js场景在地球上的位置、旋转、缩放等参数。
var origin = Cesium.Cartesian3.fromDegrees(106.643932721321, 26.623618804165, geometry.boundingSphere.radius / 2);
var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
var visualizer = new MeshVisualizer({
    modelMatrix: modelMatrix
})
//将three.js显示器加入cesium场景
viewer.scene.primitives.add(visualizer);
//cesium太阳光作为局部场景的光源
visualizer.useSunLight = true;
viewer.shadowMap.enabled = true
viewer.shadowMap.maximumDistance = 1000000

//添加three.js场景
visualizer.add(scene3js)

//将`Cesium`相机定位到three.js场景
var boundingSphere = Cesium.BoundingSphere.clone(geometry.boundingSphere)
visualizer.localToWorldCoordinates(boundingSphere.center, boundingSphere.center, scene3js.up)
viewer.camera.flyToBoundingSphere(boundingSphere)

效果视频:

5.cesium和three.js深度融合

可以看出:

  • 遮挡关系正确:无论是两个Entity还是地形都可以把Three.js创建的圆环节遮挡;
  • 光照同步正常:两个场景的物体都可以同步接收太阳光;
  • 阴影关系正确:Entity的阴影可以投射到圆环节上,圆环节的阴影也可以投射到Entity上。

这就是这个方案的独特优势,实现了真正的深度融合。

这里的深度不仅指两个场景“深度缓冲区”统一这一层含义,更有渲染通道、光照、阴影、透明、半透明混合(Cesium的translucent不只是等同于Three.js的transparent)、贴地、后期特效等等的统一。
*

“深度缓冲区”统一,可以通过修改Three.js渲染器,让CesiumThree.js共享context,达到共享深度缓冲区的目的,一定程度上解决了遮挡关系的问题。
而渲染通道、光照、阴影、透明、半透明混合(Cesium的translucent不只是等同于Three.js的transparent)、贴地、后期特效等等的统一,仅通过共享context是远远达不到的。

这个方案的缺点是不能直接使用Three.js的后期特效代码,后期特效统一使用Cesium的后期处理机制,也是为了确保两个场景在后期特效上的统一,部分常用的Three.js社区特有的后期特效,如OutlinePass(3D描边)、UnrealBloomPass(泛光特效)、SMAAPass(另一种抗锯齿算法)等,我们已经实现了Cesium版本,且在不使用Cesium+Three.js的情况下也可以单独使用,一定程度上弥补了方案的一大缺点。

Three.js迁移到Cesium的后期特效,前几期文章就已经介绍过了,感兴趣的可以回顾一下。

3.6、更多深度融合示例

CesiumThree.js深度融合方案经过一年的应用、更新和完善,示例覆盖了Three.js绝大部分特性,还增加了许多优化的特性。

5.深度融合示例

有商用需求的企业或者团队,欢迎咨询试用。

欢迎关注微信公众号【三维网格3D】,第一时间获取最新文章

在这里插入图片描述

  • 13
    点赞
  • 103
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
当将CesiumThree.js结合时,可以创建出强大的3D可视化效果。下面是一个简单的例子,展示了如何将Cesium的地球作为Three.js中的纹理,并在Three.js中添加自定义模型: ```javascript // 创建地球模型 const earthGeometry = new THREE.SphereGeometry(5, 32, 32); const earthMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff }); const earthMesh = new THREE.Mesh(earthGeometry, earthMaterial); // 创建Cesium的地球对象 const viewer = new Cesium.Viewer("cesiumContainer"); // 将Cesium的地球作为Three.js中的纹理 const cesiumRenderer = viewer.scene.highDynamicRangeSupported ? viewer.scene.highDynamicRange : viewer.scene.context; const earthTexture = cesiumRenderer.createTexture(); cesiumRenderer.bindTexture(earthTexture); cesiumRenderer.copyFramebufferToTexture(new Cesium.Cesium3DTileFramebuffer(viewer.scene)); // 将地球纹理应用到Three.js模型 const earthTextureLoader = new THREE.TextureLoader(); const earthTexture = earthTextureLoader.load(earthTexture); earthMaterial.map = earthTexture; earthMaterial.needsUpdate = true; // 创建Three.js场景并添加地球模型 const scene = new THREE.Scene(); scene.add(earthMesh); // 创建Three.js渲染器并将其连接到HTML文档中的容器 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 渲染场景 function render() { requestAnimationFrame(render); renderer.render(scene, camera); } render(); ``` 这个例子中,我们首先创建一个Three.js中的球体模型,然后创建Cesium的地球对象。通过将Cesium的地球对象渲染到纹理中,我们得到了一个包含地球数据的纹理。最后,将这个纹理应用到Three.js模型上,并使用Three.js渲染器渲染整个场景。 请注意,这只是一个基本的示例,你可以根据自己的需求进行更复杂的定制。希望对你有帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值