2、应用阶段:CPU与GPU的分工、Draw Call的概念、场景数据准备
好,咱们正式开始聊渲染管线的第一站——应用阶段。
很多人一上来就盯着着色器、盯着GPU怎么画三角形。但我得说一句:应用阶段才是整个管线的起点,也是CPU和GPU第一次握手的地方。你想想看,如果这一阶段的数据没准备好,后面GPU再强也是白搭。
2.1 CPU与GPU的分工:谁该干什么?
先问一个问题:渲染这件事,CPU和GPU到底谁说了算?
答案是:CPU负责“准备”,GPU负责“执行”。
CPU干的是管理、调度、数据准备这些活儿。比如:
- 加载模型文件,解析顶点数据
- 处理物理碰撞、动画更新
- 判断哪些物体在视锥体内(视锥剔除)
- 把最终要渲染的物体打包成“命令”,发给GPU
GPU呢?它只管一件事:拿到命令,拼命画。
说白了,CPU是项目经理,GPU是施工队。项目经理把图纸、材料、施工顺序都安排好,施工队只管闷头干。
我在项目中遇到过一种典型问题:CPU把大量时间花在了逐顶点计算上。比如在CPU端做蒙皮动画的顶点变换,结果每帧几万个顶点算下来,CPU直接跑满,GPU反而闲着。嗯,这就是分工没做好。
2.2 Draw Call:CPU和GPU之间的“快递单”
接下来聊一个绕不开的概念——Draw Call。
什么叫Draw Call?我习惯这么理解:CPU给GPU下的一道“绘制命令”。
每次CPU说“嘿,GPU,把这个网格画出来”,这就是一次Draw Call。命令里包含了:
- 要画哪个网格(顶点缓冲、索引缓冲)
- 用什么材质(纹理、着色器)
- 变换矩阵(位置、旋转、缩放)
- 渲染状态(是否开启深度测试、混合模式等)
你可能会问:一次Draw Call能画多个物体吗?
理论上不行。每个物体通常对应一次Draw Call。但这里有个坑——Draw Call本身是有开销的。
所以,减少Draw Call是优化CPU性能的关键。常用的手段有:
- 静态合批:把多个不动的物体合并成一个网格
- 动态合批:运行时把共享材质的小物体合并
- 实例化(Instancing):一次Draw Call画多个相同网格,只传不同的变换矩阵
2.3 场景数据准备:顶点、索引、纹理
好,现在CPU决定要画一个物体了。它得准备哪些数据?
2.3.1 顶点数据
顶点是渲染的最小单元。一个顶点不只是“一个点”,它包含:
- 位置(float3):在模型空间或世界空间中的坐标
- 法线(float3):用于光照计算
- 切线(float3):用于法线贴图
- 纹理坐标(float2):也叫UV,决定纹理怎么贴上去
- 颜色(float4):顶点颜色,有时用得到
举个例子,一个简单的三角形,三个顶点:
// 三个顶点,每个顶点包含位置和UV
Vertex vertices[] = {
{ {-0.5f, -0.5f, 0.0f}, {0.0f, 0.0f} }, // 左下
{ { 0.5f, -0.5f, 0.0f}, {1.0f, 0.0f} }, // 右下
{ { 0.0f, 0.5f, 0.0f}, {0.5f, 1.0f} } // 顶部
};
这些数据会被上传到GPU的显存里,存在顶点缓冲(Vertex Buffer)中。
2.3.2 索引数据
你想想看,如果每个三角形都存三个独立顶点,那相邻三角形会重复存很多顶点。比如一个正方形需要两个三角形,共6个顶点,但实际只有4个不同的顶点。
索引缓冲(Index Buffer)就是来解决这个问题的。它存的是顶点的“编号”:
// 4个顶点
Vertex vertices[] = {
{ {-0.5f, -0.5f, 0.0f}, ... }, // 索引0
{ { 0.5f, -0.5f, 0.0f}, ... }, // 索引1
{ { 0.5f, 0.5f, 0.0f}, ... }, // 索引2
{ {-0.5f, 0.5f, 0.0f}, ... } // 索引3
};
// 两个三角形,用索引表示
uint16_t indices[] = {
0, 1, 2, // 第一个三角形
0, 2, 3 // 第二个三角形
};
这样,GPU只需要处理4个顶点,而不是6个。节省了显存,也节省了带宽。
2.3.3 纹理数据
纹理就是贴在模型表面的图片。但GPU不直接认jpg或png,它需要把图片解码成像素数据(texels),然后上传到显存中的纹理对象。
纹理的常见参数:
- 分辨率:如1024x1024、2048x2048
- 格式:RGBA8、BC7、ETC2等压缩格式
- Mipmap:预先生成的一系列缩小版本,用于远处物体
- 采样方式:线性过滤、最近点采样、各向异性过滤
2.4 知识体系总览
下面这张图,我把应用阶段的核心逻辑画出来了。你可以看到数据从CPU流向GPU的完整路径:
从这张图可以看得很清楚:CPU负责准备和调度,GPU负责执行和渲染,显存是两者之间的数据仓库。整个流程环环相扣,哪一环慢了,都会拖累整体帧率。
2.5 小结
应用阶段虽然看起来“不涉及渲染”,但它是整个管线的基石。我个人习惯在项目初期就花精力把这一块理清楚:
- 明确CPU和GPU的分工边界
- 控制Draw Call数量,避免CPU成为瓶颈
- 顶点、索引、纹理数据要提前准备好,上传到GPU后尽量不动
把这些基础打牢了,后面的渲染阶段才能跑得顺畅。嗯,今天就先聊到这儿。
公众号:蓝海资料掘金营,微信deep3321