- Home /
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 deter$$anonymous$$e it.
@vvkkumar06 5 years too late and repeating what @duck said. But, other than that, useful comment...
Answer by duck · Nov 19, 2009 at 03:12 PM
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?
Irritatingly OnBecameInvisible happens for all cameras in the scene, including the editor camera. This is a bit rubbish tbh, because it means behaviour changes between editor and game and there's no coding around this. Being able to detect that an object is visible by a camera and then ceases to be visible by that same camera is very very useful, and it being visible by the editor camera is not something I need to know in game, but could be handled by this...
Ho hum, I'll have to do something computationally more expensive by the sounds of things :o(
EDIT: I thought OnBecameVisible() was called for each camera I think it is also called once, but it is relatively random as to which camera considers it visible, so Camera.current might be (irritatingly) the scene camera, or it might be a real in-game camera, but if both see it, it will only be called once.
@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.
On the object you are testing if it's visible or not, in the update, put the following:
if (GetComponent<Renderer>().isVisible)
{
//Visible code here
}
else {
//Not visible code here
}
Please note that even if the object is out of view of the camera, it may still be classed as visible if the object needs to be rendered for shadows that it is casting that are still visible.
Answer by runevision · Nov 19, 2009 at 03:20 PM
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 crea$$anonymous$$ 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.
This solution worked great for me. Just be sure to check the Z value as well.
Here is a script example. I am checking to see when the user has looked at something for over X seconds.
public Transform targetPoint;
public float holdGazeTimeInSeconds = 2;
private float currentGazeTimeInSeconds = 0;
void Update () {
Vector3 screenPoint = playerHead.leftCamera.WorldToViewportPoint (targetPoint.position);
if (screenPoint.z > 0 && screenPoint.x > 0 && screenPoint.x < 1 && screenPoint.y > 0 && screenPoint.y < 1) {
currentGazeTimeInSeconds += Time.deltaTime;
if(currentGazeTimeInSeconds >= holdGazeTimeInSeconds){
currentGazeTimeInSeconds = 0;
//DO STUFF
}
} else {
currentGazeTimeInSeconds = 0;
}
}
Answer by V-Jankaitis · Mar 20, 2015 at 04:58 PM
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);
}
Answer by tall-josh · Mar 22, 2017 at 10:13 AM
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.
Answer by oPless_ · Mar 07, 2016 at 07:28 PM
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 :)