当前位置:   article > 正文

Unity性能优化(2):渲染管线和优化策略_渲染 性能优化

渲染 性能优化

目录

管线渲染的简化

GPU 前端

GPU 后端

优化策略

降低几何复杂度

减少曲面细分Subdivision Surface

应用GPU实例化

使用遮挡剔除

使用优化纹理文件

优化粒子系统

着色器(Shader)优化

1.不同平台只用不同的着色器

2.使用小的数据类型

3.在重排时避免修改经度

4.使用 GPU 优化的辅助函数

5.禁用不需要的特性

6. 删除不必要的输入数据

7. 只公开所需的变量

8. 减少数学计算的复杂度

9.减少纹理采样

10.减少数据依赖

11.表面着色器

13. 使用基于着色器的 LOD

光照优化

1.道慎地使用实时阴影

2.使用剔除遮罩

3. 使用烘焙的光照纹理

优化移动设备的渲染性能

1.避免 alpha 测试

2.最小化 Draw Call

3. 最小化材质数量

4. 最小化纹理大小 

5. 确保纹理是方形且大小为2 的幂次方

6. 在着色器中尽可能使用最低的精度格式


管线渲染的简化

对管线渲染了解得越多,就越能理解可能的性能增强。下图为一个典型的管线渲染的简化图。第一行展示了 CPU 中进行的工作,其中包含通过硬件驱动调用图形 API 并将命令推送到 GPU。接下来的两行展示了 GPU 中进行的工作。由于 GPU 的复杂性,其内都流程通常分为两个不同的部分:前端和后端。

GPU 前端

GPU 前端是指渲染过程中GPU 处理顶点数据的部分,其工作原理如下。

  1. 前端从CPU 中接收网格数据(一大堆顶点信息,并发出DrawCall。
  2. GPU 从网格数据中收集顶点信息,通过顶点着色器进行传输,对数据按1:1的比例进行修改和输出。
  3. GPU 得到一个需要处理的图元列表(三角形——3D 图形中最基本的形状)。
  4. 光栅化器获取这些图元,确定最终图形的哪些像素需要绘制,并根据顶点的位置和当前的相机视图创建图元。这个过程中生成的像素列表称为片元,将在后端进行处理。

GPU 后端

GPU 后端描述了管线渲染中处理片元的部分,其工作原理如下。

  1. 每个片元都通过片元着色器(也成为像素着色器)来处理。与顶点着色器相比,片元着色器往往涉及更复杂的活动,例如深度测试、alpha测试、着色、纹理采样、光照、阴影,以及一些可行的后期效果处理。
  2. 之后,将这些数据输入帧缓冲区,帧缓冲区保存了当前图像,一旦当前帧的渲染任务完成,图像就发送到显示设备(例如显示器)。正常情况下,图形AP默认使用两个帧缓沖区(尽管可以给自定义的渲染方案生成更多的帧缓冲区)。
  3. 在任何时候,一个帧缓冲区包含渲染到帧中并显示到屏幕上的数据;另一个帧缓冲区则在GPU 完成命令缓冲区中的命令后被激活,进行图形绘制。
  4. 一旦 GPU 完成 swap buffers 命令(CPU 请求完成指定帧的最后一条指会),就翻转帧缓冲区,以呈现新的帧。
  5. GPU 则使用旧的帧缓冲区绘制下一帧。
  6. 每次渲染新的帧时,都重复此过程,因此,GPU 只需要两个帧缓冲区就可以处理这个任务。

补充:

片元着色器(Fragment Shader)是计算图形渲染管线中的一个阶段,也被称为像素着色器(Pixel Shader)。它负责对每个渲染图元的片元(像素)进行颜色计算,从而决定最终的像素颜色。

片元着色器通常用于实现光照、纹理映射、阴影、透明度和其他表面特性的计算。它接收输入数据,如纹理坐标、光照数据、顶点属性等,并生成输出颜色。

官方手册

OpenGL的官方手册:

Rendering Pipeline Overview - OpenGL Wiki (khronos.org)

DirectX的官方手册:

​​​​Graphics pipeline - Win32 apps | Microsoft Learn

DirectX 图形入门 - Win32 apps | Microsoft Learn

Unity的官网手册:

Unity - Manual: Render pipelines (unity3d.com)

优化策略

  • 降低几何复杂度

1.减少多边形(面)数量是提升性能的最明显的方法。

2.调整网格压缩Mesh Compression。Unity提供了4种网格压缩的选项,以减小内存占用和提高性能。通过减少网格数据的存储空间和传输带宽,网格压缩可以在保持可接受的视觉质量的同时,减少游戏的资源占用。

 3.使用网络的自动剔除特性(Level of Detail,LOD)

Unity - Manual: Level of Detail (LOD) for meshes

LOD通过在不同距离或屏幕空间中使用不同细节级别的模型来减少渲染开销。LOD系统的核心思想是在越远的距离上使用更简化的模型,而在越近的距离上使用更具细节的模型。这样可以减少要渲染的多边形数量和纹理采样次数,从而显著提高游戏的性能。LOD还可以应用于其他方面,如光照、材质细节等。

  • 减少曲面细分Subdivision Surface

曲面细分可以用于创建更具有光滑感和细节的模型,使得曲面表现更加真实和逼真。然而,需要注意的是,在使用曲面细分时,网格的顶点和面数量会增加,这可能会增加渲染和计算的负担。

  • 应用GPU实例化

GPU 实例化 - Unity 手册

GPU实例化(GPU instancing)是一种图形渲染技术,通过复制和批量渲染几何体或对象实例,以提高渲染性能。它利用GPU的并行处理能力,在一个渲染调用中同时处理多个实例,减少了CPU和GPU之间的数据传输和绘制调用的开销。

在传统的渲染方式中,为了渲染场景中的多个相同几何体或对象实例,需要通过多次绘制调用和数据传输来完成,这会导致较高的CPU开销和数据传输带宽消耗。而使用GPU实例化技术,可以通过使用相同的几何体数据,并将各实例的位置、大小等属性存储在缓冲区中,然后一次性渲染多个实例。

  • 使用遮挡剔除

遮挡剔除 - Unity 手册

“遮挡剔除”过程可防止 Unity 为那些被其他游戏对象完全挡住(遮挡)的游戏对象执行渲染计算。

摄像机在每一帧中执行剔除操作,这些操作会检查场景中的渲染器,并排除(剔除)那些不需要绘制的渲染器。默认情况下,摄像机执行视锥体剔除,这一过程将排除所有不在摄像机视锥体范围内的渲染器。但是,视锥体剔除不会检查渲染器是否被其他游戏对象遮挡,因此 Unity 仍会浪费 CPU 和 GPU 时间进行在最终帧中不可见的渲染器的渲染操作。遮挡剔除将阻止 Unity 执行这些徒劳的操作。

  • 使用优化纹理文件

针对纹理文件的优化,可以看这篇文章:Unity性能优化:纹理文件

  • 优化粒子系统

使用Automatic Culling Mode

选择是否在离屏时继续模拟粒子系统。Catch-up暂停屏幕外的模拟,但当它们变得可见时执行一个大的模拟步骤,使它们看起来从未暂停过。Automatic对循环系统使用Pause模式,如果不循环则使用AlwaysSimulate模式。

  • 着色器(Shader)优化

片元着色器是填充率和内存带宽的主要消耗者。消耗成本取决于它们的复杂度:纹理采样的数量,使用的数学函数量,以及其他因素。GPU 的并行特性意味着线程中的任何瓶颈都将限制每一帧中通过该线程推送的片元数量。这个过程很像工厂里的流水线,任何一个环节的卡顿都会导致整条生产线的卡顿。

可以考虑从以下几个方面优化着色器:

1.不同平台只用不同的着色器
2.使用小的数据类型

GPU 使用更小的数据类型来计算比使用更大的数据类型(特别是在移动平台上)往往更快,因此可以尝试的第一个调整是用较小的版本(16位浮点)或甚至固定长度(12 位定长)替换浮点数据类型(32 位浮点)。前述数据类型的大小将根据目标平台偏好的浮点格式而有所不同。列出的数据兴型都是最常见的。

3.在重排时避免修改经度

重排(Swizzling)是一种着色器编程技术,它将组件按照所需的顺序列出并复制到新的结构中,从现有向量中创建一个新的向量(一组数值)。

在着色器中将一种精度类型转换为另一种精度类型是一项很耗时的操作,在重排时转换精度类型会更加困难。如果有使用重排的数学运算,请确保它们不会转换精度类型。在这些情况下,更明智的做法是从一开始就只使用高精度数据类型,或者全面降低精度,以避免需要更改精度。

4.使用 GPU 优化的辅助函数

着色器编译器通常能很好地将数学计算简化为 GPU 的优化版本,但自定义代码的编译不太可能像 Cg 库的内置辅助函数和 Unity Cg 包含文件提供的其他辅助函数那样有效。如果使用包含自定义函数代码的着色器,也许可以在Cg 或 Unity库中我到对应的辅助函数,它能更好地完成自定义代码的工作。

Unity - Manual: Built-in shader include files

可以获得完整的最新的额Include文件及其附带的辅助函数列表。

5.禁用不需要的特性

只要禁用不重要的着色器特性,就可以节省成本。着色器真的需要透明度、Z写入、alpha 测试或alpha 混合吗?调整这些设置或删除这些功能,是否可以很好地达到所需的效果而不会丢失太多的图形保真度?做这样的改变是降低填充率的一个好办法。

6. 删除不必要的输入数据

有时,编写着色器的过程需要反复地编辑代码,在场景中查看代码。这个过程的一般结果是,当着色器进行早期开发时所需的输入数据,现在一旦获得了期望的效果,就会变成冗余数据。如果这个过程拖得很长,就很容易忘记对它做了什么更改。然而,这些元余数据可能会花费 GPU 宝贵的时间,因为即使着色器没有显式地使用它们,它们也必须从内存中获取。因此,应该仔细检查着色器,以确保输入的所有几何体、顶点和片段数据都被实际使用。

7. 只公开所需的变量

从着色器中将不必要的变量公开给附带材质会有较大消耗,因为 GPU 不能假设这些值是常量,这意味着编译器不能编译这些值。这些数据必须在每个过程中从 CPU 推送到 GPU,因为它们会通过诸如 SetColor()和 SetFloat()等材质对象的方法在运行时发生修改。如果在项目结束时发现这些变量一直使用相同的值,就应该在着色器中使用常量替代它们,以去除过量的运行时负载。

8. 减少数学计算的复杂度

复杂的数学计算会成为渲染流程中严重的瓶颈,因此应该尽可能限制其危害。完全可以提前计算复杂的数学函数,并将其输出作为浮点数据存储在纹理文件中,作为复杂数学函数的映射图。毕竟,纹理文件只是一个巨大的浮点值数据块,可以通过x、y,和颜色(rgba)这三个维度进行快速索引。
可以将这张纹理提供给着色器,并且在运行时在着色器中采样提前生成的表格,而不是在运行时进行复杂的计算。

9.减少纹理采样

纹理采样是所有内存带宽开销的核心消耗。使用的纹理越少,制作的纹理越小,则效果越好。使用的纹理越多,则在调用过程中丢失的缓存就越多,制作的纹理越大,将它们传输到纹理缓存中所消耗的内存带宽就越多。因此应尽可能简化此类情況,以避免产生严重的 GPU 瓶颈。

更糟糕的是,不按顺序进行纹理采样可能会给 GPU 带来一些非常昂贵的缓存丢失。所以,如果这样做,纹理需要重新排序,以便按顺序进行采样。例如,如果通过反转x 和y坐标进行采样。例如,用 tex2D(y,x)替代 tex2D(x,y),那么纹理查找操作将垂直遍历纹理,然后水平遍历纹理,几乎每次迭代都会造成缓存丢失。简单地旋转纹理文件数据,并按正确的顺序执行采样(tex2d(x,y)),可以节省大量性能损耗。

10.减少数据依赖

以下代码描述了指令之间非常强的数据依赖性,因为每个指令都依赖从上一条指令中采样的纹理数据。

  1. float4 val1 = tex2D(_tex1, input.texcoord.xy);
  2. float4 val2 = tex2D(_tex2, val1.yz);//从val1中获取数据
  3. float4 val3 = tex2D(_tex3, val2.zw);//从val2中获取数据

这段代码有一个数据依赖的关系,每个计算都需要等待上一个计算结束才能开始。

11.表面着色器

Unity 的表面着色器是片元着色器的简化形式,允许 Unity 开发人员以更简化的方式进行着色器编程。Unity引擎负责转换表面着色器代码,并对刚刚提到的一些优化点进行抽象。然而,它提供了一些可以用作替换的其他值,这降低了精度,但简化了代码生成过程中的数学运算。表面着色器的设计初衷是高效地处理常见情况。

13. 使用基于着色器的 LOD

可以强制 Unity 使用更简单的着色器来渲染远端对象,这是一种节省填充率的有效方法,特别是将游戏部署到多个平合或需要支持多种硬件功能时。LOD关键字可以用来在着色器中设置着色器支持的屏幕尺寸参数。如果当前 LOD 级别不匹配此参数值,它将转到下一个回退的着色器,以此类推,直到找到支持给定尺寸参数的着色器。还可以在运行时使用maximumLOD 属性更改给定着色器对象的LOD值。
注意:有关基于着色器的LOD 的更多信息,请参见参考Unity 文档Unity - Manual: ShaderLab: assigning a LOD value to a SubShader

  • 光照优化

1.道慎地使用实时阴影

阴影很容易成为 Draw Call 和填充率的最大消耗者之一,因此应该花时间调整这些设置,直到获得所需的性能和图形质量。


在 Edit / Project Settings / Quality / Shadows 下有一些重要的阴影设置。对 Shadows 选项而言,Soft Shadows(软阴影)所需代价最大,Hard Shadows(硬阴影)所需代价最小,No Shadows(无阴影)不需要付出代价。Shadow Resolution(阴影分辨率)、Shadow Projection(阴影投影)、 Shadow Distance(阴影距离)和 ShadowCascades(阴影级联)也是影响阴影性能的重要设置。


Shadow Distance 是运行时阴影渲染的全局乘数。在离相机很远的地方渲染阴影几乎没有什么意义,所以这个设置应该针对游戏以及在游戏期间希望看到的阴影量进行配置。这也是选项界面中显示给用户的常见设置,用户可以选择谊染明影的距离,以使游戏的性能与其硬件相匹配(至少在台式计算机上)。


较高的 Shadow Resolution 和 Shadow Cascades 值将增加内存带宽和填充率的消耗。这两种设置都有助于抑制阴影谊染中生成伪影的影响,但代价是Shadowmap 纹理尺寸会大得当,并将增加内存节宽和 VRAM 的消耗。


2.使用剔除遮罩

灯光组件的剔除遮單属性是基于层的遮罩,可用于限制受给定灯光髟响的对象。剔除進罩是一种降低照明开销的有效方法,它假设图层交互也与如何使用图层进行物理优化有关。剔除遮單的对象只能是单个图层的一部分,在大多数情况下,减少物理开销可能比减少照明开销更重要。因此,如果两者存在冲突,那么这可能不是理想的方法。

注意:延迟着色的使用对别除遮軍的支持很有限。因为延迟着色是以全局的方式处理照明,其只能从遮军中禁用4 个图层,限制了优化共行为的能力。

3. 使用烘焙的光照纹理

与在运行时生成光照和阴影相比,在场景中烘焙光照和阴影对处理器的计算强度要求要低很多。其缺点是增加了应用程序的磁盘占用、内存消耗和内存带宽滥用的可能性。


优化移动设备的渲染性能

1.避免 alpha 测试
2.最小化 Draw Call
3. 最小化材质数量
4. 最小化纹理大小 
5. 确保纹理是方形且大小为2 的幂次方
6. 在着色器中尽可能使用最低的精度格式
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/825341
推荐阅读
相关标签
  

闽ICP备14008679号