文章目录
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