godotengine / godot

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

unproject_position and is_position_in_frustum returns wrong results if window is resized to a different aspect ratio #77906

Open mayonnace opened 1 year ago

mayonnace commented 1 year ago

Godot version

4.0.3

System information

Windows 10 - Godot v4.0.3.stable - Vulkan (Forward+) - dedicated NVIDIA GeForce GTX 1070 (Game Ready Version 528.49) - Intel Core i7-7700K CPU @ 4.20GHz (8 Threads)

Issue description

Problem: I get wrong results for unproject_position and is_position_in_frustum when the aspect ratio is not the same as set in the project settings' viewport size.

As you can see in my settings, I have 1:1 aspect ratio in project settings' viewport size, in order to support both landscape and portrait screens. It's a recommended setting in one of the green tip boxes at https://docs.godotengine.org/en/stable/tutorials/rendering/multiple_resolutions.html. Which also seems to be working the best for keeping relative sizes of GUI elements for different aspect ratios. But I'm using 1.77 aspect ratio in project settings' viewport override size, so the game's initial window can fit my own screen during development. And this is how I encountered the problem.

Although the 3D object is visible on screen, is_position_in_frustum returns false if the object is a bit far on left or right side of the viewport. And unproject_position for global_transform.origin can return even negative values on left parts of the viewport, and it doesn't match the correct coordinates on the right side either. It behaves as if the window is narrower on x axis.

If I change camera's "Keep Aspect" value from keep height to keep width, then the functions start working correctly for x axis, but they behave as if the viewport is higher, like, is_position_in_frustum keeps returning true till some point even when the object is vertically outside of the viewport, like very below or above and not visible.

The functions return correct results only if the current aspect ratio of the window is the same with the viewport size in the project settings.

What do I expect? I expect the functions to return correct results, regardless of the aspect ratio being same or not with the viewport size set in the project settings.

Steps to reproduce

My settings:

PROJECT viewport width 1920 viewport height 1920 window width override 640 window height override 400 resizable true mode viewport aspect expand scale 1 orientation sensor

SUBVIEWPORTCONTAINER stretch true stretch shrink 1

SUBVIEWPORT size 1920 1920 size 2d override 1920 1920 size 2d override stretch true

CAMERA keep aspect keep height projection perspective

My node structure:

Control

My logic:

The mesh instance in subject never moves, and my camera has RTS like angle. I use _physics_process(delta), and I simply make the camera move left, right, forward and backward with W, A, S, D and print(camera.is_position_in_frustum(subject.global_transform.origin), camera.unproject_position(subject.global_transform.origin), subviewport.size, subviewport.size_2d_override, get_viewport().size, get_viewport().get_visible_rect().size, get_viewport_rect().size, get_global_rect().size). Then I just compare the values, but they don't match with anything.

Unless my window's aspect ratio is the same with the viewport size in the project settings, I keep getting wrong results on x axis. And if I change the camera's Keep Aspect value from keep height to keep width, then I keep getting wrong results on y axis.

Minimal reproduction project

terminal.zip

FoggyFella commented 1 year ago

Still present in 4.1.2

pwestling commented 2 weeks ago

This bug appears to still be present, for anyone else who ends up here, the following code is a workaround that seems to work at least for my case:

func buggy_dont_use(camera: Camera3D, position: Vector3) -> Vector2:
    var true_viewport := camera.get_viewport()
    if true_viewport is SubViewport:
        var true_subviewport: SubViewport = true_viewport
        var size: Vector2 = true_subviewport.size if (true_subviewport.size_2d_override.x == 0 or true_subviewport.size_2d_override.y == 0) else true_subviewport.size_2d_override
        var midpoint: Vector2 = Vector2(size.x / 2.0, size.y / 2.0)

        var unprojected_position: Vector2 = camera.unproject_position(position)

        var vector_to_midpoint: Vector2 = unprojected_position - midpoint
        vector_to_midpoint = vector_to_midpoint * true_subviewport.get_final_transform().affine_inverse()
        unprojected_position = vector_to_midpoint + midpoint

        return unprojected_position
    else:
        return camera.unproject_position(position)

EDIT: nevermind this code does not work, it just appears to in a limited set of cases

pwestling commented 1 week ago

Ok, for me a workaround was to not use size_2d_override - when I disabled this feature unproject_position started to return the correct results