着色器的性能分析

我们现在知道该如何减少色器可能出现的内存消耗,让我们来了解一下在场景中,如何在大量同时运行的游戏对象,着色器和脚本等包含的大量着色器中,找出有问题的着色器。要在整个游戏中去找到某一个单独的游戏对象或者着色器可能会让人有点望而却步,但是Unity给我们提供了它内建的性能分析工具。它可以让我们知道在游戏中每一帧都发生了什么,CPU和GPU资源使用情况。


通过使用性能分析工具,我们可以使用其界面创建分析作业块,来单独分析诸如着色器、几何图新和一些一般渲染项。我们可以筛选出我们要寻找的影响性能的单个游戏对象。这让我们能够在运行时观察对象执行其功能时对 CPU和GPU的影响。


让我们来看看性能分析工具的不同部分和并且学习如何调试我们的场景,当然更重要的是如何调试我们的着色器。


  • 始前准备
    为了使用性能分析器,我们要准备好一些资源并且打开我们的性能分析窗口:

    • 1.我们就使用介绍上一个知识点时的场景,然后通过菜单 Window | ProfilerCtrl + 7 打开性能分析窗口。
    • 2.我们多复制几个球体,看看这样会对渲染有什么影响。
      你会看到跟下图类似的一些东西:
      diagram

  • 操作步骤
    使用性能分析工具的时候,你会在该窗口看到一些UI元素。在我们点击运行按钮前,让我们了解一下该如何从性能分析器中获取我们想要的信息:

    • 1.首先,点击 Profiler 窗口中的 GPU UsageCPU UsageRendering 这几栏。你可以在窗口的左上角找到这几栏:
      diagram

    使用这几栏,我们可以看到跟游戏主要功能相关的不同的数据。CPU Usage 给我们展示的是我们大部分脚本在干什么,当然还有物理运算和总体渲染。GPU Usage 这一栏给我们的是光照,阴影和渲染队列等的详情信息。最后,Rendering 这一栏有每一帧中 drawcall 和游戏场景中集合体的数量这些信息。

    点击其中的一栏,我们就可以把在 性能会话(profiling session) 中看到的数据类型单独分离出来分析。


    • 2.现在,我们可以点击性能分析栏中的小颜色块然后点击编辑器的运行按钮或者用 Ctrl + P 快捷键运行场景。
      这样选择性的查看,可以让我们更加深入的分析性能会话,因为这样我们可以选择我们想分析的内容。当场景运行的时候,再 GPU使用(GPU Usage) 栏中取消其他所有的颜色小块的勾选,然后留下 Opaque 这一勾选。请注意这样我们就可以知道 Opaque 渲染队列中的游戏对象再渲染中花了多长时间了:
      diagram

    • 3.性能分析窗口中另一个非常好用的功能是可以在图形窗口中进行的拖拽操作。这个操作会自动暂停你的游戏,这样你就可以更细致的分析图形中某一个具体的波峰从而找出引起性能问题的具体项了。你可以在图形区域内点击或者拖拽移动来暂停游戏,从而了解这一功能的具体效果:
      diagram
    • 4.现在让我们把目光聚焦到性能分析窗口的下半部分,你会发现当我们选中GPU那一栏的时候这里会有一个下拉选择框。我们可以把它展开从而获得更多当前激活的性能会话中的详细信息,这种情况下我们可以知道有关于当前摄像机渲染情况和花费时间的更多信息:
      diagram

    它能让我们全面了解Unity在某一帧中内部工作都在处理什么。在这个例子中,我们能看到场景中的球体和我们优化过的着色器在绘制到屏幕上花了大概0.14毫秒,花了7个drawcall,并且这个处理每帧花费了大概3.1%的GPU时间。通过这些类型的信息我们可以去诊断和解决跟着色器性能相关的问题。让我们准备一个测试,看看如果我们给着色器添加一个额外的纹理并且用 lerp 函数把两张漫反射纹理混合到一块会造成什么样的影响。你将会在在性能分析器中看到清晰的影响。


    • 5.修改着色器的 属性块(Properties block) ,然后添加下面的代码,这样就可以为我们的着色器添加另一个纹理了:
    Properties
     {
         _MainTex ("Albedo (RGB)", 2D) = "white" {}
         NormalMap ("Normal Map", 2D) = "bump" {}
         _BlendTex ("Blend Texture", 2d) = "white" {}
     }
    
    • 6.然后在 CGPROGRAM 中添加一个变量用来使用这个纹理:
    CGPROGRAM
    #pragma surface surf SimpleLambert exclude_path:prepass noforwardadd
    sampler2D _MainTex;
    sampler2D _NormalMap;
    sampler2D _BlendTex;
    
    • 7.相应的我们也要去修改一下我们的 surf() 函数以便我们能将纹理和漫反射纹理混合到一块:
    void surf (Input IN, inout SurfaceOutput o)
    {
        // Albedo comes from a texture tinted by color
        fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
        fixed4 blendTex = tex2D(_BlendTex, IN.uv_MainTex);
        c = lerp(c, blendTex, blendTex.r);
        o.Albedo = c.rgb;
        o.Alpha = c.a;
        o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_MainTex));
    }
    

    当你保存了你着色器的修改并且回到Unity编辑器后,你就可以运行我们的游戏并且看看我们的着色器增加的时间消耗。回到Unity后点击运行按钮并且在性能分析器窗口查看结果:
    diagram
    你可以看到场景中我们着色器渲染 Opaque 队列的时间消耗数量从 0.140 毫秒增加到了 0.179 毫秒。从添加了另一张额外的纹理后和使用了 lerp() 函数后,我们的球体的渲染时间增加了。当然这个变化非常的小,但是想象一下如果有20个着色器用不同的工作方式在不同的游戏对象上,那消耗就多了。

    利用这里给出的这些信息,你可以更快的精确的定位到引起性能下降的原因并且用上一个知识点介绍的技术解决它们。


  • 原理介绍
    描述这个工具内部是如何工作已经完全超出了这本书的范畴,我们可以推测Unity已经给了我们方法观察当游戏运行的时候电脑的运行表现是什么样子。通常来说,这个窗口跟CPU和GPU紧密相关并且实时反馈给我们关于我们的每一个脚本,游戏对象和渲染队列所占用的时间。使用这些信息,我们就可以追踪我们着色器的编写效率从而消除有问题的区域和代码。

  • 额外内容
    也可以专门对移动平台进行性能分析。如果build目标平台在 Build Settings 中设置成了Android和IOS,Unity还会给我们提供一系列额外的特性。当游戏运行的时候我们能从移动设备中实时获取信息。这变得非常的有用,因为你可以直接在移动设备上进行性能分析而不是在编辑器上。如果想了解更多关于这个过程的信息,可以参考下面链接中的Unity文档【作者提供的地址已经失效,下面是新的】:
    https://docs.unity3d.com/Manual/profiler-profiling-applications.html