《HTC_VIVE开发基础》

版本

作者

参与者

完成日期

备注

HTC_VIVE_V01_1.0

严立钻

 

2018.08.23

 

 

 

 

 

 

 

 

 

HTC_VIVE开发基础》发布说明:

++++“HTC_VIVE开发基础”是2018年最重要的一个技术突破点,VR虚拟现实将改变人们的生活,以后VR内容开发将成为一个爆发点,期待,期待,期待!

++++“HTC_VIVE开发基础”:定位是一个入门级的基础知识,通过这篇博文,带大家进入一个魅力爆棚的VR世界;

 

 

 

##《HTC_VIVE开发基础》目录

#第一篇:快速入门

#第二篇:基础夯实

#第三篇:中级进阶

#第四篇:高级成魔

vr应用开发需要什么架构 vr开发基础_VIVE

 

 

 

 

 

 

 

#第一篇:快速入门篇

#第一篇:快速入门篇

#第一篇:快速入门篇

++++A.1、VR行业介绍

++++A.2、SteamVR开发环境配置

++++A.3、SteamVR Plugins简单使用

++++A.4、SteamVR简单案例开发

++++A.5、Teleport系统

++++A.6、Vive与UI交互

++++A.7、Vive Controller Haptic

++++A.8、案例分析

++++A.9、立钻哥哥带您Vive快速入门

vr应用开发需要什么架构 vr开发基础_Unity_02

 

 

 

 

 

 

##A.1、VR行业介绍

##A.1、VR行业介绍

++A.1、VR行业介绍

++++A.1.1、VR概念

++++A.1.2、VR行业兴起及主流硬件

++++A.1.3、VR主流应用商店及领域

 

 

###A.1.1、VR概念

###A.1.1、VR概念

++A.1.1、VR概念

++++立钻哥哥:虚拟现实技术是一种可以创建和体验虚拟世界的计算机仿真系统,它利用计算机生成一种模拟环境,是一种多源信息融合的、交互式的三维动态视景和实体行为的系统仿真,使用户沉浸到该环境中

 

 

 

###A.1.2、VR行业兴起及主流硬件

###A.1.2、VR行业兴起及主流硬件

++A.1.2、VR行业兴起及主流硬件

++++立钻哥哥:

++PC端

++++2014年Facebook收购Oculus,总交易额约为20亿美金,其中包括4亿美元现金;

++++Oculus先后推出DK1、DK2、CV1;

++++SteamVR联合HTC发布HTC VIVE,是目前效果体验最好,开发者最友好的设备;

++++PS推出基于PlayStation的PSVR;

++++Microsoft联合各大OEM推出内置追踪系统的MixedReality头盔;

vr应用开发需要什么架构 vr开发基础_Unity_03

 

++移动端

++++2014年Google发布廉价、简易的VR设备Cardboard;

++++Sumsung发布GearVR;

++++Google发布Tango设备(Motion Tracking);

++++SamSung发布GearVR2(With Controller);

++++HTC发布移动端VR一体机

++++Google发布Daydream平台;

vr应用开发需要什么架构 vr开发基础_VIVE_04

 

 

 

###A.1.3、VR主流应用商店及领域

###A.1.3、VR主流应用商店及领域

++A.1.3、VR主流应用商店及领域

++++立钻哥哥:主流商店:Viveport、Oculus Store、SteamVE

vr应用开发需要什么架构 vr开发基础_VIVE_05

 

++应用领域

++++娱乐、游戏;(RoboRecall:https://www.epicgames.com/roborecall/en-US/home#recall

++++StoryTelling;(Henry)

++++教育;(CalcFlow:https://store.steampowered.com/app/547280/Calcflow/

++++模拟;(The Body VR)

++++Creativity;(TiltBrush:https://store.steampowered.com/app/327140/Tilt_Brush/

 

 

 

 

 

 

##A.2、SteamVR开发环境配置

##A.2、SteamVR开发环境配置

++A.2、SteamVR开发环境配置

++++A.2.1、硬件基本配置

++++A.2.2、ViveSetUp

++++A.2.3、Unity开发环境配置

 

 

###A.2.1、硬件基本配置

###A.2.1、硬件基本配置

++A.2.1、硬件基本配置

++++立钻哥哥:

vr应用开发需要什么架构 vr开发基础_VIVE_06

 

 

 

###A.2.2、Vive SetUp

###A.2.2、Vive SetUp

++A.2.2、Vive SetUp

++++立钻哥哥:刚入手的Vive需要:Vive设置教程、Viveport、ViveHome、SteamVR、测试及体验等步骤

 

++Vive设置教程

++++安装软件

++++硬件连线

++++Room SetUp

++++测试

 

++Viveport

 

++ViveHome

++++立钻哥哥:测试程序或默认启动程序

 

++SteamVR

vr应用开发需要什么架构 vr开发基础_Unity_07

 

++测试及体验

++++立钻哥哥:通过任意一款VR应用,让我们来体验一下

 

 

 

 

###A.2.3、Unity开发环境配置

###A.2.3、Unity开发环境配置

++A.2.3、Unity开发环境配置

++++立钻哥哥:借助SteamVR开发的插件才能与VIVE硬件进行沟通(如监听硬件按钮,捕捉硬件位移和旋转等)

++++步骤:打开AssetStore,搜索SteamVR,找到SteamVR Plugin下载并导入到Unity中;(SteamVR:https://assetstore.unity.com/packages/templates/systems/steamvr-plugin-32647

++++测试:借助Scene过滤器,运行官方自带的案例,查看运行是否正确;

vr应用开发需要什么架构 vr开发基础_Unity_08

 

 

 

 

 

 

 

##A.3、SteamVR Plugins简单使用

##A.3、SteamVR Plugins简单使用

++A.3、SteamVR Plugins简单使用

++++A.3.1、目录简介

++++A.3.2、Prefab简介

++++A.3.3、主要作用

++++A.3.4、Simple Test Scene

++++A.3.5、Unity5.6的一个bug

 

 

 

###A.3.1、目录简介

###A.3.1、目录简介

++A.3.1、目录简介

++++立钻哥哥:主要有Plugins和SteamVR两个目录

++++SteamVR主要存储openvr为Unity提供的接口,包括EditorPrefabScript等相关文件;

vr应用开发需要什么架构 vr开发基础_VIVE_09

 

++Plugins主要存储openvr作用到的各个平台的库文件(.so)

++++A、x86存储32位平台库;x86_64储存64位平台库;

++++B、openvr_api.cs用于定义C#端的部分接口;

++++C、openvr_api.bundle用于存储Mac平台对应的库文件;

vr应用开发需要什么架构 vr开发基础_HTC_10

 

 

 

 

###A.3.2、Prefab简介

###A.3.2、Prefab简介

++A.3.2、Prefab简介

++++立钻哥哥:Prefab常用:CameraRig和SteamVR

++++[CameraRig]:提供访问HeadSetController的能力;

++++[SteamVR]:处理SteamVR的渲染等底层;

vr应用开发需要什么架构 vr开发基础_vr应用开发需要什么架构_11

 

 

 

 

###A.3.3、Plugins主要作用

###A.3.3、Plugins主要作用

++A.3.3、Plugins主要作用

++++立钻哥哥:

++++方便快速的开发应用;

++++当调出System菜单时SteamVR会暂停游戏并且会同步物理和渲染系统;

++++提供访问底层硬件的接口(TouchPad的数值等),而不用关心底层代码实现;

++++处理Room-Scale中相机和手柄的平滑移动;

 

 

 

 

###A.3.4、Simple Test Scene

###A.3.4、Simple Test Scene

++A.3.4、Simple Test Scene

++++立钻哥哥:创建一个简单的SteamVR场景并测试

 

++目的

++++立钻哥哥:创建最基本的VR场景,可以应用于场景漫游类的应用

 

++步骤

++++1、删除默认的Camera

++++2、添加CameraRigSteamVR预制体

 

++测试

++++立钻哥哥:运行场景,查看是否追踪了HeadSetController

 

 

 

 

 

###A.3.5、Unity5.6的一个bug

###A.3.5、Unity5.6的一个bug

++A.3.5、Unity5.6的一个bug

++++立钻哥哥:

a bug introduced in Unity5.6 where the controller wouldn’t be tracked

++++With Camera(eye) still selected, add a Steam VR_Update Poses component to it;

 

 

 

 

 

 

 

 

##A.4、SteamVR简单案例开发

##A.4、SteamVR简单案例开发

++A.4、SteamVR简单案例开发

++++A.4.1、简单输入控制

++++A.4.2、高级输入控制

 

 

###A.4.1、简单输入控制

###A.4.1、简单输入控制

++A.4.1、简单输入控制

++++立钻哥哥:简单输入控制:向计算机输入数据和信息的设备;是计算机与用户或其他设备通信的桥梁

++++输入设备是用户和计算机系统之间进行信息交换的主要装置之一;

++++键盘,鼠标,摄像头,扫描仪,光笔,手写输入板,游戏杆,语音输入装置等都属于输入设备;

++++输入设备(InputDevice):是人或外部与计算机进行交互的一种装置,用于把原始数据和处理这些数的程序输入到计算机中;

++++计算机能够接收各种各样的数据,既可以是数值型的数据,也可以是各种非数值型的数据,如图形、图像、声音等都可以通过不同类型的输入设备输入到计算机中,进行存储、处理和输出;

 

++输入类型

++++立钻哥哥:LightHouse、HeadSet、Vive Controller

vr应用开发需要什么架构 vr开发基础_Yanlz_12

 

++跟踪原理浅析

vr应用开发需要什么架构 vr开发基础_HTC_13

 

++应用场景

++++立钻哥哥:自从Controller出现之后,几乎所有的VR设备都离不开这种控制器,而且这种控制器在应用中的作用也五花八门

++++[画笔]:TiltBrush;

++++[Hand]

++++[工具和武器]

++++[导航设备]

 

++Unity中实现

++++立钻哥哥:通过SteamVR_Controller.Input((int)SteamVR_TrackedObject);获取SteamVR_Controller.Device获取手柄设备

SteamVR_Controller.Input((int)SteamVR_TrackedObject);

SteamVR_Controller.Device;

++++获取输入的方法:

Device.GetAxis();

Device.GetHairTrigger();

Device.GetPressDown(SteamVR_Controller.ButtonMask);

 

++示例代码参考

using System.Collections;
using System.Collectons.Generic;
using UnityEngine;
 
public class ViveInputTest : MonoBehaviour{
    private SteamVR_TrackedObject trackedObject;
 
    private SteamVR_Controller.Device controller{
        get{
            return SteamVR_Controller.Input((int)trackedObject.index);
        }
    }
 
    private void Start(){
trackedObject = GetComponent<SteamVR_TrackedObject>();
    }
 
    void Update(){
        if(controller.GetAxis() != Vector2.zero){
 Debug.Log(“立钻哥哥:TouchPad Pressed: ” + controller.GetAxis());
        }
 
        if(controller.GetHairTriggerDown()){
Debug.Log(“立钻哥哥:Trigger Down.”);
 
         //测试代码
            Renderer[] renderers = GetComponentsInChildren<Renderer>();
            foreach(Renderer render in renderers){
render.enabled = true;
            }
        }
 
        if(controller.GetHairTriggerUp()){
 Debug.Log(“Trigger Up”);
 
 //测试代码
            Renderer[] renderers = GetComponentsInChildren<Renderer>();
            foreach(Renderer render in renderers){
render.enabled = false;
            }
        }
 
        //获取非预设键(也可以自己封装成上述形式)
        if(controller.GetPressDown(SteamVR_Controller.ButtonMask.Grip)){
 Debug.Log(“立钻哥哥:Grip Down.”);
        }
 
    }    //立钻哥哥:void Update(){}
 
}    //立钻哥哥:public class ViveInputTest:MonoBehaviour{}

 

 

 

 

 

###A.4.2、高级输入控制

###A.4.2、高级输入控制

++A.4.2、高级输入控制

++++立钻哥哥:高级输入控制我们从“抓取物体”和“发射射线”来分析突破

++++[抓取物体]:在VR中当手柄靠近物体时,可以通过某些操作(如按下Trigger),可以将物体“抓”起来,让物体跟随手柄移动和旋转;

++++[发射射线]:当玩家按下TouchPad时会从手柄当前位置朝前发射一条射线;

 

++A.4.2.1、抓取物体

++++立钻哥哥:在VR中当手柄靠近物体时,可以通过某些操作(如按下Trigger),可以将物体“抓”起来,让物体跟随手柄移动和旋转;(并且当执行某些操作(如松开Trigger)时可“放开”物体,不跟随手柄移动和旋转)

++++抓取类型:带物理属性和不带物理属性;

 

++++[带物理属性]:松开物体后,物体会有物理行为(如下落或者被扔出来);

--借助FixedJoint对物体进行物理约束,之后[Break the Joint]即可,扔出去的时候给物体添加力;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class MyGrabObject : MonoBehaviour{
    private SteamVR_TrackedObject trackedObj;
    private GameObject collidingObject;    //碰撞器碰撞到的物体
    private GameObject objectInHand;    //手中抓到的物体
 
    //获取控制器
    private SteamVR_Controller.Device Controller{
        get{
            return SteamVR_Controller.Input((int)trackedObj.index);
        }
    }    //立钻哥哥:private SteamVR_Controller.Device Controller{}
 
    void Awake(){
trackedObj = GetComponent<SteamVR_TrackedObject>();
    }
 
    #region 更新CollidingObject
    public void OnTriggerEnter(Collider other){
        SetCollidingObject(other);
    }
 
    public void OnTriggerExit(Collider other){
        if(!collidingObject){
            return;
        }
 
        collidingObject = null;
    }
    #endregion
 
    void Update(){
        if(Controller.GetHairTriggerDown()){
            if(collidingObject){
                GrabObject();
            }
        }
 
        if(Controller.GetHairTriggerUp()){
            if(objectInHand){
                ReleaseObject();
            }
        }
    }    //立钻哥哥:void Update(){}
 
    private void SetCollidingObject(Collider col){
        if(collidingObject || !col.GetComponent<Rigidbody>){
            return;
        }
 
        collidingObject = col.gameObject;
    }    //立钻哥哥:private void SetCollidingObject(){}
 
    private void GrabObject(){
objectInHand = collidingObject;
collidingObject = null;
 
        var joint = AddFixedJoint();
joint.connectedBody = objectInHand.GetComponent<Rigidbody>();
    }    //立钻哥哥:private void GrabObject(){}
 
    private FixedJoint AddFixedJoint(){
        FixedJoint fx = gameObject.AddComponent<FixedJoint>();
fx.breakForce = 20000;
fx.breakTorque = 20000;
 
        return fx;
    }    //立钻哥哥:private FixedJoint AddFixedJoint(){}
 
    private void ReleaseObject(){
        if(GetComponent<FixedJoint>){
            GetComponent<FixedJoint>().connectedBody = null;
            Destroy(GetComponent<FixedJoint>);
 
      //如果物体受重力影响则扔出去,否则停留在当前位置
            ThrowObjectWithCondition();
        }
 
        objectInHand = null;
    }    //立钻哥哥:private void ReleaseObject(){}
 
    //根据物体是否受重力影响来判断物体是否会飞出去
    void ThrowObjectWithCondition(){
        Rigidbody handObjectRigidbody = objectInHand.GetComponent<Rigidbody>();
    
        if(handObjectRigidbody.useGravity){
objectInHand.GetComponent<Rigidbody>().velocity = Controller.velocigy;
objectInHand.GetComponent<Rigidbody>().angularVelocity = Controller.angularVelocity;
        }
    }    //立钻哥哥:void ThrowObjectWithCondition(){}
 
}    //立钻哥哥:public class MyGrabObject:MonoBehaviour{}

 

++++[不带物理属性]:松开物体后,物体停留在被松开的位置,不会有任何物理行为;

--借助FixedJoint对物体进行物理约束,之后[Break the Joint]即可,扔出去的时候不给物体添加了;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class MyGrabObject : MonoBehaviour{
    private SteamVR_TrackedObject trackedObj;
    private GameObject objectInHand;    //碰撞器碰撞到的物体
    private GameObject objectInHand;    //手中抓到的物体
 
    //获取控制器
    private SteamVR_Controller.Device Controller{
        get{    return SteamVR_Controller.Input((int)trackedObj.index);  }
    }
 
    void Awake(){
trackedObj = GetComponent<SteamVR_TrackedObject>();
    }
 
    #region 更新CollidingObject
    public void OnTriggerEnter(Collider other){
        SetCollidingObject(other);
    }
 
    public void OnTriggerExit(Collider other){
        if(!collidingObject){
            return;
        }
 
        collidingObject = null;
    }
    #endregion
 
    void Update(){
        if(Controller.GetHairTriggerDown()){
            if(collidingObject){
                GrabObject();
            }
        }
 
        if(Controller.GetHairTriggerUp()){
            if(objectInHand){
                ReleaseObject();
            }
        }
    }    //立钻哥哥:void Update(){}
 
    private void SetCollidingObject(Collider col){
        if(collidingObject || !col.GetComponent<Rigidbody>()){
            return;
        }
 
        collidingObject = col.gameObject;
    }
 
    private void GrabObject(){
objectInHand = collidingObject;
collidingObject = null;
    
        var joint = AddFixedJoint();
joint.connectedBody = objectInHand.GetComponent<Rigidbody>();
    }
 
    private FixedJoint AddFixedJoint(){
        FixedJoint fx = gameObject.AddComponent<FixedJoint>();
fx.breakForce = 20000;
fx.breakTorque = 20000;
        return fx;
    }
 
    private void ReleaseObject(){
        if(GetComponent<FixedJoint>){
            GetComponent<FixedJoint>().connectedBody = null;
            Destroy(GetComponent<FixedJoint>);
 
//不管物体是不是受重力影响都扔出去
            ThrowObject();
        }
 
        objectInHand = null;
    }
 
    //设置物体的速度,让物体可以“飞”出去
    void ThrowObject(){
objectInHand.GetComponent<Rigidbody>().velocity = Controller.velocity;
objectInHand.GetComponent<Rigidbody>().angularVelocity = Controller.angularVelocity;
    }
 
}    //立钻哥哥:public class MyGrabObject:MonoBehaviour{}

 

 

 

 

 

 

 

##A.5、Teleport系统

##A.5、Teleport系统

++A.5、Teleport系统

++++A.5.1、概念

++++A.5.2、使用场景

++++A.5.3、分类

++++A.5.4、实现

 

 

 

###A.5.1、Teleport概念

###A.5.1、Teleport概念

++A.5.1、Teleport概念

++++立钻哥哥:在VR中玩家的移动有两种方式

++++第一种:靠头盔的移动(支持头盔移动的设备);

++++第二种:靠瞬移;(Teleport:即玩家从当前位置瞬间移动到另一个位置)

vr应用开发需要什么架构 vr开发基础_Unity_14

 

 

 

 

###A.5.2、Teleport使用场景

###A.5.2、Teleport使用场景

++A.5.2、Teleport使用场景

++++立钻哥哥:

++++适用于较大的场景,超过Room-Scale的大小;(参考案例中《紫禁城》)

++++适用于不能检测头盔移动的VR设备,如Oculus DK1 DK2;(设备本身只支持旋转,不支持位移;所以如果想位移必须借助Teleport

 

 

 

 

 

###A.5.3、Teleport分类

###A.5.3、Teleport分类

++A.5.3、Teleport分类

++++立钻哥哥:按照复杂程度和效果可以分为三类:Simple Line Teleport;Complex Line Teleport;Arc-Line Teleport

++++[Simple Line Teleport]:从手柄处朝向正前方发射一条射线,在HitPoint处显示一个Marker标识要移动到的目标;

++++[Complex Line Teleport]:从手柄处朝向正前方发射一条射线,在HitPoint处显示一个Maker标识要移动到的目标点;

++++[Arc-Line Teleport]:从手柄朝向手柄正前方发射一条弧线(Arc),弧度会随着手柄与地面的夹角变化而变换;

 

++[Simple Line Teleport]

++++立钻哥哥:从手柄处朝向正前方发射一条射线,在HitPoint处显示一个Marker标识要移动到的目标点

++++[表现形式]:一条射线,外加一个Maker;

++++[缺点]:射线可以在指定的LayerMask中自由移动射线和Marker;但是当进入非指定LayerMask后射线则不刷新;

vr应用开发需要什么架构 vr开发基础_HTC_15

vr应用开发需要什么架构 vr开发基础_HTC_16

 

++[Complex Line Teleport]

++++立钻哥哥:从手柄处朝向正前方发射一条射线,在HitPoint处显示一个Marker标识要移动的目标点;但是解决了射线在指定LayerMask之外时也刷新;

++++[表现形式]:同样为一条射线,外加一个Maker;但是当在指定LayerMask之内会呈现绿色;在指定LayerMask之外会呈现红色;

++++[缺点]:功能虽然能实现,但是不够美观;

vr应用开发需要什么架构 vr开发基础_VIVE_17

vr应用开发需要什么架构 vr开发基础_Yanlz_18

 

++[Arc-Line Teleport]

++++立钻哥哥:从手柄处朝手柄正前方发射一条弧线(Arc),弧度会随着手柄与地面的夹角变化而变换;并且在HitPoint处显示Marker;

++++[表现形式]:一条弧线,外加一个Marker;同样支持检测是否在指定的LayerMask之内,如果在则为正常颜色;如果不在则变为红色;

vr应用开发需要什么架构 vr开发基础_Yanlz_19

vr应用开发需要什么架构 vr开发基础_vr应用开发需要什么架构_20

 

 

 

 

 

###A.5.4、Teleport实现

###A.5.4、Teleport实现

++A.5.4、Teleport实现

++++立钻哥哥:Teleport实现:Simple Line Teleport;Complex Line Teleport;Arc-Line Teleport

 

++[Simple Line Teleport]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class MySimpleLineTeleport : MonoBehaviour{
    public Transform cameraRigTransform;
    public GameObject teleportReticlePrefab;
    public Transform headTransform;
    public Vector3 teleportReticleOffset;
    public LayerMask teleportMask;
    private bool shouldTeleport;
    private SteamVR_TrackedObject trackedObj;
    public GameObject laserPrefab;
    private GameObject laser;
    private Transform laserTransform;
    private Vector3 hitPoint;
    private GameObject reticle;
    private Transform teleportReticleTransform;
 
    private SteamVR_Controller.Device Controller{
        get{  return SteamVR_Controller.Input((int)trackedObj.index);  }
    }
 
    void Awake(){
trackedObj = GetComponent<SteamVR_TrackedObject>();
    }
 
    void Start(){
laser = Instantiate(laserPrefab);
laserTransform = laser.transform;
reticle = Instantiate(teleportReticlePrefab);
teleportReticleTransform = reticle.transform;
    }
 
    void Update(){
        if(Controller.GetPress(SteamVR_Controller.ButtonMask.Touchpad)){
            RaycastHit hit;
 
            if(Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100, teleportMask)){
hitPoint = hit.point;
                ShowMyLaser(hit);
 
reticle.SetActive(true);
teleportReticleTransform.position = hitPoint + teleportReticleOffset;
shouldTeleport = true;
            }    //立钻哥哥:if(Physics.Raycast()){}
 
        }else{
laser.SetActive(false);
reticle.SetActive(false);
        }
 
        if(Controller.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad) && shouldTeleport){
            MyTeleport();
        }
    }    //立钻哥哥:void Update(){}
 
    private void ShowMyLaser(RaycastHit hit){
        laser.SetActive(true);
        laser.Transform.position = Vector3.Lerp(trackedObj.transform.position, hitPoint, 0.5f);
        laserTransform.LookAt(hitPoint);
        laserTransform.lcoalScale = new Vector3(laserTransform.localScale.x, laserTransform.localScale.y, hit.distance);
    }    //立钻哥哥:private void ShowMyLaser(){}
 
    private void MyTeleport(){
        shouldTeleport = false;
        reticle.SetActive(false);
    
        Vector3 difference = cameraRigTransform.position - headTransform.position;
        difference.y = 0;
        cameraRigTransform.position = hitPoint + difference;
    }    //立钻哥哥:private void MyTeleport(){}
 
}    //立钻哥哥:public class MySimpleLineTeleport:MonoBehaviour{}

 

++[Complex Line Teleport]

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class MyComplexLineTeleportWithError : MonoBehaviour{
    public Transform cameraRigTransform;
    public GameObject teleportReticlePrefab;
    public Transform headTransform;
    public Vector3 teleportReticleOffset;
    public string teleportMaskName;
    private bool shouldTeleport;
    private SteamVR_TrackedObject trackedObj;
    public GameObject laserPrefab;
    private GameObject laser;
    private Transform laserTransform;
    private Vector3 hitPoint;
    private GameObject reticle;
    private Transform teleportReticleTransform;
 
    private SteamVR_Controller.Device Controller{
        get{  return SteamVR_Controller.Input((int)trackedObj.index);  }
    }
 
    void Awake(){
trackedObj = GetComponent<SteamVR_TrackedObject>();
    }
 
    void Start(){
laser = Instantiate(laserPrefab);
laserTransform = laser.transform;
reticle = Instantiate(teleportReticlePrefab);
teleportReticleTransform = reticle.transform;
    }
 
    void Update(){
        if(Controller.GetPress(SteamVR_Controller.ButtonMask.Touchpad)){
            RaycastHit hit;
      
            if(Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100)){
hitPoint = hit.point;
                ShowMyLaser(hit);
reticle.SetActive(true);
 
                if(hit.collider.gameObject.layer == LayerMask.NameToLayer(teleportMaskName)){
reticle.GetComponent<Renderer>().material.color = Color.white;
teleportReticleTransform.position = hitPoint + teleportReticleOffset;
laser.GetComponent<Renderer>().material.SetColor(“_Color”, Color.green);
shouldTeleport = true;
 
                }else{
reticle.GetComponent<Renderer>().material.color = Color.red;
teleportReticleTransform.position = hitPoint + teleportReticleOffset;
laser.GetComponent<Renderer>().material.SetColor(“_Color”, Color.red);
shouldTeleport = false;
                }
            }    //立钻哥哥:if(Physics.Raycast()){}
 
        }else{
laser.SetActive(false);
reticle.SetActive(false);
        }    //立钻哥哥:if(Controller.GetPress()){}
 
        if(Controller.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad) && shouldTeleport){
            MyTeleport();
        }    //立钻哥哥:if(Controller.GetPressUp()){}
    }    //立钻哥哥:void Update(){}
 
    private void ShowMyLaser(RaycastHit hit){
laser.SetActive(true);
laserTransform.position = Vector3.Lerp(trackedObj.transform.position, hitPoint, 0.5f);
laserTransform.LookAt(hitPoint);
laserTransform.localScale = new Vector3(laserTransform.localScale.x, laserTransform.localScale.y, hit.distance);
    }    //立钻哥哥:private void ShowLaser(){}
 
    private void MyTeleport(){
shouldTeleport = false;
reticle.SetActive(false);
        Vector3 difference = cameraRigTransform.position - headTransform.position;
difference.y = 0;
cameraRigTransform.position = hitPoint + difference;
    }    //立钻哥哥:private void MyTeleport(){}
 
}    //立钻哥哥:public class MyComplexLineTeleportWithError:MonoBehaviour{}

 

++[Arc-Line Teleport]

++++立钻哥哥:“Unity Vive Teleport”

++++1、创建[ViveNavMesh]预制体,烘培[NavMesh],之后设置[ViveNavMesh],点击[UpdateNavMeshData]

++++2、创建[Pointer]预制体,设置[NavMesh]

++++3、为[SteamVR->Camera(Eye)]添加[ViveTeleportor]组件并设置[Pointer][OriginTransform][HeadTransform][Controllers]等即可;

vr应用开发需要什么架构 vr开发基础_vr应用开发需要什么架构_21

vr应用开发需要什么架构 vr开发基础_HTC_22

vr应用开发需要什么架构 vr开发基础_VIVE_23

vr应用开发需要什么架构 vr开发基础_HTC_24

 

 

 

 

 

 

 

 

##A.6、Vive与UI交互

##A.6、Vive与UI交互

++A.6、Vive与UI交互

++++A.6.1、2D UI和3D UI

++++A.6.2、3D UI的实现方式

 

 

###A.6.1、2D UI和3D UI

###A.6.1、2D UI和3D UI

++A.6.1、2D UI和3D UI

++++立钻哥哥:

++++[UI的三种模式]:ScreenSpace-Overlay;ScreenSpace-Camera;World;

++++[VR中使用3D UI]的必要性

--VR本身强调沉浸感;

--2D UI在VR中没有立体感且无法交互;

 

 

 

###A.6.2、3D UI的实现方式

###A.6.2、3D UI的实现方式

++A.6.2、3D UI的实现方式

++++立钻哥哥:3D UI的实现方式:UI+碰撞器扩展Vive插件

 

++[UI+碰撞器]

++++立钻哥哥:在UI上加BoxCollider,在OnTriggerEnter中调用UI的方法

++++缺点1:有些UI(如Button)可以利用上述方法来实现事件调用;但是调用Normal、Higlight、Pressed、Disabled等状态非常麻烦;

++++缺点2:有些UI(如Slider,Dropdown)无法利用上述方法实现,虽然可以指定碰撞器,但是必须得足够小且要实时刷新位置;实现起来非常非常非常麻烦,甚至有些复杂UI是无解的,如UI嵌套,下拉菜单和ScrollView;

++++缺点3:即使按照上面做能够实现功能;但每一个UI都添加一个碰撞器很麻烦;如果3D UI比较复杂,界面比较多,后期维护也非常麻烦;

====>[解决方案]:鉴于上述方法非常麻烦,BUG比较多,而且效率不高;那就用“扩展Vive插件: ViveUGUIModule”;

//立钻哥哥:在UI上加BoxCollider,在OnTriggerEnter中调用UI的方法
private void OnTriggerEnter(Collider collider){
    if(collider == viveController){
btn = GetComponent<Button>();
        UnityEvent btnClick = btn.onClick;
 
        if(btnClick != null){
btnClick.Invoke();
        }
    }
}    //立钻哥哥:private void OnTriggerEnter(){}

 

++[扩展Vive插件]

++++立钻哥哥:“ViveUGUIModule”https://github.com/VREALITY/ViveUGUIModule

++++优点1:该项目是在GitHub上的一个开源库,通过应用该项目中几个简单的操作可以实现ViveController与普通的UGUI交互;

++++优点2:项目是开源的,能拿到源码,可以根据自己的需求进行更改;且没有商业授权等纠纷;

++++优点3:体量小,只有两个脚本(ViveControllerInput.cs和UIIgnoreRaycast.cs),且几乎没有依赖关系;

vr应用开发需要什么架构 vr开发基础_VIVE_25

 

++利用ViveControllerInput和UIIgnoreRaycast脚本

++++立钻哥哥:将默认的InputModule移动到VRCamera的子物体,去掉默认的StandardInputModule并添加ViveController,指定CursorSprite即可正常使用

++++如果不正常则设置EventCamera即可;

 

++[UIIgnoreRaycast.cs]

++++立钻哥哥:ViveUGUIModule-master\Assets\UIIgnoreRaycast.cs

using UnityEngine;
using System.Collections;
 
namespace UnityEngine.UI{
    [AddComponentMenu(“UI/Raycast Filters/Ignore Raycast Filter”)]
    public class UIIgnoreRaycast : MonoBehaviour, ICanvasRaycastFilter{
        public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera){
            return false;
        }
    }    //立钻哥哥:public class UIIgnoreRaycast:MonoBehaviour,ICanvasRaycastFilter{}
}    //立钻哥哥:namespace UnityEngine.UI{}

 

++[ViveControllerInput.cs]

++++立钻哥哥:ViveUGUIModule-master\Assets\ViveControllerInput.cs

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
 
public class ViveControllerInput : BaseInputModule{
     public static ViveControllerInput Instance;
 
    [Header(“[Cursor setup]”)]
    public Sprite CursorSprite;
    public Material CursorMaterial;
    public float NormalCursorScale = 0.00025f;
 
    [Space(10)]
 
    [Header(“[Runtime variables]”)]
    [Tooltip(“Indicates whether or not the gui was hit by any controller this frame”)]
    public bool GuiHit;
 
    [Tooltip(“Indicates whether or not a button was used this frame”)]
    public bool ButtonUsed;
 
    [Tooltip(“Generated cursors”)]
    public RectTransform[] Cursors;
 
    private GameObject[] CurrentPoint;
    private GameObject[] CurrentPressed;
    private GameObject[] CurrentDragging;
 
    private PointerEventData[] PointEvents;
    private bool Initialized = false;
 
    [Tooltip(“Generated non rendering camera(use for raycasting ui)”)]
    public Camera ControllerCamera;
 
    private SteamVR_ControllerManager ControllerManager;
    private SteamVR_TrackedObject[] Controllers;
    private SteamVR_Controller.Device[] ControllerDevices;
 
    protected override void Start(){
        base.Start();
 
        if(Initialized == false){
Instance = this;
 
ControllerCamera = new GameObject(“Controller UI Camera”).AddComponent<Camera>();
ControllerCamera.clearFlags = CameraClearFlags.Nothing;   //Flag.Depth;
ControllerCamera.cullingMask = 0;    //1<<LayerMask.NameToLayer(“UI”);
 
ControllerManager = GameObject.FindObjectOfType<SteamVR_ControllerManager>();
Controllers = new SteamVR_TrackedObject[]{
                ControllerManager.left.GetComponent<SteamVR_TrackedObject>(),
                ControllerManager.right.GetComponent<SteamVR_TrackedObject>()
            };
            ControllerDevices = new SteamVR_Controller.Device[Controllers.Length];
            Cursors = new RectTransform[Controllers.Length];
 
            for(int index = 0;  index < Cursors.Length;  index++){
                GameObject cursor = new GameObject(“Cursor ” + index);
                Canvas canvas = cursor.AddComponent<Canvas>();
cursor.AddComponent<CanvasRenderer>();
cursor.AddComponent<CanvasScaler>();
cursor.AddComponent<UIIgnoreRaycast>();
cursor.AddComponent<GraphicRaycaster>();
 
canvas.renderMode = RenderMode.WorldSpace;
canvas.sortingOrder = 1000;    //Set to be on top of everything
 
                Image image = cursor.AddComponent<Image>();
image.sprite = CursorSprite;
image.material = CursorMaterial;
 
                if(CursorSprite == null){
                    Debug.LogError(“立钻哥哥:Set CursorSprite on” + this.gameObject.name + “ to the sprite you want to use as your cursor.” + this.gameObject);
                }
 
                Cursors[index] = cursor.GetComponent<RectTransform>();
            }
 
            CurrentPoint = new GameObject[Cursors.Length];
            CurrentPressed = new GameObject[Cursors.Length];
            CurrentDragging = new GameObject[Cursors.Length];
            PointEvents = new PointerEventData[Cursors.Length];
 
            Canvas[] canvases = GameObject.FindObjectsOfType<Canvas>();
            foreach(Canvas canvas in canvases){
canvas.worldCamera = ControllerCamera;
            }
 
            Initialized = true;
        }
    }    //立钻哥哥:protected override void Start(){}
 
    //use screen midpoint as locked pointer location, enabling look location to be the “mouse”
    private bool GetLookPointerEventData(int index){
        if(PointEvents[index] == null){
PointEvent[index] = new PointerEventData(base.eventSystem);
        }else{
PointEvent[index].Reset();
        }
 
        PointEvents[index].delta = Vector2.zero;
        PointEvents[index].position = new Vector2(Screen.width/2, Screen.height/2);
        PointEvents[index].scrollDelta = Vector2.zero;
 
        base.eventSystem.RaycastAll(PointEvents[index], m_RaycastResultCache);
        PointEvents[index].pointerCurrentRaycast = FindFirstRaycast(m_RaycastResultCache);
        if(PointEvents[index].pointerCurrentRaycast.gameObject != null){
GuiHit = true;    //gets set to false at the beginning of the process event
        }
 
        m_RaycastResultCache.Clear();
 
        return true;
    }    //立钻哥哥:private bool GetLookPointerEventData(){}
 
    //update the cursor location and whether it is enabled
    //this code is based in Unity’s DragMe.cs code provided int the UI drag and drop example
    private void UpdateCursor(){
        if(PointEvent[index].pointerCurrentRaycast.gameObject != null){
Cursors[index].gameObject.SetActive(ture);
 
            if(pointData.pointerEnter != null){
                RectTransform draggingPlane = pointData.pointerEnter.GetComponent<RectTransform>();
                Vector3 globalLookPos;
                if(RectTransformUtility.ScreenPointToWorldPointInRectangle(draggingPlane, pointData.position, pointData.enterEventCamera, out globalLookPos)){
Cursors[index].position = globalLookPos;
Cursors[index].rotation = draggingPlane.rotation;
 
 //scale cursor based on distance to camera
                    float lookPointDistance = (Cursors[index].position - Camera.main.transform.position).magnitude;
                    float cursorScale = lookPointDistance * NormalCursorScale;
                    if(cursorScale < NormalCursorScale){
cursorScale = NormalCursorScale;
                    }
 
                    Cursors[index].localScale = Vector3.one * cursorScale;
                }
            }
        }else{
Cursor[index].gameObject.SetActive(false);
        }
    }    //立钻哥哥:private void UpdateCursor(){}
 
    //clear the current selection
    public void ClearSelection(){
        if(base.eventSystem.currentSelectedGameObject){
base.eventSystem.SetSelectedGameObject(null);
        }
    }
 
    //select a game object
    private void Select(GameObject go){
        ClearSelection();
 
        if(ExecuteEvents.GetEventHandler<ISelectHandler>(go)){
            base.eventSystem.SetSelectedGameObject(go);
        }
    }
 
    //send update event to selected object
    //needed for InputField to receive keyboard input
    private bool SendUpdateEventToSelectedObject(){
        if(base.eventSystem.currentSelectedGameObject == null){
            return false;
        }
 
        BaseEventData data = GetBaseEventData();
        ExecuteEvents.Execute(base.eventSystem.currentSelectedGameObject, data, ExecuteEvents.updateSelectedHandler);
 
        return data.used;
    }
 
    private void UpdateCameraPosition(int index){
ControllerCamera.transform.position = Controllers[index].transform.position;
ControllerCamera.transform.forward = Controllers[index].transform.forward;
    }
 
    private void InitializeControllers(){
        for(int index = 0;  index < Controllers.Length;  index++){
            if(Controllers[index] != null && Controllers[index].index != SteamVR_TrackedObject.EIndex.None){
ControllerDevices[index] = SteamVR_Controller.Input((int)Controllers[index].index);
            }else{
ControllerDevices[index] = null;
            }
        }
    }    //立钻哥哥:private void InitializeControllers(){}
 
    //Process is called by UI system to process events
    public override void Process(){
        InitializeControllers();
 
GuiHit = false;
ButtonUsed = false;
 
      //send update events if there is a selected object - this is important for InpuField to receive keyboard events
        SendUpdateEventToSelectedObject();
 
       //see if there is a UI element that is currently being looked at
        for(int index = 0;  index < Cursors.Length;  index++){
            if(Controllers[index].gameObject.activeInHierarchy == false){
                if(Cursors[index].gameObject.activeInHierarchy == true){
Cursors[index].gameObject.SetActive(false);
                }
 
                continue;
            }
 
            UpdateCameraPosition(index);
 
            bool hit = GetLookPointerEventData(index);
            if(hit == false){
                Continue;
            }
            CurrentPoint[index] = PointEvents[index].pointerCurrentRaycast.gameObject;
 
            //handle enter and exit events(highlight)
            base.HandlePointerExitAndEnter(PointEvents[index], CurrentPoint[index]);
 
            //update cursor
            UpdateCursor(index, PointEvents[index]);
 
            if(Controllers[index] != null){
                if(ButtonDown(index)){
                    ClearSelection();
 
PointEvents[index].pressPosition = PointEvents[index].position;
PointEvents[index].pointerPressRaycast = PointEvents[index].pointerCurrentRaycast;
PointEvents[index].pointerPress = null;
 
                    if(CurrentPoint[index] != null){
CurrentPressed[index] = CurrentPoint[index];
                        GameObject newPressed = ExecuteEvents.ExecuteHierarchy(CurrentPressed[index], PointEvents[index], ExecuteEvents.pointerDownHandler);
 
                        if(newPressed == null){
//some UI elements might not have click handler and not pointer down handler
newPressed = ExecuteEvents.ExecuteHierarchy(CurrentPressed[index], PointEvents[index], ExecuteEvents.pointerClickHandler);
                            if(newPressed != null){
CurrentPressed[index] = newPressed;
                            }
                        }else{
CurrentPressed[index] = newPressed;
//we want to do click on button down at same time, unlike regular mouse processing which does click when mouse goes up over same object it went down on reason to do this is head tracking might be jittery and this makes it easier to click buttons
                            ExecuteEvents.Execute(newPressed, PointEvents[index], ExecuteEvents.pointerClickHandler);
                        }
 
                        if(newPressed != null){
PointEvents[index].pointerPress = newPressed;
CurrentPressed[index] = newPressed;
                            Select(CurrentPressed[index]);
ButtonUsed = true;
                        }
 
                        ExecuteEvent.Execute(CurrentPressed[index], PointEvent[index], ExecuteEvents.beginDragHandler);
                        PointEvents[index].pointerDrag = CurrentPressed[index];
                        CurrentDragging[index] = CurrentPressed[index];
                    }
                }
 
                if(ButtonUp(index)){
                    if(CurrentDragging[index]){
                        ExecuteEvents.Execute(CurrentDragging[index], PointEvents[index], ExecuteEvents.endDragHandler);
                        if(CurrentPoint[index] != null){
                            ExecuteEvents.ExecutedHierarchy(CurrentPoint[index], PointEvents[index], ExecuteEvents.dropHandler);
                         }
                         PointEvents[index].pointerDrag = null;
                         CurrentDragging[index] = null;
                     }
 
                    if(CurrentPressed[index]){
                         ExecuteEvents.Execute(CurrentPressed[index], PointEvents[index], ExecuteEvents.pointerUpHandler);
PointEvents[index].rawPointerPress = null;
PointEvents[index].pointerPress = null;
CurrentPressed[index] = null;
                    }
                }
 
                //drag handing
                if(CurrentDragging[index] != null){
                    ExecuteEvents.Execute(CurrentDragging[index], PointEvents[index], ExecuteEvents.dragHandler);
                }
 
            }    //立钻哥哥:if(Controllers[index] != null){}
        }    //立钻哥哥:for(int index = 0; index < Cursors.Length; index++){}
    }    //立钻哥哥:public override void Process(){}
 
    private bool ButtonDown(int index){
        return (ControllerDevices[index] != null && ControllerDevices[index].GetPressDown(Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger) == true);
    }
 
    private bool ButtonUp(int index){
        return (ControllerDevices[index] != null && ControllerDevices[index].GetPressUp(Valve.VR.EVRButtonId.K_EButton_SteamVR_Trigger) == true);
    }
 
}    //立钻哥哥:public class ViveControllerInput:BaseInputModule{}

 

 

 

 

 

 

##A.7、Vive Controller Haptic

##A.7、Vive Controller Haptic