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,查了三天才发现是某个阶段的数据格式没对齐。嗯,从那以后我特别重视管线各阶段的接口规范。

整个管线大致分这么几个阶段:

  1. 应用程序阶段——CPU干活
  2. 几何处理阶段——GPU干活
  3. 光栅化阶段——GPU干活
  4. 像素处理阶段——GPU干活
  5. 输出合并阶段——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单元 颜色+深度 帧缓冲

说白了,渲染管线就是个数据加工流水线。每个阶段都有固定的输入输出格式。你只要保证数据格式对得上,管线就能跑起来。

我记得刚入行的时候,总觉得渲染管线很神秘。后来亲手调过几个项目,发现其实就是这么回事——数据流、计算单元、存储单元,三者配合。搞懂了这些,后面学纹理映射、像素处理就轻松多了。

下一章咱们聊纹理映射单元,那个才是真正有意思的东西。