HTC Vive Pro eye 眼动数据简单获取


前言

本文旨在介绍如何使用HTC Vive Pro eye,结合Unity,读取到眼动的数据。假设文章内容面向的是此前从未接触过unity以及刚上手HTC Vive Pro eye的同学(这也是我对自己的个人定位),文章会尽量写的详尽一些,如果阅读内容已经掌握,可以选择跳过。
另外,本文仅对一些不方便搜索得知的内容进行说明,如有疑问可以自行Google或者百度。
最终成果为一个简单的Demo,可以在unity的Log栏中看到输出的信息。如下图所示,右下角的信息栏中可看到读取到的眼动数据。
右下角的信息栏中可看到读取到的眼动数据
另外,本文支持转载,转载时请注明作者与出处。希望大家在研究的过程中可以收获到分享与交流的快乐。
如发现错误,请通过评论或是私信的方式批评指正。

感谢b站的大佬邓布利多军,在个人空间中分享过几例测试demo,本文也是在其耐心的指导下完成的。由衷的表示感谢。


一、unity软件的安装

unity的官方网址
选择红框中的“Get Started”
在这里插入图片描述

在进入的页面中,以学生身份或是个人身份,进行unity账户的注册(此处略过)。之后运行unity客户端时,需要个人身份验证才可以正常使用。
在这里插入图片描述
unity 下载界面
在这里插入图片描述
推荐使用unity hub,即unity官方提供的管理平台进行不同版本的客户端的安装。

选择想要下载的unity版本进行下载安装即可(至于安装过程中工具包的具体选择,视个人需要,笔者是在windows平台,所以选择了与windows相关的工具包)。

以下为几点个人在过程中遇到的问题:
1、通常在安装unity各版本的客户端时,由于网络问题,“Document”经常下载失败或是下载进度为零,可以直接选择不进行下载。
2、如要使用其他人的unity项目,请注意版本的问题。通常若两个项目的unity版本不一致,会发生错误。

新建项目时,选择3D项目即可。
在这里插入图片描述



二、OpenXR与SteamVR插件的安装

在新建的项目中,选择“window”,在子菜单栏中选择Package Manager
在这里插入图片描述
在Unity Registry中选择OpenVR Plugin,点击右下方的install即可。
在这里插入图片描述
有两种方式安装SteamVR。一是通过网页
unity store 搜索 steam vr 后的结果,选择红框对应的插件,选择在unity中打开即可。
在这里插入图片描述
在这里插入图片描述
第二种方法近似,即在菜单栏的windows中选择Assert Store,步骤与一近似。

下载好steamVR插件后, 需要在package manager中的My Asserts中选择进行import。
在这里插入图片描述
到这里,基本的工作就已经做好了。
可以在steam VR官方提供的场景中进行交互测试。
在这里插入图片描述
在这里插入图片描述

三、SRanipaRuntime SDK的安装与使用

HTC Vive 官方提供了SDK
安装网址
两个SDK都需要安装,根据步骤一步一步来即可。
在这里插入图片描述
安装好两个SDK后,可以在安装目录中,看到如此的两个文件夹。
在这里插入图片描述
在SDK文件夹下存放着说明文档,C、Unity、虚幻环境的demo。
在这里插入图片描述

此处提供一个方便查阅SDK 中 各API的网址,也是官方提供的,在下图所示路径下。
在这里插入图片描述
在这里插入图片描述
而在SRainipal文件夹中,则存放着本次实验的关键应用。具体使用方法请阅读SDK中提供的Eye_SRanipal_SDK_Guide。
在这里插入图片描述

在如图所示的路径下,可以找到HTC官方提供的demo package。在unity中选择该package file,进行import。
在这里插入图片描述

导入后,可以在Project中看到如图所示红框对应的文件夹ViceSR,选择Scenes中的Eye,双击EyeSample_v2。
在这里插入图片描述
之后则可以在Hierarchy中看到如此的结构。
在这里插入图片描述
笔者此处在Gaze Ray Sample_v2和Avatar_Fairy_V2组件的Inspector进行了勾选。unity通过红框中的对勾即可以实现各组件的随意添加与移除。
在这里插入图片描述

读取数据

在Gaze Ray Sample v2组件中,挂载了当前脚本。在这里插入图片描述

在该脚本中,修改代码,结果为下面所示。
修改后,需要启动SRanipalRuntime,同时启动SteamVR,使得HMD正常运行。点击unity中的play,即可看到最终结果。

//========= Copyright 2018, HTC Corporation. All rights reserved. ===========
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Assertions;

namespace ViveSR
{
    namespace anipal
    {
        namespace Eye
        {
            public class SRanipal_GazeRaySample_v2 : MonoBehaviour
            {
                public int LengthOfRay = 25;
                [SerializeField] private LineRenderer GazeRayRenderer;
                private static EyeData_v2 eyeData = new EyeData_v2();
                private bool eye_callback_registered = false;
                //此处为增加的变量
                private float pupilDiameterLeft, pupilDiameterRight;
                private Vector2 pupilPositionLeft, pupilPositionRight;
                private float eyeOpenLeft, eyeOpenRight;
                private void Start()
                {
                    if (!SRanipal_Eye_Framework.Instance.EnableEye)
                    {
                        enabled = false;
                        return;
                    }
                    Assert.IsNotNull(GazeRayRenderer);
                }

                private void Update()
                {
                    if (SRanipal_Eye_Framework.Status != SRanipal_Eye_Framework.FrameworkStatus.WORKING &&
                        SRanipal_Eye_Framework.Status != SRanipal_Eye_Framework.FrameworkStatus.NOT_SUPPORT) return;

                    if (SRanipal_Eye_Framework.Instance.EnableEyeDataCallback == true && eye_callback_registered == false)
                    {
                        SRanipal_Eye_v2.WrapperRegisterEyeDataCallback(Marshal.GetFunctionPointerForDelegate((SRanipal_Eye_v2.CallbackBasic)EyeCallback));
                        eye_callback_registered = true;
                    }
                    else if (SRanipal_Eye_Framework.Instance.EnableEyeDataCallback == false && eye_callback_registered == true)
                    {
                        SRanipal_Eye_v2.WrapperUnRegisterEyeDataCallback(Marshal.GetFunctionPointerForDelegate((SRanipal_Eye_v2.CallbackBasic)EyeCallback));
                        eye_callback_registered = false;
                    }

                    Vector3 GazeOriginCombinedLocal, GazeDirectionCombinedLocal;

                    if (eye_callback_registered)
                    {
                        if (SRanipal_Eye_v2.GetGazeRay(GazeIndex.COMBINE, out GazeOriginCombinedLocal, out GazeDirectionCombinedLocal, eyeData)) { }
                        else if (SRanipal_Eye_v2.GetGazeRay(GazeIndex.LEFT, out GazeOriginCombinedLocal, out GazeDirectionCombinedLocal, eyeData)) { }
                        else if (SRanipal_Eye_v2.GetGazeRay(GazeIndex.RIGHT, out GazeOriginCombinedLocal, out GazeDirectionCombinedLocal, eyeData)) { }
                        else return;
                    }
                    else
                    {
                        if (SRanipal_Eye_v2.GetGazeRay(GazeIndex.COMBINE, out GazeOriginCombinedLocal, out GazeDirectionCombinedLocal)) { }
                        else if (SRanipal_Eye_v2.GetGazeRay(GazeIndex.LEFT, out GazeOriginCombinedLocal, out GazeDirectionCombinedLocal)) { }
                        else if (SRanipal_Eye_v2.GetGazeRay(GazeIndex.RIGHT, out GazeOriginCombinedLocal, out GazeDirectionCombinedLocal)) { }
                        else return;
                    }

                    Vector3 GazeDirectionCombined = Camera.main.transform.TransformDirection(GazeDirectionCombinedLocal);
                    GazeRayRenderer.SetPosition(0, Camera.main.transform.position - Camera.main.transform.up * 0.05f);
                    GazeRayRenderer.SetPosition(1, Camera.main.transform.position + GazeDirectionCombined * LengthOfRay);
                    //以下为新增的部分
					//pupil diameter 瞳孔的直径
                    pupilDiameterLeft = eyeData.verbose_data.left.pupil_diameter_mm;
                    pupilDiameterRight = eyeData.verbose_data.right.pupil_diameter_mm;

                    //pupil positions 瞳孔位置
                    //pupil_position_in_sensor_area手册里写的是The normalized position of a pupil in [0,1],给坐标归一化了
                    pupilPositionLeft = eyeData.verbose_data.left.pupil_position_in_sensor_area;
                    pupilPositionRight = eyeData.verbose_data.right.pupil_position_in_sensor_area;

                    //eye open 睁眼
                    //eye_openness手册里写的是A value representing how open the eye is,也就是睁眼程度,从输出来看是在0-1之间,也归一化了
                    eyeOpenLeft = eyeData.verbose_data.left.eye_openness;
                    eyeOpenRight = eyeData.verbose_data.right.eye_openness;

                    Debug.Log("左眼瞳孔直径:" + pupilDiameterLeft + " 左眼位置坐标:" + pupilPositionLeft + "左眼睁眼程度" + eyeOpenLeft);
                    Debug.Log("右眼瞳孔直径:" + pupilDiameterRight + " 右眼位置坐标:" + pupilPositionRight + " 左眼睁眼程度" + eyeOpenRight);

                }
                private void Release()
                {
                    if (eye_callback_registered == true)
                    {
                        SRanipal_Eye_v2.WrapperUnRegisterEyeDataCallback(Marshal.GetFunctionPointerForDelegate((SRanipal_Eye_v2.CallbackBasic)EyeCallback));
                        eye_callback_registered = false;
                    }
                }
                private static void EyeCallback(ref EyeData_v2 eye_data)
                {
                    eyeData = eye_data;
                }
            }
        }
    }
}

该方法有些取巧,前文提到的“邓布利多军”是通过新建脚本实现的该功能。笔者也在进一步的尝试中。

总结

笔者从一头雾水,到如今简单的实现了该功能,要感谢官方论坛中许多位前辈,以及官方技术人员的技术支持。同时也要感谢众多曾在网络上分享技术,发表见解的前辈们。

最后,再次感谢“邓布利多军”。希望有更多未来的开发者们,加入技术分享的大家庭中。

  • 22
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 52
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 52
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值