Search Unity

Resolved Why my custom tracking is stuttery?

Discussion in 'XR Interaction Toolkit and Input' started by illinar, Jan 7, 2022.

  1. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    (I solved this as I was writing the post, but still I'm posting in case someone might find this useful in the future. Solution is in the end.)

    Hi. I tried to make my own tracker scrip as a part of custom from scratch VR setup, but my results for some reason are way worse than Unity's.

    My script:
    Code (CSharp):
    1.  
    2.  
    3. using UnityEngine;
    4. using UnityEngine.InputSystem;
    5.  
    6. public class XRTracker : MonoBehaviour
    7. {
    8.     [SerializeField] private InputActionReference PositionActionReference;
    9.     [SerializeField] private InputActionReference RotationActionReference;
    10.  
    11.     void Start()
    12.     {
    13.         PositionActionReference.action.actionMap.Enable();
    14.     }
    15.  
    16.     void Update()
    17.     {
    18.         transform.position = PositionActionReference.action.ReadValue<Vector3>();
    19.         transform.rotation = RotationActionReference.action.ReadValue<Quaternion>();
    20.     }
    21. }
    22.  
    23.  
    Unity's script:
    Code (CSharp):
    1. using System;
    2. using UnityEngine.InputSystem.LowLevel;
    3.  
    4. namespace UnityEngine.InputSystem.XR
    5. {
    6.     /// <summary>
    7.     /// The <see cref="TrackedPoseDriver"/> component applies the current pose value of a tracked device
    8.     /// to the <see cref="Transform"/> of the <see cref="GameObject"/>.
    9.     /// <see cref="TrackedPoseDriver"/> can track multiple types of devices including XR HMDs, controllers, and remotes.
    10.     /// </summary>
    11.     /// <remarks>
    12.     /// For <see cref="positionInput"/> and <see cref="rotationInput"/>, if an action is directly defined
    13.     /// in the <see cref="InputActionProperty"/>, as opposed to a reference to an action externally defined
    14.     /// in an <see cref="InputActionAsset"/>, the action will automatically be enabled and disabled by this
    15.     /// behavior during <see cref="OnEnable"/> and <see cref="OnDisable"/>. The enabled state for actions
    16.     /// externally defined must be managed externally from this behavior.
    17.     /// </remarks>
    18.     [Serializable]
    19.     [AddComponentMenu("XR/Tracked Pose Driver (Input System)")]
    20.     public class TrackedPoseDriver : MonoBehaviour, ISerializationCallbackReceiver
    21.     {
    22.         /// <summary>
    23.         /// Options for which <see cref="Transform"/> properties to update.
    24.         /// </summary>
    25.         /// <seealso cref="trackingType"/>
    26.         public enum TrackingType
    27.         {
    28.             /// <summary>
    29.             /// Update both rotation and position.
    30.             /// </summary>
    31.             RotationAndPosition,
    32.  
    33.             /// <summary>
    34.             /// Update rotation only.
    35.             /// </summary>
    36.             RotationOnly,
    37.  
    38.             /// <summary>
    39.             /// Update position only.
    40.             /// </summary>
    41.             PositionOnly,
    42.         }
    43.  
    44.         [SerializeField]
    45.         TrackingType m_TrackingType;
    46.         /// <summary>
    47.         /// The tracking type being used by the Tracked Pose Driver
    48.         /// to control which <see cref="Transform"/> properties to update.
    49.         /// </summary>
    50.         /// <seealso cref="TrackingType"/>
    51.         public TrackingType trackingType
    52.         {
    53.             get => m_TrackingType;
    54.             set => m_TrackingType = value;
    55.         }
    56.  
    57.         /// <summary>
    58.         /// Options for which phases of the player loop will update <see cref="Transform"/> properties.
    59.         /// </summary>
    60.         /// <seealso cref="updateType"/>
    61.         public enum UpdateType
    62.         {
    63.             /// <summary>
    64.             /// Update after the Input System has completed an update and right before rendering.
    65.             /// </summary>
    66.             /// <seealso cref="InputUpdateType.Dynamic"/>
    67.             /// <seealso cref="InputUpdateType.BeforeRender"/>
    68.             UpdateAndBeforeRender,
    69.  
    70.             /// <summary>
    71.             /// Update after the Input System has completed an update.
    72.             /// </summary>
    73.             /// <seealso cref="InputUpdateType.Dynamic"/>
    74.             Update,
    75.  
    76.             /// <summary>
    77.             /// Update right before rendering.
    78.             /// </summary>
    79.             /// <seealso cref="InputUpdateType.BeforeRender"/>
    80.             BeforeRender,
    81.         }
    82.  
    83.         [SerializeField]
    84.         UpdateType m_UpdateType = UpdateType.UpdateAndBeforeRender;
    85.         /// <summary>
    86.         /// The update type being used by the Tracked Pose Driver
    87.         /// to control which phases of the player loop will update <see cref="Transform"/> properties.
    88.         /// </summary>
    89.         /// <seealso cref="UpdateType"/>
    90.         public UpdateType updateType
    91.         {
    92.             get => m_UpdateType;
    93.             set => m_UpdateType = value;
    94.         }
    95.  
    96.         [SerializeField]
    97.         InputActionProperty m_PositionInput;
    98.         /// <summary>
    99.         /// The action to read the position value of a tracked device.
    100.         /// Must support reading a value of type <see cref="Vector3"/>.
    101.         /// </summary>
    102.         public InputActionProperty positionInput
    103.         {
    104.             get => m_PositionInput;
    105.             set
    106.             {
    107.                 if (Application.isPlaying)
    108.                     UnbindPosition();
    109.  
    110.                 m_PositionInput = value;
    111.  
    112.                 if (Application.isPlaying && isActiveAndEnabled)
    113.                     BindPosition();
    114.             }
    115.         }
    116.  
    117.         [SerializeField]
    118.         InputActionProperty m_RotationInput;
    119.         /// <summary>
    120.         /// The action to read the rotation value of a tracked device.
    121.         /// Must support reading a value of type <see cref="Quaternion"/>.
    122.         /// </summary>
    123.         public InputActionProperty rotationInput
    124.         {
    125.             get => m_RotationInput;
    126.             set
    127.             {
    128.                 if (Application.isPlaying)
    129.                     UnbindRotation();
    130.  
    131.                 m_RotationInput = value;
    132.  
    133.                 if (Application.isPlaying && isActiveAndEnabled)
    134.                     BindRotation();
    135.             }
    136.         }
    137.  
    138.         Vector3 m_CurrentPosition = Vector3.zero;
    139.         Quaternion m_CurrentRotation = Quaternion.identity;
    140.         bool m_RotationBound;
    141.         bool m_PositionBound;
    142.  
    143.         void BindActions()
    144.         {
    145.             BindPosition();
    146.             BindRotation();
    147.         }
    148.  
    149.         void BindPosition()
    150.         {
    151.             if (m_PositionBound)
    152.                 return;
    153.  
    154.             var action = m_PositionInput.action;
    155.             if (action == null)
    156.                 return;
    157.  
    158.             action.performed += OnPositionPerformed;
    159.             action.canceled += OnPositionCanceled;
    160.             m_PositionBound = true;
    161.  
    162.             if (m_PositionInput.reference == null)
    163.             {
    164.                 action.Rename($"{gameObject.name} - TPD - Position");
    165.                 action.Enable();
    166.             }
    167.         }
    168.  
    169.         void BindRotation()
    170.         {
    171.             if (m_RotationBound)
    172.                 return;
    173.  
    174.             var action = m_RotationInput.action;
    175.             if (action == null)
    176.                 return;
    177.  
    178.             action.performed += OnRotationPerformed;
    179.             action.canceled += OnRotationCanceled;
    180.             m_RotationBound = true;
    181.  
    182.             if (m_RotationInput.reference == null)
    183.             {
    184.                 action.Rename($"{gameObject.name} - TPD - Rotation");
    185.                 action.Enable();
    186.             }
    187.         }
    188.  
    189.         void UnbindActions()
    190.         {
    191.             UnbindPosition();
    192.             UnbindRotation();
    193.         }
    194.  
    195.         void UnbindPosition()
    196.         {
    197.             if (!m_PositionBound)
    198.                 return;
    199.  
    200.             var action = m_PositionInput.action;
    201.             if (action == null)
    202.                 return;
    203.  
    204.             if (m_PositionInput.reference == null)
    205.                 action.Disable();
    206.  
    207.             action.performed -= OnPositionPerformed;
    208.             action.canceled -= OnPositionCanceled;
    209.             m_PositionBound = false;
    210.         }
    211.  
    212.         void UnbindRotation()
    213.         {
    214.             if (!m_RotationBound)
    215.                 return;
    216.  
    217.             var action = m_RotationInput.action;
    218.             if (action == null)
    219.                 return;
    220.  
    221.             if (m_RotationInput.reference == null)
    222.                 action.Disable();
    223.  
    224.             action.performed -= OnRotationPerformed;
    225.             action.canceled -= OnRotationCanceled;
    226.             m_RotationBound = false;
    227.         }
    228.  
    229.         void OnPositionPerformed(InputAction.CallbackContext context)
    230.         {
    231.             Debug.Assert(m_PositionBound, this);
    232.             m_CurrentPosition = context.ReadValue<Vector3>();
    233.         }
    234.  
    235.         void OnPositionCanceled(InputAction.CallbackContext context)
    236.         {
    237.             Debug.Assert(m_PositionBound, this);
    238.             m_CurrentPosition = Vector3.zero;
    239.         }
    240.  
    241.         void OnRotationPerformed(InputAction.CallbackContext context)
    242.         {
    243.             Debug.Assert(m_RotationBound, this);
    244.             m_CurrentRotation = context.ReadValue<Quaternion>();
    245.         }
    246.  
    247.         void OnRotationCanceled(InputAction.CallbackContext context)
    248.         {
    249.             Debug.Assert(m_RotationBound, this);
    250.             m_CurrentRotation = Quaternion.identity;
    251.         }
    252.  
    253.         /// <summary>
    254.         /// This function is called when the script instance is being loaded.
    255.         /// </summary>
    256.         protected virtual void Awake()
    257.         {
    258. #if UNITY_INPUT_SYSTEM_ENABLE_VR && ENABLE_VR
    259.             if (HasStereoCamera())
    260.             {
    261.                 UnityEngine.XR.XRDevice.DisableAutoXRCameraTracking(GetComponent<Camera>(), true);
    262.             }
    263. #endif
    264.         }
    265.  
    266.         /// <summary>
    267.         /// This function is called when the object becomes enabled and active.
    268.         /// </summary>
    269.         protected void OnEnable()
    270.         {
    271.             InputSystem.onAfterUpdate += UpdateCallback;
    272.             BindActions();
    273.         }
    274.  
    275.         /// <summary>
    276.         /// This function is called when the object becomes disabled or inactive.
    277.         /// </summary>
    278.         protected void OnDisable()
    279.         {
    280.             UnbindActions();
    281.             InputSystem.onAfterUpdate -= UpdateCallback;
    282.         }
    283.  
    284.         /// <summary>
    285.         /// This function is called when the <see cref="MonoBehaviour"/> will be destroyed.
    286.         /// </summary>
    287.         protected virtual void OnDestroy()
    288.         {
    289. #if UNITY_INPUT_SYSTEM_ENABLE_VR && ENABLE_VR
    290.             if (HasStereoCamera())
    291.             {
    292.                 UnityEngine.XR.XRDevice.DisableAutoXRCameraTracking(GetComponent<Camera>(), false);
    293.             }
    294. #endif
    295.         }
    296.  
    297.         protected void UpdateCallback()
    298.         {
    299.             if (InputState.currentUpdateType == InputUpdateType.BeforeRender)
    300.                 OnBeforeRender();
    301.             else
    302.                 OnUpdate();
    303.         }
    304.  
    305.         protected virtual void OnUpdate()
    306.         {
    307.             if (m_UpdateType == UpdateType.Update ||
    308.                 m_UpdateType == UpdateType.UpdateAndBeforeRender)
    309.             {
    310.                 PerformUpdate();
    311.             }
    312.         }
    313.  
    314.         protected virtual void OnBeforeRender()
    315.         {
    316.             if (m_UpdateType == UpdateType.BeforeRender ||
    317.                 m_UpdateType == UpdateType.UpdateAndBeforeRender)
    318.             {
    319.                 PerformUpdate();
    320.             }
    321.         }
    322.  
    323.         protected virtual void SetLocalTransform(Vector3 newPosition, Quaternion newRotation)
    324.         {
    325.             if (m_TrackingType == TrackingType.RotationAndPosition ||
    326.                 m_TrackingType == TrackingType.RotationOnly)
    327.             {
    328.                 transform.localRotation = newRotation;
    329.             }
    330.  
    331.             if (m_TrackingType == TrackingType.RotationAndPosition ||
    332.                 m_TrackingType == TrackingType.PositionOnly)
    333.             {
    334.                 transform.localPosition = newPosition;
    335.             }
    336.         }
    337.  
    338.         bool HasStereoCamera()
    339.         {
    340.             var cameraComponent = GetComponent<Camera>();
    341.             return cameraComponent != null && cameraComponent.stereoEnabled;
    342.         }
    343.  
    344.         protected virtual void PerformUpdate()
    345.         {
    346.             SetLocalTransform(m_CurrentPosition, m_CurrentRotation);
    347.         }
    348.  
    349.         #region DEPRECATED
    350.  
    351.         // Disable warnings that these fields are never assigned to. They are set during Unity deserialization and migrated.
    352.         // ReSharper disable UnassignedField.Local
    353. #pragma warning disable 0649
    354.         [Obsolete]
    355.         [SerializeField, HideInInspector]
    356.         InputAction m_PositionAction;
    357.         public InputAction positionAction
    358.         {
    359.             get => m_PositionInput.action;
    360.             set => positionInput = new InputActionProperty(value);
    361.         }
    362.  
    363.         [Obsolete]
    364.         [SerializeField, HideInInspector]
    365.         InputAction m_RotationAction;
    366.         public InputAction rotationAction
    367.         {
    368.             get => m_RotationInput.action;
    369.             set => rotationInput = new InputActionProperty(value);
    370.         }
    371. #pragma warning restore 0649
    372.         // ReSharper restore UnassignedField.Local
    373.  
    374.         /// <summary>
    375.         /// Stores whether the fields of type <see cref="InputAction"/> have been migrated to fields of type <see cref="InputActionProperty"/>.
    376.         /// </summary>
    377.         [SerializeField, HideInInspector]
    378.         bool m_HasMigratedActions;
    379.  
    380.         /// <summary>
    381.         /// This function is called when the user hits the Reset button in the Inspector's context menu
    382.         /// or when adding the component the first time. This function is only called in editor mode.
    383.         /// </summary>
    384.         protected void Reset()
    385.         {
    386.             m_HasMigratedActions = true;
    387.         }
    388.  
    389.         /// <inheritdoc />
    390.         void ISerializationCallbackReceiver.OnBeforeSerialize()
    391.         {
    392.         }
    393.  
    394.         /// <inheritdoc />
    395.         void ISerializationCallbackReceiver.OnAfterDeserialize()
    396.         {
    397.             if (m_HasMigratedActions)
    398.                 return;
    399.  
    400. #pragma warning disable 0612
    401.             m_PositionInput = new InputActionProperty(m_PositionAction);
    402.             m_RotationInput = new InputActionProperty(m_RotationAction);
    403.             m_HasMigratedActions = true;
    404. #pragma warning restore 0612
    405.         }
    406.  
    407.         #endregion
    408.     }
    409. }
    410.  
    Mine is stattery, fom time to time it's like it's freezes for a frame or just looks lower frame rate, and Unity's is perfectly smooth.

    I glanced over the code and I thought maybe it is because they are subbing to input events, so I made a version of mine where I do too, but the results are the same.

    What could be the difference making Unity's solution work so smooth and mine not?

    P.S. I also tried to poll input twice: on Update and on OnPreRender() with same results

    P.P.S The solution: The testing revealed that unitys tracking looks just like mine if it is in Update Only mode. So I'm just not updating correctly. Looking closer I see that they are subbubg to InputSystem.onAfterUpdate
    So I did the same and it is now smooth.

    The current code:
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.InputSystem;
    4.  
    5. public class XRTracker : MonoBehaviour
    6. {
    7.     [SerializeField] private InputActionReference PositionActionReference;
    8.     [SerializeField] private InputActionReference RotationActionReference;
    9.  
    10.     void OnEnable()
    11.     {
    12.         PositionActionReference.action.Enable();
    13.         RotationActionReference.action.Enable();
    14.         InputSystem.onAfterUpdate += PerformUpdate;
    15.     }
    16.  
    17.     void OnDisable()
    18.     {
    19.         InputSystem.onAfterUpdate -= PerformUpdate;
    20.     }
    21.  
    22.     void PerformUpdate()
    23.     {
    24.         transform.position = PositionActionReference.action.ReadValue<Vector3>();
    25.         transform.rotation = RotationActionReference.action.ReadValue<Quaternion>();
    26.     }
    27. }
    28.  
     
    Last edited: Jan 7, 2022