GodotVR / godot_openvr

GDNative based Open VR module
MIT License
227 stars 35 forks source link

ARVROrigin's world_scale vs translation latency issue #44

Open Windfisch opened 6 years ago

Windfisch commented 6 years ago

When changing the world_scale property of an ARVROrigin and simultaneously changing its translation, the translation change seems to come into effect with a delay of one frame, while the world scale change comes into effect immediately. This leads to a jumpy image and causes nausea.

My example application (scene world_scale_translation_latency.tscn) implements a "zoom" effect, where world_scale and translation are altered together in such a way, that the ARVRCamera's global world-space position does not change. The relevant snippet in ARVROrigin.gd is:

func update_world_scale_nomove(new_scale):
    var old_scale = world_scale
    var old_campos = $ARVRCamera.translation # relative to ARVROrigin = self
    var new_campos = old_campos / old_scale * new_scale
    translation -= (new_campos - old_campos)
    world_scale = new_scale # YYY

The delay in updating translation inserts one frame where the camera position is wrong, because the world scale is updated already, but the update to translation hasn't happened yet.

In the attached world_scale_bug.zip demo project, you can try this out: With the up/down arrow keys, the world scale can be continuously in/decreased, and with right/left, the same thing can be done in single steps. (translating movement can be done with WASD)

The attached video world_scale_bug_video.zip demonstrates this: I started by repeatedly pressing Arrow left and right (single step), and in the end I pressed Arrow up and down alternatingly.

System specs:

Windfisch commented 6 years ago

Interestingly, when swapping the XXX for YYY lines in the script, the jumps disappear, however now increasing and decreasing the world scale repeatedly makes the camera position drift. I suspect this being due to latency issues as well, but I'm not sure.

BastiaanOlij commented 6 years ago

Hey @Windfisch ,

This is unfortunately by design and not part of the openvr driver but far more core to the ARVR implementation in Godot.

In single threading mode Godot handles physics and script processing first, then renders the frames, in multitasking the physic and script processing works in a separate thread from rendering.

To minimize the time between getting accurate tracking position and rendering the frame, the tracking data for the headset is read right before rendering and thus after physics and script processing is run in single threading mode and completely independent of this in multi threading mode. This has the effect that your codes impact is shifted a frame, or in multi tasking might be even more disjoint.

In order to "solve" your issue the head tracking would have to be done before physics and script processing increasing the time between obtaining tracking information and displaying the end result which increases the inaccuracy of the displayed image leading to more nausea, not to mention the total disjoint when multi threading is used. Not nice for normal gameplay and detrimental to most experiences.

I'll have to find some time to experiment with your demo, you're really trying to do something this setting was never intended for. Your main issue is that you're trying to compensate for the distance from your origin point to the headset scaling (which is what it should do) which makes your character "move" when you've walked away from the center of your play area as a side effect to changing the scale.

I think you can get much better results using the center_on_hmd to ensure the ARVR system centers the headsets location on the origin point. Though this is mostly intended to be used for teleporting with a fade in/fade out effect and not to continuously run it may provide a way to improve your scenario.

In the end though, I'm not prepared to worsen all VR experiences just to work around one edge case.

lboklin commented 6 years ago

What about marking certain objects to be processed together with the HMD?

Windfisch commented 6 years ago

A possible solution would be to add a function to atomically change the world scale and keep the global camera position the same.

But I'll experiment with re-scaling and moving the actual world instead, maybe that helps. I just hope that all colliders support this already.

Windfisch commented 5 years ago

I have found a workaround by calling .notification(NOTIFICATION_INTERNAL_PROCESS) on all ARVR nodes:

The relevant ARVROrigin.gd-snippet:

    self.notification(NOTIFICATION_INTERNAL_PROCESS)
    for c in [ $OVRController, $OVRController2, $OVRController3, $OVRController4 ]:
        if (c.get_controller_name() != "Not connected"):
            c.notification(NOTIFICATION_INTERNAL_PROCESS)

This probably needs to be called on all anchors, too.

@BastiaanOlij, do you think this workaround is fine and should be documented, or is it too dirty and should rather be solved "properly" by adding a similar function to the Godot code base?

BastiaanOlij commented 5 years ago

Ah, yes the position is updated in the physics process on each node so you could end up checking things before it has updated.

But that kind of is the nature of the beast, you don’t want to update the position until after the parent nodes has its physics run or there could be glitches there.

That said, having the ARVR origin node update all positions of its ARVR children in its physics process might be better then having the children do this for themselves

On Thu, 22 Nov 2018 at 12:56 am, Florian Jung notifications@github.com wrote:

I have found a workaround by calling .notification(NOTIFICATION_INTERNAL_PROCESS) on all ARVR nodes:

The relevant ARVROrigin.gd-snippet:

self.notification(NOTIFICATION_INTERNAL_PROCESS) for c in [ $OVRController, $OVRController2, $OVRController3, $OVRController4 ]: if (c.get_controller_name() != "Not connected"): c.notification(NOTIFICATION_INTERNAL_PROCESS)

This probably needs to be called on all anchors, too.

@BastiaanOlij https://github.com/BastiaanOlij, do you think this workaround is fine and should be documented, or is it too dirty and should rather be solved "properly" by adding a similar function to the Godot code base?

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/GodotVR/godot_openvr/issues/44#issuecomment-440670131, or mute the thread https://github.com/notifications/unsubscribe-auth/AB2vaWpGg-1QeGe9_7h-ACILihAqjcQQks5uxVuNgaJpZM4VYBqP .

-- Kindest regards,

Bastiaan Olij

https://www.facebook.com/bastiaan.olij https://twitter.com/mux213 https://www.youtube.com/BastiaanOlij https://www.youtube.com/channel/UCrbLJYzJjDf2p-vJC011lYw https://github.com/BastiaanOlij