How can I know if a gameObject is 'seen' by a particular camera?
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@Gillissie (several years late): I got this to work just by moving that line into the Update method.
– indolenceNote: 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.
– BrianLedsworthGSNthere is a problem with OnWillRenderObject, Renderer.isVisible, Renderer.OnBecameVisible, and OnBecameInvisible , it consider rendering even if object is just enable in scene.
– DropoutGamerThe 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..
– BovineThis 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 ?
– Fire-Dragon-DoLI 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.
– VipHaLongProNice 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; }
– grimprophecyI 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()
-
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
-
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.
– jackson_31this should be the right answer, isVisible and Frustum do not deliver the describe request by OP.
– Hassan-KansoThanks for this, I was putting off doing this cause I thought it was going to be a PITA but this worked like a charm.
– dzliergaardprivate 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.
– BrianLedsworthGSNThis is perfect, I wanted to test if absolutely any part of the collider was visible and this worked perfectly thanks!
– ZebbiJust private bool ICanSee(GameObject object) { Plane[] planes = GeometryUtility.CalculateFrustumPlanes(Camera); return GeometryUtility.TestPlanesAABB(planes , Object.collider.bounds); }
– Gabi_RPComing 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 ![]()
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.
– Bovinerenderer.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 ![]()
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
I think you can also use RayCast from camera to object to determine it.
– vvkkumar06@vvkkumar06 5 years too late and repeating what @duck said. But, other than that, useful comment...
– tanoshimi