How to detect if a ReflectionProbe is inside camera frustum with TestPlanesAABB() ?

Hi,

I’m trying to detect if a ReflectionProbe is inside camera frustum using TestPlanesAABB(). But 'till now, without success…

I put the following script on the ReflectionProbe itself and dragged the appropriate camera to its field. But not working…

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LPUpdate : MonoBehaviour {

    [SerializeField] private ReflectionProbe _waterReflectionProbe;
    [SerializeField] private int _wRPRenderID = -1;

    [SerializeField] private Camera _cam;
    private Plane[] _planes;

    // Start is called before the first frame update
    void Start () {
        this._waterReflectionProbe = this.GetComponent<ReflectionProbe> ();
        this._planes = GeometryUtility.CalculateFrustumPlanes (this._cam);
    }

    // Update is called once per frame
    void Update () {

        // Verifica se o Reflection probe está dentro do campo de visão da camera
        if (GeometryUtility.TestPlanesAABB (this._planes, this._waterReflectionProbe.bounds)) {
            RefreshReflProbe (true);
        } else {
            RefreshReflProbe (false);
        }
    }

    IEnumerator RefreshReflProbe (bool shouldRefresh) {
        while (shouldRefresh) {
            yield return new WaitForSeconds (0.15f);
            this._wRPRenderID = this._waterReflectionProbe.RenderProbe ();
        }
    }
}

Need some help, please.

That sure seems like you’re doing it right.

I recommend using Debug.Log to print out the bounds values, see whether they are reasonable compared to the camera frustum.

You can also just construct a bounds that you KNOW is in the frustum planes and check.

Using Debug.Log looks like the ReflectionProbe is always inside the camera frustum, no matter if the camera is far away from the ReflectionProbe and looking to oposite direction. Also, it doesn’t start my coroutine…

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LPUpdate : MonoBehaviour {

    [SerializeField] private ReflectionProbe _waterReflectionProbe;
    [SerializeField] private int _wRPRenderID = -1;

    [SerializeField] private Camera _cam;
    private Plane[] _planes;

    private IEnumerator _refreshRP;

    // Start is called before the first frame update
    void Start () {
        this._waterReflectionProbe = this.GetComponent<ReflectionProbe> ();
        this._planes = GeometryUtility.CalculateFrustumPlanes (this._cam);
    }

    // Update is called once per frame
    void Update () {

        // Verifica se o Reflection probe está dentro do campo de visão da camera
        if (GeometryUtility.TestPlanesAABB (this._planes, this._waterReflectionProbe.bounds)) {
            Debug.Log ("ReflectionProbe inside the camera field of view");

            if (this._refreshRP != null)
                StopCoroutine (this._refreshRP);

            this._refreshRP = RefreshReflProbe (true);

            StartCoroutine (this._refreshRP);
        } else {
            Debug.Log ("ReflectionProbe outside the camera field of view");
            if (this._refreshRP != null)
                StopCoroutine (this._refreshRP);

            this._refreshRP = RefreshReflProbe (false);

            StopCoroutine (this._refreshRP);
        }
    }

    IEnumerator RefreshReflProbe (bool shouldRefresh) {
        while (shouldRefresh) {
            yield return new WaitForSeconds (0.15f);
            this._wRPRenderID = this._waterReflectionProbe.RenderProbe ();
        }
    }
}

I see four possible outcomes

  • the bounds is bigger than you think
  • the frustum is WAY bigger than you think
  • both bounds and frustum are bigger than you think
  • there’s broken code in the TestPlanesAABB

You alone are positioned the best to identify which of the four above things are happening… or maybe… maybe it’s something ELSE!

But I would highly advise from one engineer to another, eliminate the things we CAN imagine, then move onto more imaginary things if you actually need to.

New attempt:
Now I try with the bound of a collision box. Got the size of the collider (is trigger check) by the size of the “Box Size” of the ReflectionProbe component. The coroutine is starting now. But it never reaches the else of the GeometryUtility.TestPlanesAABB() test, no matters where the camera is or where its looking at…
I think that, maybe, the bounds are strange. The Log return:

ReflectionProbe inside the camera field of view.
Bounds: Center: (605.0, 6.1, 812.9), Extents: (9.0, 7.5, 9.0)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LPUpdate : MonoBehaviour {

    [SerializeField] private ReflectionProbe _waterReflectionProbe;
    [SerializeField] private BoxCollider _waterReflectionProbeColl;
    [SerializeField] private int _wRPRenderID = -1;

    [SerializeField] private Camera _cam;
    private Plane[] _planes;

    private IEnumerator _refreshRP;
    private bool _coroutineRunning = false;
    [SerializeField] private float _waitTimeToRefreshReflProbe = 0.15f;

    // Start is called before the first frame update
    void Start () {
        this._waterReflectionProbe = this.GetComponent<ReflectionProbe> ();
        this._waterReflectionProbeColl = GetComponent<BoxCollider> ();
        this._waterReflectionProbeColl.size = this._waterReflectionProbe.size;
        this._planes = GeometryUtility.CalculateFrustumPlanes (this._cam);

        this._refreshRP = RefreshReflProbe2 ();

    }

    // Update is called once per frame
    void Update () {

        // Verifica se o Reflection probe está dentro do campo de visão da camera
        if (GeometryUtility.TestPlanesAABB (this._planes, this._waterReflectionProbeColl.bounds)) {

            Debug.Log ("ReflectionProbe inside the camera field of view.\nBounds: " + this._waterReflectionProbeColl.bounds);

            if (!this._coroutineRunning && this._refreshRP != null)
                StartCoroutine (this._refreshRP);

        } else {

            Debug.Log ("ReflectionProbe outside the camera field of view");

            if (this._coroutineRunning && this._refreshRP != null) {
                StopCoroutine (this._refreshRP);
                this._coroutineRunning = false;
            }

        }
    }

    IEnumerator RefreshReflProbe2 () {
        while (true) {
            this._coroutineRunning = true;
            yield return new WaitForSeconds (this._waitTimeToRefreshReflProbe);
            this._wRPRenderID = this._waterReflectionProbe.RenderProbe ();
        }
    }
}

One thing’s for sure, this one

 this._planes = GeometryUtility.CalculateFrustumPlanes (this._cam);

needs to be called more often, at least when the camera changes (moves, rotates, field of view changed…),

At the moment you only calculate the planes just once in Start. The planes won’t change if you don’t calculate them again, you need to update them.

3 Likes

I think that must be something wrong… I try this with the default cube and the Debug.Log always returns the else of the test, even when I was in front of the cube…

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class logBounds : MonoBehaviour {

    [SerializeField] private Camera _cam;
    private Plane[] _planes;

    // Start is called before the first frame update
    void Start () {
        this._planes = GeometryUtility.CalculateFrustumPlanes (this._cam);

    }

    // Update is called once per frame
    void Update () {
        Debug.Log ("Cube bounds: " + this.transform.GetComponent<BoxCollider> ().bounds);
        if (GeometryUtility.TestPlanesAABB (this._planes, this.transform.GetComponent<BoxCollider> ().bounds)) {
            Debug.Log ("The cube is inside the camera field of view.");
        } else {
            Debug.Log ("The cube is outside the camera field of view.");
        }
    }
}

I put it in Update() and it did the trick!!
Thanks a lot mate!!

1 Like

You might want to use my manual method to recalculate the frustum planes. I’ve posted it in a nested comment over here but I’ll copy it over since UA becomes more and more unreliable

private static Plane FromVec4(Vector4 aVec)
{
     Vector3 n = aVec;
     float l = n.magnitude;
     return new Plane(n / l, aVec.w/l);
}

public static void UpdatePlanes(Plane[] planes, Matrix4x4 m)
{
     if (planes == null || planes.Length < 6)
         return;
     var r0 = m.GetRow(0);
     var r1 = m.GetRow(1);
     var r2 = m.GetRow(2);
     var r3 = m.GetRow(3);
    
     planes[0] = FromVec4(r3 + r0); // Left
     planes[1] = FromVec4(r3 - r0); // Right
     planes[2] = FromVec4(r3 + r1); // Bottom
     planes[3] = FromVec4(r3 - r1); // Top
     planes[4] = FromVec4(r3 + r2); // Near
     planes[5] = FromVec4(r3 - r2); // Far
}
public static void UpdatePlanes(Plane[] planes, Camera cam)
{
     UpdatePlanes(planes, cam.projectionMatrix * cam.worldToCameraMatrix);
}

This allows to update the 6 frustum planes without any garbage allocations as long as you reuse your planes array.

3 Likes