Search Unity

ScreenPointToRay and ViewportPointToRay not working with VR

Discussion in 'AR/VR (XR) Discussion' started by haydenjameslee, May 17, 2017.

  1. haydenjameslee

    haydenjameslee

    Joined:
    Apr 11, 2014
    Posts:
    18
    Environment:
    - Unity 5.6
    - VR is active and rendering to the HMD

    My goal is to raycast from the mouse into the scene, with a VR camera.

    When using ScreenPointToRay on a VR camera the ray's direction is way off. Here's an image when my cursor was in the center:



    When using ViewportPointToRay the ray's direction is much better but still not perfect. Here's an image showing the difference (sorry for mouse pic, my screen capturer wasn't picking up the mouse):



    Here's the test code to reproduce in a clean Unity 5.6 project and scene:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Raycaster : MonoBehaviour
    4. {
    5.     public Camera sourceCamera;
    6.     public bool useScreenPoint;
    7.     public bool useViewport;
    8.     public Transform viewportSphere;
    9.     public float sphereDistance;
    10.  
    11.     private void Update () {
    12.         Vector3 mousePos = Input.mousePosition;
    13.         Ray ray = new Ray();
    14.  
    15.         if (useScreenPoint) {
    16.             ray = sourceCamera.ScreenPointToRay(mousePos);
    17.         }
    18.         else if (useViewport) {
    19.             float normalWidth = mousePos.x / Screen.width;
    20.             float normalHeight = mousePos.y / Screen.height;
    21.             Vector3 viewMousePos = new Vector3(normalWidth, normalHeight, 0);
    22.             ray = sourceCamera.ViewportPointToRay(viewMousePos);
    23.  
    24.             if (viewportSphere != null) {
    25.                 viewportSphere.transform.position = ray.origin + ray.direction * sphereDistance;
    26.             }
    27.         }
    28.         Debug.DrawRay(ray.origin, ray.direction, Color.red);
    29.     }
    30. }
    31.  
    The simple solution to this would be to use another camera that displays just to the monitor, however the project is very performance restricted and the new camera would be displaying exactly the same thing as the existing VR camera.

    Has anybody had to tackle this or have any ideas on how to solve this without an extra camera?
     
    Last edited: May 18, 2017
  2. greggtwep16

    greggtwep16

    Joined:
    Aug 17, 2012
    Posts:
    1,539
    First off what is the camera? Is it just a single camera set to both eyes or is it the left or right eye? In VR raycasts should actually be done from the midpoint between the two eyes. If the camera is set to both this is fine, if it's one or the other you should calculate the midpoint before raycasting and likely have this in your rig as a transform.

    Once you have that you can just have the origin be the position and the direction be that midpoints position + transform.forward.
     
    Last edited: May 17, 2017
  3. haydenjameslee

    haydenjameslee

    Joined:
    Apr 11, 2014
    Posts:
    18
    @greggtwep16 In these screenshots I'm using a single camera set to both eyes. However, I have repro'd the same problem when I switch to left and right eyes too (that's the setup in the actual project I'm working on).

    I'm a bit confused by the rest of your comment. I might not have been clear enough with what I'm trying to achieve. I want to get the ray from the mouse into the scene, as ViewportPointToRay and ScreenPointToRay are supposed to do - whereas your solution seems to be the solution for getting the raycast from the head in the direction of the gaze.

    To visualize the use case: there is one person in VR and one person not in VR but on the PC using the mouse to click on things the VR person is looking at. As I mentioned in my comment the easiest way to do this would be using an extra camera dedicated to the non-VR view, however I'd like to avoid this for performance reasons.
     
    Last edited: May 18, 2017
  4. greggtwep16

    greggtwep16

    Joined:
    Aug 17, 2012
    Posts:
    1,539
    Ah, my mistake. Didn't realize you wanted it from the mouse and not the HMD. I can't say I've used the mouse much since most of the Time in VR I've been using controllers. That being said at least from the two screenshots the first one had the ray way lower than it should have been. The second had it slightly higher and your mouse position was a bit higher than center. I'm wondering if it's basically the VR distoritions meaning if the mouse was exactly in the center if it'd be correct and then the further from center you are the more you are off (both vertically and left/right). You should be able to run some tests to see if that's the case. If so, then it's off because of the distortion and you'd want to undo that distortion before you call those methods if you didn't want another camera. I'm not familiar if there is a Unity method for doing this but maybe there is some info posted on the internet. Just a guess based on your screenshots.
     
  5. haydenjameslee

    haydenjameslee

    Joined:
    Apr 11, 2014
    Posts:
    18
    Yeah I think you're right re: VR distortions. I've had some really great fun trying to undo the distortion just by trial and error... Today's task is trying to do it in a proper way from any online sources I can find. If anybody else reading this has any ideas on how to solve this I'm all ears.

    There's still the problem that Unity's ViewportPointToRay and ScreenPointToRay are simply wrong when using them in VR. I hope Unity updates their docs to say that its either not supported, or fixes the problem (fingers crossed).
     
    ZenUnity likes this.
  6. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    5,978
    maybe could set new camera to render only some empty layer?
     
  7. haydenjameslee

    haydenjameslee

    Joined:
    Apr 11, 2014
    Posts:
    18
  8. greggtwep16

    greggtwep16

    Joined:
    Aug 17, 2012
    Posts:
    1,539
    You can definitely do that in a second camera. Like OP said in the original post it will take up additional resources but you can perhaps minimize these.
     
  9. haydenjameslee

    haydenjameslee

    Joined:
    Apr 11, 2014
    Posts:
    18
    I'm hoping that it will work with the camera "disabled" and therefore not rendering (stranger things have happened with Unity APIs...)
     
  10. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    5,978
    how about just hiding the default mouse cursor and drawing your own cursor, same place as the sphere ?
     
  11. haydenjameslee

    haydenjameslee

    Joined:
    Apr 11, 2014
    Posts:
    18
    Have thought about that too, the problem is the person in VR will see the virtual cursor.

    (Keep the ideas coming please!)
     
  12. haydenjameslee

    haydenjameslee

    Joined:
    Apr 11, 2014
    Posts:
    18
    I haven't confirmed this but I believe the same problem happens when using "OnMouseEnter/Exit" and a VR camera. I'm guessing these problems all have the same root cause.
     
  13. greggtwep16

    greggtwep16

    Joined:
    Aug 17, 2012
    Posts:
    1,539
    Yes, that would have the same cause. I've actually done that on raycasting to a Unity GUI based on the mouse logic and it's off in a pattern that looks like the distortion in VR. My actual usecase was a laser pointer so I used a second camera from the origin of the laser pointer and it's orientation, but sounds like you are going for something different than a laser pointer.
     
  14. haydenjameslee

    haydenjameslee

    Joined:
    Apr 11, 2014
    Posts:
    18
    I have an approximate solution! I added a camera on a gameObject below the MainCamera, disabled it (although it needs to be enabled for one frame to get the correct FoV due to a Unity quirk), set the FoV to 2/3 the FoV of the VR camera (have only tested with Oculus which has an FoV of 96.6) and then I apply a linear transform to the position.... and it kinda works! I'll post code when I've done a bit more testing.
     
  15. haydenjameslee

    haydenjameslee

    Joined:
    Apr 11, 2014
    Posts:
    18
    My solution only works for the aspect ratio of the window. When I change the aspect ratio the y-axis is wrong.
     
  16. Mechanical_Man

    Mechanical_Man

    Joined:
    Jul 4, 2017
    Posts:
    1
    I ended up filing a Unity bug for this exact same issue, as using Screen.Width/Height worked fine with VR prior to Unity 5.4. It turns out however that after 5.4 you should instead use VRSettings.eyeTextureWidth/eyeTextureHeight respectively when dealing with the width/height of a VR headset.

    https://docs.unity3d.com/ScriptReference/VR.VRSettings.html
     
  17. KeithKong

    KeithKong

    Joined:
    May 31, 2015
    Posts:
    73
    While it makes sense on a technical level that you should be able to raycast from a screen point to the world space accurately, the user experience of using the mouse this way would be quite confusing. Moving the mouse from one eye to the other would create a location loop snap (because both eyes are viewing the same area). So if you had a laser pointer lighting up the contact point and you moved from right eye to left eye in screen space, you would notice the light jump back to the right suddenly.

    Every time I've seen a cursor or needed to use the mouse it's either been a cursor in close 3D space, on a flat 3D surface in the scene, or completely relative motion (and invisible). I would recommend using one of these solutions.

    Edit: Perhaps an invisible object close to the VR camera which moves relatively with the mouse in front of the camera at a fixed Z distance. Hide the mouse and rotate the object to face away from the camera center and raycast forward. You'll have to play with how much the object moves when you move the mouse but it shouldn't be too hard to find a good balance.
     
    Last edited: Aug 3, 2017
  18. jtesler

    jtesler

    Joined:
    Jul 19, 2017
    Posts:
    6
    Have you come up with a reliable solution to this? I have tried a solution based on what is written here, and although it is much more accurate than using the main camera directly, it is still significantly off. I've tried the code below. The goal is to place an object in the VR scene when the user at the PC clicks on the scene. In addition to what is here, I have also tried using ViewportPointToRay instead of ScreenPointToRay, and also delaying the raycast one cycle, neither of which changed things. I tried changing the ratio instead of using XRSettings.eyeTextureWidth / XRSettings.eyeTextureHeight, and although that moved the point, I was unable to find an accurate ratio.

    In the code below, camera is a second camera, not the main camera. It has the target eye set to None, because if I leave it at Both like the main camera, Unity won't let me change the fov.

    Code (CSharp):
    1. public class Sparkler : MonoBehaviour {
    2.     [SerializeField] private GameObject sparkle;  // The actual visible object.
    3.     [SerializeField] private Camera camera;
    4.  
    5.  
    6.     private void Update() {
    7.         if (Input.GetMouseButtonDown(0)) {
    8.             camera.gameObject.SetActive(true);
    9.             camera.transform.position = Camera.main.transform.position;
    10.             camera.transform.rotation = Camera.main.transform.rotation;
    11.             camera.fieldOfView = Camera.main.fieldOfView * XRSettings.eyeTextureWidth / XRSettings.eyeTextureHeight;
    12.             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    13.             RaycastHit hit;
    14.             if (Physics.Raycast(ray, out hit)) {
    15.                 sparkle.transform.position = hit.point;
    16.                 sparkle.SetActive(true);
    17.             }
    18.             camera.gameObject.SetActive(false);
    19.         }
    20.         if (Input.GetMouseButtonUp(0)) {
    21.             sparkle.SetActive(false);
    22.         }
    23.     }
    24. }
     
  19. jtesler

    jtesler

    Joined:
    Jul 19, 2017
    Posts:
    6
    Apologies, the above code line 12 should read

    Ray ray = camera.ScreenPointToRay(Input.mousePosition);

    Unfortunately, for some reason I couldn't edit it.
     
  20. MaeL0000

    MaeL0000

    Joined:
    Aug 8, 2015
    Posts:
    35
    Have you managed to get a more precise method to work?
     
  21. d_grass

    d_grass

    Joined:
    Mar 6, 2018
    Posts:
    15
unityunity