Unity Shader学习笔记(30)Unity中渲染优化技术

发表于2018-01-02
评论0 4.5k浏览
Unity内置了一些工具,来帮助我们方便地查看和渲染相关的各个统计数据,这些数据可以帮助我们分析游戏渲染性能,从而更有针对性地进行优化。下面就来看看那些影响渲染优化的性能因素和批处理方法。

影响性能因素

CPU负责帧率,GPU复杂分辨率。

  • CPU 
    • 过多的draw call。
    • 复杂的脚本或物理模拟。
  • GPU 
    • 顶点处理: 
      • 过多的顶点。
      • 过多的逐顶点计算。
    • 片源处理: 
      • 过多的片元。(分辨率高或overdraw)
      • 过多的逐片元计算。
  • 带宽 
    • 使用尺寸很大且为压缩的纹理。
    • 分辨率过高的帧缓存。

常见优化技术

  • CPU 
    • 用批处理技术减速draw call数目。
  • GPU 
    • 减少顶点数目: 
      • 优化几何体。
      • 使用LOD(Level of Detail)技术。
      • 使用遮挡剔除(Occlusion Culling)计算。
    • 减少片元数目: 
      • 控制描绘顺序。
      • 警惕透明物体。
      • 减少实时光照。
    • 减少计算复杂度,优化代码。

渲染统计窗口(Rendering Statistics Window)

信息名称描述
FPSframes per seconds,每秒帧数(帧率)。 括号内为渲染一帧需要的时间。
CPUmain:主线程计算时间。 render thread:渲染线程计算时间。
Save by batching合并的批处理数目,数字代表节省了多少draw call。
Tris & Verts三角形片面、顶点数目。
Screen屏幕大小,占用内存大小。
SetPass calls渲染Pass的数目,每个Pass都需要Unity的runtime来绑定一个新的Shader。
Shadow casters场景可以投射阴影的物体数量。
Visible skinned meshes渲染蒙皮网格数目。
Animations播放动画数目。

批处理(batching)

  同一个材质的物体可以一起处理,不同物体区别在于顶点数。

动态批处理

  把每一帧可以批处理的物体合并(每一帧都重新合并),并传递给GPU,使用同一材质渲染。

主要限制条件: 
- 网格顶点属性规模要小于900。例如shader中使用顶点位置、法线、纹理坐标这三个顶点属性,顶点数要小于300(3*300 = 900)才能进行批处理。 
- 缩放时有奇数个坐标是负值时无法批处理。即xyz值中其中一个为负或同时为负。 
- 使用了光照纹理的物体,需要保证光照纹理中的同一位置,才能被批处理。 
- 多Pass的shader会中断批处理。如在前向渲染时,要用额外Pass添加更多光照时,无法被动态批处理。

静态批处理

  只在开始阶段,把模型合并到一个新网格(即使使用不同材质),使用同一网格的物体都会变成独立网格。即不能在运行时移动。往往需要占用更多内存来合并几何结构(每个物体的都会对应一个网格的复制品,每次发送给GPU都会变成多个网格)。对于除了平行光除了的Pass,其他额外Pass部分都不会进行批处理。

内部实现: 先把静态物体变换到世界空间下,构建一个大的顶点和索引缓存。对于使用同一个材质物体的只需调用一次draw call,对于不同材质物体,会减少draw call之间的状态切换。

共享材质

  • 两个材质之间只有纹理不同时,合并纹理得到更大的纹理称为图集(atlas),使用同一张纹理就可使用同一个材质,使用不同采样坐标即可采样。
  • 想在同一材质上是设置不同属性(颜色,数值等)时,可以使用网格的顶点数据来存储这些参数。
  • 经过批处理后的物体会变成更大的VBO(Vertex Buffer Object, 顶点缓冲对象)发送给GPU,VBO的数据可以传递给顶点着色器。
  • 访问共享材质使用Renderer.sharedMaterial,修改会影响所有使用该材质的物体。使用Renderer.material会创建一个该材质的复制品(会破坏批处理对该物体的应用)。

注意事项

  • 因为批处理是在世界空间下合并的,所以一些shader在模型空间计算坐标时,会得到错误结果。解决方法是使用DisableBatching标签来禁止该材质批处理。
  • 半透明物体需要从后往前的顺序绘制。Unity先保证这个顺序,再尝试批处理。即当渲染顺序无法满足时,批处理无法在这些物体成功应用。

减少需要处理的顶点数目

  在建模软件和Unity中,同一物体在Unity显示顶点数往往多于建模软件显示的。因为建模软件显示顶点数目就是几何体的点数,而Unity是从GPU角度计算顶点数目的。对于GPU,一些顶点需要一分为多:一个是分离纹理坐标(uv splits),另一个是产生平滑的边界(smoothing splits)。 
  本质是因为对GPU来说,每一个属性和顶点都必须一对一的关系。 
- 分离纹理坐标,是因为建模时一个顶点的纹理坐标有多个。例如一个立方体,三个面会有一个相交的顶点,而对于不同面,这个顶点对应不同纹理坐标。 
- 产生平滑的边界,顶点可能对应多个法线信息或切线信息,通常是因为要决定一个边是硬边(hard edge)还是平滑边(smooth edge)。

优化几何体

  • 移除不必要的硬边以及纹理衔接。
  • 避免边界平滑和纹理分离。

LOD技术(Level of Detail)

原理:当物体原理摄像机时,减少物体片元数量,提高性能。在用Unity中可以使用LOD Group组件来构建LOD。 

遮挡剔除技术(Occlusion Culling)

  消除在物体后面看不见的物体。详细可见官网API:Occlusion Culling。 
原理:用一个虚拟的摄像机来遍历场景,构建一个对象集合的层级结构。运行时使用这个数据来识别。


减少需要处理的片元数目

  重点在于减少overdraw。overdraw指的是同一个像素被绘制多次。

在场景中选择overdraw可以查看物体互相遮挡层数。 

控制绘制顺序

  深度测试的存在本身就减少了overdraw。尽可能把物体设置为不透明物体的渲染队列(从前往后渲染),避免半透明队列(从后往前渲染)。对于第一(第三)人称游戏,人造物主角通常先渲染(使用更小渲染队列),对于躲在掩体后面的地方角色,在不透明物体渲染完后再渲染(使用更大渲染队列)。对于天空盒子,可以设置队列为“Geometry + 1”。

警惕透明物体

  因为半透明物体几乎都会造成overdraw,类似GUI对象经常会设置成半透明,需要尽可能减少GUI所占面积。或者把GUI绘制和三维场景的绘制分给不同相机处理。 
  在移动平台,透明度测试使用discard或clip操作,会导致硬件优化策略失效,这种时候透明度混合性能比透明度测试好。

减少实时光照和阴影

  如场景包含3个点光源,并且使用逐像素的Shader,那draw call数目最多提高3倍(物体要再渲染一次,CPU瓶颈),同时会增加overdraw(无法批处理逐像素光源,GPU瓶颈)。

  • 使用烘培技术,先把光照烘培到一张光照纹理(lightmap)中,运行时再对其采样即可。
  • 使用God Ray,模拟光源,即透明纹理模拟得到。

      对于实时阴影,可以使用烘培把静态物体的阴影信息存到光照纹理中。


减少计算复杂度

Shader的LOD技术

在SubShader中使用LOD,小于LOD值的Shader才会被使用:

SubShader {
    Tags { "RenderType" = "Opaque" }
    LOD 200

Unity内置的Shader使用了不同LOD值,Diffuse为200,Bumped Specualr为400。

代码优化

  • 游戏中计算数目排序:对象数 < 顶点数 < 像素数。所以计算尽量在对象和逐顶点上。
  • 尽量使用低精度浮点值。避免不同精度转换。 
    • float/highp 适用存储顶点坐标等。
    • half/mediump 适用与标量、纹理坐标等变量,计算速度是float的两倍。
    • fixed/lowp 适用大多数颜色变量和归一化后的方向矢量,计算速度是float的四倍。屏幕后处理使用低精度计算。
  • 尽可能把多个特效合并到一个Shader中。
  • 尽可能不要使用分支语句和循环语句。
  • 尽可能避免sin、tan、pow、log等复杂数学运算。可以用查表法来代替。
  • 尽可能不要使用discard操作,会影响硬件优化。
Unity Shader学习笔记系列教程:

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引