- Home /
SetPixels too slow
I am having trouble with making bullet holes in an airplane model I put together in Maya. The crux of the issue seems to be the speed at which either SetPixels() or mainTex.Apply() operate since that is what all my debugging points to. Right now a single bullet hole will slow the game to a very sad 0.9 seconds per frame. The textures being edited are both 2048 X 2048 which I know is quite large (I am thinking about cutting this up into smaller textures and mapping them separately on each part of the airplane).
Without too much talking, I present my commented code for your help and ask this one question: "How can I speed up the operations?"
var mainTex : Texture2D[]; // all editable textures of the object (some objects will have two or more) added before runtime
private var holesArray = new Array(); // x (x position), y (y position), z (radius)
private var holeTimers = new Array(); // cooling timer
function Update () {
UpdateBulletHoles(); // cycle through the bullet holes that already exist and aply their texture changes
}
// this function is called by the bullet, explosive, or other such object which comes in contact with this object
function AddHole (xPos : float, yPos : float, rad : float) {
holesArray.Push(Vector3(xPos * mainTex[0].width, yPos * mainTex[0].height, rad)); // x and y are set up for positioning the hole on the texture; z takes the radius of the projectile
holeTimers.Push(0.0); // all bullet holes start at zero cooling time
}
function UpdateBulletHoles () {
if (holesArray.length == 0) // if there are no active bullet holes then don't do anything
return;
// this block of code is used to remove holes that have fully cooled and to ensure that the hole is drawn as being fully cooled
var holesToRemove = new Array();
for (i = 0; i < holesArray.length; i++)
{
holeTimers[i] += Time.deltaTime * 10;
if (holeTimers[i] >= 20)
{
holesToRemove.Push(i);
holeTimers[i] = 20;
}
}
// cycle through textures
for (l = 0; l < mainTex.length; l++)
{
// cycle through holes
for (k = 0; k < holesArray.length; k++)
{
// don't draw holes on parts of the geometry that have not had their UV coordinates layed out perfectly (nor ever will)
if (holesArray[k].x >= 5 || holesArray[k].y <= 2040)
{
var pix : Color;
// get a square of pixels from the texture that is as wide and as tall as the drawn hole and centered on that hole
var pixels = mainTex[l].GetPixels(holesArray[k].x - holesArray[k].z - 3, holesArray[k].y + holesArray[k].z + 3, (holesArray[k].z * 2) + 6, (holesArray[k].z * 2) + 6, 0);
var pixPos : Vector2;
// set up ring color to show its cooling progress
if (holeTimers[k] <= 10)
pix = Color.Lerp(Color(1, 1, 0, 1), Color(1, 0.5, 0, 1), holeTimers[k] / 10);
else
pix = Color.Lerp(Color(1, 0.5, 0, 1), Color(0.5, 0.5, 0.5, 1), (holeTimers[k] - 10) / 10);
// cycle through the square of pixels just extracted from the texture
for (i = 0; i < pixels.length; i++)
{
// this block of code finds the distance of each pixel from the center of the hole
var dist : float;
pixPos.x = ((i / (holesArray[k].z + 3)) - Mathf.Floor(i / (holesArray[k].z + 3))) * (holesArray[k].z + 3);
pixPos.y = Mathf.Floor(i / (holesArray[k].z + 3));
dist = Mathf.Sqrt(Mathf.Pow(pixPos.x - (holesArray[k].z + 3), 2) + Mathf.Pow(pixPos.y - (holesArray[k].z + 3), 2));
// draw the ring on the outside of the hole 3 pixels thick and punch a hole with the previously given radius
if (dist <= holesArray[k].z + 3 && dist > holesArray[k].z)
if (pixels[i].a > 0.5)
pixels[i] = pix;
if (dist <= holesArray[k].z)
{
pixels[i] = pix;
pixels[i].a = 0;
}
}
// set the square of pixels with the altered pixels
mainTex[l].SetPixels(holesArray[k].x - holesArray[k].z - 3, holesArray[k].y + holesArray[k].z + 3, (holesArray[k].z * 2) + 6, (holesArray[k].z * 2) + 6, pixels, 0);
}
}
mainTex[l].Apply(); // send the pixel changes to the graphics card
}
// remove fully cooled holes from the arrays
for (i = 0; i < holesToRemove.length; i++)
{
holesToRemove.RemoveAt(i);
holesArray.RemoveAt(i);
holeTimers.RemoveAt(i);
}
}
Thanks, -Conan Horus
Nothing to do with SetPixels; when you use Apply() it has to upload the entire texture no matter how few or how many pixels have changed.
Answer by Ronnymonny · Jul 19, 2019 at 06:17 PM
@ConanHorus, I'm no expert, but I would say you might have better luck using particles facing away from the plane at the correct angle. Not only are particles cheap resource-wise, but they also allow you to modify the bullet hole's appearance as a separate texture rather than hardcoding it. The only major issue with this approach are the angles. Perhaps these could be inferred from the collider? While you're tinkering, you might need to view the Unity documentation here: https://docs.unity3d.com/Manual/ParticleSystems.html
Answer by JonPQ · Jul 19, 2019 at 10:19 PM
A few ideas and pointers for you. Looks like you are directly modifying pixels in a texture... If you have to do it that way....
Note:- sqrt is slow, pow is probably slow also.... you don't really want to be doing math on the processor, per pixel. (hint:- thats what the graphics hardware is for)
version 1.5 it would be better if you have a collection of pre-drawn textures of bullet holes (there are many on the asset store) perhaps pre-processed into 2d arrays, and do direct pixel copies, copy one array into the other... with no sqrt/pow math required. It would be much faster. Your method is slow to write pixels, but is fast for rendering and 0 cost (except when adding or removing holes) once you have modified the texture.
version 2.0 (aim for this soln if you can ) you could use a blit or camera with render to texture to draw to your texture. This is replacing your loop code, with hardware blit to draw the bullet hole into the render texture... take a look here... https://docs.unity3d.com/ScriptReference/Graphics.Blit.html
Alternate idea is to use decals.. when your raycastHit... impacts, the collision point information returns a point position and a normal. You could place a single quad, at that position, and parent it to the plane's transform, so it moves with the gameObject, orient it with its surface pointing up along the normal (something like... decal.transform.up=hit.normal; ) that quad would have a bullet hole texture/material on it.
it could have a timer script that destroys itself, or you could have a manager with a list of decals.. that manages the timers on all of them. Decals are quick and easy to implement, but suffer issues when hitting corners and edges, they intersect, or overlap outside. For example. small bullet hole decals on large walls work great mostly. But when the size of the hole gets large compared to the curve on surfaces or detail size, they look bad. There is also some performance cost.. the more decals, the more quads must be drawn and add to objects in the scene. Decals also work anywhere, and don't require keeping copies of textures to modify.
One last option... projecting / calculating geometry (small patch) to match the impact shape and geometry, and project the bullet hole texure onto that. Complex solution, that will look good..... but take a lot of work. (unless it exists on the asset store already)
good luck
Your answer
Follow this Question
Related Questions
Is it expensive to use SetPixels() every frame for a second or 2? 1 Answer
Baking decals into a texture 3 Answers
Fade Out Background Based On Movement or Lack Thereof (Webcam Texture) 0 Answers
How to paint color on plane in runtime on mouse position? 0 Answers
Setpixels leads to a blank texture? 1 Answer