Unity(C#)快速设置大量连续颜色纹理像素的优化方案咨询
Unity中高效更新垂直柱状图纹理的优化方案
问题背景
我在Unity(C#)里要绘制一张约2000×200像素的自定义纹理,本质是垂直柱状图——每列1像素宽,上方是同色区块,下方是透明像素,每列的色块高度和颜色都不一样。这个纹理需要每秒更新一次,但当前实现设置像素的速度太慢,导致帧率波动,想找最快的实现思路。
现有实现代码
初始化阶段
Color32[] m_pixelData = new Color32[width * height]; Texture2D m_texture = new Texture2D(width, height, TextureFormat.RGBA32, false); Color32 m_transparent = new Color32(0,0,0,0);
每次更新时
for (int x = 0; x < width; x++) { Color32 barColor = CalculateBarColorForThisColumn(x); int barHeight = CalculateBarHeightForThisColumn(x); for (int y = 0; y < height; y++) { int pixelIndex = y * width + x; if (y <= barHeight) m_pixelData[pixelIndex] = barColor; else m_pixelData[pixelIndex] = m_transparent; } } m_texture.SetPixels32(m_pixelData);
优化思路
1. 减少循环次数,避免重复赋值
当前代码对每个像素都执行一次赋值操作,完全可以优化:
- 初始化时一次性将
m_pixelData填充为透明色,后续更新无需再重复设置透明像素 - 每列仅对上方
barHeight个像素赋值颜色,跳过下方已为透明的像素
修改后的更新代码示例:
// 初始化阶段执行一次,填充全透明 Array.Fill(m_pixelData, m_transparent); // 每次更新时 for (int x = 0; x < width; x++) { Color32 barColor = CalculateBarColorForThisColumn(x); int barHeight = CalculateBarHeightForThisColumn(x); // 仅处理需要上色的像素 for (int y = 0; y <= barHeight; y++) { int pixelIndex = y * width + x; m_pixelData[pixelIndex] = barColor; } } m_texture.SetPixels32(m_pixelData);
如果barHeight平均值远小于200,这个优化能直接减少大量循环操作(比如平均高度100的话,循环次数直接减半)。
2. 批量内存操作替代逐像素赋值
对于同色的连续像素,可以用Buffer.BlockCopy批量复制数据,比逐像素循环赋值更高效:
// 初始化阶段执行一次 Array.Fill(m_pixelData, m_transparent); // 每次更新时 for (int x = 0; x < width; x++) { Color32 barColor = CalculateBarColorForThisColumn(x); int barHeight = CalculateBarHeightForThisColumn(x); if (barHeight < 0) continue; // 创建对应高度的同色数组 Color32[] colorBlock = new Color32[barHeight + 1]; Array.Fill(colorBlock, barColor); // 计算字节偏移:每个Color32占4字节,起始位置为x列第0行 int startByteOffset = x * 4 + 0 * width * 4; // 批量复制到目标数组 Buffer.BlockCopy(colorBlock, 0, m_pixelData, startByteOffset, colorBlock.Length * 4); } m_texture.SetPixels32(m_pixelData);
这个方法在barHeight较大时,性能提升会更明显。
3. 后台线程分担计算压力
如果上述优化后仍存在帧率波动,可以将像素数据的计算逻辑移到后台线程:
- Unity的
Texture2D相关API只能在主线程调用,所以后台线程仅负责计算Color32数组,计算完成后再回到主线程更新纹理 - 用线程安全的方式传递计算结果,比如简单的锁或者
ConcurrentQueue
示例伪代码:
private object pixelDataLock = new object(); private Color32[] pendingPixelData; private bool isDataReady = false; private int width, height; private Color32 m_transparent = new Color32(0,0,0,0); // 触发更新的方法 public void TriggerTextureUpdate() { Task.Run(() => { Color32[] newPixelData = new Color32[width * height]; Array.Fill(newPixelData, m_transparent); for (int x = 0; x < width; x++) { Color32 barColor = CalculateBarColorForThisColumn(x); int barHeight = CalculateBarHeightForThisColumn(x); for (int y = 0; y <= barHeight; y++) { int pixelIndex = y * width + x; newPixelData[pixelIndex] = barColor; } } // 线程安全地传递数据到主线程 lock (pixelDataLock) { pendingPixelData = newPixelData; isDataReady = true; } }); } // 主线程Update中处理纹理更新 void Update() { if (isDataReady) { lock (pixelDataLock) { m_texture.SetPixels32(pendingPixelData); m_texture.Apply(); isDataReady = false; } } }
4. 细节优化
- 提前缓存
width、height等固定值,避免循环中重复访问成员变量 - 优化
CalculateBarColorForThisColumn和CalculateBarHeightForThisColumn方法,比如缓存计算结果、减少重复逻辑,降低每列的计算开销
结论
当前实现并非最优,优先尝试减少循环次数的优化,这是成本最低、效果最显著的方案;如果仍不满足需求,再考虑批量内存操作;最后才是后台线程方案。多数情况下,前两种优化就能解决帧率波动问题。
内容的提问来源于stack exchange,提问作者Dogtanian
相关产品推荐
相关产品推荐

