约 20 分钟技术学习游戏引擎

Games104学习笔记(4)

GAMES104:游戏引擎中的渲染实践

课程:GAMES104《现代游戏引擎:从入门到实践》
讲次:Lecture 04 / Rendering on Game Engine
讲者:王希(Xi Wang,Booming Tech)
视频时长:约 1 小时 40 分 56 秒
笔记性质:根据本地视频中的幻灯片与讲解主题整理,不是逐字稿;时间点为复习定位用的近似值。

一句话理解

游戏引擎中的渲染系统,不只是“调用图形 API 把模型画出来”,而是一套围绕现代硬件高度优化的工程流程:它要把场景数据组织为 GPU 可读的资源,筛掉不该画的对象,复用相同资源,并在有限帧时间内高效提交绘制工作。

场景 / Game Object / Mesh Renderer
          ↓
可见性判断、排序、批处理、实例化
          ↓
Mesh / Material / Texture / Shader 等资源绑定
          ↓
CPU 准备命令与数据,尽量减少传输
          ↓
GPU 并行执行绘制与着色
          ↓
屏幕画面

本讲学习目标

  • 从工程角度理解实时渲染系统,而不是孤立地学习某个绘制算法。
  • 认识 CPU、GPU、主存、显存与总线对渲染设计的影响。
  • 理解纹理采样、SIMD / SIMT、顶点与索引缓冲区等基础数据路径。
  • 了解 Mesh Renderer、Submesh、Material、Shader、Texture 和实例化的关系。
  • 掌握可见性裁剪、PVS、纹理压缩与 GPU-Driven 的核心动机。
  • 建立“尽量少传数据、尽量少画对象、尽量让 GPU 批量工作”的渲染工程意识。

1. 游戏引擎渲染的定位

视频将游戏引擎渲染定义为:

为满足 PC、主机与移动设备等现代硬件上的关键游戏渲染需求,而高度优化的实用软件框架。

它的重点是如何组织和运行渲染系统,包括数据、资源、硬件、调度和性能;不等同于一门只讨论某种光照公式或图形学效果的课程。

1.1 本讲关注什么

  • CPU 与 GPU 的协作方式。
  • 资源如何从引擎进入 GPU。
  • 网格、材质、纹理与 Shader 如何组织。
  • 如何减少冗余绘制和数据搬运。
  • 如何利用实例化、批处理和 GPU 并行能力。
  • 如何从传统渲染走向 GPU-Driven 管线。

1.2 本讲不展开什么

视频明确将以下方向排除在本讲的主要范围之外:

  • 卡通 / 非真实感渲染。
  • 完整的 2D 渲染引擎设计。
  • 次表面散射等特定材质效果。
  • 毛发、毛皮等高成本专用效果。

这不是说这些技术不重要,而是本讲优先建立现代实时渲染系统的主干框架。


2. 实时渲染的核心挑战

2.1 渲染必须服从硬件结构

现代计算机不是一个统一的计算体:CPU、GPU、主存、显存、缓存和总线各自擅长不同工作。渲染系统必须面对这种异构结构,而不能假设所有数据都能被任意处理器以相同速度访问。

部件 更适合的工作
CPU 逻辑控制、资源调度、场景遍历、命令准备、复杂分支决策
主存 存放 CPU 侧的世界状态、资源元数据和准备中的数据
GPU 大规模并行的顶点处理、像素处理、纹理采样和光栅化
显存 存放 GPU 频繁访问的网格、纹理、缓冲区和渲染目标
CPU-GPU 总线 传输资源与命令,但延迟较高、带宽有限

2.2 数据搬运往往比计算更昂贵

视频给出的工程提示是:

在可能的情况下,始终最小化 CPU 与 GPU 之间的数据传输。

原因是:

  • CPU 到 GPU 的路径具有更高延迟和有限带宽。
  • GPU 本地显存中的并行计算与读取通常快得多。
  • 每帧重复上传同一份网格、纹理或状态,会快速耗尽带宽预算。
  • 频繁同步可能让 CPU 等待 GPU,或让 GPU 因数据未准备好而空转。

因此,常见策略包括:

  • 将长期使用的网格和纹理常驻或缓存到显存。
  • 上传共享资源一次,让多个对象复用。
  • 使用句柄、实例数据或小型参数更新,而不是重复复制完整资源。
  • 批量提交相关绘制任务。
  • 尽可能把可并行的筛选和生成工作留在 GPU 侧。
  • 尽量让数据单向流动传输。

3. GPU 计算基础:纹理采样、SIMD 与 SIMT

3.1 纹理采样:画面质量与性能的平衡

纹理并不是简单地“按一个坐标取一个像素”。当纹理在屏幕上被缩小、倾斜或远离相机时,直接采样容易造成闪烁、锯齿和细节不稳定。

视频以 Mipmap 与三线性过滤为例:

  1. 选择最接近目标像素密度的两个 Mipmap 层级。
  2. 分别在这两个层级中执行双线性插值。
  3. 再对两次采样结果做线性插值。
两个相邻 Mipmap
      ↓
各自双线性过滤
      ↓
两个结果线性插值
      ↓
较稳定的纹理采样结果

这类操作看似基础,但会在屏幕上的大量像素中重复执行,因此非常适合 GPU 的并行架构。

3.2 SIMD 与 SIMT

概念 含义 常见场景
SIMD Single Instruction, Multiple Data;一条指令处理多份数据 CPU 向量化、批量数学运算
SIMT Single Instruction, Multiple Threads;大量线程以相近指令流执行 GPU 着色器、并行像素与顶点处理

两者的共同点是:通过让同一种操作作用于很多数据,提高吞吐量。区别在于,SIMD 更强调向量指令,SIMT 更强调 GPU 对大量线程组的调度。

对引擎开发者而言,重要启示是:

  • 连续、相似、可批量处理的数据更容易获得并行收益。
  • 过多分支或每个对象完全不同的处理路径,会降低并行效率。
  • 资源和数据布局会影响硬件能否真正发挥吞吐量。

4. 从 Game Object 到可绘制数据

4.1 Mesh Renderer Component

第三讲中的 Game Object / Component 模型,在渲染系统中通常通过 Mesh Renderer Component 落地:

Game Object
    ├─ Transform:位置、旋转、缩放
    ├─ Mesh Renderer:负责可见时的绘制描述
    │   ├─ Mesh / Submesh
    │   ├─ Material
    │   ├─ Texture
    │   └─ Shader
    └─ 其他组件:动画、物理、逻辑等

Mesh Renderer 并不保存全部顶点和纹理副本,而是通常引用由资源系统管理的资源。这样一个场景中大量对象可以共享网格、材质、Shader 和纹理。

4.2 Mesh、Submesh 与多材质

一个完整模型可能需要多种材质,例如角色的皮肤、金属武器、布料和透明部件。为了支持这种情况,模型通常会被拆成多个 Submesh:

Model
  ├─ Submesh 0 → Material A
  ├─ Submesh 1 → Material B
  └─ Submesh 2 → Material C

本讲的明确结论是:Submesh 设计用于支持一个模型使用多个材质。

这也意味着,材质数和 Submesh 划分会影响绘制调用、资源绑定和批处理机会;不是拆得越细越好。

4.3 顶点与索引缓冲区

GPU 需要以结构化缓冲区读取几何数据。

数据 作用
Vertex Declaration 声明每个顶点包含哪些属性,以及它们的布局
Vertex Buffer 存放顶点位置、法线、UV、颜色、骨骼权重等数据
Index Declaration 声明索引数据格式
Index Buffer 用索引复用顶点,并定义三角形连接关系

一个顶点可能包含:

Position + Normal + UV + Tangent + Color + Bone Weights ...

索引缓冲区的价值在于复用顶点。例如相邻三角形共享边时,不需要重复存储相同顶点,只需让多个索引引用它。

视频还展示了常见图元组织方式:

  • Triangle List:每三个索引定义一个三角形。
  • Triangle Strip:后续顶点与前面的顶点组合成连续三角形。
  • Triangle Fan:多个三角形共享一个中心顶点。

不同图元拓扑影响数据组织和绘制方式,但现代资产管线中最常见的基础仍是三角形。


5. Shader、Material 与资源复用

5.1 Shader 的角色

Shader 是运行在 GPU 上的程序,用来定义顶点如何变换、像素如何着色,以及资源如何参与最终画面。

视频从固定功能着色到自定义 Shader 的演变说明:现代 GPU 把越来越多可配置的计算交给开发者。引擎需要负责:

  • 管理 Shader 资源和变体。
  • 将材质参数与 Shader 对应。
  • 绑定纹理、常量和缓冲区。
  • 为不同平台选择可用的实现。
  • 控制状态切换,避免无效的重复绑定。

5.2 Material 是资源绑定与参数组合

可以把 Material 理解为“某个 Shader + 一组资源 + 一组参数”的可复用配置:

Material
   ├─ Shader
   ├─ Base Color Texture
   ├─ Normal Texture
   ├─ 其他贴图 / 缓冲区
   └─ 浮点、颜色、开关等参数

不同对象可以共享一个 Material,也可以在共享基础上只传递少量实例差异。

5.3 Instance:使用 Handle 复用资源

视频展示了以句柄(Handle)复用 Mesh、Shader 和 Texture Resource Pool 的方法:

资源池
  ├─ Mesh 资源
  ├─ Shader 资源
  └─ Texture 资源
          ↑
多个对象实例只保存对这些资源的引用

这能避免“每个士兵都保存一份完全相同的模型和贴图”。每个实例只保留自己的 Transform、动画状态、少量参数或对象 ID,而共享大体积资源。

资源复用带来两类收益:

  • 降低内存和显存占用。
  • 更容易将相同资源的对象组织为批次或实例化绘制。

6. 可见性裁剪:只画真正需要的对象

6.1 为什么裁剪是渲染系统的核心

一个大型场景中,绝大多数对象在当前相机视角下并不需要绘制:它们可能在屏幕外、被遮挡、距离过远,或位于封闭空间的另一侧。

视频强调的原则是:

使用裁剪算法,尽可能少地绘制对象。

可以把渲染前的筛选理解为:

全场景对象
    ↓
视锥裁剪:是否在相机视野内?
    ↓
遮挡 / 区域裁剪:是否可能被看到?
    ↓
距离、LOD、状态和材质筛选
    ↓
仅提交候选可见对象

6.2 Portal 与 PVS

在室内、走廊、房间等被墙体明显分隔的场景中,可以用 Portal 表示区域之间的开口,并预先或运行时计算 PVS(Potentially Visible Set,潜在可见集合)。

核心思路是:

  • 不必精确判断每个像素是否可见。
  • 先根据 Portal 找出从当前位置“有可能看见”的空间叶节点。
  • 只把这些区域内的对象交给后续绘制或更精细裁剪。

视频中的表述是:从 Portal 直接确定潜在可见的叶节点集合。

PVS 的价值是用空间结构和预处理数据减少无意义的对象遍历,尤其适用于几何遮挡明显的场景。

该技术或许不算最优,但思路想法值得研究。

6.3 裁剪与实例化的协同

实例化可以减少资源重复和调用开销,但不意味着应该把所有实例都绘制出来。理想流程是:

  1. 先筛掉明显不可见的对象或 Cluster。
  2. 再把同资源、同材质的可见实例组织在一起。
  3. 最后以更少的调用提交给 GPU。

即:先减少工作量,再提高剩余工作的吞吐量。


7. 纹理压缩与内容规模

7.1 纹理压缩是引擎必备能力

视频把 Texture Compression 标为“游戏引擎必须掌握”的能力。原因是纹理通常是游戏资源中最占空间、最消耗带宽的一类数据。

纹理压缩的目标是在可接受的画质损失下:

  • 减少磁盘包体。
  • 降低主存和显存占用。
  • 减少加载与上传时间。
  • 改善 GPU 采样时的带宽压力。

选择压缩格式时需要同时考虑:

  • 目标平台的硬件支持。
  • 颜色、法线、遮罩、HDR 等纹理用途。
  • 画质、压缩比和解码成本。
  • 是否需要 Mipmap。

7.2 内容制作与运行时表示不同

建模、雕刻等 DCC 工具会产生高细节的原始内容;运行时渲染系统则需要把它们转换为适合硬件访问的网格、Submesh、纹理和材质资源。

引擎的资产 Pipeline 通常会处理:

  • 网格划分与顶点格式。
  • 材质 / Submesh 对应关系。
  • 纹理生成、压缩和 Mipmap。
  • 平台特定数据格式。
  • LOD、边界盒或其他裁剪辅助数据。

这说明渲染质量不只由 Shader 决定,也由内容如何被组织和烘焙决定。


8. GPU-Driven 与 Cluster-Based Mesh Pipeline

8.1 为什么要把更多工作移向 GPU

视频的第四条总结是:随着 GPU 越来越强大,更多工作被移到 GPU 中执行,这就是 GPU-Driven 的趋势。

传统方式中,CPU 需要遍历大量对象、执行可见性判断、生成绘制调用,并通过总线将命令交给 GPU。场景越来越大时,CPU 侧的对象管理和 Draw Call 提交可能成为瓶颈。

GPU-Driven 的方向是:

  • 让 GPU 对更多绘制单元进行筛选。
  • 使用间接绘制减少 CPU 的逐对象提交。
  • 用更细粒度的 Cluster 管理几何。
  • 把适合并行的数据处理保留在 GPU 侧。

8.2 Cluster-Based Mesh Pipeline

视频展示了一个 GPU-Driven 的 Cluster-Based Mesh Pipeline:

  • 一个 Draw Call 可以处理任意数量的网格实例。
  • GPU 可依据 Cluster Bounds 进行裁剪。
  • 可以对 Cluster 做深度排序。

并给出一层从粗到细的几何组织:

Batch
  └─ 一次 API Draw,可由很多 Surf 组成
      └─ Surf:基于材质的 Submesh,由多个 Cluster 组成
          └─ Cluster:更细粒度的三角形集合

视频示例中,Cluster 以约 64 个三角形的 Strip 为一个处理单位。具体大小和组织方式会随引擎、硬件和资产特征变化,但关键思想是:把大网格拆成足够细的可裁剪、可并行处理的单元。

8.3 与传统批处理的差别

传统批处理主要目标是减少 CPU 发起的 API 调用;Cluster 化进一步把可见性与几何调度推向 GPU。两者的共同目标是降低无效工作,但优化位置和粒度不同:

方法 主要优化对象 典型收益
资源共享 / 实例化 重复网格、材质、纹理 降低资源占用和调用开销
CPU 侧批处理 Draw Call 与状态切换 减少 CPU 提交压力
传统裁剪 不可见对象 减少无效绘制
GPU-Driven Cluster 几何细粒度可见性与调度 提高大规模场景的并行吞吐

9. 本讲四条 Take Away

视频最后给出四条总结:

  1. 游戏引擎设计与硬件架构设计密切相关。
  2. Submesh 用于支持一个模型具有多个材质。
  3. 使用裁剪算法,尽可能少地绘制对象。
  4. 随着 GPU 更强,越来越多工作被转移到 GPU,即 GPU-Driven。

可以把它们压缩成一条工程原则:

理解硬件 → 组织正确数据 → 删除无效工作 → 让并行硬件完成更多合适的工作

10. 复习用速记

渲染系统 = 数据组织 + 资源管理 + 可见性筛选 + 绘制提交 + GPU 并行执行

核心约束:
CPU ↔ GPU 传输有延迟和带宽限制
→ 尽量少传大数据,尽量复用资源

Mesh Renderer:
Game Object + Transform + Mesh / Submesh + Material + Texture + Shader

几何数据:
Vertex Buffer 存属性
Index Buffer 复用顶点并定义三角形

性能原则:
先裁剪不可见对象
再共享资源、实例化与批处理
最后让 GPU 处理高吞吐并行工作

趋势:
传统 CPU 主导提交 → GPU-Driven → Cluster 级细粒度裁剪与调度

11. 自测题

  1. 为什么游戏引擎渲染系统需要理解 CPU、GPU、主存、显存和总线的关系?
  2. CPU 到 GPU 的频繁数据传输会带来哪些问题?
  3. 三线性过滤的三个基本步骤是什么?
  4. SIMD 与 SIMT 的共同点和区别是什么?
  5. Mesh Renderer Component 通常需要关联哪些数据?
  6. Vertex Buffer 和 Index Buffer 分别解决什么问题?
  7. Submesh 为什么能支持一个模型使用多个材质?
  8. Material、Shader 与 Texture 各自处于什么关系?
  9. 实例化为什么可以降低内存占用和提交成本?
  10. 为什么“只画当前可见对象”比盲目增加 GPU 算力更重要?
  11. Portal 与 PVS 分别用来解决什么问题?
  12. 纹理压缩为什么是游戏引擎必须掌握的能力?
  13. GPU-Driven 与传统 CPU 侧 Draw Call 提交的主要差别是什么?
  14. Cluster-Based Mesh Pipeline 如何帮助大规模几何渲染?

13. 术语表

术语 含义
Rendering System 将场景数据组织、筛选并提交给 GPU 生成画面的系统
Mesh Renderer 描述一个游戏对象如何使用网格和材质被绘制的组件
Mesh 由顶点、索引和拓扑构成的几何资源
Submesh 具有独立材质绑定的网格子集
Vertex Buffer 存放顶点属性的 GPU 缓冲区
Index Buffer 存放顶点索引、用于复用顶点和定义图元的缓冲区
Triangle List / Strip / Fan 三种常见三角形图元组织方式
Shader 运行在 GPU 上、定义顶点或像素等计算过程的程序
Material Shader、纹理与参数的可复用组合
Texture Sampling 按坐标从纹理中读取或插值颜色 / 数据的过程
Mipmap 同一纹理的多级分辨率版本
Trilinear Filtering 在两个 Mipmap 层内双线性过滤,再对两者结果插值
SIMD 单指令多数据,常用于 CPU 向量化
SIMT 单指令多线程,常用于 GPU 大规模并行执行
Instancing 多个对象共享资源,只传递实例差异的绘制方式
Culling 筛除当前不需要绘制对象的过程
PVS Potentially Visible Set,潜在可见集合
Draw Call CPU 提交一次绘制工作的 API 调用
GPU-Driven 将更多筛选、调度或绘制准备工作转移至 GPU 的架构方向
Cluster 用于细粒度裁剪与处理的一小组几何单元

14. 参考资料