How to Develop a Game
Frame
PublishDate: 2025-06-01 | CreateDate: 2025-06-01 | LastModify: 2025-06-01 | Creator:ljf12825

在Unity中,Frame(帧)是游戏运行的基本时间单位

什么是Frame

Frame:指游戏每渲染并更新一次画面所经历的完整周期 一个Frame包含了物理模拟、逻辑更新、渲染提交等多个阶段

Frame的生命周期

[Input] -> [Physics] -> [Update] -> [AI/Animator] -> [LateUpdate] -> [Rendering] -> [Present]

详见Scripts

不同帧的分类

帧类型描述
逻辑帧(Update 帧)每帧都会执行的脚本逻辑
物理帧(FixedUpdate)固定时间调用一次,与帧率无关
渲染帧Unity 渲染一次画面

Frame与多线程

Unity中每一帧可以大致分为如下几个阶段:

Frame 开始

├─ Script UpdateMonoBehaviour Update
├─ FixedUpdate(每 N 帧触发)
├─ Animation Update
├─ Physics Update
├─ AI Navigation Update
├─ Culling(剔除)
├─ Rendering Setup

├─ Rendering(提交 DrawCalls  GPU

└─ EndFrame

这些阶段会部分并行执行(也就是说,并非所有都是主线程执行的)

多线程的参与

线程作用举例
主线程(Main Thread)执行大部分脚本、生命周期函数、UI、调度渲染Update、LateUpdate、OnGUI、Animator
Job System 线程池执行并行任务,如变换更新、骨骼动画、AI、路径查找DOTS Jobs、Transform System
渲染线程(Render Thread)独立线程打包渲染命令给 GPUDraw Call 打包、CommandBuffer
Worker Threads(如音频)异步加载资源、播放音频、网络等Audio Thread、Loading Thread
GPU(设备线程)执行实际绘制、后处理、粒子等并行任务Shader、Compute Shader、VFX Graph

一帧中多线程协同的流程图

             [一帧开始]
      ┌────────────┴─────────────┐
      │                          │
[Main Thread]              [Job System Threads]
      │                          │
 MonoBehaviour.Update()     DOTS Job: 动画、物理、AI计算等
      │                          │
 LateUpdate() ←───────────────┘(主线程同步 Job 结果)
[开始渲染准备]
      └─→ [Render Thread] → 提交 GPU 渲染命令
                   [GPU 渲染这一帧]

线程同步与一帧的边界

Frame性能调优

Frame Optimization是游戏性能优化的核心工作之一,目标是在每一帧内把CPU、GPU、内存、线程、渲染等资源使用最大化、冗余最小化,从而达到稳定的帧率

一帧的性能结构(大脑图)

Unity中一帧的总耗时通常来自这几个方面:

一帧时间Frame Time 
  CPU 脚本逻辑开销 +
  Physics 运算 +
  Animation 采样/骨骼 +
  Renderer 准备/剔除 +
  渲染线程开销Render Thread+
  GPU 渲染耗时

目标是:让这些总和 小于 1 / FPS

调优的目标:谁卡,就调谁

调优对象优化目的工具
CPU 主线程减少逻辑卡顿Profiler、Timeline
Job 多线程减少不必要的等待Profiler、JobDebugger
Render Thread减少 DrawCall 和渲染命令量Frame Debugger、Stats
GPU 渲染降低 Shader/像素复杂度GPU Profiler、RenderDoc
内存使用减少 GC 和加载卡顿Memory Profiler、Deep Profiler

常见瓶颈与优化方法(大致思路)

1.脚本逻辑太重(主线程占满)

症状优化手段
Update() 每帧循环太多对象改为 Job 或事件驱动、UpdateGroup
大量使用 Find, GetComponent, LINQ改成缓存引用、避免动态分配
高频调用 GC使用对象池、Span、少用 string 拼接

2.Draw Call太多 / Batching失效

症状优化方法
每帧几千次 DrawCall开启 SRP Batching 或 GPU Instancing
动态物体频繁改变材质合并材质、使用 Texture Atlas
UGUI 每个按钮都独立绘制使用 Canvas 分层、合批策略优化(静态 Canvas)

3.渲染管线太重 / GPU满载

症状优化手段
Shader 复杂 / 光照太多降低 Shader 复杂度,合并 Pass
每像素灯光多 / 阴影开销大减少实时光源数量,Bake 灯光
后处理堆叠太多合并效果、调低分辨率、关闭没必要的 PostFX

物理模拟耗时长

症状优化方法
大量 Collider / Rigidbody简化碰撞体、使用 Layer 避免不必要检测
FixedUpdate 太频繁调整 Fixed Timestep(如 0.02 → 0.033)
不必要的物理交互设置 isKinematic、启用睡眠

资源加载卡顿 / GC卡顿

症状优化方法
使用 Resources.Load()Instantiate() 卡顿使用 Addressables 异步加载
不断产生临时对象对象池、避免 foreach/ToList/Lambda
大量 UI 弹窗频繁创建销毁UI 预加载 + 缓存 + 对象池化管理

详见 Unity Performance Tuning

帧的底层原理

从底层角度说:

一帧 = CPU逻辑执行 + 渲染命令提交 + GPU图像输出 + 系统显示刷新

一帧在底层的完整生命周期

阶段描述涉及模块
时间触发到了下一帧时间,Unity 开始执行 Update操作系统定时器 / 游戏主循环
逻辑更新执行脚本逻辑(如移动、AI、物理)CPU,主线程,Mono
资源准备加载纹理、动画数据、Mesh 等CPU + 内存 + IO
渲染命令生成调用 Graphics API:DrawMesh、DrawCallUnity C++ 层、RenderThread
渲染命令提交传给 GPU 渲染管线(如 Vulkan、OpenGL)RenderThread → GPU
GPU 执行渲染管线顶点着色 → 光栅化 → 像素着色 → 输出到帧缓冲GPU Pipeline
vsync 同步等待下一次显示器刷新(如 60Hz)SwapChain、VSync
显示图像当前帧图像输出到屏幕显示设备、操作系统

图形API:如何控制帧

Unity底层是通过图形API驱动的,这些API控制:

Vulkan或DX中的一帧

BeginFrame(); // 开始新的一帧(获取缓冲区)
RecordRenderCommands(); // 录制渲染命令
SubmitToGPU(); // 提交到GPU
PresentFrame(); // 显示渲染结果(同步vsync)

Unity在底层封装了这些过程,开发者只看到Update()LateUpdate()Render()

帧率和显示器刷新率之间的关系

显示器刷新率理想帧率VSync
60Hz 显示器60 FPS每 16.67ms 输出一帧
120Hz 显示器120 FPS每 8.33ms 输出一帧
帧生成慢掉帧、卡顿GPU 没赶上显示器刷新节奏
帧生成太快撕裂(Tearing)若无 vsync

一帧中关键的底层数据(引擎角度)

结构/模块功能
GameLoop每帧驱动所有系统的核心循环
MonoBehaviourSystem驱动 C# 脚本的系统
RenderLoop构建和提交渲染指令
CommandBuffer存储一帧的渲染命令
SwapChain管理图像缓冲与 vsync 交换
GfxDevice抽象的 GPU 设备接口
NativeContainer管理底层数据容器(如 Transform)

每帧中资源怎么流转

Unity的高效框架优化

这些操作能在一帧时间内完成,是因为Unity通过了“并行化 + 最小化处理 + GPU卸载 + 帧缓冲机制”等一套高效框架优化,最大程度压缩了每一帧的工作流程

为什么一帧能做这么多事

原因说明
并行处理利用多个线程同时处理物理、动画、渲染准备等
GPU 异步渲染渲染任务交给 GPU,CPU 继续处理逻辑,不等待
渲染缓存机制当前帧 CPU 和上一帧 GPU 同时工作
分帧处理大任务(如寻路、加载)分帧执行,避免卡帧
批处理 + 合批合并多个渲染对象为一次提交,减少 GPU 压力
剔除(Culling)优化只渲染玩家能看到的东西
时间预算模型每帧只做预算时间内的任务,多余的等下一帧

多线程并行架构

Unity实际上一帧涉及多个并行线程

线程负责内容
主线程脚本、GameObject 生命周期、逻辑
渲染线程提交 DrawCall,生成 GPU 命令
Job System并发处理物理、动画、AI 等任务
GPU 线程执行渲染(光栅化、着色器等)

多线程图示(简化)

 #N
主线程   ─────► Update() ──► PrepareRender ─► Present
渲染线程           ─────► Submit Commands
GPU 线程                     ─────► 渲染执行

每个线程在同时工作,不是等待某个线程跑完再开始

最小化处理

Unity在每一帧中,只处理真正必要、真正可见、真正变化的内容,而不是对所有对象、组件、资源都进行全量遍历和更新
这是一种性能优化策略,目的是

最小化处理的核心原则

原则举例
只更新变化的对象静止物体不会触发动画或物理
只渲染可见的物体被遮挡或不在视野内的对象被剔除(Culling)
只处理在场景中激活的对象非激活 GameObject 不调用生命周期函数
只计算必要精度的数据LOD 降级,简化远处模型
按需执行系统模块关闭未使用系统,如 NavMesh、布料、粒子

Unity常见的“最小化处理”机制

1.剔除(Culling)

Unity会自动或手动剔除无用物体,跳过渲染或逻辑更新

类型功能
Frustum Culling相机视锥外的物体不渲染
Occlusion Culling被遮挡的物体不渲染
LOD Group距离远时使用低面数模型
Static Batching静态物体合批减少绘制指令
2.非激活物体不处理
gameObject.SetActive(false);
3.脚本生命周期函数的懒执行

Unity不会在每一帧中调用所有函数,只有对应条件满足才会触发
比如:

函数条件
Update()每帧调用(激活物体)
FixedUpdate()只在固定帧率更新(物理开启)
OnBecameVisible()物体刚出现在相机里时
LateUpdate()Update之后才调
OnTriggerEnter()只有碰撞才调
4.JobSystem / Burst的细粒度任务调度

目标:尽量避免主线程阻塞和不必要的处理

5.实例化和资源加载的最小化
技术优化点
对象池(Object Pool)重用对象,避免频繁 Instantiate/Destroy
Addressables 异步加载只在用到时加载资源
场景流(Scene Streaming)只加载玩家附近区域的场景
延迟加载(Lazy Init)某些组件在用到时再初始化

GPU卸载

GPU卸载,是指将计算密集、并行性强的工作从CPU转交给GPU来完成,以释放CPU的压力,从而提升整帧执行效率

原因

哪些任务会被卸载到GPU

卸载内容描述
顶点变换模型的顶点坐标变换(MVP矩阵)
光照计算每像素 / 顶点光照、反射、阴影等
像素着色Color blending、贴图、Fog、后期特效等
后处理Bloom、AO、DOF、MotionBlur 等
GPU Instancing一次性绘制成千上万相同模型
Compute Shader通用并行任务,如粒子模拟、布料、体积雾等

Unity如何实现GPU卸载

主线程构建渲染命令 -> 渲染线程提交 -> GPU异步执行

// Unity C#层伪流程
void Update()
{
    UpdateLogic(); // 脚本逻辑运行(CPU)
    UpdateAnimaion(); // 动画采样(CPU或Job)
    PrepareDrawCalls(); // 构建绘制命令
    // 提交给GPU渲染线程 
}

GPU着色器

类型用途
Vertex Shader每个顶点执行一次,变换模型到裁剪空间
Fragment (Pixel) Shader每像素执行一次,计算最终颜色
Geometry / Tessellation Shader细分曲面或生成额外几何体
Compute Shader并行通用计算,不限于渲染任务(如粒子模拟)

Unity Standard Shader如何借助GPU完成渲染 当你使用Unity内置Shader,如Standard Shader,它会自动在GPU上执行如下操作:

如果这些都在CPU上完成,一帧可能要跑1分钟

图示:Unity渲染流程(CPU -> GPU)

[C# 脚本逻辑 Update()]        
[动画采样/物理运算]          ├─ CPU (主线程)
[构建 DrawCall 命令]         
         
[渲染线程打包命令] ─→ CommandBuffer
         
[GFX Device 发给 GPU]
         
[GPU 执行Shader  Raster  PostFX]

CPU与GPU的帧缓冲双缓冲机制

Unity会将“本帧CPU逻辑”和上一帧“GPU渲染”并行进行

这样就不会阻塞CPU,也不会GPU空转