首页 > 其他分享 >记一次Unity性能优化

记一次Unity性能优化

时间:2023-11-03 21:34:43浏览次数:33  
标签:UnityEngine Profiling sprite int 性能 Profiler Unity var 优化

Before you make any changes, you must profile your application to identify the cause of the problem. If you attempt to solve a performance problem before you understand its cause, you might waste your time or make the problem worse. ——unity文档
在改代码之前,你需要先分析你的应用来定位问题的根源。如果你没有理解问题根源所在就去解决它,你可能会浪费时间甚至让问题变得更糟糕。

问题简介

开发过程中加入了一个模块后,可以感知到游戏渲染变得卡顿。于是开始分析导致卡顿的原因。首先打开The Rendering Statistics window简单分析一下问题。

可以看到本次问题导致了FPS缩水为原来的1/3。 CPU主线程一帧的时间从5ms变为15ms。渲染线程因为需要等待主线程的指令测得的耗时也变长。
而渲染相关的数据没有太多改变,可以初步确定问题位于主线程。

问题分析与改进

接着我们通过Window > Analysis > Profiler.打开分析窗口,勾选CPU Usage

可以看到SpriteMerge.CreateSprite函数是引起性能变慢的主要原因。它消耗了13.87ms,分配了136.4KB的内存。

点击显示SpriteMerge.Create代码


using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class SpriteMerge : MonoBehaviour
{
    public SpriteRenderer spriteRenderer;// assumes you've dragged a reference into this
    public Action OnMainCharacterSpriteUpdate = delegate{};

    // Use this for initialization
    void Start()
    {
        spriteRenderer = GetComponent();
        if(spriteRenderer == null)
            spriteRenderer = this.gameObject.AddComponent();
    }

    public void Update()
    {
        spriteRenderer.sprite = Create(this.transform);
        OnMainCharacterSpriteUpdate(spriteRenderer.sprite);
    }

    /* Takes a transform holding many sprites as input and creates one flattened sprite out of them */
    public Sprite Create( Transform input)
    {
        var spriteRendererList = input.GetComponentsInChildren().ToList();
        if (spriteRendererList.Count == 0)
        {
            Debug.Log("No SpriteRenderers found in " + input.name + " for SpriteMerge");
            return null;
        }
        spriteRendererList.Sort((sr1,sr2)=>sr1.sortingOrder.CompareTo(sr2.sortingOrder));
        var spriteList = new List();
        foreach(var spriteR in spriteRendererList)
        {
            var sprite = spriteR.sprite;
            if (sprite == null)
                continue;
            if (spriteR.gameObject == this.gameObject)
                continue;

            spriteList.Add(sprite);
        }

        var size = CalculateSize(spriteList,out Vector2Int pivot);
        if(Input.GetKeyDown(KeyCode.F1))
            Debug.Log(size);

        return CreateSprite(spriteList, size, pivot);
   }

    private Vector2Int CalculateSize(List spriteList, out Vector2Int pivot)
    {
        UnityEngine.Profiling.Profiler.BeginSample("SpriteMerge CalculateSize");

        if(spriteList.Count == 0)
        {
            pivot = Vector2Int.zero;
            return Vector2Int.zero;
        }

        int minX = int.MaxValue;
        int maxX = int.MinValue;
        int minY = int.MaxValue;
        int maxY = int.MinValue;

        int pivotX = 0;
        int pivotY=0;
        foreach(var sprite in spriteList)
        {
            var minVec =  -sprite.pivot;
            if(minVec.x < minX)
            {
                pivotX = (int)sprite.pivot.x;
                minX = (int)minVec.x;
            }
            if(minVec.y < minY)
            {
                pivotY = (int)sprite.pivot.y;
                minY = (int)minVec.y;
            }

            var maxVec = sprite.rect.size - sprite.pivot;
            maxX = (int)Mathf.Max(maxX, maxVec.x);
            maxY = (int)Mathf.Max(maxY, maxVec.y);
        }

        var result = new Vector2Int(maxX-minX,maxY-minY);
        pivot = new Vector2Int(pivotX, pivotY);

        UnityEngine.Profiling.Profiler.EndSample();
        return result;
    }

    private Sprite CreateSprite(List spriteList, Vector2Int size, Vector2Int pivotPixel)
    {
        UnityEngine.Profiling.Profiler.BeginSample($"SpriteMerge CreateSprite");
        var pivoteFloat = ((Vector2)pivotPixel) / size;
        var targetTexture = new Texture2D(size.x, size.y, TextureFormat.RGBA32, false, false);
        targetTexture.filterMode = FilterMode.Point;

        var targetPixels = targetTexture.GetPixels();

        var fillColor = new Color(0, 0, 0, 0);
        for(int i = 0; i < targetPixels.Count();++i)
        {
            targetPixels[i] = fillColor;
        }
        targetTexture.SetPixels(targetPixels);

        foreach(var sprite in spriteList)
        {
            UnityEngine.Profiling.Profiler.BeginSample("SpriteMerge CreateSprite 1 sprite");
            var offsetPixel = pivotPixel - new Vector2Int((int)sprite.pivot.x, (int)sprite.pivot.y);

            var spriteSize = sprite.rect.size;

            for(int i = 0;i< (int)spriteSize.x;i++)
            {
                for(int j = 0; j < (int)spriteSize.y;j++)
                {

                    int x = (int)sprite.rect.x + i;
                    int y = (int)sprite.rect.y + j;

                    UnityEngine.Profiling.Profiler.BeginSample("SpriteMerge CreateSprite GetPixel");
                    var color = sprite.texture.GetPixel(x, y);
                    UnityEngine.Profiling.Profiler.EndSample();

                    //避免透明的像素覆盖之前的颜色
                    if (color.a == 0)
                        continue;

                    UnityEngine.Profiling.Profiler.BeginSample("SpriteMerge CreateSprite SetPixel");
                    targetTexture.SetPixel(i + offsetPixel.x, j + offsetPixel.y, color);
                    UnityEngine.Profiling.Profiler.EndSample();
                }
            }
            UnityEngine.Profiling.Profiler.EndSample();
        }

        targetTexture.Apply(false, true);// read/write is disabled in 2nd param to free up memory
        var result =  Sprite.Create(targetTexture, new Rect(new Vector2(), size), pivoteFloat, 100, 0, SpriteMeshType.FullRect);

        UnityEngine.Profiling.Profiler.EndSample();
        return result;
    }
}

其中Self ms占用最多的标记分别是GetPixel 6.22ms,1 Sprite 5.00ms, SetPixel 2.06ms。

这里我们分别观察对应的代码。

  foreach(var sprite in spriteList)
  {
      //self cost 5.00ms
      UnityEngine.Profiling.Profiler.BeginSample("SpriteMerge CreateSprite 1 sprite");
      var offsetPixel = pivotPixel - new Vector2Int((int)sprite.pivot.x, (int)sprite.pivot.y);

      var spriteSize = sprite.rect.size;

      for(int i = 0;i< (int)spriteSize.x;i++)
      {
          for(int j = 0; j < (int)spriteSize.y;j++)
          {
              int x = (int)sprite.rect.x + i;
              int y = (int)sprite.rect.y + j;
              
              //self cost 6.22ms
              UnityEngine.Profiling.Profiler.BeginSample("SpriteMerge CreateSprite GetPixel");
              var color = sprite.texture.GetPixel(x, y);
              UnityEngine.Profiling.Profiler.EndSample();

              //避免透明的像素覆盖之前的颜色
              if (color.a == 0)
                  continue;

              //self cost 2.06ms
              UnityEngine.Profiling.Profiler.BeginSample("SpriteMerge CreateSprite SetPixel");
              targetTexture.SetPixel(i + offsetPixel.x, j + offsetPixel.y, color);
              UnityEngine.Profiling.Profiler.EndSample();
          }
      }
      UnityEngine.Profiling.Profiler.EndSample();
  }

1,3两个标记的代码比较简单,就是一行函数调用。 第2个标记,有着两层循环,很可能是循环次数太多,起到了放大的作用。因此我们这里首先对循环进行优化。

优化1: float到int的强转放到循环外。时间开销占比从22%降低到14.4%。

这里我们就会继而想去优化GetPixel的开销。
优化2: 通过使用另一个GetPixels函数,一次性获得所有的像素。

        foreach(var sprite in spriteList)
        {
            UnityEngine.Profiling.Profiler.BeginSample("SpriteMerge CreateSprite 1 sprite");
            var offsetPixel = pivotPixel - new Vector2Int((int)sprite.pivot.x, (int)sprite.pivot.y);

            var spriteSize = sprite.rect.size;

            //优化1
            int spriteSizeX = (int)spriteSize.x;
            int spriteSizeY = (int)spriteSize.y;

            int spriteRectX = (int)sprite.rect.x;
            int spriteRectY = (int)sprite.rect.y;

            //优化2
            var pixels = sprite.texture.GetPixels(spriteRectX, spriteRectY, spriteSizeX, spriteSizeY);
            for(int i = 0;i< spriteSizeX; i++)
            {
                for(int j = 0; j < spriteSizeY; j++)
                {
                    UnityEngine.Profiling.Profiler.BeginSample("SpriteMerge CreateSprite GetPixel");
                    var index = i + j * spriteSizeX;
                    var color = pixels[index];
                    UnityEngine.Profiling.Profiler.EndSample();

                    //避免透明的像素覆盖之前的颜色
                    if (color.a == 0)
                        continue;

                    UnityEngine.Profiling.Profiler.BeginSample("SpriteMerge CreateSprite SetPixel");
                    targetTexture.SetPixel(i + offsetPixel.x, j + offsetPixel.y, color);
                    UnityEngine.Profiling.Profiler.EndSample();
                }
            }
            UnityEngine.Profiling.Profiler.EndSample();
        }

总结与展望

通过分析优化,我们将帧率从66帧提高到了100帧左右。

除了本篇文章的优化方式外,我们还可以通过缓存处理结果,以及将处理过程放在另一个线程中完成来提高帧率。

标签:UnityEngine,Profiling,sprite,int,性能,Profiler,Unity,var,优化
From: https://www.cnblogs.com/dewxin/p/17807675.html

相关文章

  • unity 点击,长按和双击
    转自:Unity单击、双击、长按事件处理_unitybutton长按事件_司军礼的博客-CSDN博客原理:1.将操作抽象成三个类:ClickButton,DoubleClickButton,PressButton并实现各自的生命周期函数:ClickButton:OnPointerDown启动监测,OnPointerUp:调用点击成功事件DoubleClickButton:OnPointerDown启......
  • Oracle 性能检查SQL 语句 转载 https://blog.csdn.net/wan212000/article/details/13
    目录1.Oracle查询SQL语句1.1.性能查询常用SQL1.1.1.查询最慢的SQL1.1.2.列出使用频率最高的5个查询1.1.3.消耗磁盘读取最多的sqltop51.1.4.找出需要大量缓冲读取(逻辑读)操作的查询1.1.5.查询每天执行慢的SQL1.1.6.从V$SQLAREA中查询最占用资源的查询1.1.7.......
  • Veeam Backup&Replication V12 配置和优化代理服务器
    已经安装并完成了VeeamBackup&Replication所需的基本配置,接下来就可以配置和优化代理服务器:ProxyServers:代理服务器是VeeamBackup&Replicationv12应用程序,它们负责备份和还原作业的所有繁重任务或处理任务。VeeamBackup&Replicationv12引入了将Linux代理与持续数据保护Conti......
  • Go语言定时器实战:性能优化与实用技巧
    Go语言定时器实战:性能优化与实用技巧原创 Go先锋 Go先锋 2023-11-0307:58 发表于广东收录于合集#Go语言包29个Go先锋读完需要8分钟速读仅需3分钟  概述在日常开发中,定时器是一个非常常用且重要的功能。它可以让程序在特定的时间间隔内执行某些任务,比......
  • 从FrameDebugger看Unity渲染
     Unity如何渲染一个3D+2D的游戏画面,今天通过FrameDebugger来看下Unity内置渲染管线的渲染策略, 后续再出一些URP渲染管线相关的文章。    Unity渲染场景的几个主要部分    Unity内置渲染管线是基于摄像机来进行渲染的,每个摄像机按照摄像机的渲染顺序来依次渲染,渲......
  • mysql sql常用优化
    1 explain输出执行计划,检查orderby和where后边的字段是否建立索引2in()中的列不应过多,notin和in()数据过多都不再走索引,使用全表扫描,连续数值可以使用between1and33select后边指定字段,少用select(*)4 where子句中避免isnull/isnotnull5 应尽量避免在whe......
  • 自己上手写性能测试工具(一)
    国庆重新学习了一下go的gin高性能测试框架。用JMeter来测试gin与flask接口的性能,差别很大。为什么我自己不尝试写一个性能工具,性能工具的核心就是并发和请求。请求可以选择Python的requests库。并发可以通过python的进程、线程、协程模拟。这么一想,也不是很难了,上手撸一个。依......
  • 自己上手写性能测试工具(二)
    上周教大家如何通过Python实现性能测试工具,最后留了一下问题,今天我们继续来实现命令行工具。依赖库requests==2.22.0gevent==20.9.0numpy==1.19.2click==7.1.2click库今天的主角是click库。中文文档:https://www.osgeo.cn/click/index.html第一个例子(hello.py):importclick@c......
  • 打造性能监控平台
    在做性能测试的时候,你是如何监控被测试系统的硬件资源的,如果是云平台,那么一般提供的有可视化的监控信息,如果是本地服务,只能通过系统命令(如ps、top、lsof等)查看。我们可以自己打造一个可视化的系统系统资源监控平台,本文就教大家如何实现。准备工具:Flask:一个简单的web框架。psutil:......
  • 自己上手写性能工具(三)
    自己上手写性能测试工具(一)自己上手写性能测试工具(二)我又来更新第三篇了,因为我又改进了一些功能。显示性能测试进度前些天逛github发现了tqdm,他可以动态显示程序的执行进度。根据前面开发的功能,在运行性能时,如果并发用户和请求数很大,那么终端就处于执行状态,不会有任何信息输出,直到运......