cesium source code analyzes how a 3D scene is rendered

Keywords: Javascript html5 source code webgl cesium

Seek medical advice

  • Do you want to know the source code of cesium?
  • Have you ever lost your way in the huge source code?
  • How does cesium render the scene step by step?

Go straight to the subject

1. The origin of all things - time

//Clock.js
Clock.prototype.tick = function () {
  var currentSystemTime = getTimestamp();
  var currentTime = JulianDate.clone(this._currentTime);
  ...//ellipsis
  this._currentTime = currentTime;
  this._lastSystemTime = currentSystemTime;
  this.onTick.raiseEvent(this);
  return currentTime;
};

It can be seen from the code that when the tick function is executed, the subscribed actions in onTick will be executed synchronously.

2. Rendering frequency - frame rate

//requestAnimationFrame.js
(function () {
  // look for vendor prefixed function
  if (!defined(implementation) && typeof window !== "undefined") {
    var vendors = ["webkit", "moz", "ms", "o"];
    var i = 0;
    var len = vendors.length;
    while (i < len && !defined(implementation)) {
      implementation = window[vendors[i] + "RequestAnimationFrame"];
      ++i;
    }
  }

  // build an implementation based on setTimeout
  if (!defined(implementation)) {
    var msPerFrame = 1000.0 / 60.0;
    var lastFrameTime = 0;
    implementation = function (callback) {
      var currentTime = getTimestamp();

      // schedule the callback to target 60fps, 16.7ms per frame,
      // accounting for the time taken by the callback
      var delay = Math.max(msPerFrame - (currentTime - lastFrameTime), 0);
      lastFrameTime = currentTime + delay;

      return setTimeout(function () {
        callback(lastFrameTime);
      }, delay);
    };
  }
})();

It can be seen from the code that cesium handles the requestanimation frame of various browsers in a compatible manner.

3. Rendering -- scene

Scene.prototype.render = function (time) {
  this._preUpdate.raiseEvent(this, time);

  var frameState = this._frameState;
  frameState.newFrame = false;
  ...//ellipsis
  this._postUpdate.raiseEvent(this, time);

  if (shouldRender) {
    this._preRender.raiseEvent(this, time);
    frameState.creditDisplay.beginFrame();
    tryAndCatchError(this, render);
  }
  if (shouldRender) {
    this._postRender.raiseEvent(this, time);
    frameState.creditDisplay.endFrame();
  }
};

From the code, we can see that this function is responsible for updating the status of all primitive data in the scene, and then the data is generated by itself to decide whether to render according to the status. By the way, we also see the trigger mechanism of two important callback events, preRender and postUpdate.

Call -- from top to bottom

  • We can see the whole calling process through the above three source code fragments. Let's connect the code logic step by step
  • 1. Initialize scene
  var  viewer = new Viewer("cesiumContainer", {
     imageryProvider: imageryProvider,
     baseLayerPicker: hasBaseLayerPicker,
     scene3DOnly: endUserOptions.scene3DOnly,
     requestRenderMode: true,
   });
  • 2. What does the new Viewer() do?
function Viewer(container, options) {
...//ellipsis
// Cesium widget
var cesiumWidget = new CesiumWidget();
  • 3. It can be seen that its core is to initialize the CesiumWidget. What does new CesiumWidget() do?
function CesiumWidget(container, options) {
 ...//ellipsis
  var scene = new Scene();
  this._scene = scene;
  this._screenSpaceEventHandler = new ScreenSpaceEventHandler(canvas);
  this._useDefaultRenderLoop = undefined;
  this.useDefaultRenderLoop = defaultValue(
    options.useDefaultRenderLoop,
    true
  );
   ...//ellipsis
}
  • 4. You can see that it also the new Scene() object, and then set the useDefaultRenderLoop property to true. This simple sentence is very important because it is the beginning of the whole call loop. This operation will trigger the following code
useDefaultRenderLoop: {
  get: function () {
    return this._useDefaultRenderLoop;
  },
  set: function (value) {
    if (this._useDefaultRenderLoop !== value) {
      this._useDefaultRenderLoop = value;
      if (value && !this._renderLoopRunning) {
        startRenderLoop(this);
      }
    }
  },
},
}
  • 5. The code of the startRenderLoop function is as follows
function startRenderLoop(widget) {
  widget._renderLoopRunning = true;
  var lastFrameTime = 0;
  function render(frameTime) {
     ...//ellipsis
      widget.resize();
       widget.render();
       requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}
  • 6. From this code, we can see that it calls the requestAnimationFrame function, and then executes the widget.render() function again and again. The code is as follows
CesiumWidget.prototype.render = function () {
  if (this._canRender) {
    this._scene.initializeFrame();
    var currentTime = this._clock.tick();
    this._scene.render(currentTime);
  } else {
    this._clock.tick();
  }
};
  • 7. It can be seen from this code that it calls the scene.render() function to render the whole scene primitive, and also calls the clock.tick() function to execute all the subscribed functions of onTick.

Review -- Summary and sorting

  1. Initialize the Viewer object first
  2. Reinitialize the CesiumWidget object
  3. Trigger the requestAnimationFrame function through the useDefaultRenderLoop property
  4. Trigger the widget.render() function through the requestAnimationFrame function
  5. Trigger the scene.render and clock.tick() functions through the widget.render() function
  6. After completion, repeat 4 and 5 steps

More - join us

  1. Here is a place to explain. Due to the limited space and time, I didn't write all the code. Here are the main implementation ideas
  2. If there's anything else you don't know
  3. If you still need cesium communication
  4. Let's study together
  5. You can join our base. The address of our base is 450342630 (QQ group number)

Posted by sdlyr8 on Tue, 28 Sep 2021 01:41:45 -0700