unity shader:渲染优化

优化的原则:程序优化的第一准则就是不要优化,意思就是对问题认识不清楚或者过度的优化往往会让事情变得更加复杂,产生更多的程序错误。

优化的原因:因为移动端的GPU相对PC端的GPU而言,架构设计不一样,能够使用的带宽,功能和资源都非常的有限,所以才需要优化。

常用的性能分析工具:
1.unity本地上可以通过Statistics获取大概的渲染信息,通过Profiler获取详细的渲染信息,通过Frame Debugger获取具体的渲染过程。
2.android机器上可以通过Adreno或者NVPerfHUD工具获取渲染信息。
3.ios机器上可以使用xcode的opengl es driver instruments来获取宏观的渲染信息,也可以使用PowerVRAM来获取渲染信息,但是PowerVRAM使用的是基于瓦片的延迟渲染技术,所以渲染信息不是很实时,建议使用xcode提供的宏观渲染信息来分析。

渲染受限判定:为了渲染一帧,cpu和gpu必须都完成他们的任务。他们中的任何一个花费了太长的时间去完成任务,都会造成渲染延迟。如果当前帧渲染延迟,此时判断是cpu还是gpu受限的方式如下:
1.使用gpu usage profiler下方的cpu时间和gpu时间,如果gpu时间大于cpu时间,说明是gpu受限。
2.使用cpu usage profiler在层级视图中查看是否Gfx.WaitForPresent操作时间是最长时间消耗,如果是表明cpu等待gpu渲染时间过久,此时表明gpu受限。
3.使用cpu usage profiler在层级视图中如果出现渲染相关的函数占用时间最长,说明此时cpu调度渲染函数时间过久,此时表明cpu受限。

CPU渲染受限点查找方法:
1.通过profiler和frame debugger工具来判定cpu中哪些任务耗时比较长,并对耗时长的任务进行优化,然后测试优化后的效果。
2.通过profiler工具,判定哪个线程(主线程完成主要渲染任务,渲染线程完成cpu向gpu发送渲染指令,工人线程通过Player settings中的Graphics jobs选项开启,主要完成一些辅助单独任务)耗时比较长,找出耗时长的线程,并对该线程中耗时比较长的任务进行优化,从而优化耗时线程,然后测试优化后的效果。

常见的CPU渲染性能瓶颈以及解决方案:
1.过多的draw call:一个draw call就是cpu向gpu发送渲染指令和传递渲染图元的过程。如果一帧中需要的draw call数量越多就会造成cpu大量时间花费在提交这些draw call上。解决办法就是使用批处理技术将渲染对象合并在一个批次里面,在draw call时由cpu将这些渲染图元和渲染指令一次性传递给gpu进行渲染处理,从而减少draw call次数。unity中提供两种批处理技术,分别如下:
静态批处理:把标记为静态的渲染对象合并在一个新的网格结构中,这个新的网格中会存在每一个渲染对象的子网格,而且每个子网格中都有其他合并渲染对象的共享数据的备份,所以内存占用较大。而且合并之后的渲染对象不能移动。但是实现比较简单而且高效,对于使用同一个材质的渲染对象,unity只需调用一个批次就能全部渲染。
动态批处理:每一帧去检测渲染的对象,将使用同一个材质的渲染对象合并在一个批次里面并使用该材质对其进行渲染。实现灵活而且合并的对象可以移动。但是动态批处理很容易被破坏,不同unity版本,动态批处理的规则也不尽相同。

2.过多的setpass call:setpass call通常用来表示shader中的切换渲染状态的pass个数,而pass中状态切换又是十分耗时的,所以过多的setpass call通常会造成渲染性能低下问题。解决方法就是使用批处理或者降低渲染对象的渲染次数来减少pass执行次数。

常见的GPU渲染性能瓶颈以及解决方案:
1.过多的顶点数量:顶点数量越多,gpu在顶点着色器中处理就越久。可以使用以下方案来减少顶点数量:
优化几何体:建模软件基于人类角度理解而言,组成几何体的每一个点就是一个单独的顶点;但是对unity基于gpu角度而言,顶点的每一个属性和顶点之间必须一一对应,所以对于一些硬边(多个法线和切线信息)和纹理衔接(多个纹理坐标,如:立方体纹理)的顶点往往会由于纹理分离和边界平滑处理,从而拆分成更多的顶点。所以我们在建模几何体时应该移除不必要的三角面片,硬边和纹理衔接。

模型LOD技术:由于模型距离摄像机越来越远时,模型上的一些细节不容易被肉眼观察到,所以此时我们可以使用LOD技术来减少模型上面的面片数,从而减少顶点数量,提升渲染性能。LOD使用时就是在对象不同级别上使用不同的模型,unity自动判断级别并加载对应的模型。

遮挡剔除技术:就是使用一个虚拟的摄像机遍历整个场景中的对象,将潜在可见的对象放在一个层级集合里面,其他摄像机就从这个集合里面去取渲染对象并加以渲染,从而将一些遮挡的对象不进行渲染,实现剔除的效果。

法线贴图技术:使用法线贴图来模拟复杂的几何网格,从而降低网格的三角面数,进而降低顶点数。但是gpu加载法线纹理时也存在一些负载消耗,所以要综合考虑使用。

扫描二维码关注公众号,回复: 2157923 查看本文章

2.过多的片元数量:填充率是指gpu每秒在屏幕上绘制的像素数,片元数量越多,gpu在片元着色器中处理就越久,而且绘制的像素也就越多,从而造成填充率受限,影响渲染性能。可以使用以下方案来减少片元数量:
减少顶点数量:渲染管线的流程就是顶点着色器处理完后将顶点信息交给片元着色器以片元形式处理,所以减少顶点着色器中顶点的数量也是变相的较少片元着色器中片元的数量。

减少overdraw:overdraw就是片元着色器中同一个像素被多次渲染,减少overdraw就是减少片元数量,进而提高渲染性能。常见的减少overdraw方式如下:
1.控制渲染顺序:对于不透明的物体,我们可以通过开启深度测试和深度写入,按照从前往后的顺序来渲染物体,这样当后面物体和前面物体存在重合时,由于前面物体深度测试不满足绘制后面物体时的深度条件,进而不会再绘制一次,从而减少了overdraw。
2.时刻警惕透明物体:因为透明物体的渲染只是开启了深度测试,没有开启深度写入,而且是按照距离摄像机远近进行排序后,从后往前的顺序来渲染物体,这样这样当前面物体和后面物体存在重合时,由于后面物体深度测试满足绘制前面物体时的深度条件,所以就会再绘制一次,从而造成overdraw。所以我们时刻要警惕透明物体,当不可避免的要使用透明物体时,可以尽量控制透明物体显示的区域,不要出现重合的情况,如果出现重合区域就应该进行物体分离并重新排序来渲染,也可以对重合的区域使用不同的摄像机进行分别渲染等。
3.减少实时光照和阴影:实时光照和阴影会对每一个受影响对象进行一次重新绘制而增加overdraw,并且还不能被批处理,从而绘制时会额外增加draw call。所以对于实时光照和阴影处理时,我们可以采用烘焙技术,把光照和阴影烘焙到一张纹理中,然后从纹理中通过采样来获取光照和阴影,从而降低overdraw和draw call。

3.带宽受限:显卡带宽指的是图形芯片和显存之间一次可读入传输的数据量。大量使用未经压缩的纹理以及过大的分辨率会造成由于带宽受限而引发的性能瓶颈。所以我们应该对纹理针对不同的平台进行压缩,而且对于不同gpu设备应该设置合适的分辨率,也可以使用多级渐远纹理映射技术来生成不同分辨率的纹理,从而降低带宽造成的性能问题。

4.复杂的计算:计算复杂度越大,gpu渲染耗时就越长。常用的降低计算复杂度方法如下:
Shader的LOD技术:跟模型LOD技术类似,shader也有LOD技术,可以设置shader的LOD值,这样当渲染物体时,小于指定LOD值的shader会被执行,而大于指定LOD值的shader则会不被使用,从而降低计算量,进而降低计算复杂度。

代码层面的优化:
1.将计算放在对象或者顶点着色器中,因为计算数目排序依次是:对象 < 顶点 < 片元。
2.尽量使用低精度的浮点类型进行浮点计算。
3.尽量将插值变量和其他变量合并成一个类型变量,用不同位数做存储。
4.尽可能不要使用分支语句和循环语句,因为shader中不同的model下,指令数和寄存器数有限,而这两种操作又是十分耗费指令和寄存器的。
5.尽可能不要使用sin,tan,pow,log等比较复杂的数学计算,用查找表来代替。
6.尽量不要使用discard操作,因为在片元着色器中会舍弃片元处理,而对于一些gpu架构,如PowerVRAM采用基于瓦片的延迟渲染技术,在片元着色器前就可以确定哪些瓦片要被渲染,要是在片元着色器中使用discard后,就只有在片元着色器执行完后才能确定哪些瓦片要被渲染,从而影响gpu性能优化的策略。

根据硬件条件进行缩放:从最低的硬件设备做到兼容后,其他高配设备可以适当提高分辨率,开启部分功能等来降低低配设备上面低性能的计算处理。

猜你喜欢

转载自blog.csdn.net/zjz520yy/article/details/78790922