Skip to content

路线 A|Cocos 引擎、渲染与性能 ​

A2|Advanced Rendering:从 Node 状态到 GPU ​

适用基础: A1、Stage04~Stage05 目标: 能用帧调试和 GPU 证据解释可见性、材质、批次、带宽和 Shader 成本。


一、问题场景:DrawCall 不高但画面仍卡 ​

可能瓶颈包括过度绘制、填充率、纹理带宽、同步读回、Shader 分支、透明排序和 CPU 等待 GPU。DrawCall 只是一个指标,不能代表整帧成本。

二、一帧的渲染数据流 ​

text
Node/Component Transform
→ Camera 可见性
→ RenderData / Assembler
→ Material / Pass
→ Render Queue
→ Vertex/Index Buffer
→ GPU 状态与 DrawCall
→ FrameBuffer
→ Present

业务属性为什么会变成 GPU 工作 ​

位置变化通常先影响 Transform 和脏标记,颜色/尺寸/材质变化可能进一步影响 RenderData、材质实例或批次。Renderer 读取的是已经整理好的中间数据,而不是直接读取脚本字段;因此“只改一个属性”的成本取决于它触发了哪条脏路径。DrawCall 只是提交次数,不能代表 RenderData 重建、状态切换、片元数量和 GPU 等待。

三、坐标、相机与可见性 ​

Local、World、View、Clip、Screen 是不同空间。Camera 的视锥裁剪只能减少不可见对象,不能自动消除透明像素的填充成本。多相机还会重复绘制重叠内容。

可见性与填充率的区别 ​

视锥裁剪减少的是不可见对象提交;透明 Sprite 即使只有一个 DrawCall,也可能覆盖大量屏幕像素并触发多次片元混合。多相机、RenderTarget 和后处理会让同一像素被多次写入。看到“对象少、DrawCall 低”时仍然卡顿,应转向屏幕覆盖率、Shader 采样、RenderTarget 尺寸和带宽。

四、RenderData 与 Assembler ​

Sprite/Label 等组件把业务属性转换为顶点、UV、颜色和索引。脏标记决定何时重新生成数据。频繁改变尺寸、文本、材质和顶点属性可能让 CPU 每帧重建。

错误方案:每帧销毁并重建 UI。改进:保持节点结构稳定,只更新必要属性,并测量 dirty 次数和提交成本。

顶点与索引缓冲 ​

顶点缓冲保存位置、UV、颜色、法线等属性,索引缓冲描述顶点如何组成三角形。Assembler 的价值是把不同组件的高级语义整理成 GPU 能消费的连续数据。动态 UI 常频繁重写缓冲,静态几何则更适合复用;优化时要区分“重新计算数据”和“把数据上传 GPU”两类 CPU/带宽成本。

五、Material、Effect、Technique、Pass ​

text
Material:实例参数
Effect/Shader:程序与资源声明
Technique:渲染方案
Pass:一次具体状态和程序组合

同一纹理不代表可以合批;材质实例、Blend、Stencil、Shader 变体、纹理和排序状态都可能打断批次。合批优化必须以实际帧数据为证据。

Uniform、UBO 与状态切换 ​

Uniform 是每次绘制或材质所需的常量参数;UBO 把一组参数放入统一缓冲,便于复用和批量绑定,但具体支持取决于图形 API 和引擎后端。即使几何和纹理相同,只要每个对象需要不同、无法实例化的参数,也可能产生额外提交。减少状态切换的关键是让相邻可见对象共享兼容的 Pipeline、材质和资源,而不是单纯压缩节点数量。

六、纹理与 GPU Memory ​

解码尺寸、像素格式、Mip、压缩格式和采样方式共同决定显存和带宽。文件大小小不等于显存小,压缩纹理也有平台支持边界。

纹理内存要从解码尺寸和 GPU 格式计算,而不能只看磁盘文件大小。未压缩 RGBA8 的粗略占用是 width × height × 4,Mip、双缓冲、RenderTarget 和驱动对齐还会增加实际占用;压缩格式节省带宽和显存,但受设备格式支持、采样质量和上传路径限制。Creator 导入设置、平台压缩和运行时上传必须分开讨论。

七、Blend、Depth、Stencil 与透明排序 ​

透明物体通常需要排序和混合,深度写入策略不同于不透明物体。Stencil 能做遮罩,但多次 Pass 和大范围模板操作会增加成本。先画出状态需求,再选择最小状态组合。

不透明物体通常可以使用深度测试和深度写入,先绘制近处还能减少后续片元;透明物体若写深度容易错误遮挡后续透明层,因此常关闭深度写入并从远到近排序。UI 虽然多在二维空间,Mask、嵌套裁剪和自定义材质仍会引入 Stencil/Blend 状态边界。

八、后处理与离屏渲染 ​

text
主场景 → RenderTarget/FrameBuffer → 全屏 Pass → 屏幕

后处理常受全屏像素数和带宽限制。模糊通常需要多次采样或降分辨率;移动端应测量不同 RenderTarget 尺寸,而不是只看效果截图。

九、Shader 与 PBR 基础 ​

Vertex Shader 负责顶点变换和可插值数据,Fragment Shader 负责逐像素颜色。PBR 需要法线、粗糙度、金属度、光照和颜色空间一致,任何一个输入错误都可能被误判为 Shader Bug。

理解 Shader 时应从数据依赖出发:先确认顶点属性和坐标变换,再确认插值变量、纹理采样、颜色空间、光照输入和 RenderState。UV 错误、法线空间错误、纹理格式错误和混合状态错误可能产生相似画面,必须按输入层次逐层排除,而不是直接修改最终颜色公式。

GPU Instancing ​

Instancing 用一次提交绘制多份相同几何,通过实例缓冲提供 Transform、颜色等差异。它适合大量共享 Mesh/Material 的对象,但不自动解决透明排序、不同纹理、不同 Shader 变体和每实例数据过大的问题。实例数量太少时,准备实例缓冲的 CPU 成本也可能超过收益。

PBR 输入为什么必须一致 ​

PBR 并不是一个“更真实的 Shader 名称”,而是一组能量和材质约定。Normal/Tangent 的空间、线性/伽马颜色、金属度/粗糙度通道、环境光和曝光必须一致。错误的颜色空间会让纹理数值失真,错误切线会让法线贴图随视角翻转,缺少环境光则会让金属区域异常发黑。

十、帧调试工具的能力边界 ​

RenderDoc 或平台 Frame Debugger 能看到的内容取决于后端、构建模式和设备支持。Creator Profiler 更适合观察帧时间、DrawCall 等引擎级指标,不一定提供完整 GPU 分解。阅读一帧时应回答:

  1. 当前 DrawCall 使用哪个 Pipeline/Shader/纹理?
  2. 顶点和索引数量是多少?
  3. RenderTarget 尺寸和格式是什么?
  4. 哪个 Pass 造成大面积 overdraw?
  5. 删除或合并一次提交后,CPU 提交、GPU 时间和画面正确性如何变化?

十一、综合案例:UI 打开卡顿的因果链 ​

UI 打开卡顿可能来自脚本创建、Layout/Widget 遍历、Label 字形缓存、纹理上传、Canvas 重绘、透明 overdraw 或后处理。正确的分析顺序是先判断 CPU/GPU 主导,再把首帧拆成这些阶段,最后针对最大项做修改。工具无法提供某项 GPU 数据时,用分辨率对照、关闭某 Pass、替换纯色 Shader 等方式隔离因果,而不是把估算值当成测量值。

十二、练习与答案 ​

1. 两个 Sprite 同图集仍不能合批,查什么? ​

查材质实例、Blend/Stencil、Shader Pass、纹理采样、排序和动态 Atlas 状态;图集只是必要条件之一。

2. DrawCall 降低但 GPU 时间上升,为什么? ​

可能合批后单次提交覆盖更大区域,透明 overdraw、Shader 复杂度或带宽成为新瓶颈。需要 GPU 时间和像素相关指标共同判断。

3. 为什么要用真实设备验证? ​

GPU 架构、带宽、压缩格式和驱动行为差异很大,编辑器或桌面 GPU 不能代表移动端。

十三、阶段输出 ​

完成 A2 后应提交一份 Frame Report:帧截图、关键 DrawCall、RenderTarget、Shader、GPU 时间、修改前后数据和设备信息。