截止2022年7月30日,最新的Cesium-Three整合示例,基于如下版本:
[ Live Demo ]:Cesium integrate Threejs (syzdev.cn)
Cesium和Three都是基于WebGL的三维框架,在渲染成DOM时,本质上都是canvas
。所以两者的集成需要解决如下几个问题:
canvas
在DOM上叠加到一起,解决方式为将Three的canvas
叠加在Cesium的canvas
之上,通过CSS实现;canvas
,所以需要关闭其中一个交互功能,在Three的canvas
上添加CSS样式pointer-events:none
来屏蔽其鼠标交互,主要用Cesium来实现交互。useDefaultRenderLoop
,用cesium.viewer.render();
来手动触发帧渲染,在Three中是通过requestAnimationFrame
来实现帧渲染的,所以在每个循环渲染帧中要同步渲染Cesium和Three的场景。renderThreeObj()
方法中有详细的描述,Three场景下的3D对象,也是跟随改变其位置和方向的。Cesium官方提供的示例程序“cesium-threejs-experiment”中使用的版本如下:
显然,这在现在是过时的,若用最新的Cesium和Three版本直接套用原来的代码,会出现如下问题:
解决方法如下,注释initCesium()
方法中Cesium初始化选项creditContainer : "hidecredit"
,可以解决Cesium初始化时无法找到容器的问题:
function initCesium() {
cesium.viewer = new Cesium.Viewer(cesiumContainer, {
// creditContainer : "hidecredit", // Cannot read properties of null (reading 'appendChild')
})
}
去除renderThreeObj()
方法中的three.camera.lookAt(new THREE.Vector3(0,0,0));
语句,并在配置Three相机矩阵之前添加three.camera.lookAt(0, 0, 0)
:
function renderThreeObj() {
three.camera.lookAt(0, 0, 0) // here
three.camera.matrixWorld.set(...)
three.camera.matrixWorldInverse.set(...)
// three.camera.lookAt(new THREE.Vector3(0,0,0));
}
同时,需要给Three容器添加如下CSS样式:
#ThreeContainer canvas {
pointer-events: none;
position: absolute;
top: 0;
}
GitHub地址:https://github.com/syzdev/Cesium-Three
若读者已经安装了Git,可以在指定文件夹内打开Git Bash窗口,输入如下指令:
git clone https://github.com/syzdev/Cesium-Three.git
该指令会将代码克隆到指定文件夹中,再通过服务发布即可访问。
下面代码中的第三方库Cesium和Three都是以CDN的形式引入的,上述GitHub中的代码都是以本地文件的形式发布的:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>Cesium integrate Threejstitle>
<link rel="stylesheet" href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css">
<script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js">script>
<script src="https://cdn.jsdelivr.net/npm/three@0.143.0/build/three.min.js">script>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.cesium-viewer .cesium-widget-credits {
display: none;
}
#ThreeContainer canvas {
pointer-events: none;
position: absolute;
top: 0;
}
style>
head>
<body>
<div id="cesiumContainer">div>
<div id="ThreeContainer">div>
<script>
let three = {
renderer: null,
camera: null,
scene: null,
}
let cesium = {
viewer: null,
}
let minWGS84 = [115.23, 39.55];
let maxWGS84 = [116.23, 41.55];
let cesiumContainer = document.getElementById('cesiumContainer')
let ThreeContainer = document.getElementById('ThreeContainer')
let _3Dobjects = [] // Could be any Three.js object mesh
function _3DObject() {
this.threeMesh = null // Three.js 3DObject.mesh
this.minWGS84 = null // location bounding box
this.maxWGS84 = null
}
function initCesium() {
cesium.viewer = new Cesium.Viewer(cesiumContainer, {
useDefaultRenderLoop: false,
selectionIndicator: false,
homeButton: false,
sceneModePicker: false,
infoBox: false,
navigationHelpButton: false,
navigationInstructionsInitiallyVisible: false,
animation: false,
timeline: false,
fullscreenButton: false,
allowTextureFilterAnisotropic: false,
baseLayerPicker: false,
// contextOptions: {
// webgl: {
// alpha: false,
// antialias: true,
// preserveDrawingBuffer: true,
// failIfMajorPerformanceCaveat: false,
// depth: true,
// stencil: false,
// anialias: false,
// },
// },
targetFrameRate: 60,
// resolutionScale: 0.1,
orderIndependentTranslucency: true,
geocoder: false,
imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer',
}),
automaticallyTrackDataSourceClocks: false,
// creditContainer : "hidecredit", // Cannot read properties of null (reading 'appendChild')
dataSources: null,
clock: null,
terrainShadows: Cesium.ShadowMode.DISABLED,
})
cesium.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(
(minWGS84[0] + maxWGS84[0]) / 2,
(minWGS84[1] + maxWGS84[1]) / 2 - 1.25,
200000
),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-60),
roll: Cesium.Math.toRadians(0),
},
duration: 3,
})
}
function initThree() {
let fov = 45
let width = window.innerWidth
let height = window.innerHeight
let aspect = width / height
let near = 1
let far = 10 * 1000 * 1000
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)
}
function addBoxEntity() {
cesium.viewer.entities.add({
name: 'Box',
position: Cesium.Cartesian3.fromDegrees(
(minWGS84[0] + maxWGS84[0]) / 2,
(minWGS84[1] + maxWGS84[1]) / 2 - 0.5,
7000
),
box: {
dimensions: new Cesium.Cartesian3(16000.0, 16000.0, 16000.0),
material: Cesium.Color.RED.withAlpha(0.5),
fill: true,
outline: true,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 1.0,
},
})
}
function addPolygonEntity() {
cesium.viewer.entities.add({
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.PINK.withAlpha(0.4),
},
})
}
function addBoxGeometry() {
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshNormalMaterial()
const dodecahedronMesh = new THREE.Mesh(geometry, material)
dodecahedronMesh.scale.set(15000, 15000, 15000) // scale object to be visible at planet scale
dodecahedronMesh.position.z += 7000.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
let dodecahedronMeshYup = new THREE.Group()
dodecahedronMeshYup.add(dodecahedronMesh)
three.scene.add(dodecahedronMeshYup) // don’t forget to add it to the Three.js scene manually
// Assign Three.js object mesh to our object array
let _3DOB = new _3DObject()
_3DOB.threeMesh = dodecahedronMeshYup
_3DOB.minWGS84 = minWGS84
_3DOB.maxWGS84 = maxWGS84
_3Dobjects.push(_3DOB)
}
function init3DObject() {
// Cesium entity
addPolygonEntity()
addBoxEntity()
// Three.js Objects
addBoxGeometry()
}
function renderCesium() {
cesium.viewer.render()
}
function renderThreeObj() {
// register Three.js scene with Cesium
three.camera.fov = Cesium.Math.toDegrees(cesium.viewer.camera.frustum.fovy) // ThreeJS FOV is vertical
// three.camera.updateProjectionMatrix();
let cartToVec = function (cart) {
return new THREE.Vector3(cart.x, cart.y, cart.z)
}
// Configure Three.js meshes to stand against globe center position up direction
for (let id in _3Dobjects) {
minWGS84 = _3Dobjects[id].minWGS84
maxWGS84 = _3Dobjects[id].maxWGS84
// convert lat/long center position to Cartesian3
let center = Cesium.Cartesian3.fromDegrees(
(minWGS84[0] + maxWGS84[0]) / 2,
(minWGS84[1] + maxWGS84[1]) / 2
)
// get forward direction for orienting model
let centerHigh = Cesium.Cartesian3.fromDegrees(
(minWGS84[0] + maxWGS84[0]) / 2,
(minWGS84[1] + maxWGS84[1]) / 2,
1
)
// use direction from bottom left to top left as up-vector
let bottomLeft = cartToVec(
Cesium.Cartesian3.fromDegrees(minWGS84[0], minWGS84[1])
)
let topLeft = cartToVec(
Cesium.Cartesian3.fromDegrees(minWGS84[0], maxWGS84[1])
)
let latDir = new THREE.Vector3().subVectors(bottomLeft, topLeft).normalize()
// configure entity position and orientation
_3Dobjects[id].threeMesh.position.copy(center)
_3Dobjects[id].threeMesh.lookAt(centerHigh.x, centerHigh.y, centerHigh.z)
_3Dobjects[id].threeMesh.up.copy(latDir)
}
// Clone Cesium Camera projection position so the
// Three.js Object will appear to be at the same place as above the Cesium Globe
three.camera.matrixAutoUpdate = false
let cvm = cesium.viewer.camera.viewMatrix
let civm = cesium.viewer.camera.inverseViewMatrix
three.camera.lookAt(0, 0, 0)
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]
)
let width = cesiumContainer.clientWidth
let height = cesiumContainer.clientHeight
let aspect = width / height
three.camera.aspect = aspect
three.camera.updateProjectionMatrix()
three.renderer.setSize(width, height)
three.renderer.clear()
three.renderer.render(three.scene, three.camera)
}
function loop() {
requestAnimationFrame(loop)
renderCesium()
renderThreeObj()
}
initCesium() // Initialize Cesium renderer
initThree() // Initialize Three.js renderer
init3DObject() // Initialize Three.js object mesh with Cesium Cartesian coordinate system
loop() // Looping renderer
script>
body>