当前位置: 首页 > news >正文

Unity性能优化-渲染模块(1)-CPU侧(1)-优化方向

Unity 中渲染方面的优化大致可以划分为以下几块核心内容:

  1. CPU 优化 (减少 Draw Calls 和 CPU 瓶颈)

  2. GPU 优化 (减少像素着色和 GPU 瓶颈)

  3. 内存和显存优化 (Resource Management)

  4. 光照优化 (Lighting & Global Illumination)

       这四个方面是相互关联的。一个方面的优化可能会影响到另一个方面。在实际项目中,往往需要综合考虑,并根据 Profiler 数据确定瓶颈所在,然后有针对性地进行优化。我将从这四部分讨论渲染模块的优化。

本期我们先来了解CPU侧的优化。

也许大家之前了解过性能优化,可能听说过一些重要的性能检测指标,比如DrawCall与SetPassCall,Batch与Batching这两对都是不同的概念,我们需要明确区分。

那么这里叠个甲,以下内容含个人经验总结,如有偏差,期待大佬指正。

一.两对相关概念区分

1.DrawCall与SetPassCall

        首先明确一次SetPassCall的性能开销远大于一次DrawCall:
因为它涉及 大量 CPU 与 GPU 状态同步、Shader 切换、资源绑定 等昂贵操作,
Draw Call 只是发出一次“用当前状态绘制”的命令,相对廉价。

        因为在CPU和GPU的时间线上SetPassCall总是先于DrawCall发生,所以这里先写SetPassCall。

(1)SetPassCall

1.概念定义

【表示GPU 渲染状态的切换。次数通常对应渲染过程中实际激活的Pass数量】

        SetPassCall是CPU端的函数调用,用于准备和切换GPU的渲染状态。在CPU上执行状态切换命令的准备和提交,GPU随后执行这些状态切换的实际硬件操作。

2.引起SetPassCall的操作

1.切换 Shader 程序:

(1)最常见且开销最大的改变之一。从使用一个 Shader 切换到另一个 Shader(即使它们逻辑相似,但编译后的程序不同)。
(2)不同 Shader 变体:即使是同一个 Shader 文件,如果由于 Shader Keywords 或 multi_compile导致编译出了不同的变体,切换这些变体也可能被视为 Shader 程序改变,从而触发 SetPassCall。

2.切换 Shader 的Pass:

        在一个SubShader内部,从渲染一个Pass切换到另一个Pass。每个Pass都可以定义自己独立的渲染状态(如混合模式、深度测试、剔除模式等),因此这种切换几乎总是会导致 SetPass Call。

3.切换材质:

(1)引用了不同的材质资产: 即使两个材质看起来使用了相同的 Shader,但如果它们是不同的材质球实例,并且它们所存储的参数有所不同,就可能导致状态改变。

(2)不合理使用MaterialPropertyBlock 虽然 MaterialPropertyBlock 可以减少 Draw Call 数量,但如果每个物体的MaterialPropertyBlock都不同,导致 Draw Call 无法合批。

不合理用法:多个物体使用不同的MPB

 foreach (var cube in cubes){var renderer = cube.GetComponent<Renderer>();var mpb = new MaterialPropertyBlock();// 每个物体颜色不同,都会生成不同的MPB数据mpb.SetColor("_Color", Random.ColorHSV());  renderer.SetPropertyBlock(mpb);}

合理用法:统一MPB数据

    var mpb = new MaterialPropertyBlock();mpb.SetColor("_Color", color);  // 所有物体共享同一个颜色参数foreach (var cube in cubes){var renderer = cube.GetComponent<Renderer>();// 共享同一个 MPB 数据,SRPBatcher 可合批renderer.SetPropertyBlock(mpb);}

4.纹理绑定改变 :

        绑定了新的纹理(包括颜色贴图、法线贴图、光照贴图、阴影贴图等)。虽然许多现代 API 和 Unity 的 SRP Batcher 会尝试优化纹理切换,但从一个完全不同的纹理集切换到另一个仍然是渲染状态的改变。

5.渲染状态设置改变:

(1)混合模式 (Blend Mode): 从不透明到半透明,或从一种混合模式切换到另一种。这也是透明物体无法合批的主要原因之一。

(2)深度测试 (Depth Test):ZTest模式的改变。

(3)深度写入 (Depth Write):ZWrite on变为ZWrite off。透明物体通常关闭深度写入。

(4)剔除模式 (Cull Mode):CullBack(背面剔除)变为CullFront(正面剔除)或CullOff(双面渲染)。

(5)模板操作 (Stencil Operation): 模板缓冲区的读写操作和比较函数的改变。

(6)Alpha 测试 (Alpha Test/Clip): 是否启用 Alpha 测试,以及测试阈值的改变。

(6)写入掩码 (ColorMask/ZWriteMask): 控制哪些颜色通道或深度缓冲区可以被写入。

6.渲染目标改变:将渲染结果从主屏幕缓冲区切换到渲染纹理 (Render Texture),或者从一个渲染纹理切换到另一个。这通常发生在渲染管线的不同阶段,例如:先渲染到深度图,再渲染到主屏幕缓冲区;或者渲染到反射纹理,再将反射纹理作为输入渲染到主屏幕。

7.清除操作 (Clear Buffer): 对帧缓冲区或深度缓冲区进行清除操作。

8.渲染队列改变:Unity 通过RenderQueue对物体进行排序。从一个渲染队列切换到另一个会强制 GPU 完成当前队列中的所有操作,并为下一个队列重新配置状态。

(2)DrawCall

1.概念定义

【DrawCall是CPU端调用的渲染命令,CPU准备并提交绘制数据和状态给GPU。GPU负责执行这些绘制命令,包括顶点处理、图元装配、光栅化及像素着色等整个渲染流程。】

        DrawCall是CPU向GPU提交绘制指令的基本单位,每次调用对应一个渲染请求。需要注意的是DrawCall的基本定义其实只是绘制指令,一次DrawCall可以理解为一次图形API调用一次绘制函数(如OpenGL里的DrawElement,DrawArray)。GPU随后负责把顶点连接成三角形、进行光栅化、执行像素着色等所有后续工作。

        记得我当时学习的时候有一种疑惑,既然DrawCall本质上只是一句渲染(通信)指令,在性能优化的问题中应该是显得微不足道的(当然也不绝对,大量Drawcall也会产生一定的通信压力),可是网络上却有大量的关于DrawCall优化的资料,后来我渐渐理解了,这其中存在语境的不同。在更广泛的语境中,尤其是在讨论性能优化时:Draw Call 也常被用来代指由这条指令所引发的从 CPU 数据准备到 GPU 最终渲染完成的整个流程。

2.引起DrawCall的操作

每绘制一个网格对象,通常就是一个 Draw Call。

即使材质相同,只要有一个物体需要被绘制,就至少产生一个 Draw Call。

主要优化手段是Batching批处理。

(3)二者对CPU的性能影响

主要影响GPU侧的性能。

DrawCall

(1)每个Draw Call都需要CPU准备数据,包括顶点缓冲,索引缓冲,渲染状态等。

(2)CPU负责组织和提交渲染命令,Draw Call越多,CPU命令缓冲区构建压力越大。

(3)过多Draw Call导致CPU渲染线程被占用过多时间,降低帧率。

SetPassCall

CPU上准备渲染状态的开销通常比GPU上改变渲染状态的开销大得多。

(1)SetPassCall触发CPU端复杂的状态管理和验证逻辑。

(2)本质上是CPU发出的切换指令,会打断当前渲染流程,增加同步和缓存刷新开销

2.Batch与Batching

(1)Batch

1.概念定义

        指 最终提交给 GPU 的一组合批渲染数据。一个 Batch 通常代表一次Draw Call。

2.(Game窗口)State面板

        在 Unity 的 Profiler 或 Game 界面(Stats 面板)中,"Batches" 数量通常等同于实际的 Draw Call 数量。

(2)Batching(合批)

1.概念定义

        Batching可以理解为是多个物体被逻辑合并为一个(少量) Batch(一个 Draw Call)的过程。

        Batching是对DrawCall的一种优化技术,指 Unity将多个原本会产生独立 Draw Call 的渲染任务,通过某种优化手段合并成一个或少数几个 Draw Call(也可说Batch),统一提交给 GPU 进行渲染的结果。其目的是为了减少 CPU 向 GPU 发送 Draw Call 的次数,从而提高渲染效率。

2.Unity中的Batching手段

        在Unity中,Batching大致包括四种手段:(1)Static Batching(静态合批),(2)Dynamic Batching(动态合批),(3)GPU Instancing,(4)SRP Batcher(SRP专用)。

二.优化方向

这里仅给出大致的优化方面,后面我会分篇详细介绍并总结以下优化方式。

1.DrawCall优化方向

1. 合批(Batching)

(1)Static Batching(静态合批):适用于不移动的物体,比如地形、墙体等。Unity 会在构建时合并它们的网格,减少运行时的 Draw Call。但它会占用更多内存,因为每个合并后的网格都需要单独保存。

(2)Dynamic Batching(动态合批):适用于顶点数较少、使用相同材质的动态物体,如小道具、粒子等。它会在 CPU 上每帧打包多个物体成一次绘制提交,但会增加 CPU 负担。

(3)GPU Instancing:适用于大量重复的,可以共用同一个 Mesh 和 Shader的对象。可以通过每个实例传入不同参数(如颜色、位移等)来表现差异,CPU 开销低,现代 GPU 支持良好。

(4)SRP Batcher(SRP专用):如果你使用 URP 或 HDRP,务必开启 SRP Batcher。它将材质属性统一打包在大块常量缓冲区里,极大减少 CPU 的重复提交成本。

2. 减少物体数量 / 合并 Mesh

        多个小物体会分别产生 Draw Call。如果这些物体在逻辑上不需要分离(如一整面墙、一个复杂模型),可以在建模时或运行时合并成一个 Mesh,从而一次性绘制。

要注意的是:合并后可能会丧失剔除(Culling)的能力,导致某些看不到的物体仍然被绘制。

3. 优化 UI 绘制

        Unity 的 UI 系统(UGUI)在 Canvas 更新时会重建 Mesh,这会造成大量 Draw Call。

通过将 UI 分层(多个 Canvas),设置 CanvasGroup、使用Canvas.overSorting()等方式,可以减少 Canvas 的重建频率,从而控制 UI 引起的 Draw Call。

4.其他(打包图集)

        打包图集是 “减少材质切换”的手段,本身不直接减少 Draw Call,而是通过减少材质数量,间接支持和促进Batching来降低 Draw Call。

2.SetPassCall优化方向

1. 减少材质数量、统一 Shader

        如果你有多个对象使用了相似的 Shader,但却分别用了不同的材质(即使只是颜色不同),Unity 也会认为它们是不同的 Shader 状态,从而触发新的 SetPass Call。

        使用共享材质,并通过MaterialPropertyBlock来修改不同对象的颜色或参数,就可以避免这个问题而不会打断合批。

2. 合并纹理贴图(打包图集)

        如果你场景中很多对象只是贴图不同,但本质材质逻辑一样,可以将多个纹理打成一张图集(Atlas),然后通过不同的 UV 区域来显示它们。

这样可以使用同一个材质和 Shader,就不会增加额外 SetPass Call。

3. 减少Shader中Pass 数量

        一个 Shader 可能包含多个 Pass,比如用于正面渲染、阴影渲染、深度写入等。每个 Pass 都可能导致新的 SetPass Call。

        如果不需要某些 Pass(如阴影,DepthOnly),建议删掉或通过构建设置剔除(Shader stripping)。特别是在使用 Forward 渲染时,多个灯光可能导致同一个对象被绘制多次(每个光源一个 Pass)。

注意:URP项目中强烈建议主Pass渲染。

4. 确保 Shader 支持 SRP Batcher(如果使用 URP/HDRP)

        要在URP项目中支持 SRP Batcher,需要注意两点:

(1)全局开启:在项目的渲染管线资产配置中开启SRPBatcher。

(2)Shader内编写:Shader 的属性声明必须符合 Unity 的标准结构(如统一定义在 CBuffer 中)。

SRP Batcher 的原理是会把多个对象的材质数据(如各种参数)统一打包到一个大块的 GPU 常量缓冲区(CBuffer)里,并且这些参数必须布局完全一致,这样 GPU 才能批量读取,避免 CPU 多次提交状态切换。如果你的 Shader 参数不是统一定义在 CBuffer 中,而是零散声明的,比如用不同的 uniform 变量或者其他方式,SRP Batcher 就不能正确识别和利用它们。

符合的写法(统一定义在 CBuffer 中)

CBUFFER_START(UnityPerMaterial)float4 _Color;float _Glossiness;float _Metallic;
CBUFFER_END

不符合的写法(零散声明)

float4 _Color;
float _Glossiness;
float _Metallic;

在 Frame Debugger 中,你可以看到哪些物体的材质成功参与了 SRP Batcher,哪些没有。

这里仅给出大致的优化方面,后面我会分篇详细介绍并总结以上优化方式。

本篇完

http://www.lryc.cn/news/576206.html

相关文章:

  • Spring Boot整合Redis指南
  • C++ 快速回顾(三)
  • PICkit3编程器MCLR引脚全解析
  • vue-27(实践练习:将现有组件重构为使用组合式 API)
  • <script setup> 语法糖
  • Netty入门案例:简单Echo服务器(同步)
  • 预训练语言模型
  • 关于USB模式的一些内容(附USB接口颜色释义图)
  • Veo 3 视频生成大模型完整操作教程(2025)
  • Ai大模型 - ocr图像识别形成结构化数据(pp-ocr+nlp结合) 以及训练微调实现方案(初稿)
  • 82、高级特性-配置加载优先级
  • debain切换 opensuse 我都安装了什么
  • 【数据挖掘】数据采集和预处理
  • Milvus报错,reson=timestamp lag too large
  • [Python]-基础篇1- 从零开始的Python入门指南
  • C++11 <chrono> 库特性:从入门到精通
  • SpringMVC系列(四)(请求处理的十个实验(下))
  • 【请关注】制造企业机械加工数据脱敏解决方案
  • 【数据分析,相关性分析】Matlab代码#数学建模#创新算法
  • 目标跟踪存在问题以及解决方案
  • Linux信号机制:从入门到精通
  • Windows VMWare Centos环境下安装Docker并配置MySql
  • 香港 8C 站群服务器买来可以做哪些业务?
  • opi是opensuse独占的吗?
  • 工厂“智能指挥家”上线,富唯智能调度系统让机器人高效协作
  • 关于SAP产品名称变更通知 SAP云认证实施商工博科技
  • 导出docker-compse.yml中docker镜像成tar文件
  • 基于fpga的串口控制的音乐播放器
  • 从0开始学习计算机视觉--Day04--损失函数
  • 微信小程序:实现树形结构组件