You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何检测相机前方对象是否可见?VisibleOnScreenNotifier3D节点问题

检测相机前方对象是否可见

要判断相机前方对象是否可见(无遮挡且处于相机视锥体内),需结合视锥体范围校验和遮挡检测:

  • 视锥体校验:使用Camera3D.is_position_in_frustum(object.global_position)判断对象是否在相机可视范围内;也可通过VisibleOnScreenNotifier3D的screen_entered/screen_exited信号监听对象进出视锥体,但该节点不处理遮挡逻辑。
  • 遮挡检测:用RayCast3D或ShapeCast3D从相机位置发射射线至对象关键位置,检查是否被带碰撞的物体(如开启碰撞的MeshInstance3D墙体)阻挡。若射线未命中障碍物,则对象无遮挡。

示例GDScript代码:

func is_object_visible_to_camera(camera: Camera3D, object: Node3D) -> bool:
    # 先校验是否在视锥体内
    if not camera.is_position_in_frustum(object.global_position):
        return false
    # 再检测是否有遮挡
    var ray = RayCast3D.new()
    ray.global_position = camera.global_position
    ray.target_position = object.global_position
    ray.collision_mask = 1  # 匹配墙体所在的碰撞层
    add_child(ray)
    ray.force_raycast_update()
    var obstructed = ray.is_colliding()
    remove_child(ray)
    return not obstructed
检测对象是否正在被渲染

判断对象是否被实际渲染到视口,需综合节点可见性、视口剔除掩码、视锥体范围和遮挡情况:

  • 基础校验:先检查object.visible和object.is_inside_tree()确保节点本身启用可见且在场景树中。
  • 视口掩码校验:调用get_viewport().get_cull_mask_bit(object.layers)确认对象所在层未被视口剔除。
  • 结合前面的可见性检测:只有对象在视锥体内且无遮挡时,才会被实际渲染。

示例代码:

func is_object_rendered(object: MeshInstance3D) -> bool:
    if not object.visible or not object.is_inside_tree():
        return false
    var viewport = get_viewport()
    if not viewport.get_cull_mask_bit(object.layers):
        return false
    var camera = viewport.get_camera_3d()
    return is_object_visible_to_camera(camera, object)
解决VisibleOnScreenNotifier3D墙体遮挡不触发screen_exited的问题

VisibleOnScreenNotifier3D仅检测对象包围盒是否在相机视锥体内,不考虑场景中的遮挡物,因此对象被墙体遮挡但仍在视锥内时,不会触发screen_exited信号。

解决方案:

  1. 保留VisibleOnScreenNotifier3D监听对象进出视锥体的状态。
  2. 当对象处于视锥体内时,额外执行遮挡检测,手动判断是否被遮挡,以此模拟“不可见”状态。

示例实现:

extends Node3D

@onready var notifier = $VisibleOnScreenNotifier3D
@onready var target = $MeshInstance3D
var in_frustum = false
var is_obscured = false

func _ready():
    notifier.screen_entered.connect(_on_enter_frustum)
    notifier.screen_exited.connect(_on_exit_frustum)

func _process(_delta):
    if in_frustum:
        is_obscured = check_obstruction()
        if is_obscured:
            _on_object_obscured()
        else:
            _on_object_visible()

func _on_enter_frustum():
    in_frustum = true

func _on_exit_frustum():
    in_frustum = false
    is_obscured = false
    _on_object_left_frustum()

func check_obstruction() -> bool:
    var camera = get_viewport().get_camera_3d()
    var ray = RayCast3D.new()
    ray.global_position = camera.global_position
    ray.target_position = target.global_position
    ray.collision_mask = 1  # 匹配墙体碰撞层
    add_child(ray)
    ray.force_raycast_update()
    var result = ray.is_colliding()
    remove_child(ray)
    return result

# 自定义回调逻辑
func _on_object_obscured():
    print("对象被遮挡,未实际渲染")

func _on_object_visible():
    print("对象在相机前方可见")

func _on_object_left_frustum():
    print("对象离开相机视锥体")

内容的提问来源于stack exchange,提问作者cyner boy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 00:17:50