Unity VR开发教程 OpenXR+XR Interaction Toolkit UI

Posted YY-nb

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity VR开发教程 OpenXR+XR Interaction Toolkit UI相关的知识,希望对你有一定的参考价值。

文章目录


往期回顾:
Unity VR开发教程 OpenXR+XR Interaction Toolkit (一) 安装和配置
Unity VR开发教程 OpenXR+XR Interaction Toolkit (二) 手部动画
Unity VR开发教程 OpenXR+XR Interaction Toolkit (三) 转向和移动
Unity VR开发教程 OpenXR+XR Interaction Toolkit (四) 传送

在 VR 的交互中,与 UI 进行交互是很常见的功能。本篇教程,我将介绍如何在 VR 世界中用射线进行 UI 的交互。


📕教程说明

使用的 Unity 版本: 2020.3.36

使用的 VR 头显: Oculus Quest 2

教程使用的 XR Interaction Toolkit 版本:2.1.1(此教程尽量考虑了向上兼容,如果有过期的地方,欢迎大家指出)

前期的配置:环境配置参考教程一,手部模型参考教程二。本篇教程的场景基于上一篇教程搭建的场景进行延申。

项目源码(持续更新):https://github.com/YY-nb/Unity_XRInteractionToolkit_Tutorial2022/tree/master

最终实现的效果:手部射线(一开始是看不见的)对准 UI 时,会显示一条指向 UI 的射线。按下手柄的 Trigger 键,能与可交互的 UI(如 Button,Toggle,Slider 等)进行互动。


📕制作 World Space 模式的 UI

首先在场景中创建一个 Canvas 游戏物体。基于 VR 应用的沉浸感,其中的 UI 应该是 3D 世界中的一部分,因此我们在制作 UI 的时候要把 Canvas 组件的 Render Mode 改为 World Space

然后调整一下 Canvas 的大小和位置,就可以在 Canvas 上添加 UI 了。这里我就加上一个 Button,一个 Toggle 和 一个 Slider:


📕添加 Tracked Device Graphic Raycaster 脚本

在 Canvas 上添加 Tracked Device Graphic Raycaster 脚本。添加了这个脚本后,UI 就能被射线响应。


📕添加 XR UI Input Module 脚本

在 EventSystem 游戏物体上添加 XR UI Input Module 脚本,并且把原先的 Standalone Input Module 脚本移除。XR UI Input Module 配合 Event System 组件,可以让 Input Action 中的动作配置作用于 VR 中的 UI,让 UI 能够被交互,比如让按钮能被射线点击。


📕添加 UI 射线相关脚本

因为在我们的需求中,是用射线与 UI 进行交互,所以我们需要添加和射线相关的脚本。这时候我们可以联想一下我写的上一篇传送教程(Unity VR开发教程 OpenXR+XR Interaction Toolkit 2.1.1 (四) 传送)。因为传送也有用到射线,所以我们完全可以用类似的思路实现 UI 射线,即分别在左右手的控制器上添加发送射线的相关脚本。

我们沿用上一篇传送教程中的 XR Origin (因此保留了传送功能),然后分别在 LeftHand Controller 和 RightHandController 下创建 LeftHand UIController 和 RightHand UIController 游戏物体:

模仿传送功能,添加上 XR Controller(Action-based) (关闭 Enable Input Tracking,并且添加对应的 Preset),XR Ray Interactor(Line Type 为 Straight Line,因为我们希望 UI 的射线是一条直线),Line Renderer(可以复制 TeleportController 上的 Line Renderer 组件),XR Interactor Line Visual 脚本。

左右手都添加了这些组件后,就能够发射一条直的射线。我们可以运行一下程序,这时候会发现两只手都会射出一条直的射线,当射线射到 UI 上时,按下手柄的 Trigger 键能被可交互的 UI (Button,Toggle,Slider)响应,比如能用射线点击按钮,拖动滑动条。


📕过滤 UI 射线的目标

但是仍然存在一个问题,我们的 XR Interactor Line Visual 脚本规定了当 UI 射线被激活时,射线颜色为白色;未被激活时射线颜色为红色。见 XR Interactor Line Visual 的 Valid Color Gradient 和 Invalid Color Gradient:

但是当我们的 UI 射线射在地面上的时候仍然处于激活的状态,还会显示传送功能中射线末端的 Reticle,并且当我们按下手柄的 Grip 键,居然也会触发传送。(见下图,UI 射线射到地面上时颜色为白色,说明处于激活的状态)

这个问题其实和上一篇传送教程中出现的问题是一样的,此时我们拥有两种射线,一种是传送射线,被上一篇教程中我们自己写的 TeleportationController 脚本所控制;另一种是刚刚创建的 UI 射线,它使用的是默认的配置。

因为我们之前给地面添加了 Teleportation Area 脚本,默认情况下是当射线射到地面的碰撞体时,会视为选中了传送区域,然后因为负责 UI 射线的 XR Controller 中的 Select Action 默认绑定的是 XRI LeftHand/RightHand Interaction 下的 Select 动作,而 Select 动作又绑定了 “Grip 键按下” 这个操作,所以按下 Grip 键会响应到传送的功能。而且是本应该负责与 UI 交互的射线响应了传送的触发。

但我们希望的是 UI 射线只有射到 UI 上的时候才能被激活。所以我们要对能激活 UI 射线的目标做个过滤。解决办法也很简单,我们找到 LeftHand UIController 物体和 RightHand UIController 物体上挂载的 XR Ray Interactor 脚本,把 Raycast Mask 中的 Everything 改成 UI

修改前:

修改后:

现在我们再运行一下程序,这时候 UI 射线只有射到 UI 上才会变成白色,而射到地面上显示的是红色,说明只有 UI 激活了 UI 射线。

但是这里还可以有个优化,在当前的程序中,我们会看到手部一直射出一条有颜色的射线,即使没有指向 UI,场景中依然存在一条红色的射线。而我们可以这样改进:让射线射到 UI 上时才能显示,而射到其他地方不显示射线。


📕使射线射到 UI 上时才显示射线颜色

改进方法很简单,找到 XR Interactor Line Visual 脚本,修改 Invalid Color Gradient,我们可以把它的透明度改为 0。这样,当射线射到不是 UI 的地方时,射线就是透明的,在我们的眼中就是不显示的。

点开 Invalid Color Gradient 后,找到左上角和右上角白色的角标,点击后找到 Alpha 值,将它改成 0 就能让射线在未激活状态下处于透明状态。

修改前:

修改后:

现在再次运行程序,这时候只有射线射到 UI 上的时候才会看到白色的射线。


📕改变射线发射的位置

现在我们的射线是从大拇指附近射出来的,但是如果我想改变射线发射的位置呢?

其实操作也很简单,我们在手部模型下创建一个子物体作为射线发射的起始点,我这里将起始点的位置移至食指的地方,相当于让射线从食指处发出,射线发射的方向与起始点本地坐标的 z 轴方向一致

找到 XR Ray Interactor 脚本中的 Ray Origin Transform

然后将刚刚创建的射线起始点分别拖入对应的 XR Ray Interactor 脚本的 Ray Origin Transform

现在试着运行程序,射线就会从食指射出啦!😊

Unity VR开发教程 OpenXR+XR Interaction Toolkit 手部动画

文章目录


往期回顾:
Unity VR开发教程 OpenXR+XR Interaction Toolkit (一) 安装和配置
上一篇教程我们成功安装和配置了 Unity OpenXR+XR Interaction Toolkit 开发 VR 的环境,最终将 VR 头显和电脑进行串流后,能通过头显看到 Unity 中的场景,并且头部、手柄的位移和转动也能准确定位。

但是因为我们没有添加手部的模型,所以手柄的位置是空荡荡的,如下图:

而 VR 世界里怎么能看不见自己的手呢?🤔 所以本篇教程就来介绍如何在我们的 VR 世界中添加手部模型,以及如何用手柄的按键来控制手部模型的动作。


📕教程说明

使用的 Unity 版本: 2020.3.36

使用的 VR 头显: Oculus Quest 2

教程使用的 XR Interaction Toolkit 版本:2.1.1(此教程尽量考虑了向上兼容,如果有过期的地方,欢迎大家指出)

前期的配置:见教程一

项目源码(持续更新):https://github.com/YY-nb/Unity_XRInteractionToolkit_Tutorial2022/tree/master

最终实现的手部动画:按下手柄的 Trigger 键让大拇指和食指做出捏合的动画,按下手柄的 Grip 键让整只手做出抓握的动画。了解原理后也可以自定义手部动画和手柄按键的对应关系,做出适合自己需求的手部动画。

效果图:

注:本篇博客的内容参考自油管大佬的这个视频 https://m.youtube.com/watch?v=8PCNNro7Rt0 ,相当于基于这个教程整理的一篇学习笔记。


📕第一步:导入手部模型

我使用的是 Oculus Hand Unity 资源包,里面自带了手部模型的动画。这里提供一个百度云盘的下载链接:

链接:https://pan.baidu.com/s/15Y03XzgMUf7TQO_060zWyg?pwd=1voo
提取码:1voo

还有一个 CSDN 资源的链接:

Oculus Hands VR手部模型Unity资源包 (含有动画)

接下来我会简单分析一下这个资源包,以及手部动画制作的思路。如果你用了其他模型,并且想制作自己的手部动画,那么也可以用类似的思路进行制作。

下载完毕后就是一个 Unity 的 Package,然后我们要在 Unity 中导入这个资源,点击菜单栏的
Assets -> Import Package -> Custom Package,导入成功后可以看到 Unity 中多出了这个文件夹:

注:这个资源包的模型需要用到 URP 渲染管线如果项目是普通的 3D 项目,需要将项目升级为 URP,升级方法可以自行搜索,网上也有很多相关的教程。

然后我们简单地看一下这个资源包, Prefabs 里就是左手和右手的模型:

关键是 Animations 文件夹,这个资源包已经帮我们做好了一些手部的动画,包含了 Animator Controller 和相应的 Animation:

然后我们打开左手的 Animator Controller 看一看,发现里面有一个动画混合树:

控制动画的 Parameter 包含了 float 类型的 Grip 和 Trigger,代表了按下手柄的 Grip 键和 Trigger 键。为什么是 float 类型呢?因为这两个手柄的按键会有一个按下的程度,按下的程度不同,手部的动画也会有所不同。比如轻微按下 Grip 键,手做出微微抓取的姿势;完全按下 Grip 键,手做出紧紧抓握的姿势。

然后打开 Blend Tree ,可以看到这是一个 2D 混合树:

这边就两个重要的动画,一个是捏合(hand_pinch_anim),一个是抓握(hand_first)。我们可以点击下图中的这些动画文件查看具体的动画:

我这边打开一个 l_hand_pinch_anim,具体面板见下图:

第二个关键帧的手部模型姿势如下:

因此上图就是完全按下 Trigger 键后手部应该有的姿势。因为这个资源包里的动画是只读的,所以我们没法对这些动画进行修改。如果对已有的手部动画不满意,或者导入的是其他不带动画的手部模型,我们可以模仿它的思路,自定义手部的姿势。感兴趣的小伙伴可以自己创建手部的 Animation ,在第二个关键帧去修改手部模型各个关节的旋转角度,如下图的这些节点:


那么这个 Oculus Hands 资源包的讲解差不多就到这。我们也可以模仿它的做法去自定义 Animator Controller 和 相应的手部动画。当然也可以不用 2D 混合树去混合动画,因为很多时候我们会用三个按键去控制手的动画(大部分 VR 应用是这样),那么这时候也可以用动画分层的思想,为每个按键分配一个动画层,然后将状态间的切换进行连线。


📕第二步:将手部模型拖入场景

首先依照本系列教程的第一篇配置好场景:

然后把左手和右手的模型分别作为 LeftHand Controller 和 RightHand Controller 的子物体:

在手的 Prefab 中加上对应的 Animator Controller:

这个时候我们可以试着运行一下程序,我们可以发现手部模型已经可以跟随手柄运动了,但是按下手柄的按键后手没有任何反应。因此,我们需要写个脚本去控制 Animator。


📕第三步:编写脚本控制手部动画(版本一:基于 Input System,推荐做法)

我们新建一个脚本,挂载到手部模型上,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class AnimateHandController : MonoBehaviour

    public InputActionProperty pinchActionProperty;
    public InputActionProperty gripActionProperty;
    private InputAction pinchAction;
    private InputAction gripAction;
    private Animator animator;
    // Start is called before the first frame update
    void Start()
    
        pinchAction = pinchActionProperty.action;
        gripAction = gripActionProperty.action;
        animator = GetComponent<Animator>();
    

    // Update is called once per frame
    void Update()
    
        float triggerValue = pinchAction.ReadValue<float>();
        animator.SetFloat("Trigger", triggerValue);

        float gripValue = gripAction.ReadValue<float>();
        animator.SetFloat("Grip", gripValue);
    


注意点:

1)需要 using UnityEngine.InputSystem,因为我们按键绑定的动作已经在默认 Input Action Asset 中配置好了(这个东西是在配置环境的时候创建的),所以会用到 InputSystem 的一些知识。

2)我们需要将 Input Action Asset 中的动作和我们创建的这个脚本进行绑定,分别需要按下 Trigger 键的动作和按下 Grip 键的动作。这里我使用了 InputActionProperty 这个类,既可以引用 Input Action Asset 中已经存在的 Action,也可以自己新建 Action,Inspector 面板里显示如下:

我们直接用已有的 Action,所以勾选 Use Reference:

那么我们需要将什么东西赋给这些 Reference 呢?我们可以打开默认的 Input Action Asset(如下):

这里以左手为例(见下图):

找到 XRI LeftHand Interaction 下的 Select Value 和 Activate Value,可以看到 Select Value 代表了 grip 按键, Activate Value 代表了 trigger 按键。那为什么是选 Value 呢?这和 Animator Controller 中的 Parameter 是一样的道理,我们需要根据按键按下的程度,来决定动画播放的程序。

那么接下来就是找到我们想要的 InputActionReference,右手也是同理。


3)读取手部按键按下的程度使用 InputAction 类的 ReadValue<T>() 这个方法,而使用 InputActionProperty 类中的 action 属性可以获取它的 InputAtion。所以现在,你应该能够看懂上面的这一段代码了吧。

接下来,我们可以运行一下程序,正常来说,这时候手的动画能够成功运行了。但实际上,你应该会感觉此时的效果离我们想象中的还有一些差距,所以我们还需要做一些微调。

📕第三步:编写脚本控制手部动画(版本二:基于 XR Input Subsystem)

版本一的脚本运用了 Input System 的知识。而 XR Input Subsytem 是 Unity 提供的另一种用于处理设备输入的系统,需要运用到 UnityEngine.XR 这个命名空间,以下是基于 XR Input Subsystem 实现的手部动画控制代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;

public class HandPresence : MonoBehaviour

    public InputDeviceCharacteristics controllerCharacteristics;    
    private InputDevice targetDevice;
    private Animator handAnimator;
    
    private void Start()
    
        handAnimator = GetComponent<Animator>();
        TryInitialize();
    


    private void TryInitialize()
    
        List<InputDevice> devices = new List<InputDevice>();

        InputDevices.GetDevicesWithCharacteristics(controllerCharacteristics, devices);
        if (devices.Count > 0)
        
            targetDevice = devices[0];
        
    

    private void UpdateHandAnimation()
    
        if (targetDevice.TryGetFeatureValue(CommonUsages.trigger, out float triggerValue))
        
            handAnimator.SetFloat("Trigger", triggerValue);
        
        else
        
            handAnimator.SetFloat("Trigger", 0);
        

        if (targetDevice.TryGetFeatureValue(CommonUsages.grip, out float gripValue))
        
            handAnimator.SetFloat("Grip", gripValue);
        
        else
        
            handAnimator.SetFloat("Grip", 0);
        
    

    private void Update()
    
        if(!targetDevice.isValid)
        
            TryInitialize();
        
        else
        
            UpdateHandAnimation();
        
    


Unity 的 XR 架构提供了不同厂商的硬件设备和输入之间的映射。虽然不同设备的设计会有些不同,但是可以检测相同的输入的动作,比如不同的手柄都能触发 “trigger”,“grip” 等动作。因此 Unity 只要检测某个输入动作是否发生,就能将这个输入动作映射到不同硬件设备的按键上,比如我按下了 Oculus 手柄的 Grip 按键,就代表输入了 “grip” 这个动作;再比如我转动 Oculus 手柄的摇杆(JoyStick)或者转动 Vive 手柄上的那个圆形触摸板(Touchpad),都可以用 “primary2DAxis” 来表示,得到的是一个 Vector2 类型的坐标,代表摇杆或触摸板位移的程度,这经常用于人物的移动当中。这样做能消除不同硬件设备的差异,和 Input System,OpenXR 的思想是类似的。

下图是 Unity 官方文档的不同厂商设备按键与动作输入的映射表,这些是 Unity 目前支持的设备:

上图中第一列的 InputFeatureUsage 类型的变量都存储在了 CommonUsages 这个静态类中,如图:

那怎么去使用这些变量呢?我们还需要了解一下其他相关的类。

Unity 的 XR 系统用 InputDevice 这个类来表示用于检测输入的硬件设备,用 InputDeviceCharacteristics 这个类来表示硬件设备的一些特征。我们回看版本二的脚本代码,在开头声明了 public InputDeviceCharacteristics controllerCharacteristics; 因此,我们可以到 Unity 编辑器的 Inspector 面板中看看这是什么东西。在此之前,我们要把这个脚本分别挂载到左手和右手的模型 Prefab 上,如图:

可以看到这边有个类似选项框的东西,有很多选项可以选择(可多选):

这些选项就包含了输入设备的一些特征。因为我们要检测手柄的输入,所以左手柄选择 Controller 和 Left,右手柄选择 Controller 和 Right。

约束了特征之后,就能够借助 InputDevices.GetDevicesWithCharacteristics 这个方法筛选出想要检测输入的设备列表。因为一个手部动画的脚本对应一个手柄输入的检测,所以返回列表的第一个元素就能得到对应的 InputDevice 类型的手柄。

然后用 InputDevice 类下的 TryGetFeatureValue 方法就能获取按键输入的值:

targetDevice.TryGetFeatureValue(CommonUsages.trigger, out float triggerValue)
targetDevice.TryGetFeatureValue(CommonUsages.grip, out float gripValue)

接下来的思路就和版本一的代码差不多了,读取到了按键的输入,我们就可以将输入的值用于 Animator。此外,最好在 Update 里判断一下当前的设备是否还有效,如果失去检测了,需要重新初始化检测的设备。

private void Update()
    
        if(!targetDevice.isValid)
        
            TryInitialize();
        
        else
        
            UpdateHandAnimation();
        
    

以上便是版本二的代码讲解,大家可以从两个版本中选择一个自己喜欢的作为手部动画的控制脚本。基于 XR Input Subsystem 的版本在 Inspector 面板中的操作更为方便,而基于 Input System 的版本在动作与输入的绑定上更加灵活。因为 XR Input Subsystem 中的动作与设备的按键是绑死了的,而 Input System 可以自定义动作与哪个按键进行绑定,比如我想按下手柄的 Trigger 键来进行抓握,可以直接在 Input Action Asset 里进行更改。但是 XR Input Subsystem 只允许按下手柄的 Grip 键来进行抓握。


📕第四步:调整场景

这时候,可能会存在这么几个问题,那么我们就来一个一个解决它们。

问题一:
手部模型的旋转角度看起来不大对。模型默认是手背朝上,但是这和现实中我们拿着手柄的角度是不同的,所以这时候的手感是比较奇怪的。

解决方法:
调整手部模型的旋转角度,我这里把左手的 Z Rotation 设为 90,把右手的 Z Rotation 设为 -90。这个时候,模型的角度如下,可以看到手的侧面朝上,这与现实中拿手柄的角度是接近的。

问题二:
VR 里手的位置会和现实世界的手有些偏移,造成使用手感较为奇怪。

解决方法:
调整手部模型的 position。我这边调的 position 如下,大家可以用来参考,测试手部模型和现实中手部的位置的偏移程度。

问题三:
手的阴影显示不正常。看到的是奇怪的形状,而不是手的阴影。

解决方法:
找到 URP 的配置文件,调整 Shadows -> Cascade Count 的值,如图所示:

我这里把值调到了 4,此时阴影显示就稍微正常了些。如果想要影子的效果更精细一点,可以试试调整 Shadows 下的其他数值。


这时候,你也许会疑惑为什么我的手没有发出射线。其实我先把它关闭了,大家可以分别在 LeftHand Controller 和 RightHand Controller 游戏物体找到 XR Ray Interactor 这个组件,然后将它关闭。


那么现在,咱们的 Demo 就大功告成了!快去试一试用手柄控制虚拟世界中的双手吧!😊

以上是关于Unity VR开发教程 OpenXR+XR Interaction Toolkit UI的主要内容,如果未能解决你的问题,请参考以下文章

Unity VR开发教程 OpenXR+XR Interaction Toolkit (六)手与物品交互(触摸抓取)

Unity VR开发教程 OpenXR+XR Interaction Toolkit 手部动画

Unity 使用OpenXR和XR Interaction Toolkit 开发 HTCVive(Vive Cosmos)

Unity开发OpenXR | 使用 OpenXR 制作一款简单VR示例场景 的全过程详细教程,包含两个实战案例。

Unity开发OpenXR |使用 OpenXR 添加一个运动系统,实现传送抓取功能 的简单VR示例场景 的全过程详细教程

Unity开发OpenXR | 使用 OpenXR 制作一款简单VR示例场景 的全过程详细教程,包含两个实战案例。