首页 > 解决方案 > 在 Unity 中绘制纹理非常慢

问题描述

过去几周我一直在学习 Unity,以便创建一个简单的蚂蚁模拟。我渲染所有内容的方式是写入Update函数中相机大小的纹理。它可以工作,但问题是它非常慢,这样做只能获得大约 3-4 FPS。可以做些什么来加快它的速度?也许是完全不同的渲染方式?

这是一个简单测试的代码,其中一些蚂蚁只是在随机方向移动。我将所有内容都写入其中AntScript.cs的纹理连接到相机上。Canvas

AntScript.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;

public class Ant 
{
    public int thisX, thisY, val;
    public bool carryingFood = false;
    
    public Ant(int x, int y) 
    {
        thisX = x;
        thisY = y;
    }
}

public class AntScript : MonoBehaviour 
{
    private Texture2D myTexture;
    public int imageWidth = 1920;
    public int imageHeight = 1080;
    public int gridWidth = 160;
    public int gridHeight = 90;
    private int cellSize;
    public GameObject imageElement;

    private List<Ant> ants = new List<Ant>();

    void Start() 
    {
        // Initialise texture

        myTexture = new Texture2D(
            imageWidth, 
            imageHeight, 
            TextureFormat.ARGB32, 
            false
        );
        
        // Calculate the size of a grid pixel
        cellSize = (int)Math.Min(
            imageWidth / gridWidth, 
            imageHeight / gridHeight
        );
        Debug.Log(cellSize);
 
        // Add test ants
        for (int i = 0; i < 5; i++) 
        {
            for (int j = 0; j < 5; j++) 
            {
                ants.Add(new Ant(i * 4, j * 4));
            }
        }
    }

    void updImage() 
    {
        System.Random rnd = new System.Random();
        List<int> xDirs = new List<int> {0, 1, 1, 1, 0, -1, -1, -1};
        List<int> yDirs = new List<int> {1, 1, 0, -1, -1, -1, 0, 1};

/*--------------------- UPDATE ALL ELEMENTS ---------------------*/

        // Move ants
        foreach (Ant ant in ants) 
        {
            int randDirInt = rnd.Next(0, 8);
            ant.thisX += xDirs[randDirInt];
            ant.thisY += yDirs[randDirInt];
        }

/*--------------------- DRAW ALL ELEMENTS ---------------------*/

        // Clear texture
        for (int i = 0; i < imageWidth; i++) 
        {
            for (int j = 0; j < imageHeight; j++) 
            {
                myTexture.SetPixel(i, j, Color.black);
            }
        }
        
        // Draw ants
        foreach (Ant ant in ants) 
        {
            for (int i = 0; i < cellSize; i++) 
            {
                for (int j = 0; j < cellSize; j++) 
                {
                    myTexture.SetPixel(
                        ant.thisX * cellSize + i, 
                        ant.thisY * cellSize + j, 
                        Color.white
                    );
                }
            }
        }

        myTexture.Apply();

        imageElement.GetComponent<Image>().sprite = Sprite.Create(
            myTexture, 
            new Rect(0, 0, imageWidth, imageHeight), 
            new Vector2(0.5f, 0.5f)
        );
    }
    
    void Update() 
    {
        updImage();
    }
}

场景元素:

元素

脚本参数:

脚本参数

标签: c#unity3d

解决方案


通常,不要Texture2D.SetPixel在单个像素上使用Texture2D.GetPixels,而是Texture2D.SetPixels在整个图像(或您更改的部分)上使用和。

这已经更有效率了!

然后使用Texture2D.GetPixels32Texture2D.SetPixels32使用原始字节颜色格式(0 到 255 而不是 0f 到 1f)甚至更快!

var pixels = myTexture.GetPixels32();
for (int i = 0; i < pixels.Length; i++) 
{
    pixels[i] = Color.black;
}
    
    // Draw ants
foreach (Ant ant in ants)
{
    for (int i = 0; i < cellSize; i++)
    {
        for (int j = 0; j < cellSize; j++) 
        {
            // TODO: calculate the index of the pixel in the flat array
            // Not 100% sure on that one tbh ;)
            var index = (ant.thisX * cellSize + i) + myTexture.width * (ant.thisY * cellSize + j);
            
            pixels[index] = Color.white
        }
    }
}

myTexture.SetPixels32(pixels);

myTexture.Apply();

一般来说,虽然做这种基于像素的东西总是会非常昂贵 - 所以你可以例如将繁重的工作转移到线程/任务中,并且只在完成后更新或使用一些本机插件来直接操作底层纹理。

然后为了让它更快,不要一直清除整个图像。只需获取当前蚂蚁位置,清除这些像素,然后移动蚂蚁,然后写入新像素就足够了。

var pixels = myTexture.GetPixels32();

foreach (Ant ant in ants)
{
    var gridX = ant.thisX * cellSize;
    var gridY = ant.thisY * cellSize;

    // Clear current pixels
    for (int i = 0; i < cellSize; i++)
    {
        for (int j = 0; j < cellSize; j++) 
        {
            // TODO: calculate the index of the pixel in the flat array
            // Not 100% sure on that one tbh ;)
            var index = (gridX + i) + myTexture.width * (gridY + j);
            
            pixels[index] = Color.black;
        }
    }

    // move this ant
    int randDirInt = rnd.Next(0, 8);
    ant.thisX += xDirs[randDirInt];
    ant.thisY += yDirs[randDirInt];

    gridX = ant.thisX * cellSize;
    gridY = ant.thisY * cellSize;

    for (int i = 0; i < cellSize; i++)
    {
        for (int j = 0; j < cellSize; j++) 
        {
            // TODO: calculate the index of the pixel in the flat array
            // Not 100% sure on that one tbh ;)
            var index = (gridX + i) + myTexture.width * (gridY + j);
            
            pixels[index] = Color.white
        }
    }
}

myTexture.SetPixels32(pixels);

myTexture.Apply();

并且只有第一次初始化

private void Start()
{
    ...

    // Clear texture
    for (int i = 0; i < imageWidth; i++) 
    {
        for (int j = 0; j < imageHeight; j++) 
        {
            myTexture.SetPixel(i, j, Color.black);
        }
    }
}

另请注意,

imageElement.GetComponent<Image>().sprite = Sprite.Create(
        myTexture, 
        new Rect(0, 0, imageWidth, imageHeight), 
        new Vector2(0.5f, 0.5f)
    );

绝对没有必要!只要坚持一个Sprite实例。每当您之后更改纹理的像素时,任何Sprite引用此纹理的内容都会显示相同的更改。

只需分配并创建一次该精灵然后不再关心它。

void Start() 
{
    // Initialise texture

    myTexture = new Texture2D(
        imageWidth, 
        imageHeight, 
        TextureFormat.ARGB32, 
        false
    );

    imageElement.GetComponent<Image>().sprite = Sprite.Create(
        myTexture, 
        new Rect(0, 0, imageWidth, imageHeight), 
        new Vector2(0.5f, 0.5f)
    );

    ...
}

还有一点需要改进:将这些设置为只读,不要创建,然后一遍又一遍地忘记它们。这只会给 GC 带来很多工作

private readonly System.Random rnd = new System.Random();
private readonly List<int> xDirs = new List<int> {0, 1, 1, 1, 0, -1, -1, -1};
private readonly List<int> yDirs = new List<int> {1, 1, 0, -1, -1, -1, 0, 1};

void updImage() 
{
    // Move ants
    foreach (Ant ant in ants) 
    {
        var randDirInt = rnd.Next(0, 8);
        ant.thisX += xDirs[randDirInt];
        ant.thisY += yDirs[randDirInt];
    }

    ...
}

推荐阅读