Cesium 第一帧渲染指南

Cesium 第一帧渲染指南
Cesium作为开源地图三维可视化的巨擘,在行业内有着广泛的应用。这里先不讨论内部实现原理,仅就渲染流程做个分析。

1 Viewer

下面一句话,就可以创建出一个默认的地球
var viewer = new Cesium.Viewer(“cesiumContainer”);
如下图所示,不仅有地球,还有右上角的工具栏,左下角的一个时钟,底部的一个文字标签和时间轴等小部件。这些都是如何呈现出来的呢,现在就进入到new Viewer() 内部看看。
在这里插入图片描述


function Viewer(container, options) {
  //...
  
  var that = this;
  Util.log("创建div容器viewerContainer,添加到map的div容器中");
  var viewerContainer = document.createElement("div");
  viewerContainer.className = "cesium-viewer";
  container.appendChild(viewerContainer);

  // Cesium widget container
  Util.log("创建div容器cesiumWidgetContainer,添加到viewerContainer的div容器中")
  var cesiumWidgetContainer = document.createElement("div");
  cesiumWidgetContainer.className = "cesium-viewer-cesiumWidgetContainer";
  viewerContainer.appendChild(cesiumWidgetContainer);

  // Bottom container
  Util.log("创建bottomContainer的div容器添加到viewerContainer");
  var bottomContainer = document.createElement("div");
  bottomContainer.className = "cesium-viewer-bottom";

  viewerContainer.appendChild(bottomContainer);

  //...

  //系统时钟
  Util.log('创建Clock时间系统');
  var clock;
  var clockViewModel;
  var destroyClockViewModel = false;
  if (defined(options.clockViewModel)) {
    clockViewModel = options.clockViewModel;
    clock = clockViewModel.clock;
  } else {
    clock = new Clock();
    clockViewModel = new ClockViewModel(clock);
    destroyClockViewModel = true;
  }

  if (defined(options.shouldAnimate)) {
    clock.shouldAnimate = options.shouldAnimate;
  }

  // Cesium widget
  Util.log("\n    CesiumWidget构造函数,初始化各种小部件,比如天空和skybox,图层的provider等\n    这里有个useDefaultRenderLoop布尔类型选项,默认为true,如果为false,则不会调用Viewer.render,就无法\n    循环渲染每一帧,只会渲染第一帧\n  ");
  var cesiumWidget = new CesiumWidget(cesiumWidgetContainer, {
    imageryProvider:
      createBaseLayerPicker || defined(options.imageryProvider)
        ? false
        : undefined,
    clock: clock,
    skyBox: options.skyBox,
    skyAtmosphere: options.skyAtmosphere,
    sceneMode: options.sceneMode,
    mapProjection: options.mapProjection,
    globe: options.globe,
    orderIndependentTranslucency: options.orderIndependentTranslucency,
    contextOptions: options.contextOptions,
    useDefaultRenderLoop: options.useDefaultRenderLoop,
    targetFrameRate: options.targetFrameRate,
    showRenderLoopErrors: options.showRenderLoopErrors,
    useBrowserRecommendedResolution: options.useBrowserRecommendedResolution,
    creditContainer: defined(options.creditContainer)
      ? options.creditContainer
      : bottomContainer,
    creditViewport: options.creditViewport,
    scene3DOnly: scene3DOnly,
    terrainExaggeration: options.terrainExaggeration,
    shadows: options.shadows,
    terrainShadows: options.terrainShadows,
    mapMode2D: options.mapMode2D,
    requestRenderMode: options.requestRenderMode,
    maximumRenderTimeChange: options.maximumRenderTimeChange,
  });
  Util.log('工具小组件: ');
  Util.logObj(cesiumWidget);
  Util.log("创建dataSource集合dataSourceCollection");
  var dataSourceCollection = options.dataSources;
  var destroyDataSourceCollection = false;
  if (!defined(dataSourceCollection)) {
    dataSourceCollection = new DataSourceCollection();
    destroyDataSourceCollection = true;
  }
  var scene = cesiumWidget.scene;
  //...
}

从代码中可以看到,首先创建了几个div容器,用来存放地球上的小部件。接着创建了一个Clock时钟。时钟创建之后,实例化了一个非常重要的变量cesiumWidget,从代码中可以看到,cesiumWidget的实例化中有很多参数,其中就包含上一步创建的Clock时钟。注意实例化cesiumWidget之后,会将其上面挂载的scene赋值给viewer里面的scene。接着,我们进入到cesiumWidget中看实例化过程中都执行了什么。

2 CesiumWidget

function CesiumWidget(container, options) {
  //...
  
  Util.log("创建canvas");
  var canvas = document.createElement("canvas");
 
  try {
    //地球也是一个widget,创建一个场景
    var scene = new Scene({
      canvas: canvas,
      contextOptions: options.contextOptions,
      creditContainer: innerCreditContainer,
      creditViewport: creditViewport,
      mapProjection: options.mapProjection,
      orderIndependentTranslucency: options.orderIndependentTranslucency,
      scene3DOnly: defaultValue(options.scene3DOnly, false),
      terrainExaggeration: options.terrainExaggeration,
      shadows: options.shadows,
      mapMode2D: options.mapMode2D,
      requestRenderMode: options.requestRenderMode,
      maximumRenderTimeChange: options.maximumRenderTimeChange,
    });
    this._scene = scene;

    //...

    var globe = options.globe;
    if (!defined(globe)) {
      globe = new Globe(ellipsoid);
    }
    if (globe !== false) {
      scene.globe = globe;
      scene.globe.shadows = defaultValue(
        options.terrainShadows,
        ShadowMode.RECEIVE_ONLY
      );
    }

    //...
    var skyBox = options.skyBox;
    this._useDefaultRenderLoop = undefined;
    //设置useDefaultRenderLoop
    
    Util.log("设置useDefaultRenderLoop,Cesium里面对useDefaultRenderLoop进行了劫持,在setter里面调用了渲染函数");
    // debugger
    this.useDefaultRenderLoop = defaultValue(
      options.useDefaultRenderLoop,
      true
    );

    //...
  } catch (error) {
    //...
  }
}

可以看到,在cesiumWidget中创建了一个canvas,就是在这个canvas上面使用webgl绘制三维球的。接着又实例化了一个Scene,也就是创建了一个场景。注意在cesiumWidget中创建了地球相关的部件,比如天空盒,晨昏线,大气层等。还有一个至关重要的参数useDefaultRenderLoop,再往下翻,可以看到这个参数是做了数据劫持的:

  useDefaultRenderLoop: {
    get: function () {
      return this._useDefaultRenderLoop;
    },
    set: function (value) {
      if (this._useDefaultRenderLoop !== value) {
        this._useDefaultRenderLoop = value;
        if (value && !this._renderLoopRunning) {
          Util.log("在设置useDefaultRenderLoop的时候启动渲染");
          startRenderLoop(this);
        }
      }
    },
  },

用来判断是否对第一帧之后的每一帧进行渲染。如果设置且为true,则渲染,否则不渲染。
进入到实例化Scene的构造函数中来。

3 Scene

function Scene(options) {
  Util.log("创建Scene的初始选项: ");
  //根据GPU性能选择适合的渲染上下文渲染状态。可以是"high-performance", "low-power" 或者 "default"。默认为 "default"。
  contextOptions.webgl.powerPreference = defaultValue(
    contextOptions.webgl.powerPreference,
    "high-performance"
  );
  
  //...

  //>>includeEnd('debug');
  var hasCreditContainer = defined(creditContainer);

  var context = new Context(canvas, contextOptions);

  this._jobScheduler = new JobScheduler();
  this._frameState = new FrameState(
    context,
    new CreditDisplay(creditContainer, " • ", creditViewport),
    this._jobScheduler
  );
  
  //...

  this._frameState.scene3DOnly = defaultValue(options.scene3DOnly, false);
  this._removeCreditContainer = !hasCreditContainer;
  this._creditContainer = creditContainer;

  this._canvas = canvas;
  this._context = context;
  this._computeEngine = new ComputeEngine(context);
  this._globe = undefined;
  this._globeTranslucencyState = new GlobeTranslucencyState();
  Util.log("创建PrimitiveCollection");
  this._primitives = new PrimitiveCollection();
  this._groundPrimitives = new PrimitiveCollection();
  
  //...

  Util.log("创建事件,_preUpdate,_postUpdate,_renderError,_preRender,_postRender");
  this._preUpdate = new Event();
  this._postUpdate = new Event();

  this._renderError = new Event();
  this._preRender = new Event();
  this._postRender = new Event();

  //...
  
  updateFrameNumber(this, 0.0, JulianDate.now());
  this.updateFrameState();
  this.initializeFrame();
}

Scene中多是和webgl相关的。主要看最后三个函数,第一个函数updateFrameNumber更新帧数,第二个参数就是第几帧,很明显这里是第1帧,第二和第三个函数 this.updateFrameState(); this.initializeFrame();,就是对第一帧的渲染。
至此Scene实例化完成,回到cesiumWidget中去,紧接着在cesiumWidget中完成了地球的创建等。cesiumWidget实例化完成,回到Viewer中去,此时Viewer实例化完毕,整个地球也就创建好了。

总的来说,整个渲染过程主要的三个实例化:
● Viewer的实例化。
● 在Viewer的实例化中完成cesiumWidget的实例化。
● 在cesiumWidget的实例化中完成Scene的实例化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值