You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Unity实时更新图像性能优化求助:现有实现卡顿严重

嘿,我看了你的问题和代码,单帧500ms确实拖慢了整个流程,咱们一步步拆解瓶颈,给你几个针对性的优化方向和替代方案~

先揪出当前代码里的核心性能问题

你的流程里有几个明显的冗余和阻塞点:

  • 绕远路的Bitmap转换:从生成Bitmap→转字节数组→用BMPLoader解析→转Texture2D像素,这一套下来额外消耗了大量CPU,完全是没必要的中间环节。
  • 主线程阻塞的同步计算ConvertStringToSignalsListCreateMouseHeatmap都是耗时操作,却直接放在Update()里同步执行,每帧都卡主线程,自然拖慢帧率。
  • 不必要的每帧重复操作:比如ri.rectTransform.sizeDelta的设置,屏幕尺寸不会每帧变化,没必要重复赋值。

优化方案按优先级排序

1. 砍掉Bitmap环节,直接操作Texture2D像素

这是见效最快的优化——跳过所有Bitmap相关的转换,直接生成Texture2D需要的Color32数组,省掉中间所有冗余步骤。

把原来的CreateMouseHeatmap改成直接输出像素数据:

// 替代原有的Bitmap生成逻辑,直接返回Color32数组
private Color32[] GenerateHeatmapPixels(int width, int height, List<Signal> signals)
{
    Color32[] pixels = new Color32[width * height];
    // 把原来生成Bitmap的像素逻辑,直接改成填充这个pixels数组
    // 比如根据鼠标位置计算热力点的颜色、扩散范围等
    return pixels;
}

然后更新纹理的逻辑简化成:

private void UpdateTexture(Color32[] pixelData)
{
    if (oneTexture == null)
    {
        // 初始化时创建和屏幕同尺寸的Texture,关闭mipmap(实时更新不需要)
        oneTexture = new Texture2D(Screen.width, Screen.height, TextureFormat.RGBA32, false);
        oneTexture.filterMode = FilterMode.Point; // 不需要模糊的话用Point模式更快
    }
    oneTexture.SetPixels32(pixelData);
    oneTexture.Apply(false); // 关闭mipmap生成,大幅减少耗时
}

2. 把耗时计算移到后台线程

如果你的ConvertStringToSignalsList和热力图生成逻辑确实无法简化,那绝对不能放在主线程里执行。用后台线程处理计算,完成后再回到主线程更新纹理,避免阻塞渲染。

示例代码:

private void Update()
{
    // 只在主线程做轻量操作:获取鼠标位置
    double xValue = Input.mousePosition.x;
    double yValue = Screen.height - Input.mousePosition.y;
    var currentMousePoint = $"{xValue},{yValue}{Environment.NewLine}";

    // 把耗时计算丢给线程池,避免每帧创建新线程
    ThreadPool.QueueUserWorkItem(_ => 
    {
        // 后台线程执行耗时操作
        var signals = Program.ConvertStringToSignalsList(currentMousePoint);
        var pixelData = GenerateHeatmapPixels(Screen.width, Screen.height, signals);
        
        // 回到主线程更新纹理(需要一个主线程调度器)
        UnityMainThreadDispatcher.Instance.Enqueue(() => 
        {
            UpdateTexture(pixelData);
            ri.texture = oneTexture;
        });
    });

    // 数据库写入继续用协程,不影响主线程
    StartCoroutine(WriteToDB(xValue, yValue));
}

这里需要一个简单的主线程调度器(自己实现就行):

public class UnityMainThreadDispatcher : MonoBehaviour
{
    public static UnityMainThreadDispatcher Instance { get; private set; }
    private readonly Queue<Action> _pendingActions = new Queue<Action>();

    private void Awake()
    {
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    private void Update()
    {
        lock (_pendingActions)
        {
            while (_pendingActions.Count > 0)
            {
                _pendingActions.Dequeue().Invoke();
            }
        }
    }

    public void Enqueue(Action action)
    {
        lock (_pendingActions)
        {
            _pendingActions.Enqueue(action);
        }
    }
}

3. 终极优化:用GPU/Shader实现热力图

如果你的热力图逻辑可以用Shader实现,那性能会有质的飞跃——GPU并行计算完全不占用CPU,单帧耗时能降到几毫秒甚至更低。

比如写一个简单的透明混合Shader,直接在GPU上绘制热力点:

Shader "Custom/RealTimeHeatmap"
{
    Properties
    {
        _MainTex ("Base Texture", 2D) = "black" {}
        _MousePos ("Mouse Position", Vector) = (0,0,0,0)
        _HeatRadius ("Heat Radius", Float) = 60
        _HeatColor ("Heat Color", Color) = (1,0,0,0.15)
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; };
            struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; };

            sampler2D _MainTex;
            float2 _MousePos;
            float _HeatRadius;
            float4 _HeatColor;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                // 计算当前像素到鼠标位置的距离
                float2 pixelPos = i.uv * _ScreenParams.xy;
                float dist = distance(pixelPos, _MousePos);
                
                if (dist < _HeatRadius)
                {
                    // 根据距离衰减透明度,模拟热力扩散
                    float alpha = 1 - (dist / _HeatRadius);
                    col += _HeatColor * alpha;
                }
                return col;
            }
            ENDCG
        }
    }
}

然后在代码里只需要每帧传递鼠标位置给Shader:

public Material heatmapMaterial;

void Update()
{
    Vector2 mousePos = new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y);
    heatmapMaterial.SetVector("_MousePos", mousePos);
    
    StartCoroutine(WriteToDB(mousePos.x, mousePos.y));
}

4. 小细节优化

  • StringBuilder代替字符串拼接:myPoints的拼接改成StringBuilder,减少内存分配。
  • 提前初始化UI尺寸:ri.rectTransform.sizeDelta只在Start()或屏幕尺寸变化时设置,不用每帧重复赋值。

内容的提问来源于stack exchange,提问作者user3665751

火山引擎 最新活动