Improving Performance with Explicit Rendering
通过显式渲染提高性能
默认情况下,Cesium会像游戏引擎一样渲染新的帧(frames),即以目标帧率定期渲染。虽然这对于具备动态数据的Cesium应用或是具备持续数据流的视图来说很有效,但大量的Cesium应用都能够受益于较低的渲染频率。渲染一个新的帧需要占用CPU的资源,如应用处于闲置状态,这往往是没必要的。通过显示渲染提高性能意味着你可以运行你的Cesium应用而不必担心你的笔记本电脑风扇高速运转,也不必担心消耗移动设备的电池寿命。
从Cesium 1.42开始,一个可选的新功能出现了,显式渲染模式,这使得开发者开发者根据应用的需要精确控制Cesium中的渲染。在其设置为可用状态时,只在以下场景才会渲染新的帧:
- 相机切换,无论是用户操作还是通过Cesium API控制的;
- 模拟时间超过了指定的阈值;
- 在加载地形、图像、3D Tiles或数据源时,包括每个单独的瓦片加载。在低等级时,当网络请求通过URI或BLOB解决、或web worker返回异步过程时会触发;
- 地球图像图层(globe imagery layers)添加、移除或发生变化;
- 地球地形数据源(globe terrain providers)发生变化;
- 场景模式(scene mode)发生变化;
- 通过Cesium API对一个帧进行显式渲染。
这涵盖了许多Cesium常用的看见功能,诸如加载地形、图像和像3D Tiles这样的数据。对于对场景的更细微的更改,而不是监视可能需要渲染的每个更新,这取决于应用程序。如果应用程序以上述情况之外的方式对场景或其内容进行了更改,例如使用实体(Entity)或原始类型(Primitive)的API,则需要显式地请求一个新的渲染帧。
当显式渲染模式启用并对场景进行更改时,渲染就会像平时一样与目标帧同一速率出现。然而,当Cesium闲置时,CPU使用率就会大大降低。举个例子,使用Chrome浏览器的开发者工具进行测试,在空闲的Cesium场景中CPU的平均使用率是25.1%,但是在开启显式渲染模式改进性能后,CPU的平均使用率达到3.0%。这是在i7处理器的笔记本电脑上,用Google的Chrome进行测试的。
Timeline
Timeline这张图片展示了在正常渲染和在请求渲染模式(request render mode)随着时间推移的FPS以及CPU使用情况。在初始加载时,两者表现接近。初始加载结束后,请求渲染模式下就不再渲染新的帧,这使得CPU的使用率下降到了几乎0%。当场景初始加载时,FPS达到预期的60,但是在空闲时,因为不需要新的帧,所以FPS下降到了0。
配置Cesium应用使用显式渲染,要求开启请求渲染模式(request render mode),考虑到模拟时间,之后所有不自动处理的案例都必须显式渲染帧。
开启请求渲染模式(request render mode)
开启 requestRenderMode 以减少Cesium渲染一个新帧的总时间,并减应用中Cesium的总CPU占用率。
var viewer = new Cesium.Viewer('cesiumContainer', {
requestRenderMode : true
});
你也可以在创建viewer之后对场景(scene)进行这些调整。
var viewer = new Cesium.Viewer('cesiumContainer');
viewer.scene.requestRenderMode = true;
处理模拟时间变化
默认情况下,当 requestRenderMode 开启时,模拟时间改变超过0.0秒,就会请求一个新的帧。这个值可以通过设置 maximumRenderTimeChange 进行调整。如果你的场景有随时间变化的元素,比如动画、光线变化、水体掩膜或是视频,可以考虑进行相应设置。如果场景中没有元素会随着模拟时间而变化,可以考虑将 maximumRenderTimeChange 设置为一个较高的值,比如无穷大(Infinity)。
// Create a viewer that will not render frames based on changes in simulation time.
var viewer = new Cesium.Viewer('cesiumContainer', {
requestRenderMode : true,
maximumRenderTimeChange : Infinity
});
viewer.scene.debugShowFramesPerSecond = true;
显式渲染帧
在应用代码中,如果发生上述列表中没有涉及到的变化,那么需要显式地渲染一个新的帧来立即显示改变。否则,直到渲染一个新的帧时这个变化才能看到。
通过调用 Scene.requestRender 显式渲染帧。
// Hides the stars
scene.skyBox.show = false;
// Explicitly render a new frame
scene.requestRender();
更新/渲染周期事件
作为这些改变的一部分,我们已经把一些更新和渲染的逻辑分开。Cesium总是以相同的帧率更新,这取决于应用的目标帧率(60 FPS是默认值,参见 Viewer.targetFrameRate )。当使用 requestRenderMode 时,渲染函数只会在渲染新帧时被调用。我们已经重构了渲染周期事件的顺序,细节可以在下面看到。一般来说,在一个更新事件中调用 Scene.requestRender 函数(或做一些像改变相机位置这些可以自动提示渲染的变化)会为下一个帧及时将渲染加入到队列中。
scene.postUpdate.addEventListener(function() {
// This code will run at 60 FPS
if (changeToPromptRender) {
scene.requestRender();
}
});
scene.preRender.addEventListener(function() {
// This code will run when a new frame is rendered
// including when changeToPromptRender is true
});
preUpdate
这个事件在更新/渲染周期的开始被触发,在场景内容(scene contents)被更新或渲染之前。目标帧率的每个更新/渲染周期都会触发这个事件。
postUpdate
这个事件在场景(scene)更新之后,但在新帧渲染之前被触发。目标帧率的每个更新/渲染周期都会触发这个事件。
preRender
此事件在渲染新帧之前触发,但在更新之后。当 requestRenderMode 开启时,这个事件只会在渲染新帧时被触发。
postRender
这个事件在更新/渲染周期结束时被引发,在渲染新帧之后,也在更新完成之后。当 requestRenderMode 开启时,这个事件只会在渲染新帧时被触发。
用例和示例
场景渲染性能(Scene Rendering Performance)是一个官方沙箱示例,它在下拉列表中探索了一些常见的用例,涵盖了一些不需要显式呈现的示例,当然也包括一些需要显式呈现的示例。
让我们在一个示例应用程序中改进性能,该应用程序使用鼠标悬停选择来调整实体的状态。下面的代码演示了如何创建场景,添加实体,然后创建选取函数(picking function)。虽然我们可以在每次触发MOUSE_MOVE事件时调用 requestRender ,但除非实体的属性发生变化,否则没有必要调用 requestRender 。
// Create a viewer that won't render a new frame unless
// updates to the scene require it.
var viewer = new Cesium.Viewer('cesiumContainer', {
requestRenderMode : true,
maximumRenderTimeChange : Infinity
});
// Add an entity to the scene.
var entity = viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
box : {
dimensions : new Cesium.Cartesian3(1000000.0, 1000000.0, 30000.0),
material : Cesium.Color.CORNFLOWERBLUE
}
});
// If the mouse is over the billboard, change its scale and color,
// then request a new render frame.
var lastPicked;
handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
handler.setInputAction(function(movement) {
var pickedObject = scene.pick(movement.endPosition);
if (Cesium.defined(pickedObject) && (pickedObject.id === entity)) {
if (Cesium.defined(lastPicked)) {
return;
}
entity.box.material = Cesium.Color.YELLOW;
scene.requestRender();
lastPicked = pickedObject;
} else if (Cesium.defined(lastPicked)) {
entity.box.material = Cesium.Color.CORNFLOWERBLUE;
scene.requestRender();
lastPicked = undefined;
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);