- Home /
Best Method for Setting Mass Numbers of Pixels (SetPixel, SetPixels)
I have been trying to fill the entire background of an image with just a single color, but the only way I can get it to work is through 2 for loops setting the x y coordinators and then setting the color. Naturally this is horrendously inefficient but I don't know how else to do it. Any guidance on this topic would be very helpful. Thanks!
Now that you have a solution for this, let me put in a caveat. There aren't many "good" reasons to use a single-color texture in a material ins$$anonymous$$d of using a material with the same albedo/diffuse color.
That said, I can think of a few reasons why you might choose to go the texture-route (like profiling draw performance before texture assets are available), but if you don't have a good reason you're using textures, I'd recommend achieving single-colored objects with textureless materials ins$$anonymous$$d. For one, it is easier to change an objects color in Unity's Editor, and it will incur even less processing overhead.
Answer by roddles · May 04, 2015 at 09:32 AM
Using Texture2D.SetPixels32 is definitely the way to go. The question of how to quickly create an array or list with a single value is still valid. Assuming you are using C# and not JavaScript, Enumerable.Repeat is efficient at making an array a specific value. You could do something like this:
using UnityEngine;
using System.Linq;
public class MyClass {
public void CreateTexture(Texture2D texture, int width, int height, Color32 color) {
Color32[] newColors = Enumerable.Repeat(color, width * height).ToArray ();
texture.SetPixels32 (newColors);
}
}
EDIT:
Here's the method using a simple array, since – as bunny83 pointed out – the Enumerable ToArray() technique has additional overhead and excess garbage creation.
public void CreateTexture(Texture2D texture, int width, int height, Color32 color) {
Color32[] newColors = new Color32[width * height];
foreach (Color32 colorElement in newColors {
colorElement = color;
}
texture.SetPixels32 (newColors);
}
wow. Before I could only do 32 X 32 texures, now like 512 X 512 with a stable frame rate. And all of that 6 times over. Thanks!
I am actually trying to create a procedural skybox based on scene objects, and all of that. The solution must not be based on camera render distance. The whole idea is around the idea that when you move through the universe, the skybox changes at the same time. But I was having trouble getting setpixels to work. Under any other circumstance I would use a material with a solid color, but it just wouldn't work the way I want it to. It would still involve this kind of process.
"Enumerable.Repeat is efficient" is simply not true ^^ It's actually the opposite. It's just a nice short one-liner but is very inefficient:
Enumerable.Repeat returns an IEnumerable and therefore creates an unnecessary object. (That's not too bad).
Since IEnumerable doesn't have a size the ToArray method has to iterate the IEnumerable and "collect" all items. This is done the same way a generic List works. It first creates an array with 4 elements and fills item after item. Once the array is to small it creates a new array and doubles the size. So the new array has 8 items. Once it's full again it does the same again 16->32->64 .... Each time a new array is created all elements from the old array are copied into the new one. So for an array with 512*512 elements it has to create a new array 16 times. This generates huge amount of garbage.
ToArray uses a Buffer object internally. Once the array is done it again creates a new array with the actual needed size and once more copies all elements over.
Each time you want such an initialized array this method creates a new array which actually isn't necessary. If you want to change the array frequently you shouldn't create a new array each time but reuse the old one.
two nested for loops that initialize an array is way more efficient in the sense of speed and being memory friendly
@$$anonymous$$ajor:
"Naturally this is horrendously inefficient" how did you come to this conclusion? If you have n elements of an array you want to set to a specific value there's no way around setting each element to that value which you usually do in a loop. If all should have the same value you don't need two nested loops but it actually makes almost no difference.
Don't get me wrong: Using SetPixel is slow as hell. You should always use SetPixels or SetPixels32 and work with an array. But to initialize the array you really should use a loop and not Enumerable.Repeat
@Bunny83 Good to know. $$anonymous$$y knowledge of C# internals is woefully incomplete. Also, since it's a single color and row/columns don't matter, a single loop with width * height elements is fine.
public void CreateTexture(Texture2D texture, int width, int height, Color32 color) {
Color32[] newColors = new Color32[width * height];
foreach (Color32 colorElement in newColors {
colorElement = color;
}
texture.SetPixels32 (newColors);
}
@Bunny83 I was referring to using setpixel. Actually now that I have been tweaking things it comes to more of setting the values in an array in Awake, and then setting them later in update with setpixels.
Answer by Sessional · May 02, 2015 at 03:18 PM
I find SetPixels is actually pretty quick. When I was browsing the Docs they suggested using Color32 where possible to avoid something (I don't remember quite what, probably something with the float to byte conversion?)
Here's a snippet of what I do:
if (inputModule != null)
{
module = new Clamp(minimum, maximum, inputModule[0]);
colors = new Color32[example.width * example.height];
for (int texX = 0; texX < example.width; texX++)
{
for (int texY = 0; texY < example.height; texY++)
{
double value = (module.GetValue(texX, texY, 10) + -inputNodes[0].outputMin) / (inputNodes[0].outputMax - inputNodes[0].outputMin);
if (value < 0.0) value = 0.0;
if (value > 1.0) value = 1.0;
byte colorValue = (byte)(value * 255);
colors[texY * example.width + texX] = new Color32(colorValue, colorValue, colorValue, 255);
}
}
example.SetPixels32(colors);
example.Apply();
}
This is for a 200x200 example texture that I refresh on button click, which actually refreshes quick enough you don't realize it didn't refresh. (This is also an OnGUI method so I have a little bit of forgiveness on speed I guess?)
A quick google also says it's not really possible to set the default value that the array will initialize to without a loop.
If you find the loop is not quick enough you can also create default textures with the background colors already set that you use to generate on top of for whatever you need.