最近一直在思考如何能更好的做优化渲染,本篇文章只是另一种实现的思路,其实我也没完全想好怎么应用到实际游戏中来统计,希望各位看官多多提宝贵意见。

1.本例Unity的版本是Unity2019.3.1.4

2.采用URP渲染管线,老的渲染管线没有试过,大家可以试试看。

3.FrameDebugger会将每一这数据存入RT中,名字对应如下。

实际代码中就可以这样取到它的Texture了

Texture texture = Shader.GetGlobalTexture("_CameraColorTexture");
            if (!texture)
            {
                texture = Shader.GetGlobalTexture("_CameraOpaqueTexture");
            }

并非所有都能取,比如shadowmap的RT,这名字是没有意义的,如果真想取,那就用C#反射吧。 但其实有上面两个基本已经够用了。

 

4.为了让代码更快的比较两帧图片的颜色,我采用了Burst编译比暴力的for循环快的可不是一点点。

5.为了让工具更加方便,不得不在代码中做了很多反射FrameDebugger的代码。

工具使用之前,大家可以先用FrameDebugger看一下自己需要截那些帧的数据。接着运行Unity,填入开始和结束帧的索引后,点击开始截取按钮即可。

 

截取完毕后,左边会保存每帧截取的图片,最后还会生成一张第1张和最后一张的中像素变化的图片(红色的区域就表示变化)还会输出最终像素数,重复像素数,总渲染顶点数。

在回到优化上来

1.重复像素数越多,其实就是半透明overdraw比较多。

2.最终像素数表示,光栅化后mesh最终呈现的颜色数。

3.总渲染顶点数,这个数值就很重要的。比如模型参与渲染了好几万个顶点,然而结果最终只贡献给屏幕20个像素,那这显然就不太合理了。

一些问题

1.如果摄像机会移动,那么就会造成有时候离模型近,有时候离的远,这样统计就不准确的。

2.如果使用RenderDoc能看到的信息会更多,我也比较推荐用Renderdoc,这篇文章只是开放一下思路。

上代码

using System;
using System.Collections;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using Unity.EditorCoroutines.Editor;
using Unity.Collections;
using Unity.Burst;
using Unity.Jobs;
using Unity.Mathematics;
public class FrameDebugExamplle : EditorWindow
{
 
    static Type s_frameDebugType = Type.GetType("UnityEditorInternal.FrameDebuggerUtility,UnityEditor");
    static Type s_frameDebugWindowsType = Type.GetType("UnityEditor.FrameDebuggerWindow,UnityEditor");
    static bool s_HasLatSample;
    static NativeArray<Color> s_LastSample2DColor;
    static NativeArray<Color> s_FirstSample2DColor;
    static int s_FinalPixel = 0;
    static int s_ProcessPixel = 0;
    static int s_VertexCount = 0;
    static int s_StartDC;
    static int s_EndDC;
    static string s_Result;
    static string DirectoryPath = "Assets/采样";
    static EditorWindow s_FrameDebugWindows;
 
    [MenuItem("Example/开始")]
 
    public static void ShowWindow()
    {
        OpenAndEnableFrameDebugger();
    }
 
    void OnGUI()
    {
        int count = (int)s_frameDebugType.GetProperty("count", BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).GetValue(null);
        if (count == 0)
        {
            //强制打开framedebugger窗口
            OpenAndEnableFrameDebugger();
        }
        GUILayout.Label(string.Format($"DC区间 0 - {count}"));
        s_StartDC = Mathf.Clamp(EditorGUILayout.IntField("开始", s_StartDC),0,count - 1);
        s_EndDC = Mathf.Max(Mathf.Clamp(EditorGUILayout.IntField("结束", s_EndDC),0,count), s_StartDC);
          
        if (GUILayout.Button($"开始截取: {s_StartDC}DC-{s_EndDC}DC", GUILayout.Width(200), GUILayout.Height(50)))
        {
            OpenAndEnableFrameDebugger();
            EditorCoroutineUtility.StartCoroutineOwnerless(StartGetData());
        }
        GUILayout.Label(s_Result);
    }
 
    //等N帧
    IEnumerator WaitFive(int count)
    {
        for (int i = 0; i < count; i++)
        {
            yield return null;
        }
    }
    //开始获取数据
    IEnumerator StartGetData()
    {
        s_VertexCount = 0;
        s_ProcessPixel = 0;
        s_FinalPixel = 0;
        s_Result = string.Empty;
        s_HasLatSample = false;
        FileUtil.DeleteFileOrDirectory(DirectoryPath);
        Directory.CreateDirectory(DirectoryPath);
 
        for (int i = s_StartDC; i <= s_EndDC; i++)
        {
            s_frameDebugType.GetProperty("limit", BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).SetValue(null, i);
            yield return WaitFive(1); //等1帧
            RefreshFrameDebuggerWindows();
            yield return WaitFive(1); //等1帧
            //截图
            Texture2D textureSample = TextureSample();
            Color[] colorBuffer = textureSample.GetPixels();
            if (textureSample)
            {
                File.WriteAllBytes($"{DirectoryPath}/开始{i}.jpg", textureSample.EncodeToJPG());
                if (!s_FirstSample2DColor.IsCreated)
                {
                    s_FirstSample2DColor = new NativeArray<Color>(colorBuffer, Allocator.Persistent);
                }
                if (s_HasLatSample)
                {
                    //统计差异颜色数
                    var Job = new JobDiff
                    {
                        result = new NativeArray<int>(1, Allocator.TempJob),
                        current = new NativeArray<Color>(colorBuffer, Allocator.TempJob),
                        last = s_LastSample2DColor,
                    };
                    Job.Schedule(s_LastSample2DColor.Length, new JobHandle()).Complete();
                    s_ProcessPixel += Job.result[0];
                    Job.current.Dispose();
                    Job.result.Dispose();
                    s_LastSample2DColor.Dispose();
                    //统计面数
                    EditorWindow windows = EditorWindow.GetWindow(s_frameDebugWindowsType);
                    FieldInfo info = windows.GetType().GetField("m_CurEventData", BindingFlags.Instance | BindingFlags.NonPublic);
 
                    object FrameDebuggerEventData = info.GetValue(windows);
                    if (FrameDebuggerEventData != null)
                    {
                        s_VertexCount +=   (int)FrameDebuggerEventData.GetType().GetField("vertexCount", BindingFlags.Instance | BindingFlags.Public).GetValue(FrameDebuggerEventData);
                    }
                }
                if (i != s_EndDC)
                {
                    s_HasLatSample = true;
                    s_LastSample2DColor = new NativeArray<Color>(colorBuffer, Allocator.Persistent);
                }
                else
                {
                    //统计最后一张与第一张之间的差异
                    Texture2D diff = textureSample;
                    var Job = new JobFinalDiff
                    {
                        result = new NativeArray<int>(1, Allocator.TempJob),
                        first = s_FirstSample2DColor,
                        end = new NativeArray<Color>(colorBuffer, Allocator.TempJob),
                    };
                    Job.Schedule(s_FirstSample2DColor.Length, new JobHandle()).Complete();
                    s_FinalPixel = Job.result[0];
                    diff.SetPixels(Job.end.ToArray());
                    Job.end.Dispose();
                    Job.result.Dispose();
                    s_FirstSample2DColor.Dispose();
                    File.WriteAllBytes($"{DirectoryPath}/变化.jpg", diff.EncodeToJPG());
                }
            }
        }
        int pixelSqrt = (int)Mathf.Sqrt((s_ProcessPixel - s_FinalPixel));
        int finalPixelSqrt = (int)Mathf.Sqrt(s_FinalPixel);
        s_Result = $"最终像素 {finalPixelSqrt} X {finalPixelSqrt} 重复像素 {pixelSqrt} x {pixelSqrt} 总渲染顶点数 {s_VertexCount}";
        AssetDatabase.Refresh();
    }
    static Texture2D TextureSample()
    {
        try
        {
            Texture texture = Shader.GetGlobalTexture("_CameraColorTexture");
            if (!texture)
            {
                texture = Shader.GetGlobalTexture("_CameraOpaqueTexture");
            }
            if (!texture)
            {
                Debug.LogError("没有截到图输出错误");
            }
            if (texture)
            {
                var width = texture.width;
                var height = texture.height;
                RenderTexture previous = RenderTexture.active;
                RenderTexture tmp = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.sRGB);
                Graphics.Blit(texture, tmp);
                RenderTexture.active = tmp;
                Texture2D @new = new Texture2D(width, height);
                @new.ReadPixels(new Rect(0, 0, width, height), 0, 0);
                @new.Apply();
                RenderTexture.active = previous;
                return @new;
            }
        }
        catch (Exception e)
        {
            Debug.LogError("没有截到图输出错误: " + e);
        }
        return null;
    }
 
    static void OpenAndEnableFrameDebugger()
    {
        EditorWindow.GetWindow(typeof(FrameDebugExamplle), true, "标题", true);
        //打开frameDebug窗口
        s_FrameDebugWindows = EditorWindow.GetWindow(s_frameDebugWindowsType);
        s_frameDebugWindowsType.GetMethod("EnableIfNeeded", BindingFlags.Instance | BindingFlags.Public).Invoke(s_FrameDebugWindows, null);
    }
 
    static void RefreshFrameDebuggerWindows()
    {
        Type windowsType = Type.GetType("UnityEditor.FrameDebuggerWindow,UnityEditor");
        var windows = EditorWindow.GetWindow(windowsType);
        windowsType.GetMethod("RepaintOnLimitChange", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(windows, null);
    }
 
 
    [BurstCompile]
    struct JobDiff : IJobFor
    {
        [ReadOnly] public NativeArray<Color> current;
        [ReadOnly] public NativeArray<Color> last;
        public NativeArray<int> result;
 
        public void Execute(int index)
        {
            //干掉分支预测
            result[0] += math.select(0, 1, current[index] != last[index]);
        }
    }
    [BurstCompile]
    struct JobFinalDiff : IJobFor
    {
        [ReadOnly] public NativeArray<Color> first;
        public NativeArray<Color> end;
        public NativeArray<int> result;
        public void Execute(int index)
        {
            if(end[index] != first[index])
            {
                end[index] = Color.red;
                result[0]++;
            }
        }
    }
}