c# - 在 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();
}
}
场景元素:
脚本参数:
解决方案
通常,不要Texture2D.SetPixel
在单个像素上使用Texture2D.GetPixels
,而是Texture2D.SetPixels
在整个图像(或您更改的部分)上使用和。
这已经更有效率了!
然后使用Texture2D.GetPixels32
和Texture2D.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];
}
...
}
推荐阅读
- flutter - 如何解决?
- c# - 当两个客户端在同一台机器上运行时 C# TCP 多客户端问题
- node.js - 如何发送发布请求并使用 .pem 证书和密码进行身份验证
- c++ - 从基本静态方法调用派生构造函数
- javascript - 如何转义预设变量(其中有破折号)
- android - 在文本视图中使用 Retrofit 显示来自 Json 数组的数据
- xamarin.forms - 为什么 Prism.Forms DialogService 不起作用?
- matlab - 有效创建具有四个 (2 x 2) 选项的子类
- python - 将df中的日期字段转换为实际的星期几?
- grafana - 159h 与 160h 打破了 Grafana 中表格时间范围内的相对时间