godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.01k stars 21.09k forks source link

Camera3D.project_position(point,z_depth) return wrong value when z_depth==far #95786

Open waci11 opened 1 month ago

waci11 commented 1 month ago

Tested versions

v4.3.stable.official [77dcf97d8]

System information

macOS 10.15.7

Issue description

Camera3D.project_position(point,z_depth) return wrong value when z_depth==far

Steps to reproduce

add this script to a Camera3D:

extends Camera3D
func _ready() -> void:
    far=2000
    print(project_position(Vector2.ONE,999))
    print(project_position(Vector2.ONE,1000))
    print(project_position(Vector2.ONE,1001))
    print("---")
    far=1000
    print(project_position(Vector2.ONE,999))
    print(project_position(Vector2.ONE,1000))
    print(project_position(Vector2.ONE,1001))

the log:

(-1360.407, 764.1937, -999)
(-1361.769, 764.9588, -1000)
(-1363.13, 765.7237, -1001)
---
(-1360.407, 764.1937, -999)
(-0.998264, 0.996914, -1000)
(-1363.13, 765.7237, -1001)

Minimal reproduction project (MRP)

empty project with camera3D

waci11 commented 1 month ago

I think maybe the problem is because cm return a identity Projection . p_near can not as same as _far i think it is beter to change the line 304 to cm.set_perspective(fov, viewport_size.aspect(), 0 , p_near, keep_aspect == KEEP_WIDTH); But the content of the set_perspective needs to be changed, I don't know if this is feasible.

godot/core/math/projection.cpp

void Projection::set_perspective(real_t p_fovy_degrees, real_t p_aspect, real_t p_z_near, real_t p_z_far, bool p_flip_fov) {
    if (p_flip_fov) {
        p_fovy_degrees = get_fovy(p_fovy_degrees, 1.0 / p_aspect);
    }

    real_t sine, cotangent, deltaZ;
    real_t radians = Math::deg_to_rad(p_fovy_degrees / 2.0);

    deltaZ = p_z_far - p_z_near; // line 264
    sine = Math::sin(radians);

    if ((deltaZ == 0) || (sine == 0) || (p_aspect == 0)) {
        return;// line 260
    }
    cotangent = Math::cos(radians) / sine;

    set_identity();

    columns[0][0] = cotangent / p_aspect;
    columns[1][1] = cotangent;
    columns[2][2] = -(p_z_far + p_z_near) / deltaZ;
    columns[2][3] = -1;
    columns[3][2] = -2 * p_z_near * p_z_far / deltaZ;
    columns[3][3] = 0;
}

godot/scene/3d/camera_3d.cpp

Vector3 Camera3D::project_position(const Point2 &p_point, real_t p_z_depth) const {
    ERR_FAIL_COND_V_MSG(!is_inside_tree(), Vector3(), "Camera is not inside scene.");

    if (p_z_depth == 0 && mode != PROJECTION_ORTHOGONAL) {
        return get_global_transform().origin;
    }
    Size2 viewport_size = get_viewport()->get_visible_rect().size;

    Projection cm = _get_camera_projection(p_z_depth);// line 531

    Vector2 vp_he = cm.get_viewport_half_extents();

    Vector2 point;
    point.x = (p_point.x / viewport_size.x) * 2.0 - 1.0;
    point.y = (1.0 - (p_point.y / viewport_size.y)) * 2.0 - 1.0;
    point *= vp_he;

    Vector3 p(point.x, point.y, -p_z_depth);

    return get_camera_transform().xform(p);
}

Projection Camera3D::_get_camera_projection(real_t p_near) const {
    Size2 viewport_size = get_viewport()->get_visible_rect().size;
    Projection cm;

    switch (mode) {
        case PROJECTION_PERSPECTIVE: {
            cm.set_perspective(fov, viewport_size.aspect(), p_near, _far, keep_aspect == KEEP_WIDTH);// line 304
        } break;
        case PROJECTION_ORTHOGONAL: {
            cm.set_orthogonal(size, viewport_size.aspect(), p_near, _far, keep_aspect == KEEP_WIDTH);
        } break;
        case PROJECTION_FRUSTUM: {
            cm.set_frustum(size, viewport_size.aspect(), frustum_offset, p_near, _far);
        } break;
    }

    return cm;
}