LEAPMotion猎户座(VR)开发~001~


LEAPMotion猎户座(VR)开发~001~

Created by miccall (转载请注明出处 miccall.tech)

Leap Motion 介绍

  • ( 转载自我们老大的博客 - 和- 他的csdn)

    引用:

    (当然,我是给他打过招呼的)
  • 首先,关于leapmotion 我想会看此类文章的应该都知道这是什么,无关将来这个产品的发展会如何,它作为虚拟现实或现实增强开发的练手是非常合适的。因为他有着非常舒适的开发环境,简单而成熟的SDK。最最重要的是结合unity/unreal,更是一种让你能在短时间之内通过引擎完成高质量手势识别应用的方法。

  • 关于原理,和底层算法,因为代码封装完善,你根本不可能窥探到什么(当然肯定有人可以)。所以我的评价仅仅从实际效果出发先对leapmotion做出一个评价。

  • 下图为leapmotion传感器的识别区域:
    这里写图片描述

  • 我们可以看到在y轴也就是高度上 leapmotion的识别距离真的是小的可怜(根据我实际测试不会超过50cm)也就是说如果不和其他硬件搭配
    leapmotion的使用范围很窄

  • 单单的桌面使用的话,基本没有什么实用价值原因有二:

  1. 桌面使用场景下,手势识别很累低效

  2. leap仅仅依靠景深画面和算法得出的手势识别精度高,但是精确度差。也就是说可以识别你手势动作很微小的变化,但是对这些变化的呈现又表现出很大的偏差。

  • 在和oculus dk2结合之后,leapmotion的实用性总算提高了一些,但是效果仍然不理想。

  • 因此,后续leap在不推出新硬件的条件下,我十分不看好这个传感器的发展,未来的leap,要么被OVR等厂家收购,要么倒闭。

  • ok 有点跑题,绕回来。虽然他这不好那不好,但这是我们在市场上能获取到的最优秀的性价比最高的手势识别设备。以他和oculus来学习虚拟现实开发,是最合适不过的了

  • 那废话说够了,开始吧

Leap 版本 :

  • 当然 我是希望你们看完他的所有关于leap的介绍再来看我的 ,毕竟我也是看他的博客 一步步学来的 。
  • 但是呢 ,他的leap 版本是 v2 , leap最近用于 VR 的 版本是 猎户座。
  • 之前的版本我就不介绍了 ,我们从 orion 这个版本开始 。
    (请一定看完老大的博客再来看我的 ,不然初学者会看不懂我讲的是啥)

    Leap Orion 正文(源码分析篇 较难 初学者可以先绕过去看下一篇文章) :

  • 打开unity 3d 导入4.1的leap资产包 。找一个官方场景打开看看
  • 猎户座取消了HandController 获取 Frame的信息 ,而是在 LeapHandController上添加了其他两个脚本。。
    这里写图片描述

    第一个脚本LeapHandController

  • 我们一个个看一看 ,第一个脚本LeapHandController 有些面熟~~
    v2版本的leap使用的是HandController 这次在前面加了一个Leap 感觉怪怪的
    来看看里面有些什么鬼 :
    打开这个脚本
  • 1 . 它定义了两个脚本的实例 分别是LeapProvider和HandFactory

        protected LeapProvider  provider = requireComponent();
        protected HandFactory    factory = requireComponent();
    
  • 2 . 然后就是两个字典列表 一个图形手的列表(graphicsReps),一个物理手的列表(physicsReps)

    protected Dictionary graphicsReps = 
      new Dictionary();
    protected Dictionary physicsReps = 
      new Dictionary();
    
  • 3 . GIZMO_SCALE 从拇指底至小指底的参考距离,单位为mm 。
    protected const float GIZMO_SCALE = 5.0f;
    
  • 4 . 图形手和物理手模型的开关状态 并且写了一个方法来获取他们的bool值

    protected bool graphicsEnabled = true;
    protected bool physicsEnabled = true;
    
    public bool GraphicsEnabled {
      get {
        return graphicsEnabled;
      }
      set {
        graphicsEnabled = value;
      }
    }
    
    public bool PhysicsEnabled {
      get {
        return physicsEnabled;
      }
      set {
        physicsEnabled = value;
      }
    }
    
  • 5 . 重要的来了 : OnEnable 和 OnDisable

  • 执行回调的时候 provider维持了一个frame帧的事件 。
    当OnEnable的时候,向providr的frame中,添加OnUpdateFrame事件和OnFixedFrame事件。他们在不同的循环中 ,我们知道unity的update()和fixedupdate().他们也维持在不同的循环之中,就是这个道理。

    相反的,当OnDisable时,又从provider里面的frame移除这个事件。

    protected virtual void OnEnable() {
      provider = requireComponent();
      factory = requireComponent();
    
      provider.OnUpdateFrame += OnUpdateFrame;
      provider.OnFixedFrame += OnFixedFrame;
    }
    protected virtual void OnDisable() {
      provider.OnUpdateFrame -= OnUpdateFrame;
      provider.OnFixedFrame -= OnFixedFrame;
    }
    
  • 那么 ,这个事件时干什么的呢 ?

    他们在一定条件下执行一个UpdatesHandRepresentations()方法 
    这个方法基于指定的HandRepresentation字典。如果它们所表示的手仍存在于provider的currentFrame(当前帧)中,则更新活动HandRepresentation实例;否则,HandRepresentation被移除。如果新的Leap Hand对象存在于Leap HandRepresentation
    的Dictionary中,则新的HandRepresentations创建并添加到字典。
    
      /** Updates the graphics HandRepresentations. */
      protected virtual void OnUpdateFrame(Frame frame) {
        if (frame != null && graphicsEnabled) {
          UpdateHandRepresentations(graphicsReps, ModelType.Graphics, frame);
        }
      }
    
      /** Updates the physics HandRepresentations. */
      protected virtual void OnFixedFrame(Frame frame) {
        if (frame != null && physicsEnabled) {
          UpdateHandRepresentations(physicsReps, ModelType.Physics, frame);
        }
      }
    
  • 而在frame对象里面 存放的并不是手的实例 而是hand的id
    根据hand的id,由factory对象来分配到这个可以从LeapHandController接收更新的手表示对象。一一存放在HandRepresentation组成的字典当中。
    HandRepresentation rep;
    rep = factory.MakeHandRepresentation(curHand, modelType);
            if (rep != null) {
              all_hand_reps.Add(curHand.Id, rep);
    }
    
  • 估计有人会问 HandRepresentation 对象到底是个啥 ,其实也很简单,他只是由一些基本信息组成的抽象类 。包含{handid 上一次更新时间,是否存放在字典中等等 }。

  • 最后呢 我大概总结成一句话
    LeapHandController 通过使用 Factory 去创建且基于Provider收到的帧数据去 更新手部模型

  • 这样就很清楚了 我们要得到手的数据 就可以从 provider 中获取当前帧 然后从Frame帧对象中得到想要的手的数据了 .

第二个脚本LeapServiceProvider

第二个脚本主要涉及到与硬件的链接~
也许也是我们将来用到最多的一个关联脚本了 。
同样我们打开它 来看看他有写什么

  • 先来看看他的属性 ,其实也没啥用 贴出来让大家偶一个直观的认识
    [Tooltip("如果Leap Motion硬件安装在头戴设备上,则设置为true; 否则,设置为false")]
      [SerializeField]
      protected bool _isHeadMounted = false;
      [AutoFind]
      [SerializeField]
      protected LeapVRTemporalWarping _temporalWarping;
      [Tooltip("当为true时,更新帧将重新用于物理。这是一种优化, 因为需要计算的帧的总数减半 " +
               "然而,这会在物理帧中引入额外的延迟和不准确性。 " +
               " ")]
      [SerializeField]
      protected bool _reuseFramesForPhysics = false;
      [Header("Device Type")]
      [SerializeField]
      protected bool _overrideDeviceType = false;
      [Tooltip("如果启用了覆盖设备类型,手动控制器将返回此类型的设备.")]
      [SerializeField]
      protected LeapDeviceType _overrideDeviceTypeWith = LeapDeviceType.Peripheral;
      [Header("Interpolation")]
      [Tooltip("插值帧以提供更平滑的体验。 目前试验.")]
      [SerializeField]
      protected bool _useInterpolation = false;
      [Tooltip("应该将多少延迟添加到插值。 需要非零量来防止外推伪影.")]
      [SerializeField]
      protected long _interpolationDelay = 15;
    
  • 其次就是方法了 首先 最重要的一个方法 CurrentFrame(); 它提供了一个get方法来获取当前的Frame对象 。
    ```c#
    public override Frame CurrentFrame {
    get {
      return _transformedUpdateFrame;
    }
    
    }

public override Frame CurrentFixedFrame {
get {
if (_reuseFramesForPhysics) {
return _transformedUpdateFrame;
} else {
return _transformedFixedFrame;
}
}
}

- 是否使用插值 和设置参数 
```c#
public bool UseInterpolation {
      get {
        return _useInterpolation;
      }
      set {
        _useInterpolation = value;
      }
    }
public long InterpolationDelay {
      get {
        return _interpolationDelay;
      }
      set {
        _interpolationDelay = value;
      }
}
  • 获取当前的Controller实例
      /** Returns the Leap Controller instance. */
      public Controller GetLeapController() {
    #if UNITY_EDITOR
        //Null check to deal with hot reloading
        if (leap_controller_ == null) {
          createController();
        }
    #endif
        return leap_controller_;
      }
    
  • 还有一些项目必须要的重要方法
  • 判断是否链接
      /** True, if the Leap Motion hardware is plugged in and this application is connected to the Leap Motion service. */
      public bool IsConnected() {
        return GetLeapController().IsConnected;
      }
    
  • 获取设备信息
     /** Returns information describing the device hardware. */
     public LeapDeviceInfo GetDeviceInfo() {
       if (_overrideDeviceType) {
         return new LeapDeviceInfo(_overrideDeviceTypeWith);
       }
    
  • 刷新所有的Frame

    public void ReTransformFrames() {
        transformFrame(_untransformedUpdateFrame, _transformedUpdateFrame);
        transformFrame(_untransformedFixedFrame, _transformedFixedFrame);
    }
    
  • 很显然 ,这些方法都是public的 我们在自己的脚本那就可以直接调出来用了 。。。

    最后一个脚本Hand pool

    顾名思义 猜猜是干什么的 作为一个程序员 我看到名字大概可以猜到他是干什么的了

  • HandPool拥有一个IHandModels池,并使HandProxys

    当给出一个Hand和模型类型的图形模型或物理模型。
    当创建一个HandProxy时,从池中删除一个IHandModel。
    当HandProxy完成后,其IHandModel返回到池中。
    
  • 官方解释跟屎一样 简单一点说 ,这个就是管理手的碰撞器和模型网格
  • 这里我不细讲这个池的原理了 算法复杂非常人理解 ,与其研究这个,不如去看看更实用的线程池的原理 。
    不过呢 还是简单说一下怎么用吧
  • ModelsParent 一个Transform 它的子物体存放手的模型和碰撞器 这个我们以后还会细说到 工程要求我改变模型 。
  • Hand Pool
    size里面写数量 然后把ModelsParent下面的模型按要求拖进去就ok啦
    先讲这么多啦 。看第二篇吧