Beim Erstellen einer Anwendung mit Unity (2018.1.0b8) und dem offiziellen „SteamVR“ Asset wollte ich einen Gegenstand nicht direkt an den Controller binden und ihn dann als Sub-Objekt mit bewegen lassen, sondern ein anderes Objekt räumlich getrennt berechnen lassen, was aber auch live auf die Bewegungsänderungen reagiert. Das große Problem hierbei: Es gibt einen deutlich spürbaren Input Lag zwischen Controller und berechnetem Objekt.

Bei meiner Suche nach einer Lösung habe ich immer wieder Einträge gefunden, die dieses Problem durch Umschreiben des Asset-Codes bewerkstelligen wollten. Dazu wurde dann das Rendering-Skript in soweit verfälscht, dass in der Klasse „SteamVR_UpdatePoses jedes „OnPreCull()“ aus dem Skript durch „LateUpdate()“ ersetzt wurde um Controller und Objekt gleichzeitig verändert zu sehen und das Update des Controllers näher an den eigenen Code zu holen. Das führte aber dann dazu, dass andere Overlays (Wie zum Bespiel Steam) nicht mehr ordnungsgemäß ausgeführt wurden, da man damit die „schwammige“ Steuerung nur weiter übertragt und ein neues Problem erhält. Mit der aktuellen SteamVR Version (Bei mir aktuell ist Version 1.2.3) wurde diese Klasse als Deprecated markiert. Wer also versucht irgendwas damit zu machen wird in der Konsole nur eine Fehlermeldung bekommen. Die bisher vorgeschlagene Lösung geht also folglich gar nicht mehr.

Lösung:
Schaut man sich die verschiedenen Methoden an, die Unity 2018 zu bieten hat, sieht man, dass hier seit Unity 2017_1 ein neues Event eingefügt wurde, welches speziell für VR- oder Low-Latency-Applicationen gedacht ist: onBeforeRender. Dieses wird unmittelbar vor dem Renderingprozess aufgerufen und dient z.B. dem letzten Abgleichen von Controllerdaten. Es sollte unbedingt vermieden werden noch weitere, intensive Berechnungen auszuführen, da ihr sonst trotzdem starke Verzögerungen zwischen Eingabe und Rendering erhaltet (Die offizielle Beschreibung ist hier: https://docs.unity3d.com/ScriptReference/Application-onBeforeRender.html).

Möchte man nun ein Objekt bewegen, so kann man sich auf das „Application.onBeforeRender“ Event registrieren. Dafür legt man ein neues Skript an, dass die Berechnung zur Laufzeit durchführen soll und registriert einen Handler. Innerhalb der dann aufgerufenen Methode greift man auf das Objekt zu, von dem man die Daten verarbeiten möchte. Hierbei muss man beachten, dass das Objekt auch das Steam_VR_TrackedController oder Steam_VR_TrackedObject Script beinhaltet, da dieses zur Bestimmung der Position genutzt werden muss.

GameObject  controller = null;
SteamVR_TrackedObject controller_trackedObject = null;

void Start () {
  GameObject controller = GameObject.Find("Controller (left)");
  controller_trackedObject = controller.GetComponent<SteamVR_TrackedObject>();
  Application.onBeforeRender += OnBeforeRenderMethodToHandleYourStuff;
 }

private void OnBeforeRenderMethodToHandleYourStuff()
 {
  anotherObject.transform.rotation = controller_trackedObject .transform.rotation;
  anotherObject.transform.Rotate(new Vector3(30, 0, 0));
  //Hier können noch weitere Anpassungen erfolgen.
 }

So eliminiert man die kompletten Verzögerung und kann Objekte in derselben Geschwindigkeit anpassen wie den Controller selbst. Eine letzte Sache die noch beachtet werden sollte: Schaut man im Steam Asset in die Klasse „SteamVR_Render.cs“ sieht man folgenden Code:

#if UNITY_2017_1_OR_NEWER
 Application.onBeforeRender += OnBeforeRender;
#else
 Camera.onPreCull += OnCameraPreCull;
#endif

Bedeutet also: Dieses Callback gibt es erst ab Unity 2017_1. Andernfalls gibt es das Event noch nicht und ihr müsst onPreCull verwenden. Es macht hier keinen Sinn den Code dynamisch zu schreiben, der Vollständigkeit halber könnte man den Code aber auch entsprechend anpassen sodass er auf jeder Unity Version läuft. Dafür muss man um den Eventhandler herum folgendes ergänzen:

#if UNITY_2017_1_OR_NEWER
 Application.onBeforeRender += OnBeforeRenderMethodToHandleYourStuff;
#else
 Camera.onPreCull += OnBeforeRenderMethodToHandleYourStuff;
#endif

Viel Erfolg!