1、GPU渲染管线概述:从CPU到GPU的图形处理流程,渲染管线的各个阶段
各位同学,咱们今天聊聊GPU渲染管线。说白了,这就是一张图从CPU的数学描述,变成屏幕上像素的完整过程。我做了这么多年图形相关的工作,每次跟新人聊这个,都会先问一句:你知道CPU和GPU到底怎么分工的吗?
1.1 CPU与GPU的分工
CPU是个全能选手,什么都能干,但一次只能干几件事。GPU呢?它是个偏科生,只会做图形计算,但一次能同时干成千上万件事。
举个例子。你玩《原神》的时候,CPU负责处理游戏逻辑——你按了W键,角色往前走,碰撞检测,AI决策。这些事逻辑复杂,但数据量不大。GPU呢?它负责把角色、场景、光影全部画出来。这活儿计算量大,但模式单一——就是不停地算顶点、算像素。
我个人习惯把CPU比作项目经理,GPU比作施工队。项目经理想得细,但一次只能指挥几个人。施工队人多,照着图纸干就完事了。
关键点:CPU是控制流密集,GPU是数据流密集。两者通过显存和系统总线交换数据。
1.2 渲染管线的整体流程
渲染管线,你可以想象成一条流水线。模型数据从一头进去,像素从另一头出来。中间经过好几个工位,每个工位干自己的活。
我当年在项目里调试一个渲染bug,查了三天才发现是某个阶段的数据格式没对齐。嗯,从那以后我特别重视管线各阶段的接口规范。
整个管线大致分这么几个阶段:
- 应用程序阶段——CPU干活
- 几何处理阶段——GPU干活
- 光栅化阶段——GPU干活
- 像素处理阶段——GPU干活
- 输出合并阶段——GPU干活
你想想看,这五个阶段里,只有第一个是CPU在忙,后面全是GPU的天下。所以优化渲染性能,很多时候瓶颈在CPU那边——它喂数据的速度跟不上GPU吃的速度。
1.3 应用程序阶段
这个阶段发生在CPU上。应用程序把要渲染的数据准备好,比如顶点坐标、颜色、纹理坐标,然后通过图形API(OpenGL、DirectX、Vulkan)发给GPU。
具体干的事包括:
- 视锥体裁剪——看不见的物体就不发了,省带宽
- 提交绘制命令——告诉GPU你要画什么
- 更新状态——切换shader、绑定纹理
我曾经遇到过一个项目,帧率死活上不去。查了半天,发现CPU每帧都在重新上传纹理数据。后来改成预上传,帧率直接翻倍。说白了,CPU和GPU之间的数据传输,能省则省。
1.4 几何处理阶段
数据到了GPU手里,第一件事就是处理几何。这个阶段又分几个小步骤:
1.4.1 顶点着色器
每个顶点都会经过顶点着色器。它干的事包括:
- 坐标变换——把模型坐标变成屏幕坐标
- 逐顶点光照——计算每个顶点的颜色
- 纹理坐标生成——为后续纹理采样做准备
顶点着色器是程序员可以编程的。你写一段代码,GPU对每个顶点执行一遍。我记得第一次写顶点着色器的时候,觉得这东西太神奇了——几千个顶点,一瞬间就处理完了。
1.4.2 曲面细分与几何着色器
这两个是可选的阶段。曲面细分可以把一个粗糙的模型变精细,几何着色器可以生成新的几何体。说实话,实际项目中用几何着色器的不多,性能开销比较大。
1.4.3 裁剪与屏幕映射
裁剪就是把视锥体外面的部分切掉。屏幕映射是把3D坐标变成2D屏幕坐标。这一步是硬件自动完成的,程序员一般不用操心。
小技巧:调试顶点着色器的时候,可以在输出里加一个颜色标记。比如把顶点位置编码成颜色,这样一看屏幕就知道顶点位置对不对。我经常用这招。
1.5 光栅化阶段
这个阶段是把几何数据变成像素。说白了,就是把三角形拆成一个个小格子。
光栅化干的事:
- 确定哪些像素被三角形覆盖
- 计算每个像素的深度值
- 生成片元(fragment)——片元就是待处理的像素候选者
你想想看,一个三角形可能覆盖几千个像素。光栅化就是决定哪些像素归这个三角形管。这个过程是硬件固定的,程序员改不了。但你可以通过调整三角形的形状来影响光栅化效率。
我建议你记住一个数字:一个像素可能被多个三角形覆盖。这就是为什么后面需要深度测试。
1.6 像素处理阶段
片元着色器(也叫像素着色器)是这个阶段的核心。每个片元都会执行一遍片元着色器程序。
片元着色器干的事:
- 纹理采样——从纹理图片里取颜色
- 逐像素光照——计算每个像素的最终颜色
- Alpha测试——透明度处理
片元着色器是性能大户。一个1920x1080的画面,有200多万个像素。每个像素都要跑一遍你的shader代码。所以shader写得不好,帧率直接崩。
注意:片元着色器里不要写分支判断!GPU是SIMD架构,分支会导致部分线程空转。我曾经在项目里写了个if-else,性能直接掉了30%。后来改成用lerp插值,问题解决了。
1.7 输出合并阶段
这是管线的最后一站。所有片元在这里进行最终处理:
- 深度测试——离相机近的覆盖远的
- 模板测试——限制绘制区域
- 混合——透明物体的颜色叠加
- 写入帧缓冲——最终显示到屏幕上
深度测试的顺序很重要。我建议你先画不透明的物体,再画透明的。因为透明物体需要混合,混合操作比普通写入慢得多。先画不透明的,把深度缓冲填好,透明物体就能少处理一些被遮挡的像素。
1.8 管线总结
好了,咱们把整个流程串起来捋一遍:
| 阶段 | 执行单元 | 输入 | 输出 |
|---|---|---|---|
| 应用程序 | CPU | 场景数据 | 绘制命令 |
| 顶点处理 | GPU顶点单元 | 顶点数据 | 变换后的顶点 |
| 光栅化 | GPU固定单元 | 三角形 | 片元 |
| 像素处理 | GPU像素单元 | 片元 | 颜色值 |
| 输出合并 | GPU ROP单元 | 颜色+深度 | 帧缓冲 |
说白了,渲染管线就是个数据加工流水线。每个阶段都有固定的输入输出格式。你只要保证数据格式对得上,管线就能跑起来。
我记得刚入行的时候,总觉得渲染管线很神秘。后来亲手调过几个项目,发现其实就是这么回事——数据流、计算单元、存储单元,三者配合。搞懂了这些,后面学纹理映射、像素处理就轻松多了。
下一章咱们聊纹理映射单元,那个才是真正有意思的东西。