Skip to main content
摘要:本文聚焦「性能预算与优化标准」,梳理核心概念、关键方法与落地实践。
为了确保游戏在目标平台(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 个 intclass 实例,其实际占用可能高达 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):
ECS (Struct of Arrays):
为什么 SoA 更快? 参考 CPU 下的 Cache Miss
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 涉及:
  1. CPU→GPU 通信:状态切换、缓冲区绑定 (~5-50 us/次)
  2. 驱动验证:参数校验、着色器编译检查
  3. 命令队列:提交渲染命令到 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
骨骼权重 (Bones per Vertex)
每个顶点受多少骨骼影响:
Mesh 数据各分量开销
每个顶点的内存占用:
模型数据量示例