SteamVR Overlay with Unity: Dashboard Overlay

kurohuku

kurohuku

Posted on June 16, 2024

SteamVR Overlay with Unity: Dashboard Overlay

Let’s create a setting screen on the SteamVR dashboard. It switches which hand to display the watch overlay.

Image description

Create dashboard overlay

Dashboard overlay is an overlay that is displayed on the SteamVR dashboard. We use this overlay as the setting screen.

Create new script

Create DashboardOverlay.cs inside Scripts folder and copy the following code.

Image description



using UnityEngine;
using Valve.VR;
using System;

public class DashboardOverlay : MonoBehaviour
{
    private void Start()
    {
    }
}


Enter fullscreen mode Exit fullscreen mode

Put the script into the scene

On hierarchy, right click > Create Empty to create a new game object named DashboardOverlay. Drag DashboardOverlay.cs to the object.

Image description

Prepare overlay handles

Dashboard overlay consists of the main overlay and thumbnail overlay. Both overlays have their handle.
The thumbnail overlay is a small overlay at the bottom of the dashboard used to switch between overlays.

Image description
The red rectangle is the thumbnail overlay. The large "Right Hand" button is on the main overlay.

Create two variables for the two overlay handles in the DashboardOverlay.cs.



using UnityEngine;
using Valve.VR;
using System;

public class DashboardOverlay : MonoBehaviour
{
+   private ulong dashboardHandle = OpenVR.k_ulOverlayHandleInvalid;
+   private ulong thumbnailHandle = OpenVR.k_ulOverlayHandleInvalid;
}


Enter fullscreen mode Exit fullscreen mode

Create dashboard overlay

Create dashboard overlay with CreateDashboardOverlay(). (read the wiki for details)
Here, we set key as "WatchDashboardKey" and name as "Watch Setting".

CreateDashboardOverlay() creates two overlays and sets their handles to the variables of the 3rd and 4th arguments.

Create utility class

We want to run the program but we must initialize the OpenVR before using the API. The initialization code is in WatchOverlay.cs. If DashboardOverlay.cs runs earlier than that initialization, it will result in an error.

There are various ways but this time, we will create a utility to share common code like initializing OpenVR that is called from other classes.

Create new script

Create OpenVRUtil.cs inside Scripts folder. Copy the following code.



using UnityEngine;
using Valve.VR;
using System;

namespace OpenVRUtil
{
    public static class System
    {
    }
}


Enter fullscreen mode Exit fullscreen mode

Move OpenVR initialization

Move the InitOpenVR() from WatchOverlay.cs to OpenVRUtil.cs. Add static to allow access from other external classes.

WatchOverlay.cs




private void OnDestroy()
{
    DestroyOverlay(overlayHandle);
    ShutdownOpenVR();
}

- private void InitOpenVR()
- {
-   if (OpenVR.System != null) return;
-
-   var error = EVRInitError.None;
-   OpenVR.Init(ref error, EVRApplicationType.VRApplication_Overlay);
-   if (error != EVRInitError.None)
-   {
-       throw new Exception("Failed to initialize OpenVR: " + error);
-   }
- }

private void ShutdownOpenVR()
{
    if (OpenVR.System != null)
    {
        OpenVR.Shutdown();
    }
}

...


Enter fullscreen mode Exit fullscreen mode

OpenVRUtil.cs



using UnityEngine;
using Valve.VR;
using System;

namespace OpenVRUtil
{
    public static class System
    {
+       // Add as public static method
+       public static void InitOpenVR()
+       {
+           if (OpenVR.System != null) return;
+
+           var error = EVRInitError.None;
+           OpenVR.Init(ref error, EVRApplicationType.VRApplication_Overlay);
+           if (error != EVRInitError.None)
+           {
+               throw new Exception("Failed to initialize OpenVR: " + error);
+           }
+       }
    }
}


Enter fullscreen mode Exit fullscreen mode

Move OpenVR cleanup

Similarly, move the ShutdownOpenVR() as a static method.

WatchOverlay.cs



// ...

private void OnDestroy()
{
    DestroyOverlay(overlayHandle);
    ShutdownOpenVR();
}

- private void ShutdownOpenVR()
- {
-     if (OpenVR.System != null)
-     {
-         OpenVR.Shutdown();
-     }
- }

private ulong CreateOverlay(string key, string name)
{
    var handle = OpenVR.k_ulOverlayHandleInvalid;
    var error = OpenVR.Overlay.CreateOverlay(key, name, ref handle);
    if (error != EVROverlayError.None)
    {
        throw new Exception("Failed to dispose OpenVR: " + error);
    }

    return handle;
}

// ...


Enter fullscreen mode Exit fullscreen mode

OpenVRUtil.cs



using UnityEngine;
using Valve.VR;
using System;

namespace OpenVRUtil
{
    public static class System
    {
        public static void InitOpenVR()
        {
            if (OpenVR.System != null) return;

            var initError = EVRInitError.None;
            OpenVR.Init(ref initError, EVRApplicationType.VRApplication_Overlay);
            if (initError != EVRInitError.None)
            {
                throw new Exception("Failed to initialize OpenVR: " + initError);
            }
        }

+       public static void ShutdownOpenVR()
+       {
+           if (OpenVR.System != null)
+           {
+               OpenVR.Shutdown();
+           }
+       }
    }
}


Enter fullscreen mode Exit fullscreen mode

Move overlay methods

Move overlay methods from WatchOverlay.cs to OpenVRUtil.cs because they will be used by other classes later.

Move all overlay methods from CreateOverlay() to SetOverlayRenderTexture() in WatchOverlay.cs.

  • CreateOverlay()
  • DestroyOverlya()
  • SetOverlayFromFile()
  • ShowOverlay()
  • SetoverlaySize()
  • SetOverlayTransformAbsolute()
  • SetOverlayTransformRelative()
  • FlipOverlayVertical()
  • SetOverlayRenderTexture()

WatchOverlay.cs



private void OnDestroy()
{
    DestroyOverlay(overlayHandle);
    OpenVRUtil.System.ShutdownOpenVR();
}

- private ulong CreateOverlay(string key, string name)
- {
-     var handle = OpenVR.k_ulOverlayHandleInvalid;
-     var error = OpenVR.Overlay.CreateOverlay(key, name, ref handle);
-     if (error != EVROverlayError.None)
-     {
-         throw new Exception("Failed to create overlay: " + error);
-     }
- 
-     return handle;
- }
- 
- ...
- 
- private void SetOverlayRenderTexture(RenderTexture renderTexture)
- {
-     var nativeTexturePtr = renderTexture.GetNativeTexturePtr();
-     var texture = new Texture_t
-     {
-         eColorSpace = EColorSpace.Auto,
-         eType = ETextureType.DirectX,
-         handle = nativeTexturePtr
-     };
-     var error = OpenVR.Overlay.SetOverlayTexture(overlayHandle, ref texture);
-     if (error != EVROverlayError.None)
-     {
-         throw new Exception("Failed to draw texture: " + error);
-     }
- }


Enter fullscreen mode Exit fullscreen mode

Create new static class Overlay to OpenVRUtil.cs and add all the methods as public static method.

OpenVRUtil.cs



namespace OpenVRUtil
{
    public static class System
    {
        public static void InitOpenVR()
        {
            if (OpenVR.System != null) return;

            var initError = EVRInitError.None;
            OpenVR.Init(ref initError, EVRApplicationType.VRApplication_Overlay);
            if (initError != EVRInitError.None)
            {
                throw new Exception("Failed to initialize OpenVR: " + initError);
            }
        }

        public static void ShutdownOpenVR()
        {
            if (OpenVR.System != null)
            {
                OpenVR.Shutdown();
            }
        }
    }

+   public static class Overlay
+   {
+       public static ulong CreateOverlay(string key, string name)
+       {
+           var handle = OpenVR.k_ulOverlayHandleInvalid;
+           var error = OpenVR.Overlay.CreateOverlay(key, name, ref handle);
+           if (error != EVROverlayError.None)
+           {
+               throw new Exception("Failed to create overlay: " + error);
+           }
+
+           return handle;
+       }
+
+       ...
+
+       public static void SetOverlayRenderTexture(ulong handle, RenderTexture renderTexture)
+       {
+           var nativeTexturePtr = renderTexture.GetNativeTexturePtr();
+           var texture = new Texture_t
+           {
+               eColorSpace = EColorSpace.Auto,
+               eType = ETextureType.DirectX,
+               handle = nativeTexturePtr
+           };
+           var error = OpenVR.Overlay.SetOverlayTexture(handle, ref texture);
+           if (error != EVROverlayError.None)
+           {
+               throw new Exception("Failed to draw texture: " + error);
+           }
+       }
+   }
}


Enter fullscreen mode Exit fullscreen mode

Update method calls in existing code

Change WatchOverlay.cs code to call the overlay methods from the OpenVRUtil instead of WatchOverlay.cs itself.

WatchOverlay.cs



using System;
using UnityEngine;
using Valve.VR;
+ using OpenVRUtil;

public class WatchOverlay : MonoBehaviour
{
    public Camera camera;
    public RenderTexture renderTexture;
    private ulong overlayHandle = OpenVR.k_ulOverlayHandleInvalid;

    [Range(0, 0.5f)] public float size;
    [Range(-0.5f, 0.5f)] public float x;
    [Range(-0.5f, 0.5f)] public float y;
    [Range(-0.5f, 0.5f)] public float z;
    [Range(0, 360)] public int rotationX;
    [Range(0, 360)] public int rotationY;
    [Range(0, 360)] public int rotationZ;

    private void Start()
    {
-       InitOpenVR();
+       OpenVRUtil.System.InitOpenVR();

-       overlayHandle = CreateOverlay("WatchOverlayKey", "WatchOverlay");
+       overlayHandle = Overlay.CreateOverlay("WatchOverlayKey", "WatchOverlay");

-       FlipOverlayVertical(overlayHandle);
-       SetOverlaySize(overlayHandle, size);
-       ShowOverlay(overlayHandle);
+       Overlay.FlipOverlayVertical(overlayHandle);
+       Overlay.SetOverlaySize(overlayHandle, size);
+       Overlay.ShowOverlay(overlayHandle);
    }

    private void Update()
    {
        var leftControllerIndex = OpenVR.System.GetTrackedDeviceIndexForControllerRole(ETrackedControllerRole.LeftHand);
        if (leftControllerIndex != OpenVR.k_unTrackedDeviceIndexInvalid)
        {
            var position = new Vector3(x, y, z);
            var rotation = Quaternion.Euler(rotationX, rotationY, rotationZ);
-           SetOverlayTransformRelative(overlayHandle, leftControllerIndex, position, rotation);
+           Overlay.SetOverlayTransformRelative(overlayHandle, leftControllerIndex, position, rotation);
        }

-       SetOverlayRenderTexture(overlayHandle, renderTexture);
+       Overlay.SetOverlayRenderTexture(overlayHandle, renderTexture);
    }

    private void OnApplicationQuit()
    {
-       DestroyOverlay(overlayHandle);
+       Overlay.DestroyOverlay(overlayHandle);
    }

    private void OnDestroy()
    {
-       ShutdownOpenVR();
+       OpenVRUtil.System.ShutdownOpenVR();
    }
}


Enter fullscreen mode Exit fullscreen mode

Add OpenVR initialize and cleanup

We made the utility class to share the common code. Let’s go back to the DashboardOverlay.cs to create a dashboard overlay.

Add OpenVR initialization and cleanup code. We already call the initialize and cleanup functions in the WatchOverlay.cs but there is no problem because if the OpenVR is already initialized or cleaned up, the methods do nothing.

DashboardOverlay.cs



using UnityEngine;
using Valve.VR;
using System;
+ using OpenVRUtil;

public class DashboardOverlay : MonoBehaviour
{
    private ulong dashboardHandle = OpenVR.k_ulOverlayHandleInvalid;
    private ulong thumbnailHandle = OpenVR.k_ulOverlayHandleInvalid;

    private void Start()
    {
+       OpenVRUtil.System.InitOpenVR();

        var error = OpenVR.Overlay.CreateDashboardOverlay("WatchDashboardKey", "Watch Setting", ref dashboardHandle, ref thumbnailHandle);
        if (error != EVROverlayError.None)
        {
            throw new Exception("Failed to create dashboard overlay: " + error);
        }
    }

+   private void OnDestroy()
+   {
+       OpenVRUtil.System.ShutdownOpenVR();
+   }
}


Enter fullscreen mode Exit fullscreen mode

Destroy dashboard overlay

Destroy the dashboard overlay at the end of the application.



public class DashboardOverlay : MonoBehaviour
{
    private ulong dashboardHandle = OpenVR.k_ulOverlayHandleInvalid;
    private ulong thumbnailHandle = OpenVR.k_ulOverlayHandleInvalid;

    private void Start()
    {
        OpenVRUtil.System.InitOpenVR();

        var error = OpenVR.Overlay.CreateDashboardOverlay("WatchDashboardKey", "Watch Setting", ref dashboardHandle, ref thumbnailHandle);
        if (error != EVROverlayError.None)
        {
            throw new Exception("Failed to create dashboard overlay: " + error);
        }
    }

+   private void OnApplicationQuit()
+   {
+       Overlay.DestroyOverlay(dashboardHandle);
+   }

    private void OnDestroy()
    {
        OpenVRUtil.System.ShutdownOpenVR();
    }
}


Enter fullscreen mode Exit fullscreen mode

As a note, we can destroy the main overlay only. If we pass the thumbnail overlay handle to DestroyOverlay(), ThumbnailCantBeDestroyed error will occur.

Show thumbnail

Let’s show image to the thumbnail.
We made a function SetOverlayFromFile() and a image in part.2 so we will use it.



private void Start()
{
   OpenVRUtil.System.InitOpenVR();

   var error = OpenVR.Overlay.CreateDashboardOverlay("WatchDashboardKey", "Watch Setting", ref dashboardHandle, ref thumbnailHandle);
   if (error != EVROverlayError.None)
   {
       throw new Exception("ダッシュボードオーバーレイの作成に失敗しました: " + error);
   }

+  var filePath = Application.streamingAssetsPath + "/sns-icon.jpg";
+  Overlay.SetOverlayFromFile(thumbnailHandle, filePath);
}


Enter fullscreen mode Exit fullscreen mode

Run the program, and check if the thumbnail is shown at the bottom of the dashboard.

Image description

Destroy the dashboard overlay at the end of the application.



public class DashboardOverlay : MonoBehaviour
{
    private ulong dashboardHandle = OpenVR.k_ulOverlayHandleInvalid;
    private ulong thumbnailHandle = OpenVR.k_ulOverlayHandleInvalid;

    private void Start()
    {
        OpenVRUtil.System.InitOpenVR();

        var error = OpenVR.Overlay.CreateDashboardOverlay("WatchDashboardKey", "Watch Setting", ref dashboardHandle, ref thumbnailHandle);
        if (error != EVROverlayError.None)
        {
            throw new Exception("Failed to create dashboard overlay: " + error);
        }
    }

+   private void OnApplicationQuit()
+   {
+       Overlay.DestroyOverlay(dashboardHandle);
+   }

    private void OnDestroy()
    {
        OpenVRUtil.System.ShutdownOpenVR();
    }
}


Enter fullscreen mode Exit fullscreen mode

As a note, we can destroy the main overlay only. If we pass the thumbnail overlay handle to DestroyOverlay(), ThumbnailCantBeDestroyed error will occur.

Show thumbnail

Let's show image to the thumbnail.
We made a function SetOverlayFromFile() and a image in part.2 so we will use it.



private void Start()
{
   OpenVRUtil.System.InitOpenVR();

   var error = OpenVR.Overlay.CreateDashboardOverlay("WatchDashboardKey", "Watch Setting", ref dashboardHandle, ref thumbnailHandle);
   if (error != EVROverlayError.None)
   {
       throw new Exception("ダッシュボードオーバーレイの作成に失敗しました: " + error);
   }

+  var filePath = Application.streamingAssetsPath + "/sns-icon.jpg";
+  Overlay.SetOverlayFromFile(thumbnailHandle, filePath);
}


Enter fullscreen mode Exit fullscreen mode

Run the program, and check if the thumbnail is shown at the bottom of the dashboard.

Image description

We don’t draw any image yet to the main overlay so it shows nothing when the thumbnail is clicked.
The name passed to CreateDashboardOverlay() is shown when the laser pointer hovers over the thumbnail. This time, it’s "Watch Settings".

Create setting screen

Let’s create the setting screen.
First, divide the game objects into two groups: the watch group and the dashboard group.

Watch Group

Right click in hierarchy > Create Empty to create an empty game object named Watch.
Move existing WatchOverlay, Camera, Canvas objects under the Watch object.

Image description

Dashboard group

Create the below objects under the Dashboard object.

  • Camera
  • UI > Canvas

Image description

Camera setting

Select Camera under the Dashboard, set Clear Flags to Solid Color in the inspector.
Click Background color, then change to opaque gray (RGBA = 64, 64, 64, 255).

Image description

Also, remove the AudioListener component.

Image description

Create render texture

Create render texture for dashboard overlay.

In the project window, right click Assets/RenderTexture folder > Render Texture to create a new render texture asset.
Change the asset name to DashboardRenderTexture.

Image description

Set the Size to 1024 x 768 in the inspector.

Image description

Click Camera under Dashboard in the hierarchy, drag DashboardRenderTexture asset to Target Texture property.

Image description

Here, the dashboard camera output goes to the render texture asset.

Canvas setting

Select Canvas object under Dashboard.
Set Render Mode to Screen Space — Camera in the Canvas inspector.
Drag Camera object under Dashboard to Render Camera.
Set Plane Distance to 10.

Image description

Move group

Open Dashboard the object’s inspector.
Set Position X to 20 to avoid overlapping groups.

Image description

Create button

Right click Canvas object under Dashboard then create UI > Button — TextMeshPro to create a new button named LeftHandButton.

Image description

Click Text (TMP) under LeftHandButton, change the text to “Left Hand”.

Image description

Select LeftHandButton and set the Width to 700 and Height to 200.

Image description

Select the Text (TMP) and set the Font Size to 100.

Image description

Duplicate LeftHandButton by right click > Duplicate.
Change the duplicated object name to RightHandButton.
Similarly, change the button text to “Right Hand”.

Image description

Set LeftHandButton Pos Y to 150 and RightHandButton Pos Y to -150.

Image description

Here, the appearance of the setting screen is done.

Draw to dashboard

Display the setting screen as a dashboard overlay.

Add camera variable

Add camera member variable to DashboardOverlay.cs.



public class DashboardOverlay : MonoBehaviour
{
+   public Camera camera;
    private ulong dashboardHandle = OpenVR.k_ulOverlayHandleInvalid;
    private ulong thumbnailHandle = OpenVR.k_ulOverlayHandleInvalid;

    // ...


Enter fullscreen mode Exit fullscreen mode

Open DashboardOverlay object inspector, drag Camera object under Dashboard to the Camera variable.

Image description

Add render texture

Add renderTexture variable in DashboardOverlay.cs.



public class DashboardOverlay : MonoBehaviour
{
    public Camera camera;
+   public RenderTexture renderTexture; 
    private ulong dashboardHandle = OpenVR.k_ulOverlayHandleInvalid;
    private ulong thumbnailHandle = OpenVR.k_ulOverlayHandleInvalid;

    // ...


Enter fullscreen mode Exit fullscreen mode

Select DashboardOverlay in the hierarchy, drag Assets/RenderTextures/DashboardRenderTexture asset to the Render Texture variable.

Image description

Flip vertical and set size

In advance, flip the texture vertically with FlipOverlayVertical() and set overlay size to 2.5 m with SetOverlaySize().

Draw render texture to dashboard overlay

Create Update() and draw the render texture to the dashboard overlay.

DashboardOverlay.cs



public class DashboardOverlay : MonoBehaviour
{
    public Camera camera;
    public RenderTexture renderTexture; 
    private ulong dashboardHandle = OpenVR.k_ulOverlayHandleInvalid;
    private ulong thumbnailHandle = OpenVR.k_ulOverlayHandleInvalid;

    private void Start()
    {
        OpenVRUtil.System.InitOpenVR();

        var error = OpenVR.Overlay.CreateDashboardOverlay("WatchDashboardKey", "Watch Setting", ref dashboardHandle, ref thumbnailHandle);
        if (error != EVROverlayError.None)
        {
            throw new Exception("Failed to create dashboard overlay: " + error);
        }

        var filePath = Application.streamingAssetsPath + "/sns-icon.jpg";
        Overlay.SetOverlayFromFile(thumbnailHandle, filePath);

        renderTexture = new RenderTexture(1024, 768, 16, RenderTextureFormat.ARGBFloat);
        camera.targetTexture = renderTexture;

        Overlay.SetOverlaySize(dashboardHandle, 2.5f);
        Overlay.FlipOverlayVertical(dashboardHandle);
    }

+   private void Update()
+   {
+       Overlay.SetOverlayRenderTexture(dashboardHandle, renderTexture);
+   }

    // ...


Enter fullscreen mode Exit fullscreen mode

Run the program, open the dashboard, and click the thumbnail. The setting screen should be shown.

Image description

Organize code

Move the creating dashboard overlay code to CreateDashboardOverlay().
It has to return two values dashboardhandle and thumbnailHandle, so we use a tuple to combine them.

Add the method to the utility’s Overlay class.

OpenVRUtil.cs



...

public static ulong CreateOverlay(string key, string name)
{
    var handle = OpenVR.k_ulOverlayHandleInvalid;
    var error = OpenVR.Overlay.CreateOverlay(key, name, ref handle);
    if (error != EVROverlayError.None)
    {
        throw new Exception("Failed to create overlay: " + error);
    }

    return handle;
}

+ public static (ulong, ulong) CreateDashboardOverlay(string key, string name)
+ {
+     ulong dashboardHandle = 0;
+     ulong thumbnailHandle = 0;
+     var error = OpenVR.Overlay.CreateDashboardOverlay(key, name, ref dashboardHandle, ref thumbnailHandle);
+     if (error != EVROverlayError.None)
+     {
+         throw new Exception("Failed to create dashboard overlay: " + error);
+     }
+ 
+     return (dashboardHandle, thumbnailHandle);
+ }

...


Enter fullscreen mode Exit fullscreen mode

Accordingly, change the function call.

DashboardOverlay.cs



private void Start()
{
    OpenVRUtil.System.InitOpenVR();

-   var error = OpenVR.Overlay.CreateDashboardOverlay("WatchDashboardKey", "Watch Setting", ref dashboardHandle, ref thumbnailHandle);
-   if (error != EVROverlayError.None)
-   {
-       throw new Exception("Failed to create dashboard overlay: " + error);
-   }
+   (dashboardHandle, thumbnailHandle) = Overlay.CreateDashboardOverlay("WatchDashboardKey", "Watch Setting");

    var filePath = Application.streamingAssetsPath + "/sns-icon.jpg";
    Overlay.SetOverlayFromFile(thumbnailHandle, filePath);

    Overlay.SetOverlaySize(dashboardHandle, 2.5f);
    Overlay.FlipOverlayVertical(dashboardHandle);
}


Enter fullscreen mode Exit fullscreen mode

Final code

DashboardOverlay.cs



using UnityEngine;
using Valve.VR;
using OpenVRUtil;

public class DashboardOverlay : MonoBehaviour
{
    public Camera camera;
    public RenderTexture renderTexture;
    private ulong dashboardHandle = OpenVR.k_ulOverlayHandleInvalid;
    private ulong thumbnailHandle = OpenVR.k_ulOverlayHandleInvalid;

    private void Start()
    {
        OpenVRUtil.System.InitOpenVR();

        (dashboardHandle, thumbnailHandle) = Overlay.CreateDashboardOverlay("WatchDashboardKey", "Watch Setting");

        var filePath = Application.streamingAssetsPath + "/sns-icon.jpg";
        Overlay.SetOverlayFromFile(thumbnailHandle, filePath);

        Overlay.FlipOverlayVertical(dashboardHandle);
        Overlay.SetOverlaySize(dashboardHandle, 2.5f);
    }

    private void Update()
    {
        Overlay.SetOverlayRenderTexture(dashboardHandle, renderTexture);
    }

    private void OnApplicationQuit()
    {
        Overlay.DestroyOverlay(dashboardHandle);
    }

    private void OnDestroy()
    {
        OpenVRUtil.System.ShutdownOpenVR();
    }
}


Enter fullscreen mode Exit fullscreen mode

OpenVRUtil.cs



using UnityEngine;
using Valve.VR;
using System;

namespace OpenVRUtil
{
    public static class System
    {
        public static void InitOpenVR()
        {
            if (OpenVR.System != null) return;

            var error = EVRInitError.None;
            OpenVR.Init(ref error, EVRApplicationType.VRApplication_Overlay);
            if (error != EVRInitError.None)
            {
                throw new Exception("Failed to initialize OpenVR: " + error);
            }
        }

        public static void ShutdownOpenVR()
        {
            if (OpenVR.System != null)
            {
                OpenVR.Shutdown();
            }
        }
    }

    public static class Overlay
    {
        public static ulong CreateOverlay(string key, string name)
        {
            var handle = OpenVR.k_ulOverlayHandleInvalid;
            var error = OpenVR.Overlay.CreateOverlay(key, name, ref handle);
            if (error != EVROverlayError.None)
            {
                throw new Exception("Failed to create overlay: " + error);
            }

            return handle;
        }

        public static (ulong, ulong) CreateDashboardOverlay(string key, string name)
        {
            ulong dashboardHandle = 0;
            ulong thumbnailHandle = 0;
            var error = OpenVR.Overlay.CreateDashboardOverlay(key, name, ref dashboardHandle, ref thumbnailHandle);
            if (error != EVROverlayError.None)
            {
                throw new Exception("Failed to create dashboard overlay: " + error);
            }

            return (dashboardHandle, thumbnailHandle);
        }

        public static void DestroyOverlay(ulong handle)
        {
            if (handle != OpenVR.k_ulOverlayHandleInvalid)
            {
                var error = OpenVR.Overlay.DestroyOverlay(handle);
                if (error != EVROverlayError.None)
                {
                    throw new Exception("Failed to dispose overlay: " + error);
                }
            }
        }

        public static void SetOverlayFromFile(ulong handle, string path)
        {
            var error = OpenVR.Overlay.SetOverlayFromFile(handle, path);
            if (error != EVROverlayError.None)
            {
                throw new Exception("Failed to draw image file: " + error);
            }
        }

        public static void ShowOverlay(ulong handle)
        {
            var error = OpenVR.Overlay.ShowOverlay(handle);
            if (error != EVROverlayError.None)
            {
                throw new Exception("Failed show overlay: " + error);
            }
        }

        public static void SetOverlaySize(ulong handle, float size)
        {
            var error = OpenVR.Overlay.SetOverlayWidthInMeters(handle, size);
            if (error != EVROverlayError.None)
            {
                throw new Exception("Failed to set overlay size: " + error);
            }
        }

        public static void SetOverlayTransformAbsolute(ulong handle, Vector3 position, Quaternion rotation)
        {
            var rigidTransform = new SteamVR_Utils.RigidTransform(position, rotation);
            var matrix = rigidTransform.ToHmdMatrix34();
            var error = OpenVR.Overlay.SetOverlayTransformAbsolute(handle, ETrackingUniverseOrigin.TrackingUniverseStanding, ref matrix);
            if (error != EVROverlayError.None)
            {
                throw new Exception("Failed to set overlay position: " + error);
            }
        }

        public static void SetOverlayTransformRelative(ulong handle, uint deviceIndex, Vector3 position, Quaternion rotation)
        {
            var rigidTransform = new SteamVR_Utils.RigidTransform(position, rotation);
            var matrix = rigidTransform.ToHmdMatrix34();
            var error = OpenVR.Overlay.SetOverlayTransformTrackedDeviceRelative(handle, deviceIndex, ref matrix);
            if (error != EVROverlayError.None)
            {
                throw new Exception("Failed to set overlay position: " + error);
            }
        }

        public static void FlipOverlayVertical(ulong handle)
        {
            var bounds = new VRTextureBounds_t
            {
                uMin = 0,
                uMax = 1,
                vMin = 1,
                vMax = 0
            };

            var error = OpenVR.Overlay.SetOverlayTextureBounds(handle, ref bounds);
            if (error != EVROverlayError.None)
            {
                throw new Exception("Failed to flip texture: " + error);
            }
        }

        public static void SetOverlayRenderTexture(ulong handle, RenderTexture renderTexture)
        {
            if (!renderTexture.IsCreated()) return;

            var nativeTexturePtr = renderTexture.GetNativeTexturePtr();
            var texture = new Texture_t
            {
                eColorSpace = EColorSpace.Auto,
                eType = ETextureType.DirectX,
                handle = nativeTexturePtr
            };
            var error = OpenVR.Overlay.SetOverlayTexture(handle, ref texture);
            if (error != EVROverlayError.None)
            {
                throw new Exception("Failed to draw texture: " + error);
            }
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

Now, we have created the setting screen as a dashboard overlay. We will make the button events in the next part.

💖 💪 🙅 🚩
kurohuku
kurohuku

Posted on June 16, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related