從買了 HTC Vive 後,Heresy 這邊一直有在研究怎麼用 OpenVR 這個 SDK 來開發 C++ 搭配 OpenGL 的虛擬實境程式,而實際上,到現在也算有些可以用的成品了。
而由於 Windows Mixed Reality 系統的頭戴顯示器(例如 Acer AH101)也可以透過「Windows Mixed Reality for SteamVR」來執行 OpenVR 的程式,所以當然也會想讓自己的程式可以支援 Windows MR 的硬體了~
基本上,在顯示的部分,只要透過 OpenVR 來開發,理論上就可以直接用(還是有些狀況就是了);但是在控制器的顯示部分,卻碰到了比較直接的問題…
那就是雖然在使用 HTC Vive 的時候是可以正確顯示的,但是在換成 Acer AH101,卻沒有辦法畫出控制器來。
在開始找問題、解決方法前,先回過頭來,看看目前的方法吧。
Heresy 這邊繪製控制器的方法,是參考官方的「hellovr_opengl」這個範例(參考)裡的方法來做的;他的基本作法,就是:
-
當發現有一個裝置(根據 vr::TrackedDeviceIndex_t 來區別不同的裝置)需要被畫出來的時候,就去呼叫 CMainApplication::SetupRenderModelForTrackedDevice() 這個函式,並將結果儲存在 m_rTrackedDeviceToRenderModel 這個 CGLRenderModel 陣列中。
-
繪製的程式則是寫在 CMainApplication::RenderScene() 中,他會依序去確認 m_rTrackedDeviceToRenderModel 內的裝置模型是否需要繪製,有的話就套用對應的位置矩陣,將它畫出來。
而 SetupRenderModelForTrackedDevice() 內部的實作,則是:
-
透過呼叫 GetTrackedDeviceString() 這個函式,來取得裝置的「繪圖模型名稱」(vr::Prop_RenderModelName_String)
(內部是使用 vr::IVRSystem::GetStringTrackedDeviceProperty() 這個 OpenVR 的函式) -
呼叫 FindOrLoadRenderModel() 這個函式,讀取指定的繪圖模型、建立出型別為 CGLRenderModel 的物件。
不過由於部分裝置是相同的模型,可以重複使用,所以這邊內部會做快取(儲存在 m_vecRenderModels),如果已經有讀取好的模型,會直接使用他。 -
在 FindOrLoadRenderModel() 這個函式中,則是:
-
透過 vr::IVRRenderModels 提供的 LoadRenderModel_Async() 這個函式、來讀取裝置的 3D 幾何資訊;其格式為 vr::RenderModel_t,裡面記錄了這個裝置模型的每一個 vertex 的資訊(position、normal、texture coord)、texture ID 以及對應的 index array。
-
透過 vr::IVRRenderModels 提供的 LoadTexture_Async() 這個函式、來把裝置的 3D 貼圖讀取出來;他的格式是 vr::RenderModel_TextureMap_t,裡面基本上就是一張圖檔。
-
都讀取完成後,就拿 vr::RenderModel_t 和 vr::RenderModel_TextureMap_t 建立出 CGLRenderModel 的物件。
-
最後再把剛剛讀出來的 vr::RenderModel_t 和 vr::RenderModel_TextureMap_t 釋放掉。
-
可以看到,這樣的方法,基本上每一個裝置就是一個 3D 模型的形式了。
而為什麼 Windows MR 的控制器畫不出來呢?
追了一下後,發現那是因為在使用 Windows Mixed Reality for SteamVR 的時候,雖然透過可以透過 OpenVR 的 API 取得到對應的模型檔案(註 1),但是裡面基本上是空的,所以當然也就畫不出來了。
不過由於 SteamVR 的基礎環境中,其實是看的到 Windows MR 的控制器的,所以代表 OpenVR 應該還是有辦法去正確讀取、顯示控制器的模型的…所以,後來比較有空的時候,就開始認真看看到底是什麼問題了。
而在稍微研究了一下後,這才注意到,其實 SteamVR 應該是有辦法顯示按鈕的狀態(尤其是類比的板機鈕、就像右圖所示)的。
但是如果按照「hellovr_opengl」這個範例的寫法,由於整個控制器都是單一模型,所以是沒辦法呈現這樣的動態效果的!
回過頭再去看 OpenVR 的 API,這才發現,原來 OpenVR 在 vr::IVRRenderModels 裡面,其實是有提供「Component」(零件、部件)相關的函式,看來是可以把整個控制器、拆成各部分來讀取、使用的!
不過,OpenVR 官方針對這部分,似乎沒有提供任何相關的範例、說明,所以就只能自己摸了…
而下面就是 Heresy 自己玩出來的一些紀錄(也就是說,錯了不負責 :p)。
首先,在取得裝置的「繪圖模型名稱」(vr::Prop_RenderModelName_String) 前的操作方法都是一樣的,但是之後本來是要直接去讀取模型,這邊要變成:
-
先透過 vr::IVRRenderModels 提供的 GetComponentCount() 這個函式,確認這個裝置的模型是否包含 component,如果沒有的話,就用舊有的方法,去讀取整個裝置的整體模型。
-
如果有包含 component 的話,那就要切換到讀取多個 component 的模式。
這邊基本上就是透過一個迴圈、去處理每一個 component(前面有數量了),其基本流程如下:-
使用 GetComponentName() 這個函式,來取得 component 的名稱。
-
使用 GetComponentRenderModelName() 這個函式,來取得 component 的繪圖模型的名稱。
要注意的是,有的 component 可能沒有包含繪圖模型,這時候函式會回傳 0;如果只是為了顯示,建議可以直接跳過這個 component。 -
接下來,可以把 component 的繪圖模型的名稱,傳回給前面的 FindOrLoadRenderModel() 這個函式,來讀取這個 component 的模型與貼圖。
-
基本上,讀取的部分大概就是這樣。
和之前的差別,主要就在於:
- 要透過 GetComponentCount() 這個函式來判斷這個裝置有沒有 component
- 一個控制器會被拆成許多的模型
理論上,這樣都讀進來的話,就可以畫出 HTC Vive 和 Windows MR 的控制器了!
而針對這些 componet,也都可以透過 vr::IVRRenderModels 的 GetComponentState() 這個函式,來取得包含動態位移在內的一些額外的資訊。
GetComponentState() 這個函式,基本上可以針對指定的 component、以及目前的控制器狀態(vr::VRControllerState_t、透過 vr::IVRSystem 的 GetControllerState() 函式取得),來計算出 componet 的狀態。
這邊要傳給 GetComponentState() 這個函式的參數,包括了裝置、component 的 render model name,控制器的狀態,以及控制器模式(vr::RenderModel_ControllerMode_State_t、目前應該只能設定要不要顯示觸控板上的虛擬滾輪)。
而最後一個參數則是他的輸出,的型別是 vr::RenderModel_ComponentState_t, 裡面包含了一個屬性值、以及兩個矩陣。
他的屬性值 uProperties 的類別是 VRComponentProperties,值的意義應該是要以 bit-wise 的形式、對照 EVRComponentProperty 來看;透過這個值,可以知道這個 component 是否為靜態的、可視狀態等等。
矩陣的部分,如果要表現他目前的位移的話,則是要使用 mTrackingToComponentRenderModel 這個矩陣;以板機鈕來說,只要在繪製 component 前額外加套用這個矩陣,就可以反映出目前按得多深了~
而雖然在視覺上不明顯,但是其他的按鈕,其實也都是會表現出按下的狀態的。
至於另一個矩陣 mTrackingToComponentLocal,Heresy 目前還沒玩過,不過應該是用來在 componet 上附加其他物件時,可以拿來用的矩陣了。
那一個控制器到底有那些 component 呢?目前看來,根據硬體的不同,會差很多。
以 HTC Vive 來說,控制器的 component 分得很細,實際對應控制器的硬體、有模型的部分包括了:
- body:控制器本體、按鈕都被挖掉後的部分
- button:App 按鈕,就是觸控板上方的小按鈕
- led:控制器上的 LED 燈
- lgrip:左側的握取鈕
- rgrip:右側的握取鈕
- sys_button:系統按鈕,就是觸控板下方的小按鈕
- trackpad:觸控板
- trigger:控制器背面的板機鈕
而對於非實體、但是有提供模型的部分,則還有:
- scroll_wheel:觸控板上的虛擬滾輪
- trackpad_scroll_cut:搭配上者的觸控板(應該吧)
- trackpad_touch:手指在觸控板上的位置
- status:狀態區,控制器握把上「Vive」 字樣的地方,這部分沒有提供有意義的貼圖,直接拿來用的話,會是沒意義的雜亂貼圖;這部分是用來讓開發者自己在控制器上畫東西的(比如說左右手圖示、或是電池電量)。
另外,下面則還有一些不包含模型的部分:
- ba
se - gdc2015
- handgrip
- tip
這部分就比較不確定到底要怎麼用了。
而以 Acer AH101 這款 Windows MR 的控制器來說,component 則相對單純,包括了:
- body:控制器本體
- handgrip:控制器背後的握取鈕
- menu_button:選單鈕
- thumbstick:正面的小型搖桿(蘑菇頭)
- trackpad:正面的觸控板
- trigger:控制器上方的板機鈕
- tip:沒有模型的 component
比較有趣的,是 Windows MR 沒有提供 Windows 鈕的模型。
而仔細看的話,也可以發現,component 的名稱也是不完全相同的!所以,如果要寫一個程式同時針對不同的硬體平台做控制器的顯示調整的話,看來是得用窮舉法來處理了… orz
另外像 HTC Vive 有提供 trackpad_touch 和 status 這兩個 component,其實算是相當實用的~微軟沒有提供這兩個 component,其實就讓 Heresy 覺得有點麻煩了。
這樣分 component 有什麼好處呢?
由於虛擬實境中看不到真的控制器以及雙手,所以能顯示出控制器目前的狀態,其實在某些時候,是很有幫助的~
尤其是觸控板的部分,在上面顯示目前接觸的位置,也是相當實用的。
另外,在自行開發應用程式的時候,如果需要幫控制器的實體按鈕附加功能的時候,其實最好也是能在控制器上做出對應的視覺提示,否則不熟係的人,很有可能會搞不清楚要按那裡。
如果在沒有 component 的情況下,如果要針對不同廠牌的控制器附加視覺提示的話,就需要個別去算對應位置,其實還滿麻煩的;但是在有 componet 的情況下,最簡單的方法,就是直接更換控制器的貼圖了!
比如果,可以直接在控制器的觸控板上,畫上不同區域的功能,就會是個比較直覺的做法。
像右圖就是 Google 的《Tilt Brush》(官網)這款遊戲裡面,控制器呈現的樣子;這邊他並沒有去使用 OpenVR 提供的控制器模型,而是自己設計了一個。
在這邊也可以看到,他有在觸控板加上額外的圖示,做基本的示意;而如果要把控制器的觸控板當成上下左右四個鈕來用的話,有把分區畫出來,應該也是比較合適的。
另外,右圖則是 Windows MR 控制器在《Fantastic Contraption》(網站)這款遊戲裡面繪製的樣子;由於此遊戲只有針對 HTC Vive 做處理,所以這邊顯示的效果其實不算正確的。
而這邊也剛好可以看的出來,這款遊戲有針對 HTC Vive 的控制器的 componet 更換貼圖為木紋,強化遊戲風格;但是由於 Windows MR 的控制器的 component 名稱和 HTC Vive 的不一致,所以很多 componet 並沒有被成功地替換材質。
不過,這邊也可以看得出來,如果想要凸顯控制器上的某個按鈕的話,直接換成其他材質,應該會是個很直接、方便的方法~
附註:
- Heresy 這邊得到的檔案是:「
C:UsersHeresyAppDataLocalMicrosoftWindowsOpenVRcontroller_1627_1118_2controller.obj」,應該會根據軟硬體而不同。