How to check if an object is visible in 3D view?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By unfa
:warning: Old Version Published before Godot 3 was released.

I’m trying to implement lens flares (halo) so a sprite is shown only if the camera can see the halo’s origin point. How can I check if the camera can see the object?

I’m trying to cast a ray from the object to the current camera, but I cannot make it work.

:bust_in_silhouette: Reply From: SupToasty

The only thing I think you can do is add a visibilitynotifier node to the object and script it for when it is in the view of a camera by if(get_node("VisibilityNotifier's Name").is_on_screen()):. If doing this the object will often appear as visible when in the corner of your FOV so make sure to edit the notifier’s AABB to adjust for this.

This should also work with the visibilityenabler node which I believe gives the the ability to freeze objects and pause animations when not on screen.

SupToasty | 2016-04-10 16:09

What is AABB?

unfa | 2016-04-10 22:58

It is what helps control collision detection and response. Think of it as dimensions of a physical object. (AABB = AxisAlignedBoundingBox) Don’t really worry about it too much though as it usually is wrapped to most objects accurately.

SupToasty | 2016-04-10 23:08

:bust_in_silhouette: Reply From: unfa

I figured this out.

For a lens flare I use this script:

extends MeshInstance # I use this with a Halo plane mesh to draw the flare

func _ready():
	# if we start with the node visible - we'll have 1 frame delay before hiding it - this could look bad
	self.hide()

func _physics_process(delta):

	# get current camera
	var camera = get_viewport().get_camera()
	
	# intersect a ray between the camera and self
	var result = get_world().direct_space_state.intersect_ray(camera.global_transform.origin, self.global_transform.origin, [self])
	
	# if we hit nothing - the flare is visible, so we show it
	if result.empty():
		self.show()
	else: # othwerwise we hide it
		self.hide()
:bust_in_silhouette: Reply From: gaycodegal

Another possibility, if by “in the 3d view” you mean say in the presently viewed hemisphere of the camera (meaning in front of the camera or up to 90 degrees in any direction away from the center of view) you can do some vector maths.

If we consider the distance between the camera and a target to be distance_to_camera

var target_pos = target.to_global(Vector3.ZERO)
var camera_pos = camera.to_global(Vector3.ZERO)
# distance from camera to target
var distance_to_camera = camera_pos - target_pos

Then consider if we cast a vector of any positive length directly backwards from the camera (say 1 unit, the length of a normalized vector)

var back_position = camera_pos + camera.get_camera_transform().basis.z
var distance_to_back = back_position - target_position

Alternatively do note that due to the vector difference already calculated the following would also be equivalent

var distance_to_back = camera.get_camera_transform().basis.z + distance_to_camera

We can then notice that if we are facing the target, back_position must necessarily be further away from the target than camera_pos, and that if we are facing away from the target back_position will be closer. Thus

if distance_to_camera.length_squared() < distance_to_back.length_squared():
 pass # target is in the visible hemisphere

I’m posting this because my googling led me to this question, but this hemisphere math I came up with was sufficient to my needs. Also note that I used the squared distance because this is quicker to compute and the relative distances will work out fine because if x < y then x ** 2 < y ** 2 where ** is the power notation.

Also casting a ray from the object to the camera only tells you if there is anything in the way, if the target is bigger than the blockage, it will still be visible even though raycasting may fail.

Edit: I have realized that the maths on this one are slightly different if you need perfect 90 degrees - you have to make sure that both points are equidistant from the center, so you need to use both positive and negative basis.z as your points of reference. That being said, the algorithm as is, is like mostly fine, you just will have a spot slightly behind the user (up to half a unit max), in which things count as visible when they are not actually visible, but then again that might be desirable in your use case, as it was in mine

Thank you, this solution is probably the most performant way to handle it.

-Sarge- | 2023-06-01 10:31