摘要:本文聚焦「性能预算与优化标准」,梳理核心概念、关键方法与落地实践。为了确保游戏在目标平台(PC 中低配 / Steam Deck / 潜在的主机)上稳定运行 60FPS,必须严格执行此预算。
1. 概述与目标 (Overview & Targets)
目标平台规格 (Target Specs)
- 基准平台: GTX 1060 / Steam Deck.
- 目标帧率: 60 FPS (16.6ms per frame).
- 分辨率: 1080p.
性能分级策略 (LOD Policy)
时间感知速查表
将各种操作放入同一个时间尺度中理解: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#)
⚠️ 小对象陷阱: 创建一个只包含 1 个int的class实例,其实际占用可能高达 24-32 bytes (Header + int + Padding)。大量小对象必须用struct。
1.1 Struct vs Class 内存深度对比
内存布局示例 (含 2 个 int 的对象):
2. Unity 对象开销 (空对象)
3. 纹理内存 (Texture Memory)
公式:宽 x 高 x BytesPerPixel
4. 网格数据 (Mesh Data)
详细数据请参阅 图形预算 > 模型与骨骼动画性能
5. 音频内存 (Audio Memory)
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):2. Burst Compiler:SIMD 向量化
Burst 将 C# 代码编译为原生机器码,并自动应用 SIMD (Single Instruction, Multiple Data):3. Job System:多核并行
Unity 传统 MonoBehaviour 只能跑在主线程。Job System 让逻辑分发到所有 CPU 核心:4. 综合性能对比 (实测参考)
场景:移动 10000 个 Entity (每帧更新 Position)🚀 结论: 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 万次 操作的理论时间差异:⚠️ 结论:
- 顺序数组遍历 (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。GC (垃圾回收) 开销
.NET/Mono 的 GC 是**“Stop The World”**模式,一旦触发,主线程暂停。⚠️ 重要结论:
- 每帧分配 1KB 内存:约每秒触发 1 次 Gen0 GC (~1-2ms 卡顿)。
- 战斗中禁止
new:所有对象必须预分配或使用对象池。- String 拼接是 GC 大户:每次
"a" + "b"都产生新对象。
IO 操作开销
IO 是毫秒级操作,绝不能在主线程执行。🔴 核心原则: 所有 IO 必须在后台线程 + 异步回调中执行。
字符串操作开销
字符串在 C# 中是不可变的,每次修改都创建新对象。反射 (Reflection) 开销
反射是运行时元数据查询,极其昂贵。替代方案: Expression Trees 编译、Source Generators、或预生成代码。
数学运算开销 (CPU)
在 CPU 上,浮点运算已非常快,但除法和超越函数仍有显著开销。网络操作延迟
网络是变化最大的不确定因素。线程与同步开销
多线程不是免费的午餐。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) 比延迟更重要。但当指令产生依赖或特定复杂运算时,开销会指数上升。光照模型
阴影技术
环境与间接光
常用特效技术
高级渲染技术
透明与混合
Overdraw 与 Fill Rate:像素级性能
Overdraw 指同一像素被多次绘制。每多画一次,就多执行一遍 Fragment Shader。分辨率与像素数量
Overdraw 开销计算
假设 1080p (约 200 万像素),Shader 每像素 50 ALU 指令:🔴 实战案例:
- 全屏粒子特效 (火焰、烟雾): 每层粒子都是一次 Overdraw。10 层粒子 = 10x Overdraw。
- 半透明 UI 叠加: 血条 + 技能栏 + 弹窗,每层都要混合。
- 后处理链: Bloom + DOF + 色调映射,每个 Pass 都是一次全屏绘制。
Fill Rate (填充率) 预算
Fill Rate = GPU 每秒能填充的像素数 (包含 Shader 计算)。⚠️ 注意: 上面是理论峰值。复杂 Shader (PBR + 多贴图) 会显著降低实际 Fill Rate。移动端尤其脆弱!
带宽开销
每个像素的显存带宽消耗:一次全屏后处理 Pass (读+写): 约 16-32 MB 带宽。 GTX 1060 带宽 ~192 GB/s → 一帧 16.6ms 内可传输 ~3.2 GB。 理论上能支持 ~100 次全屏 Pass,但加上其他开销,实际 10-20 次就是极限。
常见后处理效果开销
基于 1080p @ GTX 1060 级别的近似 GPU 时间:📊 典型后处理栈开销:16.6ms 帧预算下,高端后处理可能吃掉 50-90% 的 GPU 时间!
GPU Instancing 性能对比
GPU Instancing 让相同 Mesh + Material 的物体一次 Draw Call 批量渲染,而非逐个提交。极端场景测试 (渲染 10,000 个简单立方体)
不同物体数量的 Draw Call 对比
为什么 Draw Call 这么贵?
每个 Draw Call 涉及:- CPU→GPU 通信:状态切换、缓冲区绑定 (~5-50 us/次)
- 驱动验证:参数校验、着色器编译检查
- 命令队列:提交渲染命令到 GPU
经验法则:
- Draw Call 安全上限: PC ~2000-3000,移动端 ~200-500
- 超过上限: CPU 成为瓶颈,GPU 反而在等待命令
Instancing 使用条件
实战参考 (塔防游戏场景)
🐢 100 万次运算对比 (GPU):
- 1M 次乘加 (
x * y + z): 几乎瞬间完成 (受限于显存带宽或 ALU 峰值)。- 1M 次
pow(x, y): 比乘加慢 10-20 倍。- 1M 次依赖纹理采样: 如果发生 Cache Miss,将导致 GPU 核心大量闲置等待 DRAM。
模型与骨骼动画性能
顶点/三角形数量开销
全场景三角形预算
骨骼数量开销 (CPU Skinning)
每个骨骼需要矩阵运算 + 顶点变换,CPU Skinning 开销与骨骼数成正比。同屏骨骼角色数量上限
假设目标:每帧 Skinning 总开销 < 3ms (留给其他 CPU 工作)🚀 GPU Skinning 的优势:
- 骨骼矩阵计算移至 GPU Shader
- CPU 只需上传矩阵数据 (每角色 ~几 KB)
- 同屏角色数可提升 5-10x