Run CullingGroup less frequently.

Hi all,

I have a script that I have attached to my particles to cull them when they are not in camera view. (Pasted below)
It works really well but it kills my performance because I have so many different particles running this script. Firstly, I read it checks for object visibility every frame. That is way more than I need and I could get away with checking if the particle system is in view every, say, half a second. Is there anyway to tell this script to just check for the particle system every 0.5 seconds?

Thanks and sorry if it is a noob question.

public class CustomParticleCulling : MonoBehaviour
{
    public float cullingRadius = 10;
    public ParticleSystem target;

    CullingGroup m_CullingGroup;
    Renderer[] m_ParticleRenderers;

    void OnEnable()
    {
        // Do we need custom culling?
        if (target.proceduralSimulationSupported)
        {
            enabled = false;
            return;
        }

        if(m_ParticleRenderers == null)
            m_ParticleRenderers = target.GetComponentsInChildren<Renderer>();

        if (m_CullingGroup == null)
        {
            m_CullingGroup = new CullingGroup();
            m_CullingGroup.targetCamera = Camera.main;
            m_CullingGroup.SetBoundingSpheres(new[] { new BoundingSphere(transform.position, cullingRadius) });
            m_CullingGroup.SetBoundingSphereCount(1);
            m_CullingGroup.onStateChanged += OnStateChanged;
        }

        // We need to sync the culled state.
        Cull(m_CullingGroup.IsVisible(0));
        m_CullingGroup.enabled = true;
    }

    void OnDisable()
    {
        if(m_CullingGroup != null)
            m_CullingGroup.enabled = false;

        if(target != null) {
            target.Play(true);
        }
        SetRenderers(true);
    }

    void OnDestroy()
    {
        if (m_CullingGroup != null)
            m_CullingGroup.Dispose();
    }

    void OnStateChanged(CullingGroupEvent sphere)
    {
        Cull(sphere.isVisible);
    }

    void Cull(bool visible)
    {
        if(visible)
        {
            // We could simulate forward a little here to hide that the system was not updated off-screen.
            target.Play(true);
            SetRenderers(true);
        }
        else
        {
            target.Pause(true);
            SetRenderers(false);
        }
    }

    void SetRenderers(bool enable)
    {
        // We also need to disable the renderer to prevent drawing the particles, such as when occlusion occurs.
        foreach (var particleRenderer in m_ParticleRenderers)
        {
            particleRenderer.enabled = enable;
        }
    }

    void OnDrawGizmos()
    {
        if (enabled)
        {
            // Draw gizmos to show the culling sphere.
            Color col = Color.yellow;
            if (m_CullingGroup != null && !m_CullingGroup.IsVisible(0))
                col = Color.gray;

            Gizmos.color = col;
            Gizmos.DrawWireSphere(transform.position, cullingRadius);
        }
    }
}

as you are dealing with renderers: why don’t you simply use OnBecameInvisible()?

Sorry I’m pretty new to this. Can you explain what you mean by this and how I can incorporate this into my script?

As you can see culling is taking up so many calls. Any way to reduce this from my above post?

you fully skip culling groups and use the built in onbecamevisible/invisible instead.
you do not touch the renderer (as otherwise onbecamevisible will not work anymore) but simply pause/play the particle system.

apart from that: it is quite a while that i have worked with culling groups, but do you set up one per particle system? SetBoundingSphereCount(1)…

Yes I have one per particle system. Can you confirm that it is running every frame or did I misread that?
Also how does it know when it becomes visible and invisible given it is a particle system and not like a mesh or anything else.
Thanks.

bad. one culling group should handle all particle systems.

it should.

onbecamevisible is a callback: a particle system has bounds (just like a mesh). these get culled vs the frustum. so unity can signal when the particle renderer’s bounds get out of sight or get into sight.
it fails of course if you have something like an explosion: a particle system whose bounds change dramatically while it is playing.

So I have a lava area that has a giant lava River and I have about a 20x20 array of flames coming off the lava. These each have a culling group script. Are you saying I should wrap them in in giant culling group? Isn’t this worst because I can cull 90% instead of running all of them or did I misinterpret that?

I have the default cull on particles that allow it but need this for dynamic ones.

Or can I use the onbecamevisible in a script liked I’m currently doing but it will only run once for becoming visible and another for leaving the camera area?

you do not cull culling groups. you consume the results from the culling spheres (or callbacks).

exactly.

In 2018.4 (and maybe even 2017.4… I forget) you can simply set the culling mode in the particle system main options to “Pause when offscreen”, to achieve the exact same thing with no script.

2 Likes

Thanks mate, changed my code to so much simpler and the performance sky rocketed. Legend.

@richardkettlewell I ahve done that to the particles that can but these are dynamic particles that will not allow that setting.

private ParticleSystem ps;

    void Start() {
        ps = GetComponent<ParticleSystem>();
    }

    void OnBecameVisible()
    {
        ps.Play();
    }

    void OnBecameInvisible()
    {
        ps.Stop();
    }

I’m curious - why not? :slight_smile:

There are different reasons but you can tell whether culling will work if the top right exclamation point is there and it will tell you why it can’t.

1 Like