Unity Shader学习笔记(30)Unity中渲染优化技术
CPU负责帧率,GPU复杂分辨率。
- CPU
- 过多的draw call。
- 复杂的脚本或物理模拟。
- GPU
- 顶点处理:
- 过多的顶点。
- 过多的逐顶点计算。
- 片源处理:
- 过多的片元。(分辨率高或overdraw)
- 过多的逐片元计算。
- 顶点处理:
- 带宽
- 使用尺寸很大且为压缩的纹理。
- 分辨率过高的帧缓存。
- CPU
- 用批处理技术减速draw call数目。
- GPU
- 减少顶点数目:
- 优化几何体。
- 使用LOD(Level of Detail)技术。
- 使用遮挡剔除(Occlusion Culling)计算。
- 减少片元数目:
- 控制描绘顺序。
- 警惕透明物体。
- 减少实时光照。
- 减少计算复杂度,优化代码。
- 减少顶点数目:
信息名称 | 描述 |
---|---|
FPS | frames per seconds,每秒帧数(帧率)。 括号内为渲染一帧需要的时间。 |
CPU | main:主线程计算时间。 render thread:渲染线程计算时间。 |
Save by batching | 合并的批处理数目,数字代表节省了多少draw call。 |
Tris & Verts | 三角形片面、顶点数目。 |
Screen | 屏幕大小,占用内存大小。 |
SetPass calls | 渲染Pass的数目,每个Pass都需要Unity的runtime来绑定一个新的Shader。 |
Shadow casters | 场景可以投射阴影的物体数量。 |
Visible skinned meshes | 渲染蒙皮网格数目。 |
Animations | 播放动画数目。 |
同一个材质的物体可以一起处理,不同物体区别在于顶点数。
动态批处理
把每一帧可以批处理的物体合并(每一帧都重新合并),并传递给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操作,会影响硬件优化。