GPU 架构学习

第十五课:GPU 如何渲染一幅三维图像

这一课转向 GPU 的图形起源,讲一幅三维场景如何经过顶点处理、光栅化、像素着色和深度测试,最终变成屏幕图像。

目录一、屏幕上的三维图像本质是什么二、三维模型通常由三角形组成三、顶点 Vertex 是什么四、Vertex Shader 做什么五、为什么需要这些坐标变换六、顶点处理为什么适合并行七、Primitive Assembly八、裁剪 Clipping九、背面剔除 Back-Face Culling十、什么是光栅化 Rasterization十一、Fragment 和 Pixel 有什么区别十二、属性插值十三、透视正确插值十四、Fragment Shader 做什么十五、Fragment Shader 为什么适合 GPU十六、像素线程也会组成 Warp十七、纹理 Texture 是什么十八、Texture Unit 做什么十九、Texel 和 Pixel 的区别二十、Mipmap 是什么二十一、深度缓冲 Z-Buffer二十二、Early-Z二十三、Overdraw二十四、模板测试 Stencil Test二十五、颜色混合 Blending二十六、ROP 是什么二十七、帧缓冲 Frame Buffer二十八、完整的传统光栅化管线二十九、其他可编程 Shader 阶段Geometry ShaderTessellation ShaderMesh ShaderCompute Shader三十、图形 Shader 和 CUDA Kernel 的关系三十一、固定功能硬件为什么仍然存在三十二、传统光栅化的局限三十三、光线追踪的基本思想三十四、光线和三角形求交三十五、BVH 是什么三十六、RT Core 做什么三十七、RT Core 不负责整个光线追踪三十八、光线追踪中的分支发散三十九、光栅化与光线追踪的比较四十、为什么需要降噪四十一、DLSS 类技术的基本思想四十二、一帧图像中的不同硬件分工四十三、Draw Call 是什么四十四、CPU 在图形渲染中做什么四十五、图形中的 GPU Bound四十六、分辨率主要影响什么四十七、几何复杂度主要影响什么四十八、图形管线与通用计算的统一本课核心结论第十六课:从一张 GPU 框图理解完整芯片

导语:这一课转向 GPU 的图形起源,讲一幅三维场景如何经过顶点处理、光栅化、像素着色和深度测试,最终变成屏幕图像。

GPU 最初主要为图形渲染设计。它之所以具有大量并行计算单元,是因为一幅图像中有大量顶点、三角形和像素,可以同时处理。

一幅三维图像的生成过程,可以概括为:

第十五课:GPU 如何渲染一幅三维图像

图示:第十五课:GPU 如何渲染一幅三维图像

这套流程称为:

Graphics Pipeline,图形渲染流水线。


一、屏幕上的三维图像本质是什么

显示器最终只能显示二维像素,例如:

1920 × 1080

也就是约207万个像素。

每个像素通常包含:

红色 R
绿色 G
蓝色 B
可能还有透明度 A

但游戏或三维软件中的场景是三维的,包括:

  • 物体位置;
  • 物体形状;
  • 摄像机位置;
  • 光源;
  • 材质;
  • 遮挡关系。

GPU 的任务就是把三维场景转换成一张二维像素图像。


二、三维模型通常由三角形组成

复杂物体通常不是用连续曲面直接保存,而是由大量三角形拼接而成。

例如一个立方体可以由12个三角形组成:

一个面:2个三角形
6个面:12个三角形

一个复杂人物模型可能包含:

数万个
数十万个
甚至数百万个三角形

GPU 偏好三角形,原因包括:

  1. 三个点一定能确定一个平面;
  2. 三角形内部插值比较简单;
  3. 三角形容易判断覆盖哪些像素;
  4. 任意复杂表面都可以近似分割成三角形。

三、顶点 Vertex 是什么

三角形由三个顶点组成。

一个顶点不只是一个位置,通常还包含多个属性:

Position:三维位置
Normal:法向量
Texture Coordinate:纹理坐标
Color:顶点颜色
Tangent:切线
Bone Weight:骨骼权重

例如:

struct Vertex {
    float3 position;
    float3 normal;
    float2 texcoord;
};

一个三角形可以表示为:

Vertex 0
Vertex 1
Vertex 2

这些顶点数据通常存放在显存中的 Vertex Buffer 中。


四、Vertex Shader 做什么

Vertex Shader,顶点着色器,负责处理顶点。

它最重要的任务是坐标变换。

一个顶点最开始位于模型自己的局部坐标系中:

Model Space

经过一系列矩阵变换:

四、Vertex Shader 做什么

图示:四、Vertex Shader 做什么

数学上可以写成:

pclip=P×V×M×pmodelp_{\text{clip}} = P\times V\times M\times p_{\text{model}}

其中:

  • MM:模型矩阵;
  • VV:观察矩阵;
  • PP:投影矩阵。

顶点着色器通常会执行:

gl_Position = projection * view * model * position;

五、为什么需要这些坐标变换

假设一个模型的顶点坐标是:

(0, 0, 0)

这只是模型内部的位置。

如果要把模型放到世界中的不同位置,需要 Model Matrix:

模型自身坐标
→ 世界中的位置、旋转和缩放

摄像机也有位置和朝向,需要 View Matrix:

世界坐标
→ 以摄像机为参考的坐标

最后,Projection Matrix 把三维空间投影到二维屏幕:

透视投影:
远处物体看起来更小
近处物体看起来更大

六、顶点处理为什么适合并行

通常每个顶点的变换可以独立进行:

Vertex 0 → 矩阵变换
Vertex 1 → 矩阵变换
Vertex 2 → 矩阵变换
...

因此可以让大量线程分别处理不同顶点。

这正符合 GPU 的特点:

同一段 Vertex Shader
+
大量不同顶点数据

从执行模型看,它与 CUDA Kernel 很相似:

一个线程处理一个顶点

现代 GPU 中,Vertex Shader 通常运行在可编程 Shader Core 上,这些核心与通用计算使用的 CUDA Core 在物理资源上有较强联系。


七、Primitive Assembly

顶点处理完成后,GPU 需要根据索引把顶点组成图元。

图元可能是:

  • 点;
  • 线;
  • 三角形。

三维图形中最常见的是三角形。

例如索引缓冲区为:

0, 1, 2
2, 3, 0

表示:

Triangle 0:Vertex 0、1、2
Triangle 1:Vertex 2、3、0

这种方法允许多个三角形共享顶点,减少顶点数据量。


八、裁剪 Clipping

并不是所有三角形都位于摄像机可见范围内。

例如:

  • 三角形在摄像机后面;
  • 超出屏幕左右范围;
  • 离摄像机太近;
  • 超出远裁剪面。

GPU 会对图元进行裁剪。

完全位于视野外的三角形可以直接丢弃。

部分位于视野内的三角形,需要被切割成新的三角形。

这样后续流水线只处理可能出现在屏幕上的部分。


九、背面剔除 Back-Face Culling

对于封闭物体,通常只需要看到朝向摄像机的表面。

例如一个立方体背面的三角形通常被正面遮住。

GPU 可以根据三角形顶点在屏幕上的绕序判断正反面:

顺时针

逆时针

如果三角形朝向背面,就可以在光栅化前丢弃。

这称为:

Back-Face Culling,背面剔除。

它可以减少后续像素处理量。

但透明物体、双面材质或薄片模型可能需要关闭背面剔除。


十、什么是光栅化 Rasterization

顶点和三角形仍然是几何描述,而显示器需要的是像素。

光栅化的任务是:

判断一个三角形覆盖了屏幕上的哪些像素。

例如一个三角形投影到屏幕后:

..............
.....XX.......
....XXXX......
...XXXXXX.....
..XXXXXXXX....
..............

每个 X 表示该三角形覆盖的一个像素或采样点。

光栅化器会生成大量 Fragment。


十一、Fragment 和 Pixel 有什么区别

Fragment 可以翻译为片段。

它是:

某个图元对某个屏幕位置产生的候选像素数据。

Fragment 不一定最终成为屏幕像素。

因为它还可能被:

  • 深度测试淘汰;
  • 模板测试淘汰;
  • Shader 主动丢弃;
  • 透明混合改变;
  • 多重采样合并。

所以:

Fragment = 候选像素
Pixel = 最终帧缓冲位置

同一个像素位置可能先后产生多个 Fragment。


十二、属性插值

顶点上保存有纹理坐标、颜色、法向量等属性。

但是三角形内部的像素没有直接存储这些属性。

光栅化器会根据像素在三角形中的位置,对三个顶点属性进行插值。

假设三个顶点颜色分别为:

顶点 A:红色
顶点 B:绿色
顶点 C:蓝色

三角形内部颜色会平滑过渡。

数学上常使用重心坐标:

P=αA+βB+γCP=\alpha A+\beta B+\gamma C

其中:

α+β+γ=1\alpha+\beta+\gamma=1

同样的方法可以插值:

  • 深度;
  • 纹理坐标;
  • 法向量;
  • 顶点颜色;
  • 其他 Shader 输出变量。

十三、透视正确插值

普通线性插值在透视投影下可能导致纹理变形。

因为三维空间中的均匀变化,投影到二维屏幕后并不一定均匀。

因此 GPU 通常采用 Perspective-Correct Interpolation,即透视正确插值。

其核心会结合顶点的齐次坐标 ww,使纹理在远近变化时看起来正确。

这部分通常由固定功能光栅化硬件完成,不需要像素着色器手工计算。


十四、Fragment Shader 做什么

Fragment Shader,也常称 Pixel Shader,负责计算每个 Fragment 的颜色和相关输出。

输入可能包括:

  • 插值后的纹理坐标;
  • 法向量;
  • 世界空间位置;
  • 顶点颜色;
  • 光照参数;
  • 材质参数。

输出通常包括:

  • 颜色;
  • 深度;
  • 在延迟渲染中写入多个 G-Buffer。

一个简单 Fragment Shader 可能是:

color = texture(diffuse_texture, texcoord);

更复杂时可能包括:

纹理采样
法线贴图
光照计算
阴影判断
反射
材质模型
雾效
色调映射

十五、Fragment Shader 为什么适合 GPU

一帧图像可能有数百万像素。

假设分辨率为:

3840×21603840\times2160

大约有:

8.29×106pixels8.29\times10^6\,\mathrm{pixels}

如果开启多重采样或存在大量重叠三角形,实际处理的 Fragment 数量还会更多。

大量 Fragment 执行相同 Shader,但使用不同:

  • 屏幕坐标;
  • 纹理坐标;
  • 深度;
  • 法向量;
  • 材质数据。

这又是典型的数据并行:

同一段程序
+
大量不同像素数据

十六、像素线程也会组成 Warp

Fragment Shader 通常也会被组织成线程组,并在 NVIDIA GPU 上以 Warp 形式执行。

为了计算纹理导数、LOD 等信息,像素常被组织为 2×22\times2 的 Quad:

P0 P1
P2 P3

相邻像素共同执行,有利于计算:

ux,uy\frac{\partial u}{\partial x}, \quad \frac{\partial u}{\partial y}

这些导数用于选择纹理 Mipmap 层级。

因此图形线程的组织不仅考虑 Warp,还考虑屏幕上的空间邻近关系。


十七、纹理 Texture 是什么

纹理本质上通常是一张图像或数据数组。

例如一面墙的几何模型可能只是一个矩形,但通过纹理可以显示:

  • 砖块;
  • 裂纹;
  • 污渍;
  • 颜色变化。

顶点保存纹理坐标:

(u,v)(u,v)

Fragment Shader 使用纹理坐标查询纹理:

color = texture(texture0, uv);

十八、Texture Unit 做什么

纹理单元是 GPU 中面向纹理访问的专用硬件。

它可以负责:

  • 地址计算;
  • 纹理坐标处理;
  • 边界模式;
  • 格式转换;
  • 纹理缓存查询;
  • 双线性过滤;
  • 三线性过滤;
  • 各向异性过滤。

例如双线性插值不是只读取一个纹理像素,而是读取附近4个 Texel:

T00  T01
T10  T11

再根据位置加权:

T=(1a)(1b)T00+a(1b)T01+(1a)bT10+abT11T= (1-a)(1-b)T_{00} +a(1-b)T_{01} +(1-a)bT_{10} +abT_{11}

这些工作由纹理硬件完成,比用普通 Shader 指令逐项实现更高效。


十九、Texel 和 Pixel 的区别

Texel 是纹理中的元素。

Pixel 是屏幕上的像素。

一个屏幕像素可能对应:

  • 一个 Texel;
  • 多个 Texel;
  • 一个 Texel 的一部分;
  • 多层纹理的混合结果。

所以:

Texture Element → Texel
Screen Element  → Pixel

两者并不是同一个概念。


二十、Mipmap 是什么

当一个远处物体只占少量屏幕像素时,如果仍从原始高分辨率纹理采样,会出现:

  • 闪烁;
  • 锯齿;
  • 摩尔纹;
  • 纹理缓存效率差。

Mipmap 会预先生成多级缩小纹理:

Level 0:1024 × 1024
Level 1:512 × 512
Level 2:256 × 256
Level 3:128 × 128
...

根据纹理在屏幕上的缩放比例选择合适层级。

这样可以:

  • 减少纹理走样;
  • 提高缓存局部性;
  • 减少不必要的数据读取。

二十一、深度缓冲 Z-Buffer

多个物体可能投影到同一个像素位置。

GPU 需要判断哪个物体距离摄像机更近。

每个像素通常都有一个深度值:

Color Buffer:保存颜色
Depth Buffer:保存最近深度

当新 Fragment 到来时:

新深度 < 当前深度?

如果更近:

通过深度测试
更新颜色
更新深度

如果更远:

深度测试失败
丢弃 Fragment

这称为:

Depth Test 或 Z-Test。


二十二、Early-Z

如果 Fragment 最终会被深度测试淘汰,那么最好在执行复杂 Fragment Shader 之前就丢弃。

流程可以是:

先做深度测试

确定被遮挡

不执行 Fragment Shader

这称为 Early-Z。

它可以节省大量 Shader 运算。

但某些情况会限制 Early-Z,例如 Fragment Shader:

  • 修改深度;
  • 使用 discard
  • 存在特殊透明或副作用操作。

二十三、Overdraw

同一个像素在一帧中可能被绘制多次。

例如:

先画远处墙壁
再画中间物体
最后画近处物体

前面的像素结果最终会被后面的物体覆盖。

这种重复像素处理称为:

Overdraw,过度绘制。

Overdraw 会浪费:

  • Fragment Shader 计算;
  • 纹理带宽;
  • 深度和颜色缓冲带宽。

常见优化方法包括:

  • 使用深度预处理;
  • 大致从前向后绘制不透明物体;
  • 背面剔除;
  • 遮挡剔除;
  • 合理组织场景。

二十四、模板测试 Stencil Test

Stencil Buffer 是每像素的一小块整数状态。

它可以用于:

  • 限定某区域是否允许绘制;
  • 镜面效果;
  • 阴影体;
  • 轮廓;
  • UI 裁剪;
  • 多阶段渲染标记。

例如先在模板缓冲中标记某个区域:

区域内 Stencil = 1
区域外 Stencil = 0

后续只允许 Stencil == 1 的 Fragment 写入。

它相当于每像素的条件掩码。


二十五、颜色混合 Blending

透明物体不能简单覆盖背景。

常见 Alpha 混合为:

Cout=αCsrc+(1α)CdstC_{\text{out}} = \alpha C_{\text{src}} + (1-\alpha)C_{\text{dst}}

其中:

  • CsrcC_{\text{src}}:新 Fragment 颜色;
  • CdstC_{\text{dst}}:帧缓冲原颜色;
  • α\alpha:透明度。

例如半透明红色覆盖蓝色背景:

新颜色 = 0.5 × 红色 + 0.5 × 蓝色

得到紫色附近的结果。

颜色混合需要读取原有颜色并写回新颜色,因此会产生帧缓冲带宽需求。


二十六、ROP 是什么

ROP 通常指 Raster Operations Pipeline 或 Render Output Unit。

它负责图形流水线末端的一些操作,例如:

  • 深度测试;
  • 模板测试;
  • Alpha 混合;
  • 颜色写掩码;
  • 多重采样处理;
  • 写入帧缓冲区。

可以简化为:

二十六、ROP 是什么

图示:二十六、ROP 是什么

ROP 是固定功能硬件,不是普通 CUDA Core。

因此显卡性能不能只看 Shader Core 数量,还要考虑:

  • Texture Unit;
  • ROP;
  • 显存带宽;
  • Cache;
  • 几何处理能力。

二十七、帧缓冲 Frame Buffer

帧缓冲区保存最终图像。

通常位于显存中。

可能包含:

Color Buffer
Depth Buffer
Stencil Buffer
Velocity Buffer
G-Buffer
其他中间渲染目标

当一帧渲染完成后,显示控制器会读取最终颜色缓冲,输出到显示器。

实际系统常使用双缓冲或三缓冲:

Front Buffer:当前显示
Back Buffer:正在渲染下一帧

完成后交换:

Swap / Present

避免显示器读取到一半完成的图像。


二十八、完整的传统光栅化管线

可以将整个过程串联起来:

二十八、完整的传统光栅化管线

图示:二十八、完整的传统光栅化管线

其中部分阶段是可编程的:

Vertex Shader
Fragment Shader
以及其他 Shader 阶段

部分阶段主要由固定功能硬件完成:

Primitive Assembly
Clipping
Rasterization
Depth Test
Blending

二十九、其他可编程 Shader 阶段

现代图形管线不只有 Vertex Shader 和 Fragment Shader。

还可能包括:

Geometry Shader

接收图元,并可以生成或删除图元。

但由于效率和扩展性问题,现代高性能渲染中使用相对谨慎。

Tessellation Shader

把粗糙曲面细分成更多三角形。

适用于地形、曲面和细节增强。

Mesh Shader

更灵活地处理网格和图元生成。

可以由线程组协同处理一组顶点和三角形,减少传统固定几何管线限制。

Compute Shader

不直接绑定传统图形流水线,可以执行通用计算,例如:

  • 后处理;
  • 粒子模拟;
  • 光照剔除;
  • 图像滤波;
  • 物理计算。

三十、图形 Shader 和 CUDA Kernel 的关系

它们本质上都运行在 GPU 的可编程计算核心上,但编程模型不同。

CUDA Kernel:

程序员显式定义 Grid、Block、Thread

图形 Shader:

图形 API 和硬件根据顶点、三角形和 Fragment 自动生成线程

例如:

Vertex Shader:
每个顶点触发一次

Fragment Shader:
每个 Fragment 触发一次

程序员通常不直接指定 Fragment Shader 的 Grid 大小。

图形驱动和 GPU 前端负责将图形任务组织成 Warp 和 Block 类似的执行单位。


三十一、固定功能硬件为什么仍然存在

理论上,光栅化、纹理过滤、深度测试也可以使用普通计算核心实现。

但这些操作:

  • 非常常见;
  • 数据流规则;
  • 对吞吐率要求极高;
  • 算法相对固定。

使用专用硬件可以获得:

  • 更高性能;
  • 更低功耗;
  • 更少指令开销;
  • 更高面积效率。

这与 Tensor Core 的设计思想相同:

对极其常见的规则操作,使用专用结构提高效率。


三十二、传统光栅化的局限

光栅化非常擅长:

判断三角形覆盖哪些像素

但现实中的光传播更复杂,例如:

  • 镜面反射;
  • 多次反射;
  • 折射;
  • 软阴影;
  • 间接光照;
  • 全局光照。

传统光栅化通常使用各种近似方法:

  • Shadow Map;
  • Screen-Space Reflection;
  • Environment Map;
  • Light Map;
  • Ambient Occlusion。

这些方法效率高,但可能存在视觉误差。


三十三、光线追踪的基本思想

光线追踪从摄像机向场景发射光线。

对于每个像素:

摄像机
   ↓ 发射光线
场景
   ↓ 找到最近交点
计算材质和光照

如果要判断阴影,还需要从交点向光源发射 Shadow Ray。

如果表面有反射,还可能继续发射 Reflection Ray。

示意:

Camera Ray

命中镜面

Reflection Ray

命中其他物体

光线追踪能够更自然地模拟复杂光照,但计算量很大。


三十四、光线和三角形求交

场景中可能有数百万个三角形。

最直接的方法是让每条光线测试所有三角形:

Nray×NtriangleN_{\text{ray}}\times N_{\text{triangle}}

计算量极其巨大。

因此需要空间加速结构。

最常见的是:

BVH,Bounding Volume Hierarchy,包围体层次结构。


三十五、BVH 是什么

BVH 把多个物体或三角形包在层次化的包围盒中。

三十五、BVH 是什么

图示:三十五、BVH 是什么

光线先与较大的包围盒求交。

如果没有命中该包围盒,内部所有三角形都不需要测试。

因此:

测试少量包围盒
+
只测试可能命中的三角形

显著减少求交次数。


三十六、RT Core 做什么

RT Core 是面向光线追踪的专用硬件。

它主要加速:

  • BVH 遍历;
  • 光线与包围盒求交;
  • 光线与三角形求交。

传统情况下,这些工作需要 Shader Core 执行大量指令。

有 RT Core 后:

Shader Core:
生成光线、计算材质、运行着色逻辑

RT Core:
执行 BVH 遍历和求交测试

这样可以减轻 CUDA Core 或 Shader Core 的负担。


三十七、RT Core 不负责整个光线追踪

RT Core 并不是“完整的光线追踪处理器”。

它通常不负责:

  • 材质计算;
  • 光照模型;
  • 纹理采样;
  • 反射颜色计算;
  • 神经网络降噪;
  • 场景逻辑。

完整过程通常是:

三十七、RT Core 不负责整个光线追踪

图示:三十七、RT Core 不负责整个光线追踪

因此实时光线追踪仍然需要 Shader Core、Texture Unit、RT Core 和显存系统共同工作。


三十八、光线追踪中的分支发散

不同光线可能经历完全不同的路径:

光线 0:很快命中墙壁
光线 1:穿过玻璃
光线 2:反射两次
光线 3:没有命中任何物体

如果这些光线位于同一个 Warp 中,就可能产生严重控制流发散。

不同光线还可能访问完全不同的:

  • BVH 节点;
  • 三角形;
  • 材质;
  • 纹理。

因此光线追踪具有明显的不规则访存和分支问题。

RT Core 可以加速求交,但不能完全消除这些问题。


三十九、光栅化与光线追踪的比较

项目光栅化光线追踪
核心问题三角形覆盖哪些像素光线与哪些物体相交
并行对象顶点、三角形、Fragment光线
访问模式较规则较不规则
性能很高计算量较大
阴影反射多用近似方法更自然
专用硬件Rasterizer、ROP、Texture UnitRT Core
实时应用主流基础方法常与光栅化混合

现代游戏通常使用混合渲染:

大部分几何:
光栅化

部分反射、阴影、全局光照:
光线追踪

四十、为什么需要降噪

实时光线追踪每个像素通常只能发射少量光线。

采样不足时,图像会出现噪点:

真实结果需要大量采样
实时系统只能少量采样

图像噪声较大

因此需要降噪器,根据:

  • 当前帧;
  • 历史帧;
  • 深度;
  • 法线;
  • 运动向量;

估计更平滑的图像。

部分系统还会使用 AI 模型进行降噪或重建。


四十一、DLSS 类技术的基本思想

现代渲染不一定直接以最终分辨率完整计算每个像素。

例如先渲染较低分辨率:

1920 × 1080

再重建到:

3840 × 2160

重建可能使用:

  • 当前低分辨率图像;
  • 历史帧;
  • 运动向量;
  • 深度信息;
  • 神经网络。

目标是减少基础渲染工作量,同时尽量保持高分辨率观感。

这类技术会使用 Tensor Core 运行神经网络推理。

因此现代游戏 GPU 可能同时使用:

CUDA/Shader Core
Texture Unit
Rasterizer
ROP
RT Core
Tensor Core

四十二、一帧图像中的不同硬件分工

可以概括为:

四十二、一帧图像中的不同硬件分工

图示:四十二、一帧图像中的不同硬件分工


四十三、Draw Call 是什么

CPU 通过图形 API 提交绘制命令,例如:

使用某个模型
使用某个材质
使用某套 Shader
绘制这些三角形

这类命令通常称为 Draw Call。

每次 Draw Call 都可能涉及:

  • 状态设置;
  • Shader 切换;
  • 资源绑定;
  • 命令处理;
  • 驱动开销。

如果场景中有大量很小的 Draw Call,CPU 和 GPU 前端可能成为瓶颈。

因此图形优化不仅要关注像素和三角形数量,还要关注提交开销。


四十四、CPU 在图形渲染中做什么

CPU 通常负责:

  • 游戏逻辑;
  • 物理模拟;
  • 动画;
  • 场景管理;
  • 可见性判断;
  • 准备 Draw Call;
  • 提交命令;
  • 管理资源。

GPU 负责:

  • 顶点变换;
  • 光栅化;
  • 像素着色;
  • 纹理采样;
  • 光线追踪;
  • 后处理。

如果 CPU 无法及时提交命令,即使 GPU 很强也可能空闲。

这称为 CPU Bound。


四十五、图形中的 GPU Bound

如果 CPU 能够快速提交命令,但 GPU 渲染一帧耗时很长,则称为 GPU Bound。

GPU Bound 还可以进一步细分:

Vertex Bound:
顶点和几何处理过重

Pixel Bound:
Fragment Shader 或 Overdraw 过重

Texture Bound:
纹理采样和带宽受限

ROP Bound:
颜色、深度和混合带宽受限

RT Bound:
光线追踪求交或着色受限

这与 CUDA 中分析 Compute Bound、Memory Bound 的思想一致。


四十六、分辨率主要影响什么

分辨率从1080p提高到4K:

1920×10802.07 M1920\times1080\approx2.07\text{ M} 3840×21608.29 M3840\times2160\approx8.29\text{ M}

像素数量约增加4倍。

因此主要增加:

  • Fragment Shader 工作量;
  • 纹理采样;
  • 深度测试;
  • ROP 写入;
  • 帧缓冲带宽。

但顶点和三角形数量不一定变化。

所以高分辨率下更容易成为像素或带宽受限。


四十七、几何复杂度主要影响什么

增加模型三角形数量主要增加:

  • 顶点着色;
  • 图元组装;
  • 裁剪和剔除;
  • 光栅化前端工作;
  • BVH 构建或遍历相关数据量。

但如果大量小三角形只覆盖少量像素,还可能使光栅化效率下降。

因此三角形并非越小越好。

过小三角形可能导致:

  • Quad 利用率低;
  • Fragment Helper Lane 增多;
  • 几何前端压力增加;
  • 缓存效率下降。

四十八、图形管线与通用计算的统一

早期 GPU 的顶点和像素处理器可能是不同硬件。

现代 GPU 通常采用统一着色器架构:

同一组可编程执行核心
可以执行:
- Vertex Shader
- Fragment Shader
- Compute Shader
- CUDA Kernel
- 其他 Shader

这样可以根据场景动态分配计算资源。

例如某一帧像素任务很重,更多 Shader Core 可以处理 Fragment Shader,而不需要固定保留大量只用于顶点的硬件。

但光栅化器、纹理单元和 ROP 等固定功能硬件仍然存在。


本课核心结论

第一,三维模型通常由大量三角形组成,三角形由顶点定义。

第二,Vertex Shader 负责顶点坐标变换和顶点属性处理。

第三,光栅化把三角形转换为屏幕上的 Fragment。

第四,Fragment 是候选像素,最终可能因深度、模板或 Shader 操作被丢弃。

第五,光栅化器会对纹理坐标、颜色和法向量等顶点属性进行插值。

第六,Fragment Shader 负责计算每个 Fragment 的颜色和材质效果。

第七,Texture Unit 负责纹理寻址、缓存和过滤等专用操作。

第八,Z-Buffer 用于判断遮挡关系,Early-Z 可以提前丢弃不可见 Fragment。

第九,ROP 负责深度、模板、颜色混合和帧缓冲写回等末端操作。

第十,图形 Shader 和 CUDA Kernel 都可以运行在统一的可编程计算核心上,但任务生成和编程模型不同。

第十一,固定功能硬件通过牺牲通用性,提高光栅化、纹理和输出操作的能效。

第十二,RT Core 主要加速 BVH 遍历和光线求交,不负责完整材质和光照计算。

第十三,光线追踪访问和控制流较不规则,因此容易产生分支发散和随机访存。

第十四,现代实时渲染通常混合使用光栅化、光线追踪和 AI 图像重建。

第十五,现代 GPU 是可编程 Shader Core、Tensor Core、RT Core、Texture Unit、Rasterizer、ROP 和内存系统共同组成的异构处理器。

下一课适合回到 GPU 微架构本身:

第十六课:从一张 GPU 框图理解完整芯片

将重点讲解:

GPC、TPC、SM 的层级关系
GPU Front End
Raster Engine
L2 Cache Slice
Memory Partition
NoC / Crossbar
为什么 GPU 要划分成多个 Partition
一个数据请求如何穿过整块芯片
不同 NVIDIA 架构框图应该怎么看

第二十七课:GPU 架构学习总结与统一分析框架

最后一课将把前面所有内容压缩成一套统一分析框架,并给出面向 GPU 架构与 RTL 设计的后续实践路线。

第二十六课:如何从零设计一个简化的 SIMT GPU

这一课从 RTL 设计角度搭建一颗最小可运行的 SIMT GPU。重点是模块边界、状态机和实现顺序,而不是一开始复制完整商用 GPU。

第二十五课:稀疏计算、不规则访存,以及 GPU 不擅长什么

这一课是倒数第三课,重点解释“计算量更少”为什么不一定更快,以及不规则访存、原子冲突和负载不均衡为何会削弱 GPU 优势。