How can I know if a gameObject is 'seen' by a particular camera?

How can I know if a gameObject is 'seen' by a particular camera?

I think you can also use RayCast from camera to object to determine it.

@vvkkumar06 5 years too late and repeating what @duck said. But, other than that, useful comment...

12 Answers

12

There's a whole slew of ways to achieve this, depending on the precise functionality you need.

You can get events when an object is visible within a certain camera, and when it enters or leaves, using these functions:

OnWillRenderObject, Renderer.isVisible, Renderer.OnBecameVisible, and OnBecameInvisible

Or you can calculate whether an object's bounding box falls within the camera's view frustum, using these two functions:

GeometryUtility.CalculateFrustumPlanes GeometryUtility.TestPlanesAABB

If you've already determined that the object is within the camera's view frustum, you could cast a ray from the camera to the object in question, to see whether there are any objects blocking its view. Physics.Raycast

Is it just me, or does the example for http://unity3d.com/support/documentation/ScriptReference/GeometryUtility.TestPlanesAABB.html only work for a static camera? It gets the frustum planes in the Start() method, so if the camera moves it will be inaccurate. Am I correct in thinking that?

@Gillissie (several years late): I got this to work just by moving that line into the Update method.

Note: The "GeometryUtility.TestPlanesAABB" seems to return true even if just a "portion" of the GO is visible. I would not use this if you want to ensure the entire GO is visible.

Renderer.isVisible does not exist

there is a problem with OnWillRenderObject, Renderer.isVisible, Renderer.OnBecameVisible, and OnBecameInvisible , it consider rendering even if object is just enable in scene.

The answer given by Duck is the most accurate. If you can do with something less accurate, you can consider just checking if the position of the object is seen by the camera.

To find out if a point in the scene is seen by a camera, you can use Camera.WorldToViewportPoint to get that point in viewport space.

If both the x and y coordinate of the returned point is between 0 and 1 (and the z coordinate is positive), then the point is seen by the camera.

I suppose doing this for the extents of the GO would be enough... I've been using something more event driven, but I'm not so sure about this now (Triggers with rigid-bodies attached). Perhaps raycasts betwee . n objects are fine or squared delta comparisons, moving to something related to one of the above solutions at a late date..

This answer is very important, expecially if you have to do such a calculation very often! With CalculateFrustumPlanes you createa new object on the heap (an Array), while with this you don't allocate anything new. For intensive usage, this should be the correct method. Just a question, Z coordinate positive you mean greater than 0 or greater or equal to 0 ?

I did not test this but I've experienced a problem with the Camera.WorldToScreenPoint (some kind of similar to Camera.WorldToViewportPoint but returns absolute value). The problem is when the object is such as behind the camera, the returned value may still fall in (0,0) - (1,1). So basing on that won't help detect if the object is in the camera view.

Thank you, it's helped me and work very good.

Nice solution, thanks: private bool PointInCameraViewport(Camera camera, Vector3 point) { var v = camera.WorldToViewportPoint(point); return v.x > 0 && v.x < 1 && v.y > 0 && v.y < 1 && v.z > 0; }

I spent a fair while looking into this, the suggestions from @duck where led me down the right path, but I ended up doing it in 3 steps. Note: I am checking objects with several LODs associated with them, so I’m using GetComponentInChildren(), as uposed to GetComponent()

  1. Check if the object is in front of the screen: Convert the center point of the object (toCheck) from world coordinates, to screen relitave coordinates. In the screen relitave coordinate system the z axis represents distance infront (+) or behind (-) the screen

  2. Check if the objcect is within the camera’s field of view: I found a few ways to do this, but in the end I figured if i already have the point relitave to screen coordinates. If the x,y position in screen coordinates is outside the window size then it is obversly not in the camera’s field of view.

3)Check if the object is being occluded by anything: This was a bit tricky and in the end I used Physics.Linecast(…) instead of Physics.Raycast(…) because I already had the origin and target of the line so I did not need to figure out angles if I used Linecast(…). I just cast the line and if the object it hit did not have the same name as the object I was aiming for (in my case a tree) then I knew something was in the way.

So all together it looks like this:

private bool IsInView(GameObject origin, GameObject toCheck)
    {
        Vector3 pointOnScreen = cam.WorldToScreenPoint(toCheck.GetComponentInChildren<Renderer>().bounds.center);

        //Is in front
        if (pointOnScreen.z < 0)
        {
            Debug.Log("Behind: " + toCheck.name);
            return false;
        }

        //Is in FOV
        if ((pointOnScreen.x < 0) || (pointOnScreen.x > Screen.width) ||
                (pointOnScreen.y < 0) || (pointOnScreen.y > Screen.height))
        {
            Debug.Log("OutOfBounds: " + toCheck.name);
            return false;
        }

        RaycastHit hit;
        Vector3 heading = toCheck.transform.position - origin.transform.position;
        Vector3 direction = heading.normalized;// / heading.magnitude;
        
        if (Physics.Linecast(cam.transform.position, toCheck.GetComponentInChildren<Renderer>().bounds.center, out hit))
        {
            if (hit.transform.name != toCheck.name)
            {
                /* -->
                Debug.DrawLine(cam.transform.position, toCheck.GetComponentInChildren<Renderer>().bounds.center, Color.red);
                Debug.LogError(toCheck.name + " occluded by " + hit.transform.name);
                */
                Debug.Log(toCheck.name + " occluded by " + hit.transform.name);
                return false;
            }
        }
        return true;
    }

This is awesome. Your solution works. For me I am trying to check visibility of terrains from camera So I will just have to add more points other than just the center.

Hello quick question. Did you attach this script on the camera?

this should be the right answer, isVisible and Frustum do not deliver the describe request by OP.

Thanks for this, I was putting off doing this cause I thought it was going to be a PITA but this worked like a charm.

Late to the party, but this solution worked for me, thanks!.

private bool I_Can_See(GameObject Object)
{

     Plane[] planes = GeometryUtility.CalculateFrustumPlanes(Camera);

     if (GeometryUtility.TestPlanesAABB(planes , Object.collider.bounds))
         return true;
     else
         return false;
}

Note: This will return true even if just a "portion" of the GO is visible. I would not use this if you want to ensure the entire GO is visible.

This is perfect, I wanted to test if absolutely any part of the collider was visible and this worked perfectly thanks!

Just private bool ICanSee(GameObject object) { Plane[] planes = GeometryUtility.CalculateFrustumPlanes(Camera); return GeometryUtility.TestPlanesAABB(planes , Object.collider.bounds); }

Great example.

Coming to the party late, but here’s an alternative.

bool IsTargetVisible(Camera c,GameObject go)
    {
        var planes = GeometryUtility.CalculateFrustumPlanes(c);
        var point = go.transform.position;
        foreach (var plane in planes)
        {
            if (plane.GetDistanceToPoint(point) < 0)
                return false;
        }
        return true;
    }

GeometryUtility .TestPlanesAABB can throw exceptions, this is a more generic solution, which works for only the point case, solving for ‘bounds’ is left as an exercise for the reader :slight_smile:

that works. Do you know how to check if the object is partially visible ?

CAUTION: If you are using any of the following method: OnWillRenderObject, Renderer.isVisible, Renderer.OnBecameVisible, and OnBecameInvisible


Please note that the gameobject renderer.isVisible would always be true if you have enabled shadows in the Main Scene Directional Light. You would also need to close the scene view tab or tilt the scene view camera somewhere else during playtesting.

I am currently using Renderer.OnBecameVisible, and OnBecameInvisible to trigger checks and then use Camera frustrum check if both true then gameobject is inside the view

If the object has a renderer attached, then this should work:

Create a new class cameraSight and array of this class named seenByCameraList

class cameraSight {
	var cam : Camera;
	var sawMe : boolean;
    var seesMe : boolean;
};
var seenByCameraList: cameraSight[];

then initialize it in Start()

var tempCameraList:Camera[] = Camera.allCameras;
seenByCameraList = new cameraSight[tempCameraList.Length];
for (var i:int=0;i<seenByCameraList.Length;i++)
	{
	seenByCameraList*=new cameraSight();*

seenByCameraList_.cam=tempCameraList*;_
_seenByCameraList.sawMe=false;
seenByCameraList.seesMe=false;
}
Assuming that camera count will not be changed dynamicaly,
in Update() just clear flags to false;
for (i=0;i<seenByCameraList.Length;i++)
{
seenByCameraList.sawMe=seenByCameraList.seesMe;_

_seenByCameraList.seesMe=false;
}
If Unity whave to render this object, it will call OnWillRenderObject() , where we modify the flags for cameras, to which this object is visible
function OnWillRenderObject () {
for (var i:int=0;i<seenByCameraList.Length;i++)
{
if (seenByCameraList.cam==Camera.current) seenByCameraList.seesMe=true;
}
}
And the final function which you call for testing visibility to particular camera
function isRendererVisibleByCamera(observerCamera:Camera){
for (var i:int=0;i<seenByCameraList.Length;i++)
{
if (seenByCameraList.camera==observerCamera) return seenByCameraList.sawMe;
}
}*

To avoid clearing of flag, there is sawMe flag which copies a value of seesMe from previous frame.
So information is one frame delayed._

C#:

if(renderer.isVisible) {
    print ("Found!");
}
else {
    print ("Lost!");
}

Javascript:

function OnBecomeVisible () {
   print("Found!");
}
function OnBecomeInvisible() {
   print("Lost!");
}

Please use the code block to format your code. This won't work in the editor if the scene is visible as the scene camera counts too.

renderer.isVisible does not exist anymore. You have to use it like gameObject.GetComponent<Renderer>().isVisible now

renderer.isVisible does not exist anymore. You have to use it like gameObject.GetComponent<Renderer>().isVisible now

the code will run even if the target is blocked by an object, any solution to this?,the code will run even if the target is blocked by an object

inspired by the answer from @oPless_ , instead of checking one point, checking all the vertices of the bounding box works for me :slight_smile:

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

 public bool CheckIfObjectWithinView(GameObject go, Camera cam) {
   if (go == null) return false;

   var bounds = new Bounds();
   go.GetComponentsInChildren < Renderer > ().ForEach(r => bounds.Encapsulate(r.bounds));

   var planes = GeometryUtility.CalculateFrustumPlanes(cam);
   var points = GetEightBoundsVertices(bounds);

   if (points.Count(p => TestPoint(planes, p)) == points.Count) {
     //the whole object is within cam view
     return true;
   }
   return false;

   //check if all vertices points are in the positive direction of all cam frustum planes
   //if it is true, the gameobject is within camview
   bool TestPoint(Plane[] planes, Vector3 point) => !planes.Any(plane => plane.GetDistanceToPoint(point) < 0);

   List < Vector3 > GetEightBoundsVertices(Bounds bounds) => new List < Vector3 > () {
     bounds.min,
       bounds.max,
       new Vector3(bounds.min.x, bounds.min.y, bounds.max.z),
       new Vector3(bounds.min.x, bounds.max.y, bounds.min.z),
       new Vector3(bounds.max.x, bounds.min.y, bounds.min.z),
       new Vector3(bounds.min.x, bounds.max.y, bounds.max.z),
       new Vector3(bounds.max.x, bounds.min.y, bounds.max.z),
       new Vector3(bounds.max.x, bounds.max.y, bounds.min.z)
   };
 }

you shoud use :

GetComponent() .isVisible ();

becouse :

renderer.isVisible()

is no more working