当光线投射形成多个交点时,它在哪个点return? return可以控制哪个点?

When raycasting forms multiple intersections, which point does it return? Can which point is returned be controlled?

我正在 Godot 中开发国际象棋“游戏”,玩家可以为棋盘导入 3D 模型,并用 uv 坐标在棋盘周围包裹棋盘。该项目所需的世界、本地和 uv space 之间的所有转换都非常疯狂。但是,对于复杂的板形状,尤其是我刚进口的克莱因瓶,一个更意想不到的警告是,从相机投射一条线理论上会有多个解决方案,但只有 return 一个。

当玩家点击屏幕时,我认为光线投射应该找到离他们最近的交点,但 Godot 的 PhysicsDirectSpaceState.intersect_ray() method is returning whichever intersection it finds first. This intersection could either be the correct one or not, and with up to 4 intersection points with the klein bottle, the chances are slim that the correct intersection is returned. The RayCast 对象做同样的事情,只存储一个交点。

有没有办法 return Godot 中 ray/line 的所有交点? 这样我就可以对它们进行排序以找到最接近的交点指向玩家节点。我也觉得这首先不应该是一个问题。 Godot's Ray-Casting 教程将光线描述为 return 值时“击中某物”,这直观地告诉我与“光线原点”参数最近的交点将是 returned。

如果您对正在进行的意大利面条式代码感兴趣,我有一个 GitHub project,上面有所有源代码。这是我在游戏中进行光线投射时使用的代码:

#cast out a ray from the camera, given a physics state s
static func raycast(var p:Vector2, var c:Camera, 
    var w:World, var v:float = INF, var mask:int = 0x7FFFFFFF):
    
    #get physics state of the current scene
    var s = w.direct_space_state
    #origin and normal of the camera
    var o:Vector3 = c.project_ray_origin(p)
    var n:Vector3 = c.project_ray_normal(p)
    #get ray intersection with that scene
    var r = s.intersect_ray(o, n * v, [], mask)
    #if the intersection lands, return r
    if !r.empty():
        return r
    return null

以上来自 BoardConverter.gd, line 472.

我们可以呈现隐藏的 Viewport 并查询它。

所以让我们从添加一个 Viewport 到你的场景开始。确保它有一个尺寸集。事实上,我们可以调整大小以匹配主 Viewport 和脚本。例如:

extends Viewport


func _ready() -> void:
    # warning-ignore:return_value_discarded
    get_tree().connect("screen_resized", self, "resize")


func resize() -> void:
    size = get_viewport().size

或者:

extends Viewport


func _ready() -> void:
    # warning-ignore:return_value_discarded
    get_viewport().connect("size_changed", self, "resize")


func resize() -> void:
    size = get_viewport().size

还要确保将 render_target_update_mode 设置为 UPDATE_ALWAYS。将 render_target_v_flip 设置为 true,将 transparent_bg 设置为 true

然后你可以添加一个 Camera 子节点,脚本复制主 Cameraglobal_transform,并使其成为 current(在 Viewport 节点)。例如:

extends Camera


func _ready() -> void:
    current = true


func _process(_delta: float) -> void:
    global_transform = get_tree().root.get_camera().global_transform

您可能还想复制相机的所有相关属性以确保视图匹配:

    var tracked_cam:Camera = get_tree().root.get_camera()
    global_transform = tracked_cam.global_transform
    fov = tracked_cam.fov
    keep_aspect = tracked_cam.keep_aspect
    cull_mask = tracked_cam.cull_mask
    environment = tracked_cam.environment
    h_offset = tracked_cam.h_offset
    v_offset = tracked_cam.v_offset
    doppler_tracking = tracked_cam.doppler_tracking
    projection = tracked_cam.projection
    fov = tracked_cam.fov
    size = tracked_cam.size
    frustum_offset = tracked_cam.frustum_offset
    near = tracked_cam.near
    far = tracked_cam.far

您要在 Viewport 中渲染的是您的网格,但具有不同的 material。因此,您正在复制 MeshInstance,您可以:

  • 将其添加为原始子项,并使用 MeshInstancelayers(在检查器面板中的“VisualInstance”下)和 Cameracull_masks ] 以确保只有 Viewport 中的 Camera 可以看到它。
  • 或者,将其添加为 Viewport 的子项,做一些类似于我们为 Camera 所做的事情,因此它复制原始位置,并设置 own_worldViewport 上的 true,因此它只呈现其中的内容。

关于material,它会是一个ShaderMaterial输出UV作为颜色(写ALBEDO,并将着色器设置为unshaded):

shader_type spatial;
render_mode unshaded;

void fragment()
{
    ALBEDO = vec3(UV, 0.0);
}

然后在附加到其他一些脚本中 Node。它将具有 Texture 类型的 export var 并将其设置为您定义的 Viewport 中的新 ViewportTexture

export var texture:Texture

我们要确保 Node 进入时 Viewport 已经在场景树中。它可以找到它。因此,这个 Node 不应该是 Viewport,也不应该是 Viewport 的父级。还要避免使用 Viewport 的子项。因此,我们想要一个兄弟姐妹。以及场景树中 Viewport 之前的同级。

现在,要从 Texture 中读取一个像素,您可以使用 get_data 从中获取 Image,然后对其调用 lock,然后使用get_pixelget_pixelv。别忘了unlock它。

此代码适用于鼠标或触摸输入:

func _input(event: InputEvent) -> void:
    if not (
        event is InputEventScreenDrag
        or event is InputEventScreenTouch
        or event is InputEventMouse
    ):
        return

    var image:Image = texture.get_data()
    image.lock()
    var color := image.get_pixelv(event.position)
    if color.a != 0.0:
        print(Vector2(color.r, color.g))

    image.unlock()