Need help optimizing my Sprite effect w. Shader, Blit and/or CommandBuffer

Hi Unity Community!
I hear great things about you, so I figured you might be willing to help me with my Sprite effect.

I’ve only been using Unity and C# for a couple of weeks, and for my first project, I decided to create this sprite effect - the effect is pretty simple; it basically creates visual “echos” of a sprite, which are then tinted and/or scaled and faded out over time, with various tweak-able parameters (echo lifetime, time between echos, color etc.) - it basically looks like this:

Currently, it works great, the effect does pretty much exactly what I want it to do - it is, however, very CPU heavy. At the moment, every single echo is a separate GameObject! Not only that, but all the calculations handling tinting/scaling/alpha-fading are done by the CPU - there must be a better way to do this!

So I started looking more into it, and found some ‘candidates’ for optimization

  • Writing a custom Shader

It would be nice having a shader offload the calculations for scaling/tinting/alpha-fading to the GPU. The issue with this is, that I don’t know how to handle the positioning - I need to somehow perserve the Texture of the Sprite I want to draw, but I need to discard/replace the position information - I’ve never written a shader before, and looking at default shaders, I’m having trouble wrapping my head around how to do this (the position of a Texture seems to be embedded in the Texture?)

  • Blit / CommandBuffer

I would like to draw the echo Texture/Sprite directly to the screen, instead of having to use GameObject’s - it seems like unnecessary overhead (I wont ever need to add stuff like colliders or scripts to the echo’s - they’re supposed to be just visual, not actual objects) - I have, however, not managed to successfully make Blit actually draw anything (I DID however manage to crash my GPU driver but putting a Blit call inside FixedUpdate! WOHO!) - but as far as I can read, Graphics.Blit is indeed what you need if you want to draw directly to the screen (there’s also Graphics.DrawTexture, but it seems to be for GUI stuff?)

I’ve attached the script below (including editor-script) - If you would take a look at it, and post some feedback, I’d be very grateful - If you have any ideas or thoughts on how to optimize it, or can point me in the right direction, I’d be even more so!

2520322–174682–TrailFx.zip (2.66 KB)
2520322–174692–TrailFx.unitypackage (2.42 KB)

Use a particle system, it exists to do exactly what you’re describing. It’s still done on the CPU in Unity, but each Sprite won’t be a separate game object and the system is designed to do this efficiently.

1 Like

Hi again! I followed your advice, and made a ParticleSystem and a little script that controls a few parameters of the ParticleSystem, in order to make it ‘simulate’ my effect, and after a little testing, I have to conclude that performance is actually worse than with my original script :frowning: It does however bring down the number of batches, but this doesn’t really matter when the FPS is lower in the end (not to mention that it brings about some odd visual glitches).

What does your script do? The particle system should be capable of doing all of the features you described with out any scripting beyond being turned on and off. Also what visual glitches are you seeing?

If things are still slow with a particle system, and slower than your original individual Sprite based effect I suspect the issue is in part that you’re just rendering too much, or something is going wrong with your scripts. You should use the profiler to try to track down what is so slow.

The script pretty much does nothing but A. setting some initial starting values (like copying the texture of the Sprite it’s attached to, to be used as particles) and B. setting the starting rotation of the particles to the rotation of the GameObject (Sprite) in each Update call:

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(ParticleSystem))]
public class TrailFxPs : MonoBehaviour {

    ParticleSystem ps;

    void Start () {
        ps = GetComponent<ParticleSystem>();
        ps.simulationSpace = ParticleSystemSimulationSpace.World;
        ps.startSpeed = 0;
        ParticleSystemRenderer rendermodule = (ParticleSystemRenderer)ps.GetComponent<ParticleSystemRenderer>();
        rendermodule.material.mainTexture = GetComponent<SpriteRenderer>().sprite.texture;
        ParticleSystem.ShapeModule shapemodule = ps.shape;
        shapemodule.shapeType = ParticleSystemShapeType.Sphere;
        shapemodule.radius = 0f;
        ps.startSize = GetComponent<SpriteRenderer>().sprite.texture.width / GetComponent<SpriteRenderer>().sprite.pixelsPerUnit;

    }
  
    // Update is called once per frame
    void Update () {
        Vector3 temp = ps.startRotation3D;
        temp.z = transform.rotation.eulerAngles.z;
        ps.startRotation3D = temp;
    }
}

The visual ‘glitches’ I’m seeing are related to the position of the ParticleSystem - the position of the ParticleSystem’s ‘emitter’-object is not directly inherited from the Transform of the object it’s attached to, but is an interpolated value of the latter (somehow) - this become apparent if the object is ‘teleported’ from one position to another; the ParticleSystem will spawn particles between the current and former position of the object, as if the object had just traveled really fast. The ParticleSystem also spawns echos at a less consistent/precise rate.

And yes; I am definitely rendering too much :smile: for this test, I did 500 echo’s (one echo per 0.01secs, with a lifetime of 5 seconds), but even though my script results in more batches and more tris/verts than than when using the ParticleSystem solution, it still has better FPS, while also looking better.

Nothing obvious there, though you should just disable the shape module in the particle system rather than setting it to a sphere of 0, that shouldn’t cause any noticeable performance difference.

When teleporting sprites, yes, the particles will “streak”. I think you can avoid this by turning the emission off for that frame. This interpolation makes sense if you think about a particle system on a fast moving object emitting a lot of particles. If they’re all spawned exactly where the object is you’ll get clusters of particles being spawned instead of a smooth trail.

But none of this helps you if it’s still too slow. Like I said at this point it’s down to you digging through the profiler and trying to figure out where things are slow.

Judging by the screenshot, it should be 60fps on the very first smartphone or even an 8 bit console, so I imagine you must be doing something very odd…

Well, if you by ‘odd’ mean exploiting this effect heavily and adding thousand of echo’s to the scene at once, then yes I am :smile: It’s not that it’s unusable as it is, I just want to optimize it even further, and figure out what the best way is to do something like this in Unity.

Okay, a modification to your use of a particle system: only use one particle system and set it’s emit rate to zero. Then use http://docs.unity3d.com/ScriptReference/ParticleEmitter.Emit.html to spawn particles manually. Might be tricky to get the rotations to line up since Emit doesn’t seem to have a 3D rotation option (I think that’s getting fixed in 5.4), but just spawning them in the right place might be a good test. Basically this will spawn a particle that ignores all of the initial particle settings and only uses the over lifetime / by speed modules.

Ok, so I update my ParticleScript to use ParticleSystem#Emit (not ParticleEmitter#Emit):

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(ParticleSystem))]
public class TrailFxPs : MonoBehaviour {

    ParticleSystem ps;
    ParticleSystem.EmitParams emitter;
    public float secondsBetweenEchos = 0.5f;
    public float echoLifetime = 1f;
    float countdown;

    void Start () {
        countdown = secondsBetweenEchos;
        ps = GetComponent<ParticleSystem>();
        ps.simulationSpace = ParticleSystemSimulationSpace.World;
        ParticleSystemRenderer rendermodule = (ParticleSystemRenderer)ps.GetComponent<ParticleSystemRenderer>();
        rendermodule.material.mainTexture = GetComponent<SpriteRenderer>().sprite.texture;
        emitter.startSize = GetComponent<SpriteRenderer>().sprite.texture.width / GetComponent<SpriteRenderer>().sprite.pixelsPerUnit;
        emitter.velocity = new Vector3(0, 0, 0);
        emitter.startLifetime = echoLifetime;
    }
   
    // Update is called once per frame
    void Update () {
    }

    void FixedUpdate()
    {
        countdown -= Time.deltaTime;
        if (countdown <= 0f)
        {
            emitter.position = transform.position;
            emitter.rotation3D = transform.rotation.eulerAngles;
            ps.Emit(emitter, 1);
            countdown += secondsBetweenEchos;
        }
    }
}

This fixes the problem with interpolation / ‘teleporting’ sprites, but performance is still worse than my original script, and it doesn’t look quiet as good:

(Original script)

(ParticleSystem#Emit)

ParticleEmitter vs ParticleSystem, yeah, doh. Fat fingering on my phone there. Glad you worked out me being dumb. :slight_smile:

However your CPU usage is through the roof, no wonder your perf is so bad. Notice the render thread is much better w/ particles. Your original script is also spawning about 4x the number of sprites. You also probably shouldn’t be using FixedUpdate for the Emit call, but LateUpdate. Still doesn’t explain the crazy high CPU usage though.