详细解析 cesiumViewer.render() 和 requestAnimationFrame(render)

1. cesiumViewer.render()
作用:
手动触发一次 Cesium 场景的渲染。在默认情况下,Cesium 会自动处理渲染循环,但当你禁用默认渲染循环时,需要手动调用此方法。
参数:无参数
典型用法:
javascript
// 创建 Viewer 时禁用默认渲染循环
const viewer = new Cesium.Viewer('cesiumContainer', {
useDefaultRenderLoop: false
});
// 手动渲染一帧
viewer.render();
2. requestAnimationFrame(render)
作用:
浏览器提供的 API,用于在下一次浏览器重绘前执行指定的回调函数,通常用于创建平滑的动画或渲染循环。
参数:
callback: 下一次重绘时调用的函数- 回调函数会接收一个参数(时间戳,表示开始执行回调的时间)
3. 组合使用详解
基本渲染循环:
javascript
function render() {
// 执行场景渲染
viewer.render();
// 请求下一帧继续渲染
requestAnimationFrame(render);
}
// 启动渲染循环
render();
更完整的示例:
javascript
class CustomRenderLoop {
constructor(viewer) {
this.viewer = viewer;
this.isRendering = false;
this.requestId = null;
this.lastTime = 0;
this.frameCount = 0;
this.fps = 0;
}
start() {
if (this.isRendering) return;
this.isRendering = true;
this.lastTime = performance.now();
this.animate();
}
stop() {
if (this.requestId) {
cancelAnimationFrame(this.requestId);
this.requestId = null;
}
this.isRendering = false;
}
animate(currentTime = 0) {
if (!this.isRendering) return;
// 计算帧率
this.frameCount++;
const deltaTime = currentTime - this.lastTime;
if (deltaTime >= 1000) { // 每秒更新一次FPS
this.fps = Math.round((this.frameCount * 1000) / deltaTime);
console.log(`FPS: ${this.fps}`);
this.frameCount = 0;
this.lastTime = currentTime;
}
try {
// 执行渲染
this.viewer.render();
// 执行场景更新前的回调
if (this.onPreRender) this.onPreRender(currentTime);
// 执行自定义更新逻辑
this.update(currentTime);
// 执行场景更新后的回调
if (this.onPostRender) this.onPostRender(currentTime);
} catch (error) {
console.error('渲染错误:', error);
this.stop();
return;
}
// 请求下一帧
this.requestId = requestAnimationFrame(this.animate.bind(this));
}
update(currentTime) {
// 在这里添加自定义的更新逻辑
// 例如:更新实体位置、相机动画等
}
}
// 使用示例
const viewer = new Cesium.Viewer('cesiumContainer', {
useDefaultRenderLoop: false
});
const renderLoop = new CustomRenderLoop(viewer);
renderLoop.start();
// 添加自定义渲染回调
renderLoop.onPreRender = (time) => {
// 在渲染前执行的操作
console.log(`渲染前时间: ${time}`);
};
// 停止渲染循环
// renderLoop.stop();
4. 性能优化技巧
按需渲染(只在有变化时渲染):
javascript
let needsRender = false;
// 监听场景变化
viewer.scene.preRender.addEventListener(() => {
needsRender = true;
});
function smartRender() {
if (needsRender) {
viewer.render();
needsRender = false;
}
requestAnimationFrame(smartRender);
}
smartRender();
节流渲染(限制帧率):
javascript
function createThrottledRender(targetFPS = 60) {
const interval = 1000 / targetFPS;
let lastTime = 0;
return function throttledRender(currentTime) {
if (currentTime - lastTime >= interval) {
viewer.render();
lastTime = currentTime;
}
requestAnimationFrame(throttledRender);
};
}
const throttledRender = createThrottledRender(30); // 限制到30FPS
throttledRender();
5. 实际应用场景
场景1:与其他动画库集成
javascript
// 与Three.js集成
function renderBoth() {
// 渲染Cesium场景
viewer.render();
// 渲染Three.js场景
threeRenderer.render(threeScene, threeCamera);
requestAnimationFrame(renderBoth);
}
场景2:响应式渲染
javascript
let isUserInteracting = false;
// 监听用户交互
viewer.screenSpaceEventHandler.setInputAction(() => {
isUserInteracting = true;
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
function responsiveRender() {
if (isUserInteracting) {
// 用户交互时全速渲染
viewer.render();
isUserInteracting = false;
} else {
// 空闲时降低渲染频率
setTimeout(() => {
requestAnimationFrame(responsiveRender);
viewer.render();
}, 100); // 100ms间隔
return;
}
requestAnimationFrame(responsiveRender);
}
6. 注意事项
- 性能影响 :频繁调用
render()可能影响性能,尤其是在移动设备上 - 内存泄漏:确保在不需要时停止渲染循环
- 错误处理:在渲染循环中添加错误处理,防止崩溃
- 兼容性 :
requestAnimationFrame在不同浏览器中的表现可能略有差异
7. 最佳实践
javascript
// 使用Promise包装的渲染循环
class PromiseBasedRenderLoop {
constructor(viewer) {
this.viewer = viewer;
this.shouldStop = false;
}
async run() {
while (!this.shouldStop) {
await new Promise(resolve => {
requestAnimationFrame(() => {
viewer.render();
resolve();
});
});
// 可以在这里添加帧之间的延迟
// await this.delay(16); // ~60FPS
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
stop() {
this.shouldStop = true;
}
}
这种组合方式让你可以完全控制Cesium的渲染过程,适用于需要精细控制渲染逻辑的高级应用场景。