Bootstrap

Cesium源码解读--Primitive渲染流程

Primitive渲染流程

程序调用流程

在这里插入图片描述

Primitive渲染流程

在这里插入图片描述

渲染流程简述

基本与WebGL渲染管线一致。

  • 顶点数据
  • 顶点着色器
  • 图元装配
    • 裁剪
    • 背面剔除
    • 透视除法
    • 视口变换
  • 光栅化
  • 片元着色器
  • 测试和混合阶段
    • 裁切测试
    • alpha测试
    • 深度测试
    • 模板测试
    • alpha混合
    • 抖动处理

绑定帧缓冲区

绑定帧缓冲区。

绑定顶点数据

绑定顶点数组,获取索引缓冲区。

绑定着色器程序

设置顶点着色器、片元着色器。

应用渲染设置

applyFrontFace(gl, renderState);//设置正面朝向
applyCull(gl, renderState);//应用剔除设置
applyLineWidth(gl, renderState);//应用线宽
applyPolygonOffset(gl, renderState);//应用多边形偏移
applyDepthRange(gl, renderState);//应用深度范围
applyDepthTest(gl, renderState);//应用深度测试
applyColorMask(gl, renderState);//应用颜色遮罩
applyDepthMask(gl, renderState);//应用深度遮罩/掩码
applyStencilMask(gl, renderState);//应用模板遮罩
applyStencilTest(gl, renderState);//应用模板测试
applySampleCoverage(gl, renderState);//应用样本覆盖?
applyScissorTest(gl, renderState, passState);//应用裁剪测试
applyBlending(gl, renderState, passState);//应用混合效果
applyViewport(gl, renderState, passState);//应用视口设置

绘制

分情况,调用不同的绘制方法进行绘制。

Primitive核心功能和代码

功能入口

Viewer

代码路径:widgets/Source/Viewer/Viewer.js

function Viewer(container, options) {
...
 // Cesium widget
  const cesiumWidget = new CesiumWidget(cesiumWidgetContainer, {
...
    useDefaultRenderLoop: options.useDefaultRenderLoop,
...
 });
...
}

Viewer.prototype.render

/**
 * Renders the scene.  This function is called automatically
 * unless <code>useDefaultRenderLoop</code> is set to false;
 */
Viewer.prototype.render = function () {
  this._cesiumWidget.render();
};

CesiumWidget

代码路径:packages\engine\Source\Widget\CesiumWidget.js
CesiumWidget.prototype.render

/**
 * Renders the scene.  This function is called automatically
 * unless <code>useDefaultRenderLoop</code> is set to false;
 */
CesiumWidget.prototype.render = function () {
  if (this._canRender) {
    this._scene.initializeFrame();
    const currentTime = this._clock.tick();
    this._scene.render(currentTime);
  } else {
    this._clock.tick();
  }
};

默认会设置 useDefaultRenderLoop参数为true,系统内部调用startRenderLoop方法,依次间接调用widget.render、scene.render。

useDefaultRenderLoop

useDefaultRenderLoop: {
    get: function () {
      return this._useDefaultRenderLoop;
    },
    set: function (value) {
      if (this._useDefaultRenderLoop !== value) {
        this._useDefaultRenderLoop = value;
        if (value && !this._renderLoopRunning) {
          startRenderLoop(this);
        }
      }
    },
  },

startRenderLoop

function startRenderLoop(widget) {
  widget._renderLoopRunning = true;

  let lastFrameTime = 0;
  function render(frameTime) {
    if (widget.isDestroyed()) {
      return;
    }

    if (widget._useDefaultRenderLoop) {
      try {
        const targetFrameRate = widget._targetFrameRate;
        if (!defined(targetFrameRate)) {
          widget.resize();
          widget.render();
          requestAnimationFrame(render);
        } else {
          const interval = 1000.0 / targetFrameRate;
          const delta = frameTime - lastFrameTime;

          if (delta > interval) {
            widget.resize();
            widget.render();
            lastFrameTime = frameTime - (delta % interval);
          }
          requestAnimationFrame(render);
        }
      } catch (error) {
        widget._useDefaultRenderLoop = false;
        widget._renderLoopRunning = false;
        if (widget._showRenderLoopErrors) {
          const title =
            "An error occurred while rendering.  Rendering has stopped.";
          widget.showErrorPanel(title, undefined, error);
        }
      }
    } else {
      widget._renderLoopRunning = false;
    }
  }
  requestAnimationFrame(render);
}

Scene

代码路径:packages\engine\Source\Scene\Scene.js
Scene.prototype.render

/**
 * Update and render the scene. It is usually not necessary to call this function
 * directly because {@link CesiumWidget} will do it automatically.
 * @param {JulianDate} [time] The simulation time at which to render.
 */
Scene.prototype.render = function (time) {
  /**
   *
   * Pre passes update. Execute any pass invariant code that should run before the passes here.
   *
   */
  this._preUpdate.raiseEvent(this, time);

  const frameState = this._frameState;
  frameState.newFrame = false;

  if (!defined(time)) {
    time = JulianDate.now();
  }

  const cameraChanged = this._view.checkForCameraUpdates(this);
  if (cameraChanged) {
    this._globeHeightDirty = true;
  }

  // Determine if should render a new frame in request render mode
  let shouldRender =
    !this.requestRenderMode ||
    this._renderRequested ||
    cameraChanged ||
    this._logDepthBufferDirty ||
    this._hdrDirty ||
    this.mode === SceneMode.MORPHING;
  if (
    !shouldRender &&
    defined(this.maximumRenderTimeChange) &&
    defined(this._lastRenderTime)
  ) {
    const difference = Math.abs(
      JulianDate.secondsDifference(this._lastRenderTime, time),
    );
    shouldRender = shouldRender || difference > this.maximumRenderTimeChange;
  }

  if (shouldRender) {
    this._lastRenderTime = JulianDate.clone(time, this._lastRenderTime);
    this._renderRequested = false;
    this._logDepthBufferDirty = false;
    this._hdrDirty = false;

    const frameNumber = CesiumMath.incrementWrap(
      frameState.frameNumber,
      15000000.0,
      1.0,
    );
    updateFrameNumber(this, frameNumber, time);
    frameState.newFrame = true;
  }

  tryAndCatchError(this, prePassesUpdate);

  /**
   * Passes update. Add any passes here
   */
  if (this.primitives.show) {
    tryAndCatchError(this, updateMostDetailedRayPicks);
    tryAndCatchError(this, updatePreloadPass);
    tryAndCatchError(this, updatePreloadFlightPass);
    if (!shouldRender) {
      tryAndCatchError(this, updateRequestRenderModeDeferCheckPass);
    }
  }

  this._postUpdate.raiseEvent(this, time);

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

  /**
   * Post passes update. Execute any pass invariant code that should run after the passes here.
   */
  updateDebugShowFramesPerSecond(this, shouldRender);
  tryAndCatchError(this, postPassesUpdate);

  // Often used to trigger events (so don't want in trycatch) that the user
  // might be subscribed to. Things like the tile load events, promises, etc.
  // We don't want those events to resolve during the render loop because the events might add new primitives
  callAfterRenderFunctions(this);

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

render

/**
 * Render the scene
 *
 * @param {Scene} scene
 * @private
 */
function render(scene) {

  // 获取场景上下文
  const context = scene.context;
  const { uniformState } = context;

  // 获取默认视图
  const view = scene._defaultView;
  scene._view = view;

  // 更新帧状态
  scene.updateFrameState();
  frameState.passes.render = true;
  frameState.passes.postProcess = scene.postProcessStages.hasSelected;
  frameState.tilesetPassState = renderTilesetPassState;

  // 设置背景颜色
  let backgroundColor = defaultValue(scene.backgroundColor, Color.BLACK);
  if (scene._hdr) {
    backgroundColor = Color.clone(backgroundColor, scratchBackgroundColor);
    backgroundColor.red = Math.pow(backgroundColor.red, scene.gamma);
    backgroundColor.green = Math.pow(backgroundColor.green, scene.gamma);
    backgroundColor.blue = Math.pow(backgroundColor.blue, scene.gamma);
  }
  frameState.backgroundColor = backgroundColor;

  // 设置大气环境
  frameState.atmosphere = scene.atmosphere;
  scene.fog.update(frameState);

  // 更新统一状态
  uniformState.update(frameState);

  // 处理阴影贴图
  const shadowMap = scene.shadowMap;
  if (defined(shadowMap) && shadowMap.enabled) {
    if (!defined(scene.light) || scene.light instanceof SunLight) {
      // 将太阳方向取反,使其从太阳方向照射而非朝向太阳
      // Negate the sun direction so that it is from the Sun, not to the Sun
      Cartesian3.negate(
        uniformState.sunDirectionWC,
        scene._shadowMapCamera.direction,
      );
    } else {
      Cartesian3.clone(scene.light.direction, scene._shadowMapCamera.direction);
    }
    frameState.shadowMaps.push(shadowMap);
  }

  // 清空计算命令列表和覆盖命令列表
  scene._computeCommandList.length = 0;
  scene._overlayCommandList.length = 0;

  // 设置视口
  const viewport = view.viewport;
  viewport.x = 0;
  viewport.y = 0;
  viewport.width = context.drawingBufferWidth;
  viewport.height = context.drawingBufferHeight;

  // 设置通道状态
  const passState = view.passState;
  passState.framebuffer = undefined;
  passState.blendingEnabled = undefined;
  passState.scissorTest = undefined;
  passState.viewport = BoundingRectangle.clone(viewport, passState.viewport);

  // 处理地球仪
  if (defined(scene.globe)) {
    scene.globe.beginFrame(frameState);
  }

  // 更新环境并执行命令
  scene.updateEnvironment();
  scene.updateAndExecuteCommands(passState, backgroundColor);
  scene.resolveFramebuffers(passState);

  // 重置通道状态的帧缓冲
  passState.framebuffer = undefined;
  executeOverlayCommands(scene, passState);

  // 结束地球仪帧
  if (defined(scene.globe)) {
    scene.globe.endFrame(frameState);

    // 如果地球仪瓦片未加载完成,请求重新渲染
    if (!scene.globe.tilesLoaded) {
      scene._renderRequested = true;
    }
  }

  // 结束帧
  context.endFrame();
}

更新和渲染Primitives

updateAndRenderPrimitives
更新并渲染场景中的图元。

/**
 * 更新并渲染场景中的原始对象。
 *
 * @param {Object} scene - 场景对象。
 */
function updateAndRenderPrimitives(scene) {
  // 获取当前帧的状态
  const frameState = scene._frameState;

  // 更新地面原始对象
  scene._groundPrimitives.update(frameState);
  // 更新其他原始对象
  scene._primitives.update(frameState);

  // 更新调试视锥体平面
  updateDebugFrustumPlanes(scene);
  // 更新阴影贴图
  updateShadowMaps(scene);

  // 如果存在地球模型
  if (scene._globe) {
    // 渲染地球模型
    scene._globe.render(frameState);
  }
}

生成并执行指令

指令组装

代码路径:packages\engine\Source\Scene\Primitive.js
createCommands
根据数据资源、绘制要求,创建绘制命令。

/**
 * 创建绘制命令
 *
 * @param primitive 原始对象
 * @param appearance 外观对象
 * @param material 材料对象
 * @param translucent 是否透明
 * @param twoPasses 是否启用两个通道
 * @param colorCommands 颜色命令数组
 * @param pickCommands 拾取命令数组
 * @param frameState 帧状态对象
 */
function createCommands(
  primitive,
  appearance,
  material,
  translucent,
  twoPasses,
  colorCommands,
  pickCommands,
  frameState,
) {
  // 获取统一变量
  const uniforms = getUniforms(primitive, appearance, material, frameState);

  // 初始化深度失败统一变量
  let depthFailUniforms;
  // 如果定义了深度失败的外观
  if (defined(primitive._depthFailAppearance)) {
    // 获取深度失败的统一变量
    depthFailUniforms = getUniforms(
      primitive,
      primitive._depthFailAppearance,
      primitive._depthFailAppearance.material,
      frameState,
    );
  }

  // 根据是否透明,设置绘制通道
  const pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;

  // 计算命令的倍数
  let multiplier = twoPasses ? 2 : 1;
  // 如果定义了深度失败的外观,则命令倍数翻倍
  multiplier *= defined(primitive._depthFailAppearance) ? 2 : 1;
  // 设置颜色命令的长度
  colorCommands.length = primitive._va.length * multiplier;

  // 获取颜色命令的长度
  const length = colorCommands.length;
  // 初始化顶点数组索引
  let vaIndex = 0;
  // 遍历颜色命令
  for (let i = 0; i < length; ++i) {
    let colorCommand;

    // 如果启用两个通道
    if (twoPasses) {
      colorCommand = colorCommands[i];
      // 如果颜色命令未定义,则创建新的绘制命令
      if (!defined(colorCommand)) {
        colorCommand = colorCommands[i] = new DrawCommand({
          owner: primitive,
          primitiveType: primitive._primitiveType,
        });
      }
      // 设置顶点数组
      colorCommand.vertexArray = primitive._va[vaIndex];
      // 设置背面渲染状态
      colorCommand.renderState = primitive._backFaceRS;
      // 设置着色器程序
      colorCommand.shaderProgram = primitive._sp;
      // 设置统一变量映射
      colorCommand.uniformMap = uniforms;
      // 设置绘制通道
      colorCommand.pass = pass;

      // 递增索引
      ++i;
    }

    // 获取当前颜色命令
    colorCommand = colorCommands[i];
    // 如果颜色命令未定义,则创建新的绘制命令
    if (!defined(colorCommand)) {
      colorCommand = colorCommands[i] = new DrawCommand({
        owner: primitive,
        primitiveType: primitive._primitiveType,
      });
    }
    // 设置顶点数组
    colorCommand.vertexArray = primitive._va[vaIndex];
    // 设置正面渲染状态
    colorCommand.renderState = primitive._frontFaceRS;
    // 设置着色器程序
    colorCommand.shaderProgram = primitive._sp;
    // 设置统一变量映射
    colorCommand.uniformMap = uniforms;
    // 设置绘制通道
    colorCommand.pass = pass;

    // 如果定义了深度失败的外观
    if (defined(primitive._depthFailAppearance)) {
      // 如果启用两个通道
      if (twoPasses) {
        ++i;

        // 获取当前颜色命令
        colorCommand = colorCommands[i];
        // 如果颜色命令未定义,则创建新的绘制命令
        if (!defined(colorCommand)) {
          colorCommand = colorCommands[i] = new DrawCommand({
            owner: primitive,
            primitiveType: primitive._primitiveType,
          });
        }
        // 设置顶点数组
        colorCommand.vertexArray = primitive._va[vaIndex];
        // 设置深度失败背面渲染状态
        colorCommand.renderState = primitive._backFaceDepthFailRS;
        // 设置深度失败的着色器程序
        colorCommand.shaderProgram = primitive._spDepthFail;
        // 设置深度失败的统一变量映射
        colorCommand.uniformMap = depthFailUniforms;
        // 设置绘制通道
        colorCommand.pass = pass;
      }

      ++i;

      // 获取当前颜色命令
      colorCommand = colorCommands[i];
      // 如果颜色命令未定义,则创建新的绘制命令
      if (!defined(colorCommand)) {
        colorCommand = colorCommands[i] = new DrawCommand({
          owner: primitive,
          primitiveType: primitive._primitiveType,
        });
      }
      // 设置顶点数组
      colorCommand.vertexArray = primitive._va[vaIndex];
      // 设置深度失败正面渲染状态
      colorCommand.renderState = primitive._frontFaceDepthFailRS;
      // 设置深度失败的着色器程序
      colorCommand.shaderProgram = primitive._spDepthFail;
      // 设置深度失败的统一变量映射
      colorCommand.uniformMap = depthFailUniforms;
      // 设置绘制通道
      colorCommand.pass = pass;
    }

    // 递增顶点数组索引
    ++vaIndex;
  }
}

updateAndQueueCommands
对绘制命令进行排序、并推入帧状态对象的指令列表上。

/**
 * 更新并排队命令。
 *
 * @param primitive 原始几何对象。
 * @param frameState 帧状态对象。
 * @param colorCommands 颜色命令数组。
 * @param pickCommands 拾取命令数组。
 * @param modelMatrix 模型矩阵。
 * @param cull 是否剔除背面。
 * @param debugShowBoundingVolume 是否在调试模式下显示包围体。
 * @param twoPasses 是否使用两次通道渲染。
 * @throws 如果在2D模式下使用了非单位矩阵的模型矩阵,则抛出异常。
 */
function updateAndQueueCommands(
  primitive,
  frameState,
  colorCommands,
  pickCommands,
  modelMatrix,
  cull,
  debugShowBoundingVolume,
  twoPasses,
) {
  // 调试模式判断
  //>>includeStart('debug', pragmas.debug);
  if (
    frameState.mode !== SceneMode.SCENE3D &&
    !Matrix4.equals(modelMatrix, Matrix4.IDENTITY)
  ) {
    throw new DeveloperError(
      "Primitive.modelMatrix is only supported in 3D mode.",
    );
  }
  //>>includeEnd('debug');

  // 更新包围体
  Primitive._updateBoundingVolumes(primitive, frameState, modelMatrix);

  // 根据场景模式获取包围球
  let boundingSpheres;
  if (frameState.mode === SceneMode.SCENE3D) {
    boundingSpheres = primitive._boundingSphereWC;
  } else if (frameState.mode === SceneMode.COLUMBUS_VIEW) {
    boundingSpheres = primitive._boundingSphereCV;
  } else if (
    frameState.mode === SceneMode.SCENE2D &&
    defined(primitive._boundingSphere2D)
  ) {
    boundingSpheres = primitive._boundingSphere2D;
  } else if (defined(primitive._boundingSphereMorph)) {
    boundingSpheres = primitive._boundingSphereMorph;
  }

  // 获取命令列表和通道
  const commandList = frameState.commandList;
  const passes = frameState.passes;
  // 如果需要渲染或拾取,则继续处理
  if (passes.render || passes.pick) {
    // 获取相关参数
    const allowPicking = primitive.allowPicking;
    const castShadows = ShadowMode.castShadows(primitive.shadows);
    const receiveShadows = ShadowMode.receiveShadows(primitive.shadows);
    const colorLength = colorCommands.length;

    // 计算因子
    let factor = twoPasses ? 2 : 1;
    factor *= defined(primitive._depthFailAppearance) ? 2 : 1;

    // 遍历颜色命令并更新
    for (let j = 0; j < colorLength; ++j) {
      const sphereIndex = Math.floor(j / factor);
      const colorCommand = colorCommands[j];
      // 更新命令属性
      colorCommand.modelMatrix = modelMatrix;
      colorCommand.boundingVolume = boundingSpheres[sphereIndex];
      colorCommand.cull = cull;
      colorCommand.debugShowBoundingVolume = debugShowBoundingVolume;
      colorCommand.castShadows = castShadows;
      colorCommand.receiveShadows = receiveShadows;

      // 根据是否允许拾取设置拾取ID
      if (allowPicking) {
        colorCommand.pickId = "v_pickColor";
      } else {
        colorCommand.pickId = undefined;
      }

      // 将命令添加到命令列表
      commandList.push(colorCommand);
    }
  }
}

指令执行

代码路径
packages\engine\Source\Scene\Scene.js
executeCommands
负责执行一系列的命令,会根据 Pass 的优先顺序依次更新UniformState,然后下发给 executeCommand() 。

/**
 * Execute the draw commands for all the render passes.
 *
 * @param {Scene} scene
 * @param {PassState} passState
 *
 * @private
 */
/**
 * 执行场景中的命令
 *
 * @param scene 场景
 * @param passState 通道状态
 */
function executeCommands(scene, passState) {
  const { camera, context, frameState } = scene;
  const { uniformState } = context;

  uniformState.updateCamera(camera);

  const frustum = createWorkingFrustum(camera);
  frustum.near = camera.frustum.near;
  frustum.far = camera.frustum.far;

  const passes = frameState.passes;
  const picking = passes.pick || passes.pickVoxel;
  // Ideally, we would render the sky box and atmosphere last for
  // early-z, but we would have to draw it in each frustum.
  // Do not render environment primitives during a pick pass since they do not generate picking commands.
  if (!picking) {
    renderEnvironment(scene, passState);
  }
  ...
for (let j = 0; j < commandCount; ++j) {
      executeCommand(commands[j], scene, passState);
    }
    ...
}

executeCommand
执行指定的命令。

/**
 * 执行指定的命令。
 *
 * @param command 命令对象。
 * @param scene 场景对象。
 * @param passState 渲染通道的状态对象。
 * @param debugFramebuffer 调试帧缓冲区。
 */
function executeCommand(command, scene, passState, debugFramebuffer) {
  // 获取帧状态和上下文
  const frameState = scene._frameState;
  const context = scene._context;

  // 检查是否启用了调试命令过滤器,并且命令是否存在
  if (defined(scene.debugCommandFilter) && !scene.debugCommandFilter(command)) {
    return;
  }

  // 如果命令是清空命令,则执行清空操作并返回
  if (command instanceof ClearCommand) {
    command.execute(context, passState);
    return;
  }

  // 如果命令设置了显示包围体,并且包围体已定义,则调用显示包围体的函数
  if (command.debugShowBoundingVolume && defined(command.boundingVolume)) {
    // 调用函数显示命令的包围体
    debugShowBoundingVolume(command, scene, passState, debugFramebuffer);
  }

  // 如果帧状态启用了对数深度,并且命令定义了对数深度派生命令,则替换命令为对数深度命令
  if (frameState.useLogDepth && defined(command.derivedCommands.logDepth)) {
    command = command.derivedCommands.logDepth.command;
  }

  // 获取帧状态的渲染通道
  const passes = frameState.passes;
  // 如果当前不是拾取通道、拾取体素通道或深度通道,并且场景启用了HDR,并且命令定义了HDR派生命令
  if (
    !passes.pick &&
    !passes.pickVoxel &&
    !passes.depth &&
    scene._hdr &&
    defined(command.derivedCommands) &&
    defined(command.derivedCommands.hdr)
  ) {
    // 替换命令为HDR命令
    command = command.derivedCommands.hdr.command;
  }

  // 如果当前是拾取通道或深度通道
  if (passes.pick || passes.depth) {
    // 如果当前是拾取通道但不是深度通道
    if (passes.pick && !passes.depth) {
      // 如果帧状态有拾取元数据,并且命令定义了拾取元数据派生命令
      if (
        frameState.pickingMetadata &&
        defined(command.derivedCommands.pickingMetadata)
      ) {
        // 替换命令为拾取元数据命令并执行
        command = command.derivedCommands.pickingMetadata.pickMetadataCommand;
        command.execute(context, passState);
        return;
      }
      // 如果帧状态没有拾取元数据,并且命令定义了拾取派生命令
      if (
        !frameState.pickingMetadata &&
        defined(command.derivedCommands.picking)
      ) {
        // 替换命令为拾取命令并执行
        command = command.derivedCommands.picking.pickCommand;
        command.execute(context, passState);
        return;
      }
    } else if (defined(command.derivedCommands.depth)) {
      // 如果命令定义了深度派生命令
      command = command.derivedCommands.depth.depthOnlyCommand;
      command.execute(context, passState);
      return;
    }
  }

  // 如果场景启用了调试显示命令或调试显示视锥体
  if (scene.debugShowCommands || scene.debugShowFrustums) {
    // 执行调试显示视锥体命令
    scene._debugInspector.executeDebugShowFrustumsCommand(
      scene,
      command,
      passState,
    );
    return;
  }

  // 如果帧状态的阴影状态启用了光源阴影,命令接收阴影,并且命令定义了阴影派生命令
  if (
    frameState.shadowState.lightShadowsEnabled &&
    command.receiveShadows &&
    defined(command.derivedCommands.shadows)
  ) {
    // 如果命令接收阴影,则执行派生阴影命令
    // 一些命令(如OIT派生命令)本身没有派生阴影命令,而是内置了阴影处理
    // 在这种情况下,将正常执行下面的命令
    // 如果命令接收阴影,则执行派生阴影命令
    command.derivedCommands.shadows.receiveCommand.execute(context, passState);
  } else {
    // 否则,直接执行命令
    command.execute(context, passState);
  }
}

执行命令
DrawCommand.prototype.execute
代码路径:packages\engine\Source\Renderer\DrawCommand.js

/**
 * Executes the draw command.
 *
 * @param {Context} context The renderer context in which to draw.
 * @param {PassState} [passState] The state for the current render pass.
 */
DrawCommand.prototype.execute = function (context, passState) {
  context.draw(this, passState);
};

Context.prototype.draw
packages\engine\Source\Renderer\Context.js

Context.prototype.draw = function (
  drawCommand,
  passState,
  shaderProgram,
  uniformMap,
) {
  //>>includeStart('debug', pragmas.debug);
  Check.defined("drawCommand", drawCommand);
  Check.defined("drawCommand.shaderProgram", drawCommand._shaderProgram);
  //>>includeEnd('debug');

  passState = defaultValue(passState, this._defaultPassState);
  // The command's framebuffer takes precedence over the pass' framebuffer, e.g., for off-screen rendering.
  const framebuffer = defaultValue(
    drawCommand._framebuffer,
    passState.framebuffer,
  );
  const renderState = defaultValue(
    drawCommand._renderState,
    this._defaultRenderState,
  );
  shaderProgram = defaultValue(shaderProgram, drawCommand._shaderProgram);
  uniformMap = defaultValue(uniformMap, drawCommand._uniformMap);

  beginDraw(this, framebuffer, passState, shaderProgram, renderState);
  continueDraw(this, drawCommand, shaderProgram, uniformMap);
};

beginDraw

/**
 * 开始绘制操作
 *
 * @param context WebGL 渲染上下文
 * @param framebuffer 帧缓冲区对象
 * @param passState 渲染通道状态
 * @param shaderProgram 着色器程序
 * @param renderState 渲染状态
 * @throws 如果帧缓冲区没有深度附件但启用了深度测试,则抛出 DeveloperError
 */
function beginDraw(
  context,
  framebuffer,
  passState,
  shaderProgram,
  renderState,
) {
  // 开始调试包含
  //>>includeStart('debug', pragmas.debug);
  // 如果已定义帧缓冲区并且启用了深度测试
  if (defined(framebuffer) && renderState.depthTest) {
    // 如果深度测试已启用但帧缓冲区没有深度附件
    if (renderState.depthTest.enabled && !framebuffer.hasDepthAttachment) {
      // 抛出开发者错误
      throw new DeveloperError(
        "The depth test can not be enabled (drawCommand.renderState.depthTest.enabled) because the framebuffer (drawCommand.framebuffer) does not have a depth or depth-stencil renderbuffer.",
      );
    }
  }
  // 结束调试包含
  //>>includeEnd('debug');

  // 绑定帧缓冲区
  bindFramebuffer(context, framebuffer);
  // 应用渲染状态
  applyRenderState(context, renderState, passState, false);
  // 绑定着色器程序
  shaderProgram._bind();
  // 更新最大帧纹理单元索引
  context._maxFrameTextureUnitIndex = Math.max(
    context._maxFrameTextureUnitIndex,
    shaderProgram.maximumTextureUnitIndex,
  );
}

continueDraw

/**
 * 继续绘制函数
 *
 * @param context WebGL 渲染上下文
 * @param drawCommand 绘制命令对象,包含绘制所需的所有信息
 * @param shaderProgram 着色器程序对象
 * @param uniformMap 统一变量映射对象
 */
function continueDraw(context, drawCommand, shaderProgram, uniformMap) {
  // 获取绘制命令的原始类型
  const primitiveType = drawCommand._primitiveType;
  // 获取顶点数组
  const va = drawCommand._vertexArray;
  // 获取偏移量
  let offset = drawCommand._offset;
  // 获取绘制数量
  let count = drawCommand._count;
  // 获取实例数量
  const instanceCount = drawCommand.instanceCount;

  //>>includeStart('debug', pragmas.debug);
  // 验证原始类型是否有效
  if (!PrimitiveType.validate(primitiveType)) {
    throw new DeveloperError(
      "drawCommand.primitiveType is required and must be valid.",
    );
  }

  // 检查顶点数组是否已定义
  Check.defined("drawCommand.vertexArray", va);
  // 检查偏移量是否大于或等于0
  Check.typeOf.number.greaterThanOrEquals("drawCommand.offset", offset, 0);
  // 如果定义了绘制数量,则检查绘制数量是否大于或等于0
  if (defined(count)) {
    Check.typeOf.number.greaterThanOrEquals("drawCommand.count", count, 0);
  }
  // 检查实例数量是否大于或等于0
  Check.typeOf.number.greaterThanOrEquals(
    "drawCommand.instanceCount",
    instanceCount,
    0,
  );
  // 如果实例数量大于0且不支持实例化数组扩展,则抛出异常
  if (instanceCount > 0 && !context.instancedArrays) {
    throw new DeveloperError("Instanced arrays extension is not supported");
  }
  //>>includeEnd('debug');

  // 设置模型矩阵为默认值或绘制命令中的模型矩阵
  context._us.model = defaultValue(drawCommand._modelMatrix, Matrix4.IDENTITY);
  // 设置着色器的统一变量
  shaderProgram._setUniforms(
    uniformMap,
    context._us,
    context.validateShaderProgram,
  );

  // 绑定顶点数组
  va._bind();
  // 获取索引缓冲区
  const indexBuffer = va.indexBuffer;

  // 如果定义了索引缓冲区
  if (defined(indexBuffer)) {
    // 将偏移量从顶点数转换为字节数
    offset = offset * indexBuffer.bytesPerIndex; // offset in vertices to offset in bytes
    // 如果定义了绘制数量,则计算绘制数量,否则使用索引缓冲区的索引数量
    if (defined(count)) {
      count = Math.min(count, indexBuffer.numberOfIndices);
    } else {
      count = indexBuffer.numberOfIndices;
    }
    // 如果实例数量为0,则绘制元素
    if (instanceCount === 0) {
      context._gl.drawElements(
        primitiveType,
        count,
        indexBuffer.indexDatatype,
        offset,
      );
    } else {
      // 否则绘制实例化元素
      context.glDrawElementsInstanced(
        primitiveType,
        count,
        indexBuffer.indexDatatype,
        offset,
        instanceCount,
      );
    }
  } else {
    // 如果没有定义索引缓冲区
    // 如果定义了绘制数量,则计算绘制数量,否则使用顶点数组中的顶点数量
    if (defined(count)) {
      count = Math.min(count, va.numberOfVertices);
    } else {
      count = va.numberOfVertices;
    }
    // 如果实例数量为0,则绘制数组
    if (instanceCount === 0) {
      context._gl.drawArrays(primitiveType, offset, count);
    } else {
      // 否则绘制实例化数组
      context.glDrawArraysInstanced(
        primitiveType,
        offset,
        count,
        instanceCount,
      );
    }
  }

  // 解绑顶点数组
  va._unBind();
}

Cesium渲染相关概念

通道与指令

按通道更新renderState
指令对象转移
筛选可见集
按通道排序指令

Pass(通道)

作用:一帧是由多个通道顺序绘制构成的。在每一帧即将开始绘制前,对所有已经收集好的指令根据通道进行排序,实现顺序绘制。
代码路径:packages\engine\Source\Renderer\Pass.js

const Pass = {
  // If you add/modify/remove Pass constants, also change the automatic GLSL constants
  // that start with 'czm_pass'
  //
  // Commands are executed in order by pass up to the translucent pass.
  // Translucent geometry needs special handling (sorting/OIT). The compute pass
  // is executed first and the overlay pass is executed last. Both are not sorted
  // by frustum.
  ENVIRONMENT: 0,
  COMPUTE: 1,
  GLOBE: 2,
  TERRAIN_CLASSIFICATION: 3,
  CESIUM_3D_TILE: 4,
  CESIUM_3D_TILE_CLASSIFICATION: 5,
  CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW: 6,
  OPAQUE: 7,
  TRANSLUCENT: 8,
  VOXELS: 9,
  OVERLAY: 10,
  NUMBER_OF_PASSES: 11,
};

Command(指令)

作用: 保存WebGL 的绘制过程(行为),并最终传递给Context对象。
绘图指令
packages\engine\Source\Renderer\DrawCommand.js
清屏指令
packages\engine\Source\Renderer\ClearCommand.js
计算指令
packages\engine\Source\Renderer\ComputeCommand.js

WebGL基础

WebGL渲染流程图

在这里插入图片描述

渲染流程描述:

  • 提供顶点数据
    JavaScript提供顶点数据。
  • 顶点数据处理
    顶点计算,涉及MVP转换(模型矩阵、视图矩阵、投影矩阵转换)。
  • 顶点绘制
    顶点着色器,绘制顶点位置。
  • 图形组装
    将顶点装配成三角形。
  • 栅格化
    用像素填充三角形。
  • 着色
    片元着色器,用颜色填充像素。
  • 显示
    显示到画布(canvas)。

WebGL程序流程

WebGL程序创建总体流程:

  • 创建顶点着色器对象
  • 创建片元着色器对象
  • 创建program对象
    createProgram
  • 将顶点着色器和片元着色器添加到程序中
    attachShader
  • 将程序链接到webgl上下文
    linkProgram
  • 使用创建的program对象
    useProgram

着色器对象的创建流程:

  • 创建着色器对象
    createShader
  • 将着色器源码送入对象
    shaderSource
  • 编译着色器
    compileShader

数据传入

  • 按作用域范围区分
    – attribute:vs内部使用的变量。例如顶点坐标、顶点颜色、法线,纹理坐标等等。
    – uniform:vs、fs都可以使用的变量。例如变换矩阵,材质,光照参数和颜色等信息。
  • 按数据内容区分
    – 顶点数据
    — 顶点坐标
    — 顶点索引
    – 其他数据(纹理、深度、颜色等等)

绘制:
drawArrays
drawElements
drawArraysInstanced
drawElementsInstanced

参考资料:
https://zhuanlan.zhihu.com/p/496497442
https://zhuanlan.zhihu.com/p/514634958

;