发布于: 2023-3-22最后更新: 2023-3-24字数 00 分钟

type
status
date
slug
summary
tags
category
icon
password

DrawCall 和 Batch 的区别

DrawCall : CPU发送渲染命令给GPU
如:glDrawElements(OpenGL) / glDrawArrays(OpenGL) / DrawIndexedPrimitive(DX)。
Batch : CPU发送渲染的数据给GPU(CPU Write)
如 设置顶点数据 glBufferData(OpenGL) / glBufferSubData(OpenGL) 等。

Batching , SRP Batcher 和 SRP Instancing

Static Batching

  1. 开关设置:PlayerSettings ⇒ Other Settings ⇒ Static Batching
  1. 条件:相同的材质球 , 可以不同的Mesh
  1. 优点
    1. 节省顶点信息的绑定
    2. 节省几何信息的传递
    3. 相邻材质相同时, 节省材质的传递
  1. 缺点
    1. 离线合并 , 离线包体变大(重复Mesh)
    2. 运行时合并 , CombineMesh会造成CPU短时间峰值
    3. 内存变大(重复Mesh)

SRP Batcher

  1. 条件:相同的Shader(变体一样),可不同的Mesh。
  1. 优点:节省Uniform Buffer的写入操作
      • 按Shader 分 Batch , 预先生成Uniform Buffer
        • 相同shader , uniform 变量相同
        • PerDraw 合成一个大的Buffer
        • PerMaterial 格子合成一个小的Buffer
      • 每个Batch开始时,通过map(memcpy)的方式 一次性传入Uniform Buffer
      • Batch 内部无 CPU Write
  1. 缺点
      • Constant Buffer(CBuffer)的显存固定开销
        • 1024个PerDraw 384KB
        • 1个PerMaterial 4KB
      • 不支持MaterialPropertyBlock

GPU Instancing

  1. 条件:相同的Mesh相同的材质球
  1. 缺点
      • 可能存在负优化 (Instancing 有时候会让DrawCall变高 , 一些机器DrawCall敏感 , 所以开Instancing 需要大幅度降低DrawCall 才适用)。
      • Instancing有时候被打乱 , 导致不完美Instancing (可以自己分组用API渲染,保证不被打乱)。

其他

  1. SRP Batcher 和 Static Baching 可以兼容同时开启
  1. 优先级建议 SRP Batcher / Static Batching > GPU Instancing > Dynamic Batching
  1. 适用情况
    1. Static + SRP Batcher : 主城,副本建筑
    2. SRP Batcher Only: 种类繁多的植被
    3. GPU Instancing: 种类单一的植被
    4. Dynamic Batching: UI , 粒子 , Sprite 等

DrawMeshInstance VS DrawMeshInstanceIndirect

  • 单次上限
    • DrawMeshInstance : 128 / 256 / 512 (不同平台)
    • DrawMeshInstanceIndirect : 无
  • API兼容版本
    • DrawMeshInstance : GLES 3.0
    • DrawMeshInstanceIndirect : GLES 3.1(Shader Model 4.5)
  • 耗时
DrawMeshInstance+CullingGroup 相对于 DrawMeshInstanceIndirect+GPU Culling , DrawCall 变多(数量限制) , CPU耗时高 , GPU耗时低 , 总耗时在不同设备不一样

UIRebuild

Graphics.Rebuild(顶点修改)

  1. 性能瓶颈环境
    1. 血条
    2. 飘字
    3. 技能遮罩
  1. 解决办法:降低刷新的频率

LayoutRebuilder.Rebuild(Layout组件)

  1. 性能瓶颈环境
    1. 子物体修改时 , 会标记父节点的Layout通知修改
    2. Layout修改时 , 会标记Layout的父节点的Layout通知修改 (递归向上)
  1. 触发的情况
    1. 影响顶点位置的
      1. Text:Set_Text
      2. Image:Set_Image
    2. 事件回调
      1. OnEnable/OnDisable
      2. OnRectTrasnformDimensionsChange
      3. OnTrasnformParentChanged
      4. OnDidApplyAnimationProperties
  1. 解决办法
    1. 避免LayoutGroup的嵌套
    2. 如果只是为了第一次的自动排序的,可以在显示之后禁用相关的组件

CullRegistry.Cull(RectMask2D裁剪)

  1. 触发的情况
    1. 每帧遍历底下的元素进行裁剪
    2. 有新的元素被加进来
  1. 解决办法
    1. 性能消耗不大 , 可以不用专门关注
    2. 减少底下节点的数量(几十个内没有大问题)
    3. 保持节点没有改变减

其他

UIRebatch(Canvas为单位)

触发情况

Canvas.BuildBatch

解决办法

  1. Canvs动静分离
  1. Shader动画(代价很小)
    1. 用RawImage能拿到正确UV , Sprite不一定可以拿到正确的UV
    2. uv-0.5 = 离中心的方向
    3. 制作平移和缩放等的效果

Overdraw

Overdraw 指同一像素被重复渲染的次数 , 当被重复渲染的像素过多的时 或 重复的次数过多时候 , 则造成性能浪费.
一个像素被重复n次 光栅化 , fragment , 输出合并 . 这些都会占用 GPU像素填充率 , ALU运算 , 读取贴图的带宽.

全屏界面与场景的叠加

关闭场景相机的渲染

半屏界面与背景的叠加

  • 降低场景的分辨率(需要把场景绘制到RT上)
  • 降低场景相机的更新频率(需要把场景绘制到RT上)
  • 改成Blur静态背景(需要把场景绘制到RT上)

全透明点击区域

  • empty4raycast(不建议 , 参考钱康来的文章 , 老版本下用且有Overdraw)
  • CullTransparentMesh(新版本 , Alpha 为0 的时候则无Overdraw)

大面积Mask组件

  • Mask有两层Overdraw
  • 子节点的数量始终时,改为RectMask2D, 切裁剪区域外无Overdraw
  • 不过还是要观察一下CullingRegister.Cull(Canvas.SendWillRenderCanvas中) , 这两个组件的性能比较

大面积透明元素

  • Sprite多边形模式取出顶点,再塞入UI使用(不建议 , 参考钱康来文章 , 老版本使用)
  • Sprite:Tight模式 , Quad Mesh 变成了多边形Mesh , 但是此时只是用于Sprite2D 并不是UGUI
  • Image: Use Sprite Mesh , 配合上面的Sprite Tight 来完成UGUI Overdraw减少

边框元素的FillCenter

九宫格的时候(Image Type 为 Sliced/Tiled) 去掉 FillCenter

Shader

常见问题

  1. 卡顿
  1. ShaderLab占用内存过大

原因

  1. Shader.Parse-->Shader加载和解析
    1. 变体数控制-->Shader被编译成多份,占用内存变大
    2. 缓存控制-->避免Shader多次加载卸载,比如Shader在场景包中,切换场景被卸载加载
    3. 冗余控制-->同一Shader冗余在不同Bundle中被多次加载
  1. Shader.CreateGPUProgram-->GPU对要渲染Shader的目标平台编译
    1. 冗余控制
    2. Shader过多-->过多的时候,会导致编译过久、卡顿

解决

  1. 编译过久=⇒ 预先Warmup Shader.WarmupAllShaders()
  1. 变体控制
    1. 可以用shader_feature 代替 multi_compile
    2. 收集需要的Variants
      1. Graphics Settings --> Shader Stripping
      2. Shader Variants Collection(SVC)
    3. 剔除不需要的Variants =⇒ OnProcessShader(2018之后)
  1. 冗余控制
    1. 打包策略
    2. UWA检测
  1. 缓存控制 =⇒ Asset+脚本引用+DontDestory

场景加载

Async Upload Pipeline(AUP)

渲染线程异步上传资源
  • 条件
    • 只对纹理和Mesh生效
    • 要关闭Read/Write
  • 策略
    • 加载场景时
      • QualitySettings ⇒ Async Upload Time Slice = 4ms或者8ms
      • QualitySettings ⇒ Async Upload Buffer Size = 16 or 32

Texture Stream

  • Settings->Quality里面开启Texture Streaming
  • 贴图设置mipmap
  • Memory Budget ⇒ 给纹理的内存预算越少,纹理加载的mip level基本就越低
  • Max Reduction ⇒ 分层mipmap最大限制, 减少level越大, 图片越小

Mesh.Bake PhsX CollisionData

  • 常见性能问题
    • Mesh过大/复杂 , 每次进场景都需要运行时计算Collision数据 , 让加载过久
    • 需要开启Read/Write , 这样就不能用AUP了
  • 解决
    • Prebake Collision Meshes(Settings -> Player) 注意:开启后包体大小上升,内存占用上升
    • 使用简化Mesh或者LOD作为碰撞体

C#

实例化/激活

  1. 比如动画组件的激活/禁用

Incremental GC

  1. 2019可以在Player->Use incremental GC 开关增量GC
  1. GarbageCollector.GCMode == GarbageCollector.Mode.Disabled/Enabled
      • 可以开关GC , 比如在战斗时候关掉GC回收 , 但是要注意泄漏 ( WebGL 和 Editor 不生效)
  1. VSync/Application.targetFrameRate 限制帧数
      • 很多GC的产生都是在Update里面 , 帧数下去了 , 则GC就产生就减少了
      • 因为帧数限制 增量GC 会把GC处理分配到要等待的时间里面
  1. GarbageCollector.incrementalTimeSliceNanosecords(逐帧花费多少时间在GC上面)

堆内存累积分配

  1. Boxing--dnspy看编译
      • 拆/装箱
      • 新建数组
      • 新建对象
  1. ZString
      • 堆内存分配明显变低
      • CPU耗时变高

内存驻留(泄漏)

  1. Lua索引引用导致的Mono泄漏 -- Unity释放,Lua抓着,导致中间层的Mono内存驻留了
  1. C#引用 -- 如在Update里不规范使用C#代码 , 导致堆内存越来越多
  1. Texture.GetPixels/WWW.bytes Bug -- 一次分配较大的数据可能会概率导致泄漏 , 用GetPixels32代替 , 可以用C++代码下载或别的API下载

Lua

Lua到C#的调用次数

  • 过多的Lua->C#->Lua穿梭会导致耗时 , 可以C#写一个大方法 , 让Lua一次性去调用 , 然后一次性返回值

Lua函数耗时

  • Lua的代码性能 , 不如C# ,所以一些性能瓶颈方法可以写到C#里面

LuaGC(Incremental)

  • Lua_GCSETPAUSE
  • Lua_GCSETPSTEPMUL

临时内存

  • Vector3 , Color 等

ILRuntime

  1. 解译语言,速度还是差20~100倍,可将耗时的方法放入原生语言
  1. 动态更新DLL打Relase版本 + DISABLE_ILRUNTIME_DEBUG 否则性能会下降
  1. Android可平台特殊处理
      • 通过Assembly.Load动态加载DLL
      • 通过GetType获取类型
      • 通过AddComponent挂载组件执行逻辑
  1. 堆内存
      • 会有缓存让内存增加
        • 会缓存中间变量,临时变量,执行过的方法等
        • 但是缓存有上限.
        • 比如配置表用ILRuntime的内存比预想的内存多
      • PDB占用
        • 移除PDB文件的加载
        • 控制热更新的DLL代码

物理

  1. Auto Simulate 自动物理模拟
      • 在Settings->Auto Simulation开关
      • 默认开启,如果关闭则Rigibody.OnTrigger等物理方法就无法使用了
      • 如果碰撞很少,则可以关闭,自己可以通过距离模拟进行检测
      • 但是可以手动调用 Physics.Simulate 即模拟一帧触发物理
  1. Auto Sync Transforms 自动更新物理世界坐标
      • 在Settings->Auto Sync Transforms开关
      • 不勾选(默认),物体Trasnform发生改变,在下一次物理Update()前,碰撞体属性不会改变.
      • 勾选,物体Trasnform发生改变,碰撞体属性立即更新.同一帧数内频繁更新Trasnform则会有性能消耗.
      • 如果想在同一帧数改变位置且进行物理检测, 则可以用Physics.SyncTransform() 立即更新物理信息

动画

  1. Animator.Initialize
      • 在每次Enable的时候会调用一次 , 有性能消耗
      • 可以只禁用Render , 不禁用Animator
      • 新版本Animator.keepAnimatorControllerStateOnDisable
  1. AnyState
      • AnyState连线过多的时候,导致检测过多,耗时过久

内存

  1. GfxDriver
      • 粒子系统Mesh过大,造成内存占用过多,最终VBO = Mesh内存 * Mesh个数
      • SRP Batcher , 1024个材质球约384KB , 1个材质球实例化约4KB
  1. Audio
      • AudioClipLoadType.DecompressOnload
        • 从磁盘读取就全部解压到内存中
        • 多AudioSource使用同一份音频时,只有一份音频内存
        • CPU开销比较低 , 因为没有动态解压缩
        • 适合短音频 , 多AudioSource , 小文件(大文件勿开 , 因为音频压缩比为10倍 , ADPCM 编码约为 3.5 倍)
      • AudioClipLoadType.Streaming
        • 从磁盘读取时音频内存小 , 边播放 边从磁盘中逐渐读取加载到内存中.
        • 如果多AudioSource播放 , 因为播放的区间不同 , 则要加载多份音频区间到内存中
        • 即使没有加载任何音频数据,也有大约 200KB 的内存占用
        • 适合长音频 , 单AudioSource , 大文件

Unity中UI系统
Unity中UI系统

内置的 UI 系统 1 UGUI 自2014年的Unity4.6开始就内置的UI系统,常用的Unity运行时UI系统。 2 UIToolkit 前身是UIElement,发布于Unity2018。起初用于开发Editor编辑器面板中的UI,自Unity2019起正式支持运行时UI,并更名UIToolkit,它以Package包的形式存在。自Unity2021.2起,UIToolkit被官方内置,与UGUI地位一致。 3 IMGUI 跟随Unity诞生的原始UI系统,一般用于编辑器的扩展以及游戏内调试显示,不推荐用于构建运行时UI。


探究PBR的两种流程以及Unity中的PBS
探究PBR的两种流程以及Unity中的PBS

Albedo 可以理解为物体的基础色Base Color,即该物体本身颜色,也是漫反射光的颜色。