UPL6-3——静态批处理
UPL6-3——静态批处理 本章代码关键字 1StaticBatchingUtility.Combine() // 传入要进行静态合批的根对象,将其下的对象执行一次静态合批 静态批处理 静态批处理(Static Batching)是 Unity 用来减少 CPU Draw Call 开销的一种批处理方式 它将多个静态对象(位置、旋转、缩放都不会在运行中改变的物体)的网格数据合并成一个或少量的大网格,从而一次性提交给 GPU 绘制 前提条件:想要进行静态批处理的物体需要勾选 Static 中的 Batching Static(Inspector 窗口右上角) 主要原理: Unity 会在构建或首次运行时,把使用相同材质的静态物体的网格数据合并成更大的网格 在渲染时,这些静态物体就能通过一次 DrawCall 一起绘制,而不是每个物体单独一个DrawCall 说人话:静态批处理是一种在构建或者第一次运行时,对场景上勾选了 Batching Static 的物体进行网格合并 将多个静态物体网格会合并成一个大网格,一次性提交给 GPU,从而减少 Draw Call...
UPL6-2——动态批处理
UPL6-2——动态批处理 动态批处理 动态批处理(Dynamic Batching)是一种由 Unity 自动完成的 CPU 端优化技术 它会在运行时把多个使用相同材质的动态物体的网格数据合并到一次 DrawCall 中 从而减少 DrawCall 的调用次数,降低 CPU 提交渲染命令的开销 它的 3 个重要优势: 批处理在运行时生成 批处理中包含的对象在不同帧之间可能有所不同,取决于哪些网格在主摄像机视图中当前可见 场景中运动的对象也可以批处理 它的主要原理是: Unity 在渲染时,原本需要将每个物体的网格、材质数据通过 CPU 发送给 GPU,即一次 DrawCall 假设 3 个相同材质的 3D 对象(网格可不同,材质需相同),渲染他们需要 3 次 DrawCall 但是如果在开启动态批处理时,并且这些对象满足动态批处理的限制条件 那么动态批处理会将这 3 个对象合并为一个更大的网格缓冲区,并和相同的材质一起,通过一次 DrawCall 提交给 GPU,从而达到减少 DrawCall...
UPL6-1——DrawCall
UPL6-1——DrawCall DrawCall DrawCall(绘制调用)在 Unity 中有时也会被称为 SetPass Call(设置参数调用),本质上是 CPU 向 GPU 发送一条绘制指令 告诉 GPU 要画什么、怎么画、用什么材质、用哪段几何数据,从 CPU 到 GPU 的一次DrawCall的流程包含: CPU设置渲染状态 绑定要用的 Shader 程序(顶点着色器、片段着色器等) 绑定材质参数(纹理、颜色、常量缓冲区) 绑定几何数据(网格顶点缓冲、索引缓冲) 设置混合模式、深度测试、剔除方式等 GPU 状态 CPU发送绘制命令 告诉 GPU 从哪块缓冲取数据、绘制多少个顶点/三角形 GPU执行绘制 按状态和数据进行顶点处理、光栅化、片元处理等,最终输出到帧缓冲 关键点:一次 DrawCall ≈ 一次 准备渲染状态 + 发送命令,即使画很少的三角形,也有固定的 CPU 提交开销 如果 DrawCall 数量过多,会严重影响 CPU 性能 DrawCall 为什么会影响 CPU 性能 DrawCall...
UPL6——图形渲染优化
UPL6——图形渲染优化 前置知识点: UG2L15——Drawcall(了解 Drawcall 的概念) US5L8——帧调试器 Frame Debugger(可以观察 Drawcall 的数量) DrawCall 动态批处理(在运行时每帧将多个相同材质的动态小对象进行合批,减少 DrawCall,局限性较大) 静态批处理(在构建时将多个相同材质的静态对象网格合并为大网格,再一次性提交 GPU,用内存换性能,减少 DrawCall) GPU Instancing(让 CPU 通过一次 DrawCall 传递所有相同网格和材质对象的实例数据,让 GPU 根据实例数据绘制,在绘制大量重复对象时显著降低 DrawCall) SRP Batcher(将相同 Shader 的物体和材质数据缓存到 GPU,只在内容变化时更新数据,减少相同 Shader 但材质不同的 SetPass 的性能消耗,不减少 DrawCall) DrawCall 优化方案对比 手动合并网格(通过 Mesh API 手动合并网格,通过减少网格数量减少 DrawCall,可控性和灵活性更高)
UPL5-12——资源加载(反序列化)优化和预渲染
UPL5-12——资源加载(反序列化)优化和预渲染 本章代码关键字 123456789101112131415// 着色器变体集合相关ShaderVariantCollection // 着色器变体集合shaderVariantCollection.isWarmedUp // 判断着色器变体集合是否已经预热shaderVariantCollection.WarmUp() // 预热着色器变体集合内所有着色器变体// 渲染指令缓冲区相关material.FindPass() // 通过渲染通道名字获取此渲染通道在材质中的索引CommandBuffer // 指令缓冲区Shader.PropertyToID() // 通过字符串指定一个唯一的着色器属性整数IDcommandBuffer.GetTemporaryRT() //...
UPL5-11——禁用未使用的脚本和对象
UPL5-11——禁用未使用的脚本和对象 本章代码关键字 12345OnBecameVisible() { } // 当依附对象存在Renderer且对任一摄像机(主摄像机、UI摄像机、小地图摄像机、Scene 视图摄像机等)可见时调用OnBecameInvisible() { } // 当依附对象存在Renderer且对任何摄像机(主摄像机、UI摄像机、小地图摄像机、Scene 视图摄像机等)都不可见时调用GeometryUtility.CalculateFrustumPlanes() // 获取摄像机视锥体六个平面GeometryUtility.TestPlanesAABB() // 判断一个AABB包围盒是否与一组平面(如视锥体)相交或包含renderer.bounds //...
UPL5-10——复杂计算结果缓存
UPL5-10——复杂计算结果缓存 复杂计算结果缓存 复杂计算结果缓存指的是避免重复执行开销大的运算,将结果缓存起来复用,从而减少 CPU 运算量和时间消耗 这类优化尤其适用于 频繁执行但变化不频繁 的逻辑 常会缓存的计算结果 数学计算结果缓存 我们可以在初始化项目基础数据时,把各种数据提前算好 以三角函数为例,我们可以用一个 float 数组存储 0~359 度所有 Sin、Cos 等等三角函数值 12345float[] allSin = new float[360];for (int i = 0; i < 360; i++){ allSin[i] = Mathf.Sin(i * Mathf.Deg2Rad);} 寻路计算结果缓存 如果项目中存在 A*...
UPL5-9——容器选择
UPL5-9——容器选择 容器选择 所谓的容器选择就是指,在 Unity 中使用合适的数据结构类来装载数据 比如:我们常用的 List 和 Dictionary 容器,他们就各有各的优劣势 List: 查找的时间复杂度是 O(n)O(n)O(n) 但是它存储结构是连续的 Dictionary: 查找的时间复杂度是 O(1)O(1)O(1) 但是它存储结构是不连续的 所以我们在选择它们时一般会根据他们的特点来进行选择 经常会遍历并且中间插入较少的数据用 List 经常会进行键值对查找的数据用 Dictionary 更多容器选择 HashSet<T> 时间复杂度:查找/添加/删除都是 O(1)O(1)O(1),内部使用哈希表实现 使用场景: 只关心元素是否存在、不需要顺序、也不需要键值对映射时 一般用来存储已处理过的对象,比如当前激活的敌人列表等等 Queue<T> 时间复杂度:入队、出队...
UPL5-8——Transform 相关
UPL5-8——Transform 相关 避免运行时修改 Transform 父节点问题 在运行时修改 Transform 父节点(SetParent 方法)是一项开销较大的操作,主要原因: 会触发 Transform 的系统重建 修改父节点会让 Unity 重建该物体及其子物体的层级关系和矩阵缓存 会触发一系列递归更新,非常耗性能,尤其是在父物体或子物体很多的情况下 可能导致批处理中段 如果对象原本属于同一个静态批处理,修改父节点后会导致重新分组甚至退出批处理 可能导致缓冲区拓展 Transform 底层父子关系类似动态数组,修改父节点,可能导致缓冲区拓展,导致不必要内存分配 等等 避免运行时频繁修改 Transform 相关属性 频繁修改 Transform 属性,会牵一发动全身,可能引起连锁反应 特别是在存在大量物体的场景以及低端设备上,我们应该尽量避免这种情况的发生 position、rotation、scale 的开销 因为 Transform 组件中会存储与其父组件相关的数据 因此直接修改 Transform 对象的...
UPL5-7——GameObject 优化建议
UPL5-7——GameObject 优化建议 更快的空引用检测 我们在进行开发时,经常会进行 GameObject 对象判空处理,常常会使用类似这样的判空处理 1234if (gameObject != null){ // 存在再处理} 其实不仅是 GameObject,MonoBehaviour 等等 Unity 自带的内容如果这样进行判空处理,都是最简单的方式,但是这种方式并不是效率最高的 这里推荐一种更快的判空处理方式,利用 System.Object 中的引用判断方法 ReferenceEquals 1234if (!System.Object.ReferenceEquals(gameObject, null)){ // 存在再处理} 虽然它的写法有些复杂,但是但从性能上考虑,它的消耗更低,执行的更快,直接进行了引用地址的比较 这是因为通过 == / != 对 null 的判断,还是会进行桥接访问的方式,会有额外的桥接开销 而 System.Object 中的 ReferenceEquals...
