SteamVR(HTC Vive) Unity插件深度分析(一)
插件中的readme.txt和quickstart.pdf已经有一些描述了,这里对里面的每个重要文件进行详细分析。本文基于SteamVR Unity插件v1.1.0版本。文档最后会更新各最新版本相对于前一个版本的变化
1. openvr_api.dll
位于Plugins/
说一下OpenVR SDK与Unity插件的关系。OpenVR SDK本质上是一套C++的接口,供传统的用C/C++(比如直接使用OpenGL)来写游戏所使用的。可以看到github上的openvr目录下,SDK是以二进制的动态库(bin目录及lib目录)+头文件的方式提供的。在samples目录下有不少示例演示如何调用这个sdk。而Unity插件的核心或者说基础也是OpenVR SDK,我们可以看到Plugins目录下就是带了openvr_api.dll这个SDK,只不过,由于是给Unity开发者使用的,上面再加了一层C#脚本来封装。我们也可以看到github上供C/C++开发者使用的openvr_api.dll与供Unity开发者使用的openvr_api.dll几乎是一样的,仅仅新增了几个与Unity相关的导出函数:
OpenVRSDK中openvr_api.dll中的导出函数
UnityOpenVR插件中openvr_api.dll中的导出函数
openvr_api.dll的大小不大,只有300多K,显然它也不是VR的核心实现。也是,VR的核心实现肯定是在SteamVR的运行时当中,OpenVR作为同时支持VIVE和Oculus的SDK,可以猜测它的工作是对这两者的封装,并对外提供接口。而OpenVR的Unity插件又是通过C#对OpenVR的封装。
看一下这些导出函数都是干什么的,C#封装中没有注释,但还好C++封装中有注释和例子,可以参看
1 2 3 4 5 | /** Returns the interface of the specified version. This method must be called after VR_Init. The * pointer returned is valid until VR_Shutdown is called. */ |
获取指定版本的接口,这个接口应该是与OpenVR交互的核心。需要在调用了VR_init后再调用。但VR_init并没有导出,在C++版本的SDK中,在头文件中有此函数,是对导出函数VR_initInternal的封装
1 2 3 4 5 | VR_INTERFACE void *VR_CALLTYPE VR_GetGenericInterface( const char *pchInterfaceVersion, EVRInitError *peError ); /** Returns a token that represents whether the VR interface handles need to be reloaded */ |
返回一个表示VR接口句柄是否需要重新加载的token。根据示例代码,token其实是VR_InitInternal的返回值(VR_init返回的才是上面的VR_GetGenericInterface返回的VR接口句柄),如果VR_GetInitToken的返回值与之前保存的VR_InitInternal或者VR_GetInitToken的返回值不一样了,则表示token有更新,就需要重新调用VR_GetGenericInterface重新获取了。C++ SDK来看,所有重新操作前都会做这个检查
VR_INTERFACEuint32_t VR_CALLTYPE VR_GetInitToken();
VR_GetStringForHmdError这个函数在C++版SDK中并没有看到导出,在Unity中有,显然就是获取错误代码的描述字符串
/** Returnsan english string for an EVRInitError. Applications should callVR_GetVRInitErrorAsSymbol instead and use that as a key to look up their ownlocalized error message. This function may be called outside ofVR_Init()/VR_Shutdown(). */
获取EVRInitError(EVRInitError是一个枚举,VR_initInternal和VR_GetGenericInterface会返回它,代表初始化状态)的英文错误描述字符串。应用不要调用这个函数,而应该调用VR_GetVRInitErrorAsSymbol,用它的返回值去查询本地化的错误消息字符串。这个函数可以在VR_Init/VR_Shutdown之外调用
1 2 3 4 5 | VR_INTERFACE const char *VR_CALLTYPE VR_GetVRInitErrorAsEnglishDescription( EVRInitError error ); /** Returns the name of the enum value for an EVRInitError. This function may be called outside of VR_Init()/VR_Shutdown(). */ |
返回EVRInitError的枚举名称。这个函数可以在VR_Init/VR_Shutdown之外调用
1 2 3 4 5 | VR_INTERFACE const char *VR_CALLTYPE VR_GetVRInitErrorAsSymbol( EVRInitError error ); VR_INTERFACE uint32_t VR_CALLTYPE VR_InitInternal( EVRInitError *peError, EVRApplicationType eApplicationType ); |
这个没有注释。它在openvr_api.dll中有导出,在C++版的SDK中对外提供了一个VR_Init的封装,外部应用不会直接调用VR_InitInternal。
我们看一下VR_init的实现:
inlineIVRSystem *VR_Init( EVRInitError *peError, EVRApplicationType eApplicationType)
它里面的做法就是先调用VR_InitInternal做初始化,然后调用VR_GetGenericInterface获取VR接口句柄。
1 2 3 4 5 6 7 | /** Returns true if there is an HMD attached. This check is as lightweight as possible and * can be called outside of VR_Init/VR_Shutdown. It should be used when an application wants * to know if initializing VR is a possibility but isn't ready to take that step yet. */ |
判断有没有HMD(head mounted display,头戴式显示器,头显)连接。这个判断是轻量级的,可以在VR_Init和VR_Shutdown之外调用。当应用程序想知道是否可以初始化VR,但不一定就要准备初始化时可以调用它。
1 2 3 4 5 6 7 | VR_INTERFACE bool VR_CALLTYPE VR_IsHmdPresent(); /** Returns whether the interface of the specified version exists. */ |
返回指定版本的接口是否存在
1 2 3 4 5 | VR_INTERFACE bool VR_CALLTYPE VR_IsInterfaceVersionValid( const char *pchInterfaceVersion ); /** Returns true if the OpenVR runtime is installed. */ |
返回OpenVR(SteamVR)运行时是否安装
1 2 3 4 5 | VR_INTERFACE bool VR_CALLTYPE VR_IsRuntimeInstalled(); /** Returns where the OpenVR runtime is installed. */ |
返回OpenVR运行时的安装路径
1 2 3 4 5 | VR_INTERFACE const char *VR_CALLTYPE VR_RuntimePath(); VR_INTERFACE void VR_CALLTYPE VR_ShutdownInternal(); |
同样,VR_ShowdownInternal与VR_InitInternal一样,在C++版SDK中,对外公开的实际是VR_Showdown。可以看到VR_Shutdown就是简单地调用了VR_ShutdownInternal。
那显然最重要的是VR_GetGenericInterface返回的VR接口句柄,有一系列的VR接口,根据参数不同VR_GetGenericInterface返回不同的接口指针,这些接口有:IVRSystem、IVRApplications、IVRSettings、IVRChaperone、IVRChaperonSetup、IVRCompositor、IVRNotifications、IVROverlay、IVRRenderModels、IVRExtendedDisplay、IVRTrackedCamera、IVRScreenshots。每个接口都包含有大量的虚函数以提供完善的功能。
以上均是参考C++版的SDK的描述,那么,如果Unity插件版和C++版本所使用的openvr_api.dll是相同的,那么上面说的通过VR_GetGenericInterface获取各实际操作接口,返回的是C++对象的指针,在C#里面怎么使用呢?这个就涉及到C#如何调用DLL的问题,下节将给出解答。