Cesium倾斜摄影加载详细攻略
倾斜摄影加载流程图
一、倾斜摄影基础知识 ⭐⭐⭐
1. 什么是倾斜摄影
倾斜摄影是通过多角度、多方向拍摄的航拍技术,生成的三维模型能够真实反映地物的表面特征。在Cesium中,倾斜摄影数据通常以3D Tiles格式加载。
2. 3D Tiles格式说明
- tileset.json: 描述数据的元数据文件
- b3dm: 批量3D模型,最常见的格式
- i3dm: 实例化3D模型
- pnts: 点云格式
- cmpt: 组合格式
二、倾斜摄影数据获取与准备 ⭐⭐
1. 数据来源
- 商业平台:超图、Smart3D、ContextCapture等软件生成
- 开源数据:部分城市开放数据可获取
- 自建模型:使用无人机摄影+建模软件生成
2. 数据准备
项目目录结构示例:
/static/data/d3ties/
├── tileset.json # 主配置文件
├── 0/ # LOD 0级瓦片
│ ├── 0.b3dm
│ ├── 1.b3dm
│ └── ...
├── 1/ # LOD 1级瓦片
│ ├── 0.b3dm
│ └── ...
└── ...
三、基本加载实现 ⭐⭐⭐
async function load3dTiles() {
// 等待地形提供者准备完成
await viewer.terrainProvider.readyPromise;
// 创建并加载3D Tileset
const tileset = await Cesium.Cesium3DTileset.fromUrl('/static/data/d3ties/tileset.json', {
shadows: true, // 启用阴影
preferLeaves: true, // 优先加载叶节点
maximumScreenSpaceError: 16, // 屏幕空间误差(值越小越精细,性能消耗越大)
maximumMemoryUsage: 2048 // 最大内存使用(MB)
});
// 添加到场景
viewer.scene.primitives.add(tileset);
// 视图定位到模型
viewer.zoomTo(tileset);
}
四、详细配置参数解析 ⭐⭐⭐
完整的3D Tileset配置选项:
const tileset = await Cesium.Cesium3DTileset.fromUrl(url, {
// 基础参数
shadows: true, // 是否投射和接收阴影
maximumScreenSpaceError: 16, // 屏幕空间误差,控制LOD切换(小值 = 高质量)
maximumMemoryUsage: 2048, // 最大内存使用量(MB)
// LOD策略参数
skipLevelOfDetail: true, // 启用跳过LOD级别,可提高性能
baseScreenSpaceError: 1024, // 基础SSE,高于此值时才应用skipLevels
skipScreenSpaceErrorFactor: 16, // 跳过LOD时的误差因子
skipLevels: 2, // 跳过的LOD级别数
// 预加载参数
preloadWhenHidden: false, // 当被遮挡时是否预加载
preloadFlightDestinations: true, // 当相机飞行时预加载目标区域
preferLeaves: true, // 优先加载叶节点
// 动态LOD参数
dynamicScreenSpaceError: true, // 开启动态屏幕空间误差
dynamicScreenSpaceErrorDensity: 0.00278, // 密度参数
dynamicScreenSpaceErrorFactor: 4.0, // 动态因子
dynamicScreenSpaceErrorHeightFalloff: 0.25, // 高度衰减系数
// 细节参数
maximumSSEDistance: 20000, // 开始降低分辨率的最大距离
debugShowContentBoundingVolume: false, // 显示内容边界体(调试用)
debugShowBoundingVolume: false, // 显示边界体(调试用)
});
五、位置和形变调整 ⭐⭐
1. 模型矩阵调整 - 平移、旋转和缩放
// 获取模型中心点
const boundingSphere = tileset.boundingSphere;
const cartographicCenter = Cesium.Cartographic.fromCartesian(boundingSphere.center);
// 创建平移矩阵 - 高度调整
const surface = Cesium.Cartesian3.fromRadians(
cartographicCenter.longitude,
cartographicCenter.latitude,
0 // 地表高度
);
const targetPosition = Cesium.Cartesian3.fromRadians(
cartographicCenter.longitude,
cartographicCenter.latitude,
50 // 目标高度
);
// 计算偏移量
const translation = Cesium.Cartesian3.subtract(
targetPosition,
surface,
new Cesium.Cartesian3()
);
// 应用平移变换
tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
// 如果需要旋转,可以组合多个变换
function createRotationMatrix(longitude, latitude, height, rotationRadians) {
// 创建旋转矩阵
const center = Cesium.Cartesian3.fromRadians(longitude, latitude, height);
const rotationMatrix = Cesium.Matrix4.fromRotationZ(rotationRadians);
// 平移到原点,应用旋转,再平移回去
const translationToCenter = Cesium.Matrix4.fromTranslation(
Cesium.Cartesian3.negate(center, new Cesium.Cartesian3())
);
const translationFromCenter = Cesium.Matrix4.fromTranslation(center);
// 组合变换矩阵
let modelMatrix = new Cesium.Matrix4();
modelMatrix = Cesium.Matrix4.multiply(modelMatrix, translationFromCenter, modelMatrix);
modelMatrix = Cesium.Matrix4.multiply(modelMatrix, rotationMatrix, modelMatrix);
modelMatrix = Cesium.Matrix4.multiply(modelMatrix, translationToCenter, modelMatrix);
return modelMatrix;
}
2. 根据经纬度调整位置
// 调整模型到特定经纬度
function positionTilesetAtLonLat(tileset, longitude, latitude, height) {
// 计算从当前位置到目标位置的变换
const cartographic = Cesium.Cartographic.fromCartesian(tileset.boundingSphere.center);
const surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0);
const target = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);
const offset = Cesium.Cartesian3.subtract(target, surface, new Cesium.Cartesian3());
// 应用变换
tileset.modelMatrix = Cesium.Matrix4.fromTranslation(offset);
}
六、性能优化策略 ⭐⭐⭐
1. 内存和性能平衡参数
参数 | 说明 | 推荐值 |
---|---|---|
maximumScreenSpaceError | 控制精度和性能的主要参数 | 高性能: 32-64 平衡: 16 高质量: 4-8 |
maximumMemoryUsage | 内存使用上限(MB) | 2048-4096 |
dynamicScreenSpaceError | 远处细节降级 | true |
skipLevelOfDetail | 跳过中间LOD | true |
preferLeaves | 优先加载叶节点 | true(初次加载时) |
2. 渐进式加载策略
// 渐进式加载策略
tileset.progressiveResolutionHeightFraction = 0.5; // 初始加载时使用较低分辨率
// 优先控制
viewer.scene.primitives.add(tileset);
// 设置加载优先级 (0为最高,数字越大优先级越低)
tileset.loadingPriority = 0;
// 根据相机视图控制加载区域
tileset.cullRequestsWhileMoving = true;
tileset.cullRequestsWhileMovingMultiplier = 10; // 移动时减少请求的倍数
3. 大场景分块加载
对于超大场景,可采用分块加载策略:
// 城市不同区域独立加载
async function loadCityByDistricts() {
const districts = [
{ name: '区域1', url: '/static/data/district1/tileset.json' },
{ name: '区域2', url: '/static/data/district2/tileset.json' },
{ name: '区域3', url: '/static/data/district3/tileset.json' }
];
// 创建一个容器,用于管理所有tileset
const tilesets = [];
// 加载每个区域
for (const district of districts) {
const tileset = await Cesium.Cesium3DTileset.fromUrl(district.url, {
maximumScreenSpaceError: 16,
skipLevelOfDetail: true
});
// 根据相机距离设置不同的加载优先级
tileset.distanceDisplayCondition = new Cesium.DistanceDisplayCondition(0, 20000);
viewer.scene.primitives.add(tileset);
tilesets.push(tileset);
}
// 动态调整加载优先级
viewer.scene.postRender.addEventListener(() => {
const cameraPosition = viewer.camera.position;
// 根据距离调整优先级
tilesets.forEach(tileset => {
const distance = Cesium.Cartesian3.distance(
cameraPosition,
tileset.boundingSphere.center
);
// 距离越近,优先级越高
tileset.loadingPriority = Math.floor(distance / 1000);
});
});
}
七、样式调整和特效 ⭐⭐
1. 基础样式调整
// 设置3D Tiles的样式
tileset.style = new Cesium.Cesium3DTileStyle({
// 基于高度设置颜色
color: {
conditions: [
["${Height} >= 100", "color('blue')"],
["${Height} >= 50", "color('green')"],
["${Height} >= 25", "color('yellow')"],
["true", "color('red')"]
]
},
// 调整透明度
show: "${Height} > 0",
meta: {
description: "'Building height: ' + ${Height} + ' meters'"
}
});
2. 自定义着色器实现特效
// 使用自定义着色器实现轮廓发光效果
const customShader = new Cesium.CustomShader({
vertexShaderText: /* GLSL */ `
void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput) {
// 法线外扩
vsOutput.positionMC += normalize(vsInput.attributes.normalMC) * 0.05;
}
`,
fragmentShaderText: /* GLSL */ `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
// 设置发光颜色
material.diffuse = vec3(0.0, 1.0, 1.0); // 青色发光
material.alpha = 0.6;
}
`,
uniforms: {
u_glowColor: {
type: Cesium.UniformType.VEC3,
value: [0.0, 1.0, 1.0]
}
}
});
// 应用着色器
tileset.customShader = customShader;
3. 动态效果
// 动态透明度效果
let alpha = 0.0;
let increasing = true;
viewer.scene.postUpdate.addEventListener(() => {
// 调整透明度
if (increasing) {
alpha += 0.01;
if (alpha >= 1.0) {
alpha = 1.0;
increasing = false;
}
} else {
alpha -= 0.01;
if (alpha <= 0.3) {
alpha = 0.3;
increasing = true;
}
}
// 更新样式
tileset.style = new Cesium.Cesium3DTileStyle({
color: `rgba(255, 255, 255, ${alpha})`
});
});
八、交互功能实现 ⭐⭐
1. 鼠标点选功能
// 添加点选事件
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function(click) {
// 执行射线检测
const pickedFeature = viewer.scene.pick(click.position);
if (Cesium.defined(pickedFeature) && pickedFeature.primitive instanceof Cesium.Cesium3DTileset) {
// 获取属性信息
const properties = pickedFeature.getPropertyNames();
let propertyHtml = '<table class="cesium-infoBox-defaultTable">';
for (let i = 0; i < properties.length; i++) {
const propertyName = properties[i];
propertyHtml += `<tr><th>${propertyName}</th><td>${pickedFeature.getProperty(propertyName)}</td></tr>`;
}
propertyHtml += '</table>';
// 显示信息框
viewer.selectedEntity = new Cesium.Entity({
name: '选中建筑',
description: propertyHtml
});
// 高亮显示选中对象
highlightFeature(pickedFeature);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 高亮显示功能
function highlightFeature(feature) {
// 恢复之前的样式
if (previousHighlight) {
previousHighlight.color = previousColor;
}
// 保存当前样式
previousHighlight = feature;
previousColor = feature.color.clone();
// 设置高亮颜色
feature.color = Cesium.Color.YELLOW.withAlpha(0.8);
}
2. 模型裁剪功能
// 添加裁剪平面
function addClippingPlanes(tileset) {
// 获取模型中心点
const center = tileset.boundingSphere.center;
const transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
// 创建裁剪平面集合
const clippingPlanes = new Cesium.ClippingPlaneCollection({
planes: [
new Cesium.ClippingPlane(new Cesium.Cartesian3(1.0, 0.0, 0.0), 0.0), // 右侧裁剪面
new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 1.0, 0.0), 0.0), // 前侧裁剪面
new Cesium.ClippingPlane(new Cesium.Cartesian3(0.0, 0.0, -1.0), 0.0) // 顶部裁剪面
],
edgeWidth: 1.0,
edgeColor: Cesium.Color.WHITE,
modelMatrix: transform
});
// 应用裁剪平面
tileset.clippingPlanes = clippingPlanes;
// 添加平面控制UI
addClippingPlanesUI(clippingPlanes);
}
// 添加控制裁剪平面的UI
function addClippingPlanesUI(clippingPlanes) {
// 创建控制滑块
const xSlider = document.getElementById('xSlider');
xSlider.addEventListener('input', function() {
clippingPlanes.get(0).distance = Number(this.value);
});
// 类似添加y和z轴滑块...
}
九、常见问题和解决方案
1. 加载失败或显示不完整
// 检查加载错误并重试
tileset.readyPromise.then(function(tileset) {
console.log('3D Tiles加载成功');
}).catch(function(error) {
console.error('3D Tiles加载失败:', error);
// 尝试使用备用数据源
tryLoadBackupTileset();
});
// 设置超时重试
setTimeout(() => {
if (!tileset.ready) {
console.warn('3D Tiles加载超时,尝试重新加载');
viewer.scene.primitives.remove(tileset);
load3dTiles();
}
}, 30000); // 30秒超时
2. 性能问题
// 监控性能并自动调整
let frameRate = 0;
let frameCount = 0;
let lastTime = performance.now();
viewer.scene.postRender.addEventListener(() => {
frameCount++;
const now = performance.now();
// 每秒计算帧率
if (now - lastTime >= 1000) {
frameRate = frameCount;
frameCount = 0;
lastTime = now;
// 根据帧率动态调整质量
if (frameRate < 20) { // 帧率过低
// 降低质量以提高性能
tileset.maximumScreenSpaceError = Math.min(tileset.maximumScreenSpaceError * 1.5, 128);
console.log(`帧率低(${frameRate}FPS),调整SSE为${tileset.maximumScreenSpaceError}`);
} else if (frameRate > 40 && tileset.maximumScreenSpaceError > 8) {
// 提高质量
tileset.maximumScreenSpaceError = Math.max(tileset.maximumScreenSpaceError / 1.2, 8);
console.log(`帧率高(${frameRate}FPS),调整SSE为${tileset.maximumScreenSpaceError}`);
}
}
});
3. 纹理模糊或变形
// 调整纹理质量
viewer.scene.postProcessStages.fxaa.enabled = true; // 启用FXAA抗锯齿
viewer.scene.globe.maximumScreenSpaceError = 2; // 提高地形精度
// 如果使用的是倾斜摄影,可以通过调整采样模式提高质量
tileset.pointCloudShading = new Cesium.PointCloudShading({
attenuation: true,
maximumAttenuation: 4
});
十、实用工具和调试技巧
1. 调试工具
// 显示3D Tiles边界框(调试用)
tileset.debugShowBoundingVolume = true; // 显示边界体
tileset.debugShowContentBoundingVolume = true; // 显示内容边界体
tileset.debugShowViewerRequestVolume = true; // 显示请求区域
tileset.debugShowGeometricError = true; // 显示几何误差
tileset.debugShowRenderingStatistics = true; // 显示渲染统计
// 添加调试面板
function addDebugPanel() {
const debugPanel = document.createElement('div');
debugPanel.className = 'cesium-debug-panel';
debugPanel.innerHTML = `
<h3>3D Tiles调试</h3>
<label><input type="checkbox" id="debugBoundingVolume"> 显示边界体</label><br>
<label><input type="checkbox" id="debugShowStatistics"> 显示统计</label><br>
<label>最大屏幕空间误差: <input type="range" id="sseSlider" min="1" max="64" value="16"></label>
`;
document.body.appendChild(debugPanel);
// 绑定事件
document.getElementById('debugBoundingVolume').addEventListener('change', function() {
tileset.debugShowBoundingVolume = this.checked;
});
document.getElementById('sseSlider').addEventListener('input', function() {
tileset.maximumScreenSpaceError = Number(this.value);
console.log(`SSE调整为: ${this.value}`);
});
}
2. 实用测试和分析工具
// 显示加载统计信息
function showLoadingStatistics() {
let totalTiles = 0;
let loadedTiles = 0;
let tilesProcessed = 0;
const statsOverlay = document.createElement('div');
statsOverlay.style.position = 'absolute';
statsOverlay.style.top = '10px';
statsOverlay.style.right = '10px';
statsOverlay.style.padding = '10px';
statsOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
statsOverlay.style.color = 'white';
document.body.appendChild(statsOverlay);
// 更新统计信息
viewer.scene.postRender.addEventListener(() => {
if (tileset.ready) {
totalTiles = tileset.statistics.numberOfTilesTotal;
loadedTiles = tileset.statistics.numberOfTilesWithContentReady;
tilesProcessed = tileset.statistics.numberOfTilesProcessing;
statsOverlay.innerHTML = `
<div>总瓦片数: ${totalTiles}</div>
<div>已加载: ${loadedTiles} (${Math.round(loadedTiles/totalTiles*100)}%)</div>
<div>处理中: ${tilesProcessed}</div>
<div>内存使用: ${Math.round(tileset.statistics.memorySizeInBytes / (1024 * 1024))}MB</div>
`;
}
});
}
实战示例:完整的倾斜摄影加载与优化
async function loadOptimized3DTiles() {
try {
// 1. 创建带优化参数的3D Tileset
const tileset = await Cesium.Cesium3DTileset.fromUrl('/static/data/d3ties/tileset.json', {
shadows: true,
maximumScreenSpaceError: 16,
maximumMemoryUsage: 2048,
skipLevelOfDetail: true,
baseScreenSpaceError: 1024,
skipScreenSpaceErrorFactor: 16,
skipLevels: 1,
preferLeaves: true,
dynamicScreenSpaceError: true,
dynamicScreenSpaceErrorDensity: 0.00278,
dynamicScreenSpaceErrorFactor: 4.0,
dynamicScreenSpaceErrorHeightFalloff: 0.25,
progressiveResolutionHeightFraction: 0.5
});
// 2. 添加到场景
viewer.scene.primitives.add(tileset);
// 3. 高度调整
const heightOffset = 0; // 实际项目中可能需要调整此值
const boundingSphere = tileset.boundingSphere;
const cartographic = Cesium.Cartographic.fromCartesian(boundingSphere.center);
const surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0);
const offset = Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
heightOffset
);
const translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3());
tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);
// 4. 初始样式设置
tileset.style = new Cesium.Cesium3DTileStyle({
color: "color('white')"
});
// 5. 飞行到模型
viewer.flyTo(tileset, {
duration: 3,
offset: new Cesium.HeadingPitchRange(
0.0, // 航向角
Cesium.Math.toRadians(-30.0), // 俯仰角
tileset.boundingSphere.radius * 2.0 // 距离
)
});
// 6. 添加性能监控
setupPerformanceMonitoring(tileset);
// 7. 添加交互功能
setupTilesetInteraction(tileset);
console.log('倾斜摄影模型加载成功');
return tileset;
} catch (error) {
console.error('倾斜摄影模型加载失败:', error);
showLoadingError();
}
}
// 加载模型并开始使用
const tileset = await loadOptimized3DTiles();