⚡ 性能预算与优化标准
为了确保游戏在目标平台(PC 中低配 / Steam Deck / 潜在的主机)上稳定运行 60FPS,必须严格执行此预算。1. 概述与目标 (Overview & Targets)
目标平台规格 (Target Specs)
- 基准平台: GTX 1060 / Steam Deck.
- 目标帧率: 60 FPS (16.6ms per frame).
- 分辨率: 1080p.
性能分级策略 (LOD Policy)
| 设置 | 阴影 | 抗锯齿 | 怪物数量 | 粒子密度 |
|---|---|---|---|---|
| Low | 仅主角 | FXAA | 200 (超过不再生成) | 50% |
| Med | 实时硬阴影 | SMAA | 350 | 75% |
| High | 软阴影 (Cascade) | TAA | 500 | 100% |
时间感知速查表
将各种操作放入同一个时间尺度中理解:| 操作 | 耗时量级 | 16.6ms 帧预算占比 | 直觉类比 |
|---|---|---|---|
| L1 Cache 访问 | 1 ns | 0.00006% | 眨眼的 1/1,000,000 |
| L2 Cache 访问 | 4 ns | 0.00024% | |
| 主内存访问 | 100 ns | 0.0006% | |
| SSD 读取 | 100 us | 0.6% | |
| HDD 读取 | 10 ms | 60% ⚠️ | 半帧没了 |
| 网络延迟 (同城) | 5 ms | 30% | |
| 网络延迟 (跨国) | 200 ms | 1200% | 12 帧! |
| 完整 GC | 50 ms | 300% | 3 帧全卡 |
2. 内存预算 (Memory Budget)
内存预算 (Memory Budget)
- 总内存: < 4GB (System RAM).
- 显存 (VRAM): < 2GB.
- 纹理:
- 角色: 2048x2048 (Atlas).
- 环境: 1024x1024 (Tiling).
- 杂兵: 512x512 或 1024 合集。
- 压缩: PC 使用 DXT5/BC7, 移动端使用 ASTC。
内存微观基准 (Memory Micro-Benchmarks)
以下数据为 近似值,用于估算内存占用。1. 基础数据类型 (C#)
| 类型 (C#) | 大小 (Bytes) | 备注 |
|---|---|---|
bool | 1 (or 4) | 单个字段通常对齐到 4 字节,数组中为 1 字节。 |
byte / sbyte | 1 | 最小单位。 |
char | 2 | UTF-16 字符。 |
short / ushort | 2 | |
int / uint | 4 | 最常用的整数。 |
float | 4 | 物理、坐标常用。 |
long / ulong | 8 | 大数。 |
double | 8 | 高精度计算。 |
Reference (引用) | 8 | 64 位系统指针大小。Class 的引用。 |
| Object Header | ~16-24 | 每个 class 对象实例的额外开销 (SyncBlock + TypeHandle)。 |
⚠️ 小对象陷阱: 创建一个只包含 1 个int的class实例,其实际占用可能高达 24-32 bytes (Header + int + Padding)。大量小对象必须用struct。
1.1 Struct vs Class 内存深度对比
| 特性 | Class (引用类型) | Struct (值类型) | 备注 |
|---|---|---|---|
| 分配位置 | 堆 (Heap) | 栈 (Stack) 或 嵌入在父对象中 | Class 导致内存碎片和 GC。 |
| 额外开销 (Overhead) | ~16-24 Bytes (Header) | 0 Bytes | Struct 没有对象头。 |
| 引用开销 | 8 Bytes (64 位指针) | 0 Bytes | Class 变量存的是指针,Struct 存的是数据本身。 |
| 数组内存布局 | 指针数组: [Ptr1, Ptr2, ...] | 连续数据: [Data1, Data2, ...] | Struct 数组对 CPU Cache 极其友好。 |
| GC 压力 | 高: 需回收对象头 | 无: 随栈/父对象销毁 | 只要不装箱 (Boxing),Struct 零 GC。 |
2. Unity 对象开销 (空对象)
| 对象类型 | 内存占用 (估算) | 备注 |
|---|---|---|
GameObject (Empty) | ~100 - 200 Bytes | C++ 原生非托管内存 + C# 包装器。 |
Transform | ~400 - 500 Bytes | 含 Position, Rotation, Scale, 矩阵缓存, 父子指针。很重! |
| Empty GameObject | ~0.5 - 1 KB | 一个空的 GameObject + Transform 的总开销。 |
MeshFilter | ~100 Bytes | 仅引用 Mesh。 |
MeshRenderer | ~300 Bytes | 引用材质和 AABB。 |
3. 纹理内存 (Texture Memory)
公式:宽 x 高 x BytesPerPixel
| 格式 | Bits/Pixel | Bytes/Pixel | 1024x1024 大小 | 说明 |
|---|---|---|---|---|
| RGBA 32 bit | 32 | 4 | 4 MB | 无压缩,最高画质。UI/图标常用。 |
| RGB 24 bit | 24 | 3 | 3 MB | 无透明通道。不推荐 (对齐问题)。 |
| RGBA 16 bit | 16 | 2 | 2 MB | 抖动明显 (Dither)。复古风可用。 |
| Alpha 8 | 8 | 1 | 1 MB | 单通道掩码/SDF。 |
| DXT1 / BC1 | 4 | 0.5 | 0.5 MB (1/8) | PC 标准压缩 (无透明/Cutout)。 |
| DXT5 / BC3 | 8 | 1 | 1 MB (1/4) | PC 标准压缩 (全透明)。 |
| BC7 | 8 | 1 | 1 MB (1/4) | PC 高质量压缩 (推荐)。 |
| ASTC 4x4 | 8 | 1 | 1 MB (1/4) | 移动端高质量。 |
| ASTC 6x6 | 3.56 | ~0.45 | 0.45 MB | 移动端均衡 (推荐)。 |
| ASTC 8x8 | 2 | 0.25 | 0.25 MB (1/16) | 移动端低配/特效/背景。 |
4. 网格数据 (Mesh Data)
详细数据请参阅 图形预算 > 模型与骨骼动画性能
5. 音频内存 (Audio Memory)
| 格式 | 压缩比 (近似) | 1 分钟立体声大小 (44.1kHz) | 建议 |
|---|---|---|---|
| PCM (WAV) | 1:1 (无压缩) | ~10 MB | 仅用于极短、极低延迟的音效 (UI 点击)。 |
| ADPCM | ~3.5:1 | ~3 MB | 频繁播放的短音效 (脚步、枪声)。CPU 开销极低。 |
| Vorbis / MP3 | ~10:1 (可变) | ~1 MB | 背景音乐 (BGM)、长语音。CPU 开销较高。 |
3. 程序预算 (Program/CPU Budget)
CPU 预算 (CPU Budget)
Roguelike + 塔防意味着海量实体,CPU 是最大瓶颈。- 同屏单位数: 最大 500 (怪物 + 塔 + 投射物)。
- AI 更新频率:
- 近处怪物 (<20m): 每帧更新。
- 远处怪物 (>20m): 每 3-5 帧更新一次逻辑 (Time Slicing)。
- 不可见怪物: 仅更新位置,暂停动画和物理。
- 物理 (Physics):
- 使用
Physics.Simulate或 DOTS Physics。 - 禁止使用 MeshCollider 对动态物体。全部使用 Sphere/Capsule/Box。
- 使用
- 垃圾回收 (GC):
- 战斗中 禁止
new操作。 - 所有投射物、特效、伤害飘字必须使用 Object Pool。
- 战斗中 禁止
优化技术栈 (Tech Stack)
1. DOTS (Data-Oriented Technology Stack)
对于海量怪物的移动和简单逻辑,考虑使用 ECS (Entities) + Burst Compiler。这是解决千人同屏的终极方案。🧠 DOTS 性能提升原理详解
DOTS 的核心不是”新技术”,而是让代码适配现代 CPU 的物理特性。1. 内存布局:AoS vs SoA
传统 OOP (Array of Structs):| 场景 | OOP (AoS) | ECS (SoA) | 原因 |
|---|---|---|---|
| 遍历 10000 个 Position | 加载整个 Entity (~100 bytes) | 只加载 Position (12 bytes) | 减少 8x 内存读取 |
| Cache Line 利用率 | 每条 Cache Line (64B) 只有 ~12B 有用 | 整条 Cache Line 全部是 Position | 5x 利用率提升 |
| Prefetch 效率 | 随机跳转,Prefetcher 失效 | 顺序访问,Prefetcher 全中 | 100x 速度差距 |
2. Burst Compiler:SIMD 向量化
Burst 将 C# 代码编译为原生机器码,并自动应用 SIMD (Single Instruction, Multiple Data):| 操作 | 普通 C# | Burst + SIMD | 加速比 |
|---|---|---|---|
| 4 个 float 加法 | 4 条指令 | 1 条 SSE/AVX 指令 | 4x |
| 8 个 float 乘法 | 8 条指令 | 1 条 AVX 指令 | 8x |
| 循环 + 条件判断 | 逐个处理 | 自动展开 + 无分支 | 10-50x |
3. Job System:多核并行
Unity 传统 MonoBehaviour 只能跑在主线程。Job System 让逻辑分发到所有 CPU 核心:| CPU 核心数 | MonoBehaviour | Jobs (理论) | 实际加速 |
|---|---|---|---|
| 4 核 | 1x | 4x | ~3x (调度开销) |
| 8 核 | 1x | 8x | ~5-6x |
| 16 核 | 1x | 16x | ~8-10x |
4. 综合性能对比 (实测参考)
场景:移动 10000 个 Entity (每帧更新 Position)| 方案 | 耗时 (每帧) | 对比基准 |
|---|---|---|
| MonoBehaviour + Transform | ~15-25 ms | 100% (基准) |
| 纯 C# 脚本 (无 Burst) | ~5-10 ms | ~50% |
| ECS (无 Burst) | ~2-4 ms | ~20% |
| ECS + Burst | ~0.3-0.8 ms | ~3% |
| ECS + Burst + Jobs (8 核) | ~0.05-0.15 ms | ~0.5% 🚀 |
🚀 结论: DOTS 全栈可以带来 50-200x 的性能提升,核心原因:
- Cache 友好: 连续内存,Prefetch 全中
- SIMD: 一条指令处理多个数据
- 多核: 充分利用现代 CPU 所有核心
- 无 GC: 使用 NativeArray,战斗中零分配
2. GPU Skinning
将骨骼动画计算从 CPU 转移到 GPU Shader,解放 CPU 算力。3. Addressables
资源按需加载,避免启动时加载所有资源导致内存溢出。CPU 微观基准 (CPU Micro-Benchmarks)
以下数据为近似值(基于现代 PC/Console 架构),用于指导代码编写。CPU 下的 Cache Miss:隐形杀手
现代 CPU 极快,瓶颈通常在内存墙 (Memory Wall)。 假设 CPU 主频 3GHz (1 cycle ≈ 0.33ns)。执行 100 万次 操作的理论时间差异:| 存储层级 | 访问延迟 (Cycles) | 访问时间 (ns) | 100 万次访问总耗时 (理想估算) | 类比 |
|---|---|---|---|---|
| L1 Cache | 3 - 4 | 1 ns | 0.5 - 1 ms | 从桌子上拿一张纸 |
| L2 Cache | 10 - 12 | 3 - 4 ns | 3 - 4 ms | 从书架上拿一本书 |
| L3 Cache | 30 - 70 | 10 - 20 ns | 10 - 20 ms | 从隔壁房间拿一本书 |
| Main RAM | 200 - 300+ | 60 - 100 ns | 60 - 100 ms (掉帧!) | 下楼去图书馆拿一本书 |
⚠️ 结论:
- 顺序数组遍历 (Data Oriented): CPU 预取器 (Prefetcher) 极其高效,几乎全中 L1/L2。100 万次遍历 = 1-2ms。
- 随机指针跳转 (OOP/Linked List): 也就是传统
class引用满天飞。每次跳转都可能是 Cache Miss。100 万次跳转 = 100ms (直接卡死由 60FPS 跌至 10FPS)。- 这就是为什么我们要用 Struct 数组和 Object Pool (保持内存连续)。
Unity 常见 API 热点估算
基于 PC 平台 (i7 级别单核) 的粗略估算。移动端请将时间 x5 - x10。| 操作 | 近似耗时 (每次) | 1000 次耗时 | 危险等级 | 建议 |
|---|---|---|---|---|
transform.position (get/set) | ~20 ns | 0.02 ms | 🟢 安全 | 稍微有点开销 (C++到 C# marshalling),但在热循环中还是尽量缓存。 |
GetComponent<T>() | ~100 - 300 ns | 0.1 - 0.3 ms | 🟡 留意 | 不要在 Update 里调。在 Awake 缓存。 |
GameObject.Instantiate | 0.1 ms - 5.0 ms+ | 100ms+ (卡顿) | 🔴 致命 | 必须用对象池。包含复杂组件/子物体时极慢。 |
Destroy() | ~0.05 ms+ | 50ms+ | 🟠 严重 | 触发 GC,导致后续卡顿。 |
FindObjectOfType | 线性遍历全场景 | N/A | 🔴 禁止 | 随着场景物体增多呈线性增长。运行时绝不要用。 |
Camera.main | ~2 ms (老版本) | N/A | 🟠 慢 | 内部是 FindByTag。新版 Unity 有优化,但仍建议手动缓存 Singleton。 |
Debug.Log | 0.1 ms+ | 100ms+ | 🔴 卡顿 | 涉及字符串拼接、堆栈抓取、IO。正式包必须剔除。 |
Physics.Raycast | ~1 - 5 us | 1 - 5 ms | 🟡 适中 | 取决于场景碰撞体数量。尽量用 LayerMask 过滤。 |
GC (垃圾回收) 开销
.NET/Mono 的 GC 是**“Stop The World”**模式,一旦触发,主线程暂停。| GC 类型 | 近似耗时 | 触发条件 | 危险等级 |
|---|---|---|---|
| Gen0 (快速回收) | 0.5 - 2 ms | 临时小对象 (如 lambda 捕获、临时字符串) | 🟡 频繁则累积 |
| Gen1 (中等回收) | 2 - 10 ms | 存活过一次 Gen0 的对象 | 🟠 明显卡顿 |
| Gen2 (完整回收) | 10 - 100+ ms | 长期生存的大对象、大数组 | 🔴 灾难性帧卡顿 |
⚠️ 重要结论:
- 每帧分配 1KB 内存:约每秒触发 1 次 Gen0 GC (~1-2ms 卡顿)。
- 战斗中禁止
new:所有对象必须预分配或使用对象池。- String 拼接是 GC 大户:每次
"a" + "b"都产生新对象。
IO 操作开销
IO 是毫秒级操作,绝不能在主线程执行。| 操作类型 | 近似耗时 | 备注 |
|---|---|---|
| SSD 随机读 (4KB) | 0.05 - 0.15 ms | NVMe SSD,极快但仍远超 CPU 运算。 |
| HDD 随机读 (4KB) | 5 - 15 ms | 机械寻道,一次读取 = 掉 1 帧。 |
| 文件打开 (fopen) | 0.1 - 1 ms | 系统调用开销。 |
| 小文件完整读取 (1KB) | 0.1 - 0.5 ms (SSD) | 文件越大越慢。 |
| 大文件读取 (10MB) | 50 - 200+ ms | 必须异步 + 流式加载。 |
| PlayerPrefs.Save() | 5 - 50 ms | 同步写磁盘。只在关键点调用。 |
| JSON 反序列化 (1KB) | 0.1 - 0.5 ms | 取决于解析器复杂度。 |
| JSON 反序列化 (100KB) | 10 - 50 ms | 考虑换用 MessagePack/Protobuf。 |
🔴 核心原则: 所有 IO 必须在后台线程 + 异步回调中执行。
字符串操作开销
字符串在 C# 中是不可变的,每次修改都创建新对象。| 操作 | 近似耗时 | GC 分配 | 建议 |
|---|---|---|---|
短字符串拼接 "a" + "b" | ~50 - 100 ns | 是 (新对象) | 少量可接受,循环中禁止。 |
| 循环拼接 1000 次 | 1 - 5 ms + GC | 1000 个临时对象 | 🔴 必须用 StringBuilder。 |
StringBuilder.Append x1000 | ~0.1 - 0.3 ms | 预分配则无 | ✅ 正确做法。 |
string.Format | ~200 - 500 ns | 是 (boxing 可能) | 用 $"" 插值或 StringBuilder。 |
int.ToString() | ~100 - 200 ns | 是 | 缓存常用数字字符串 (如 “0”-“99”)。 |
string.Contains/IndexOf | O(n) 线性 | 无 | 长字符串慎用热循环。 |
反射 (Reflection) 开销
反射是运行时元数据查询,极其昂贵。| 操作 | 近似耗时 | 对比直接调用 | 建议 |
|---|---|---|---|
GetType() | ~5 - 20 ns | ~1x | 相对廉价。 |
typeof(T) | ~1 ns | 编译期确定 | ✅ 尽量用这个。 |
MethodInfo.Invoke | 1 - 10 us | 100x - 1000x 慢 | 🔴 热路径禁止。 |
GetField/GetProperty | ~100 - 500 ns | 10x - 50x 慢 | 缓存 FieldInfo/PropertyInfo。 |
Activator.CreateInstance | 1 - 5 us | 50x - 200x 慢 | 用泛型工厂 new T() 或缓存。 |
替代方案: Expression Trees 编译、Source Generators、或预生成代码。
数学运算开销 (CPU)
在 CPU 上,浮点运算已非常快,但除法和超越函数仍有显著开销。| 操作 | 近似耗时 (Cycles) | 备注 |
|---|---|---|
加/减/乘 +, -, * | 1 - 3 | 极快,流水线优化。 |
除法 / | 10 - 20 | 比乘法慢 5-10 倍。用乘法替代 (如 x * 0.5f 代替 x / 2)。 |
平方根 sqrt | 10 - 20 | 硬件指令,尚可。rsqrt (倒数平方根) 更快。 |
三角函数 sin, cos | 50 - 100 | 查表或泰勒展开近似可提速。 |
指数/对数 exp, log | 50 - 100 | 同上。 |
Mathf.Lerp | ~5 - 10 | 内部是 a + (b-a) * t。 |
Vector3.Normalize | ~20 - 50 | 包含 sqrt。如只需方向比较,用 sqrMagnitude。 |
网络操作延迟
网络是变化最大的不确定因素。| 操作 | 典型延迟 | 备注 |
|---|---|---|
| 本地回环 (localhost) | < 0.1 ms | 测试用。 |
| 局域网 (LAN) | 0.1 - 1 ms | 理想情况。 |
| 国内跨省 (同运营商) | 20 - 50 ms | 正常延迟。 |
| 国内跨运营商 | 50 - 150 ms | 可能需要加速器。 |
| 跨国 (中美) | 150 - 300+ ms | 物理限制 (光速)。 |
| DNS 查询 | 50 - 200 ms | 首次连接。缓存 IP。 |
| TCP 握手 (建连) | RTT x 1.5 | 约 1.5 倍往返时间。 |
| TLS 握手 (HTTPS) | RTT x 2-3 | 额外 1-2 次往返。复用连接! |
线程与同步开销
多线程不是免费的午餐。| 操作 | 近似耗时 | 备注 |
|---|---|---|
| 创建新线程 | 0.1 - 1+ ms | 极其昂贵!必须用线程池。 |
| 线程池任务调度 | 1 - 10 us | 比创建线程快 100 倍。 |
lock (无竞争) | 10 - 50 ns | 几乎免费。 |
lock (有竞争) | 1 us - 数 ms | 线程阻塞等待。减少锁粒度。 |
Interlocked 原子操作 | 10 - 50 ns | 无锁首选,但仍有 Cache Line 同步开销。 |
| 上下文切换 (Context Switch) | 1 - 10 us | 线程过多时 CPU 疲于切换。 |
Task.Run (调度) | ~100 - 500 ns | 加入线程池队列。 |
async/await (无阻塞) | ~50 - 100 ns | 状态机开销。相比同步代码略慢但可接受。 |
4. 图形预算 (Graphics/GPU Budget)
GPU 预算 (GPU Budget)
- Draw Calls (Batches): < 2000。
- 必须启用 GPU Instancing (大量同屏怪物)。
- UI 图集必须合并,减少 Draw Call。
- Triangle Count: < 1.5M (全场景)。
- 主角: 15k - 20k。
- 杂兵: 1.5k - 3k (使用 LOD)。
- Boss: 10k - 15k。
- Overdraw:
- 特效粒子限制透明层数。
- 避免全屏后处理叠加过多。
GPU 微观基准 (Graphics Micro-Benchmarks)
以下数据为近似值(基于现代 PC/Console 架构),用于指导 Shader 编写。Shader 指令开销参考 (GPU)
GPU 是大规模并行架构,吞吐量 (Throughput) 比延迟更重要。但当指令产生依赖或特定复杂运算时,开销会指数上升。| 操作类型 | 指令示例 | 近似开销 (Cycles) | 备注 |
|---|---|---|---|
| 基础运算 | +, -, *, madd | 1 | 现代 GPU 通常一个周期能处理 float4 的乘加。 |
| 插值/透视 | varying 插值 | 1-2 | 硬件光栅化阶段处理,通常免费。 |
| 倒数/平方根 | rcp, rsqrt | 2-4 | 特殊功能单元 (SFU) 处理。 |
| 三角函数 | sin, cos | 4-8 | SFU 处理。精度要求高时更慢。 |
| 复杂函数 | pow, log, exp, atan | 8-16+ | 昂贵。在像素 Shader 中慎用,尽量用拟合公式近似。 |
| 纹理采样 | tex2D, Sample | Latency: 400-800 | 延迟极高,但 GPU 会在等待时切换线程。依赖纹理读取(用一次采样的结果去采样下一次)会吃满延迟,导致 GPU 停顿。 |
| 分支判断 | if, loop | 1 - ??? | 如果同个 Warp/Wavefront 内所有像素走向相同分支,开销很小;如果分歧 (Divergence),则两边都要执行(开销 double)。 |
| Discard | clip, discard | Variable | 会破坏 Early-Z 优化,可能导致 Overdraw 暴增。 |
| Normal Mapping | 1x tex2D + TBN 变换 | +1 采样 + ~10 ALU | 额外一次采样 + 切线空间转换。性价比极高的细节提升。 |
| Triplanar Mapping | 3x tex2D + 混合权重 | +3 采样 + ~20 ALU | 三个轴向各采样一次并混合。开销是普通 UV 的 3 倍,用于无 UV 展开的地形/程序化模型。可优化为 2-sample 近似。 |
| Parallax Mapping | 多次 tex2D (步进) | +4-16 采样 | 视差效果需多次深度查询。移动端慎用。Steep Parallax 更贵。 |
| PBR (标准) | Albedo + Normal + MRAO | +3-4 采样 + ~50 ALU | 金属度/粗糙度/AO 贴图 + BRDF 计算。现代游戏标配,但开销不低。 |
| 屏幕空间反射 (SSR) | Ray Marching | 极高 (数百 ALU) | 逐像素射线步进。仅用于高端画质。 |
光照模型
| 光照类型 | 计算内容 | 近似开销 | 备注 |
|---|---|---|---|
| Unlit (无光) | 纯颜色/纹理 | ~5 ALU | 最便宜,用于 UI、特效、卡通描边。 |
| Lambert (漫反射) | N·L | ~10 ALU | 最简单的光照模型。 |
| Blinn-Phong | Lambert + (N·H)^n | ~20-30 ALU | 经典高光模型。 |
| GGX/Cook-Torrance (PBR) | D + F + G 项 | ~50-100 ALU | 正确的物理反射。金属/粗糙度工作流。 |
| 各向异性高光 | 额外切线计算 | ~30-50 ALU | 头发、拉丝金属。 |
| SSS (次表面散射) | 多次采样/预积分 LUT | ~50-200 ALU | 皮肤、蜡烛、树叶。实时版通常用 LUT 近似。 |
| 卡通着色 (Cel) | 离散化光照 + 描边 | ~20-40 ALU | step/smoothstep 分段。 |
阴影技术
| 阴影类型 | 采样次数 | 近似开销 | 备注 |
|---|---|---|---|
| 硬阴影 (1x PCF) | 1 | +1 采样 | 锯齿明显。 |
| 软阴影 (4x PCF) | 4 | +4 采样 + ~20 ALU | 常用质量档。 |
| 软阴影 (16x PCF) | 16 | +16 采样 + ~40 ALU | Poisson Disk 采样。 |
| CSM (级联阴影) | 取决于级数 | +1-4 采样/级 + 级联判断 | 开放世界必备。远景 LOD 阴影。 |
| Contact Shadow | 射线步进 (4-16 步) | +4-16 采样 | 物体接触处的细节阴影。 |
| PCSS (软阴影) | 查找 + 采样 | 极高 | 真实半影效果,仅高端。 |
环境与间接光
| 技术 | 采样/计算 | 近似开销 | 备注 |
|---|---|---|---|
| Cubemap 反射 | 1x texCUBE | +1 采样 | 简单环境映射。 |
| Cubemap + Mip 模糊 | 1x texCUBE (LOD) | +1 采样 | 粗糙度驱动模糊,PBR 标配。 |
| 平面反射 | 额外 RT 渲染 | Draw Call x2 | 复制场景渲染开销!谨慎使用。 |
| Lightmap 采样 | 1-2x tex2D | +1-2 采样 | 静态光照,极高性价比。 |
| Light Probe (SH) | 球谐计算 | ~20-30 ALU | 动态物体间接光。无采样。 |
| Reflection Probe | 1x texCUBE | +1 采样 | 局部反射探针。 |
常用特效技术
| 特效 | 实现方式 | 近似开销 | 备注 |
|---|---|---|---|
| Fresnel (边缘光) | 1 - N·V | ~5 ALU | 几乎免费的轮廓效果。 |
| Rim Light (边缘高光) | Fresnel + pow | ~10 ALU | 增强轮廓感。 |
| Dissolve (溶解) | 噪声采样 + clip | +1 采样 + ~10 ALU | 消融/出现效果。 |
| UV 动画 | 时间偏移 UV | ~5 ALU | 流动效果。 |
| UV 扭曲 | 噪声采样偏移 UV | +1 采样 + ~10 ALU | 热浪、水面扭曲。 |
| 顶点动画 | VS 中偏移顶点 | ~10-30 ALU (VS) | 旗帜、草、水面波动。便宜因为在 VS。 |
| GPU 粒子 | Compute/VS 模拟 | 取决于系统 | 比 CPU 粒子快 10-100x。 |
| Outline (描边) | 背面挤出 / 后处理 | +1 Pass 或 ~20 ALU | 卡通渲染常用。 |
高级渲染技术
| 技术 | 开销 | 备注 |
|---|---|---|
| Tessellation | 极高 (硬件依赖) | 动态细分。移动端不支持/极慢。 |
| Displacement Mapping | Tessellation + 采样 | 同上 + 纹理开销。 |
| Decal (投影贴花) | +1 Pass 或延迟读取 | 弹孔、血迹。Deferred 友好。 |
| Volumetric Fog | 极高 (射线步进) | 3D 体积雾。需 8-64 步,降分辨率渲染。 |
| Bent Normal / AO | +1 采样 | 预烘焙数据,增强间接光遮蔽感。 |
透明与混合
| 技术 | 开销 | 备注 |
|---|---|---|
| Alpha Test (Cutout) | ~5 ALU | clip/discard,Early-Z 破坏风险。树叶、铁丝网。 |
| Alpha Blend | 带宽开销 | 读+写 Color Buffer。Overdraw 累积! |
| Premultiplied Alpha | 同 Blend | 避免边缘黑边。 |
| Additive Blend | 同 Blend | 发光、火焰。叠加更亮。 |
| OIT (顺序无关透明) | 极高 | 多 Pass/链表。正确排序透明层。研发中技术。 |
| Dithered Transparency | ~10 ALU | 图案化 discard,伪透明,保留 Z-buffer。 |
Overdraw 与 Fill Rate:像素级性能
Overdraw 指同一像素被多次绘制。每多画一次,就多执行一遍 Fragment Shader。分辨率与像素数量
| 分辨率 | 像素数量 | 备注 |
|---|---|---|
| 720p (1280x720) | 0.92M | 约 100 万像素 |
| 1080p (1920x1080) | 2.07M | 约 200 万像素 |
| 1440p (2560x1440) | 3.69M | 约 370 万像素 |
| 4K (3840x2160) | 8.29M | 约 830 万像素 |
Overdraw 开销计算
假设 1080p (约 200 万像素),Shader 每像素 50 ALU 指令:| Overdraw 倍率 | 实际渲染像素数 | Shader 执行次数 | 相对基准 |
|---|---|---|---|
| 1x (理想) | 2M | 100M ALU ops | 100% |
| 2x (轻度) | 4M | 200M ALU ops | 200% |
| 3x (中度) | 6M | 300M ALU ops | 300% |
| 5x (严重,粒子/UI) | 10M | 500M ALU ops | 500% ⚠️ |
| 10x+ (灾难) | 20M+ | 1B+ ALU ops | 爆炸 🔴 |
🔴 实战案例:
- 全屏粒子特效 (火焰、烟雾): 每层粒子都是一次 Overdraw。10 层粒子 = 10x Overdraw。
- 半透明 UI 叠加: 血条 + 技能栏 + 弹窗,每层都要混合。
- 后处理链: Bloom + DOF + 色调映射,每个 Pass 都是一次全屏绘制。
Fill Rate (填充率) 预算
Fill Rate = GPU 每秒能填充的像素数 (包含 Shader 计算)。| GPU 档次 | 典型 Fill Rate | 1080p @ 60FPS 裕量 |
|---|---|---|
| GTX 1060 / RX 580 | ~50 GPixel/s | 2M × 60 = 120M/s → 裕量 ~400x |
| RTX 3060 / RX 6600 | ~100 GPixel/s | 裕量 ~800x |
| 移动端 (Mali-G78) | ~5-10 GPixel/s | 裕量 ~40-80x (极紧张!) |
⚠️ 注意: 上面是理论峰值。复杂 Shader (PBR + 多贴图) 会显著降低实际 Fill Rate。移动端尤其脆弱!
带宽开销
每个像素的显存带宽消耗:| 操作 | 每像素带宽 | 1080p 全屏 (2M 像素) |
|---|---|---|
| 读取 Color Buffer (RGBA8) | 4 bytes | 8 MB |
| 写入 Color Buffer | 4 bytes | 8 MB |
| 读取 Depth Buffer (24-bit) | 3-4 bytes | 6-8 MB |
| 1 张 1024x1024 纹理采样 | 依赖 Cache | Miss 时吃带宽 |
| HDR Buffer (RGBA16F) | 8 bytes | 16 MB |
一次全屏后处理 Pass (读+写): 约 16-32 MB 带宽。 GTX 1060 带宽 ~192 GB/s → 一帧 16.6ms 内可传输 ~3.2 GB。 理论上能支持 ~100 次全屏 Pass,但加上其他开销,实际 10-20 次就是极限。
常见后处理效果开销
基于 1080p @ GTX 1060 级别的近似 GPU 时间:| 后处理效果 | Pass 数量 | GPU 耗时 (近似) | 危险等级 | 备注 |
|---|---|---|---|---|
| Blit (简单拷贝) | 1 | 0.1 - 0.2 ms | 🟢 廉价 | 纯带宽开销。 |
| Gamma/Tonemapping | 1 | 0.1 - 0.3 ms | 🟢 廉价 | 每像素几条 ALU。 |
| FXAA | 1 | 0.3 - 0.5 ms | 🟢 廉价 | 快速边缘检测 + 混合。 |
| SMAA (1x) | 2-3 | 0.5 - 1.0 ms | 🟡 适中 | 多 Pass,效果更好。 |
| TAA | 1-2 | 0.5 - 1.5 ms | 🟡 适中 | 需要 Motion Vector + History Buffer。 |
| Vignette/Film Grain | 1 | 0.1 - 0.2 ms | 🟢 廉价 | 几乎免费的风格化。 |
| Color Grading (LUT) | 1 | 0.2 - 0.4 ms | 🟢 廉价 | 3D LUT 查表,一次采样。 |
| Bloom (基础) | 4-6 | 1.0 - 2.0 ms | 🟡 适中 | 降采样 + 多级模糊 + 合成。 |
| Bloom (高质量) | 8-12 | 2.0 - 4.0 ms | 🟠 较重 | 更多 Mip 级别和迭代。 |
| 景深 DOF (简单) | 2-3 | 1.0 - 2.0 ms | 🟡 适中 | CoC 计算 + 模糊。 |
| 景深 DOF (散景) | 4-8 | 3.0 - 6.0 ms | 🔴 昂贵 | 真实光圈形状需要更多采样。 |
| 运动模糊 | 1-2 | 0.5 - 1.5 ms | 🟡 适中 | 依赖 Motion Vector 质量。 |
| SSAO (简单) | 2-4 | 1.5 - 3.0 ms | 🟠 较重 | 多次深度采样。 |
| SSAO (HBAO+/GTAO) | 4-6 | 3.0 - 5.0 ms | 🔴 昂贵 | 高质量环境光遮蔽。 |
| SSR (屏幕空间反射) | 4-8 | 4.0 - 10.0 ms | 🔴 极贵 | Ray Marching + 多级模糊。移动端禁用。 |
| 体积光/God Rays | 4-8 | 2.0 - 5.0 ms | 🔴 昂贵 | 射线步进或径向模糊。 |
| 全屏模糊 (Gaussian) | 2 | 0.5 - 1.0 ms | 🟡 适中 | 分离式卷积 (H+V)。 |
| Kawase Blur | 4-6 | 0.8 - 1.5 ms | 🟡 适中 | 比高斯更高效的迭代模糊。 |
📊 典型后处理栈开销:16.6ms 帧预算下,高端后处理可能吃掉 50-90% 的 GPU 时间!
配置 效果组合 总 GPU 耗时 极简 (移动端) Tonemapping + FXAA + Vignette 0.5 - 1.0 ms 标准 (PC) Bloom + TAA + Color Grading + Vignette 2.0 - 4.0 ms 高端 (PC) Bloom + SSAO + SSR + TAA + DOF 8.0 - 15.0 ms ⚠️
GPU Instancing 性能对比
GPU Instancing 让相同 Mesh + Material 的物体一次 Draw Call 批量渲染,而非逐个提交。极端场景测试 (渲染 10,000 个简单立方体)
| 方案 | Draw Calls | CPU 提交时间 | GPU 渲染时间 | 总帧时间 |
|---|---|---|---|---|
| 无 Instancing (逐个渲染) | 10,000 | ~15-25 ms | ~2-5 ms | 20-30 ms (33 FPS) |
| GPU Instancing | 1 | ~0.1-0.3 ms | ~2-5 ms | 2-6 ms (166+ FPS) |
| 性能差异 | 10000x 减少 | 50-100x 提升 | 相同 | 5-10x 总提升 |
不同物体数量的 Draw Call 对比
| 同类物体数量 | 无 Instancing | GPU Instancing | SRP Batcher |
|---|---|---|---|
| 100 | 100 DC | 1 DC | 1-10 DC |
| 1,000 | 1,000 DC | 1 DC | 10-50 DC |
| 10,000 | 10,000 DC ⚠️ | 1 DC | 50-200 DC |
| 100,000 | 卡死 🔴 | 1-10 DC | 500+ DC |
为什么 Draw Call 这么贵?
每个 Draw Call 涉及:- CPU→GPU 通信:状态切换、缓冲区绑定 (~5-50 us/次)
- 驱动验证:参数校验、着色器编译检查
- 命令队列:提交渲染命令到 GPU
经验法则:
- Draw Call 安全上限: PC ~2000-3000,移动端 ~200-500
- 超过上限: CPU 成为瓶颈,GPU 反而在等待命令
Instancing 使用条件
| 条件 | 是否支持 Instancing |
|---|---|
| 相同 Mesh + 相同 Material | ✅ 完美支持 |
| 相同 Mesh + 不同颜色 (MaterialPropertyBlock) | ✅ 支持 |
| 不同 Mesh | ❌ 无法合批 |
| 不同 Material | ❌ 无法合批 |
| Skinned Mesh (骨骼动画) | ❌ 需 GPU Skinning 特殊处理 |
| 透明物体 (需排序) | ⚠️ 部分支持,排序可能破坏批次 |
实战参考 (塔防游戏场景)
| 场景内容 | 无 Instancing | GPU Instancing | 优化效果 |
|---|---|---|---|
| 200 个相同小兵 | 200 DC | 1 DC | ✅ 极佳 |
| 50 种不同怪物各 10 只 | 500 DC | 50 DC | ✅ 良好 (每种 1 DC) |
| 1000 颗相同子弹 | 1000 DC | 1 DC | ✅ 极佳 |
| 500 个相同草丛 | 500 DC | 1 DC | ✅ 极佳 |
| 100 个不同道具 | 100 DC | 100 DC | ❌ 无法优化 |
🐢 100 万次运算对比 (GPU):
- 1M 次乘加 (
x * y + z): 几乎瞬间完成 (受限于显存带宽或 ALU 峰值)。- 1M 次
\text{pow}(x, y): 比乘加慢 10-20 倍。- 1M 次依赖纹理采样: 如果发生 Cache Miss,将导致 GPU 核心大量闲置等待 DRAM。
模型与骨骼动画性能
顶点/三角形数量开销
| 模型类型 | 推荐顶点数 | 推荐面数 | 备注 |
|---|---|---|---|
| UI 图标/2D | < 100 | < 50 | 精灵已足够。 |
| 粒子/子弹 | 4-50 | 2-20 | Billboard 或简单形状。 |
| 杂兵 (大量同屏) | 500 - 1.5K | 300 - 1K | 对 Instancing 友好。LOD 降至 100-300。 |
| 精英怪 | 2K - 5K | 1K - 3K | 中等细节。 |
| Boss | 5K - 15K | 3K - 10K | 可添加更多细节。 |
| 主角 (玩家角色) | 10K - 25K | 5K - 15K | 最近距离观看,需要细节。 |
| 环境物体 (LOD0) | 500 - 5K | 300 - 3K | 根据屏幕占比调整。 |
| 地形 Tile | 视复杂度 | 动态 LOD | 使用 Unity Terrain LOD 或自定义。 |
全场景三角形预算
| 平台 | 目标总三角形 | 备注 |
|---|---|---|
| PC (GTX 1060) | < 1.5M | 60FPS 目标。 |
| PC (高端) | < 5M | 可更激进。 |
| Steam Deck | < 1M | 功耗敏感。 |
| 移动端 (高端) | < 500K | 严格控制。 |
| 移动端 (中低端) | < 200K | 非常严格! |
骨骼数量开销 (CPU Skinning)
每个骨骼需要矩阵运算 + 顶点变换,CPU Skinning 开销与骨骼数成正比。| 骨骼数量 | CPU 开销估算 (每角色) | 建议用途 |
|---|---|---|
| 1-10 骨骼 | ~0.01 ms | 简单道具、武器。 |
| 20-30 骨骼 | ~0.02-0.05 ms | 基础人形怪物。 |
| 50-60 骨骼 | ~0.05-0.1 ms | 标准人形 + 手指/面部。 |
| 100+ 骨骼 | ~0.1-0.3 ms | 复杂角色。主角专用。 |
| 200+ 骨骼 | ~0.3-1.0+ ms | 极端情况。避免! |
同屏骨骼角色数量上限
假设目标:每帧 Skinning 总开销 < 3ms (留给其他 CPU 工作)| 每角色骨骼数 | CPU Skinning 上限 | GPU Skinning 上限 |
|---|---|---|
| 30 骨骼 | ~60-100 角色 | 500+ 角色 |
| 60 骨骼 | ~30-50 角色 | 300+ 角色 |
| 100 骨骼 | ~15-30 角色 | 150+ 角色 |
🚀 GPU Skinning 的优势:
- 骨骼矩阵计算移至 GPU Shader
- CPU 只需上传矩阵数据 (每角色 ~几 KB)
- 同屏角色数可提升 5-10x
骨骼权重 (Bones per Vertex)
每个顶点受多少骨骼影响:| Quality 设置 | 权重数 | 开销 | 效果 |
|---|---|---|---|
| 1 Bone | 1 | 最快 | 刚性变形,关节处有裂缝。 |
| 2 Bones | 2 | +50% | 基本平滑。移动端推荐。 |
| 4 Bones (默认) | 4 | +100% | 良好平滑。PC 标准。 |
| Unlimited | 无限 | 极高 | 电影级品质。实时游戏避免。 |
Mesh 数据各分量开销
每个顶点的内存占用:| 顶点属性 | 大小 (bytes) | 备注 |
|---|---|---|
| Position | 12 (float3) | 必须。 |
| Normal | 12 (float3) | 光照必须。可压缩为 4 bytes (packed)。 |
| Tangent | 16 (float4) | Normal Map 必须。 |
| UV0 | 8 (float2) | 主纹理。 |
| UV1 | 8 (float2) | Lightmap。 |
| Color | 4 (RGBA8) | 顶点色。可选。 |
| BoneWeights | 16-32 | 4 骨骼 = 16 bytes 索引 + 16 bytes 权重。 |
| 总计 (典型) | 50-80 bytes/顶点 |
模型数据量示例
| 模型 | 顶点数 | 每顶点大小 | 内存占用 |
|---|---|---|---|
| 杂兵 (1K 顶点) | 1,000 | 60 bytes | 60 KB |
| 主角 (20K 顶点) | 20,000 | 80 bytes | 1.6 MB |
| Boss (15K 顶点) | 15,000 | 70 bytes | 1 MB |
| 100 个杂兵 (Instanced) | 1,000 | 60 bytes | 60 KB (共享) |
| 100 个杂兵 (非 Instanced) | 100,000 | 60 bytes | 6 MB ⚠️ |