写给VR手游开发小白的教程:(二)UnityVR插件CardboardSDKForUnity解析(一)

现在我们已经有了开发环境,还没安装环境的小伙伴可以看上一篇:

(一)Unity3D进行Android开发的环境搭建(虚拟机调试)


今天主要介绍的是谷歌为自己的Cardboard平台提供的开发工具Cardboard SDK插件解析。Cardboard是谷歌公司在14年发布的一款极具创意的产品,由手机内部的传感器支持,它仅需硬纸板和两片透镜就能打造移动平台上的VR体验。Cardboard这里不多做介绍,网上可以买到原装正版,价格在50以内很便宜,有条件的人可以去体验一下。


在正式开始之前,先说明一下本节需要对Unity3D引擎有一些基础才能较流畅的看完,若是中间有疑问可以评论也可私信,疑问较多也可以再补充一章专门介绍基础的东西。


上一章忘记说了,如果是VR应用的话,就不要在虚拟机里跑了(虚拟机根本没有传感器去定位你的头部,也就不存在VR一说了,不过简单的小游戏还是可以在虚拟机上跑的,所以如果是跑大型应用的话,还是选择真机吧),虚拟机的存在其实纯粹是为了学习示例用的。


没有真机的同学,这个demo在Unity里也可以跑,具体按住Alt移动鼠标就相当于转动头部,按住Ctrl移动鼠标相当于歪脖子看。




/***************************************************************分割线*******************************************************************/

首先要为Unity安装这个插件,对于本插件,目前网上有些资源是不带demo的,正好我这边有一个带demo的,附上下载地址:

http://download.csdn.net/detail/mao_xiao_feng/9577849

导入以后,工程目录下多了以下两个文件夹,双击打开DemoScene下的场景

写给VR手游开发小白的教程:(二)UnityVR插件CardboardSDKForUnity解析(一)_第1张图片

场景大概就是以下这个样子了,这个demo是插件中提供的官方示例,一定要把里面所有东西都研究透,才能很好理解SDK当中的每一个脚本的功能。

写给VR手游开发小白的教程:(二)UnityVR插件CardboardSDKForUnity解析(一)_第2张图片

直接切入主题,看到CardboardMain这个物体已经被设为了Prefab,那么它一定是获得VR效果的关键性物体,事实上所有VR的实现都在这个物体极其子物体下,我们把它一层层的剥离开来。

CardboardMain物体上只绑定了这一个脚本,开始看源码

写给VR手游开发小白的教程:(二)UnityVR插件CardboardSDKForUnity解析(一)_第3张图片

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

// The Cardboard object communicates with the head-mounted display in order to:
// -  Query the device for viewing parameters
// -  Retrieve the latest head tracking data
// -  Provide the rendered scene to the device for distortion correction

public class Cardboard : MonoBehaviour {
  // The singleton instance of the Cardboard class.
  public static Cardboard SDK {
    get {
      if (sdk == null) {
        sdk = UnityEngine.Object.FindObjectOfType();
      }
      if (sdk == null) {
        Debug.Log("Creating Cardboard object");
        var go = new GameObject("Cardboard");
        sdk = go.AddComponent();
        go.transform.localPosition = Vector3.zero;
      }
      return sdk;
    }
  }
  private static Cardboard sdk = null;

  public bool DistortionCorrection {
    get {
      return distortionCorrection;
    }
    set {
      if (value != distortionCorrection && device != null) {
        device.SetDistortionCorrectionEnabled(value && NativeDistortionCorrectionSupported);
      }
      distortionCorrection = value;
    }
  }
  [SerializeField]
  private bool distortionCorrection = true;

  public bool VRModeEnabled {
    get {
      return vrModeEnabled;
    }
    set {
      if (value != vrModeEnabled && device != null) {
        device.SetVRModeEnabled(value);
      }
      vrModeEnabled = value;
    }
  }
  [SerializeField]
  private bool vrModeEnabled = true;

  public bool EnableAlignmentMarker {
    get {
      return enableAlignmentMarker;
    }
    set {
      if (value != enableAlignmentMarker && device != null) {
        device.SetAlignmentMarkerEnabled(value && NativeUILayerSupported);
      }
      enableAlignmentMarker = value;
    }
  }
  [SerializeField]
  private bool enableAlignmentMarker = true;

  public bool EnableSettingsButton {
    get {
      return enableSettingsButton;
    }
    set {
      if (value != enableSettingsButton && device != null) {
        device.SetSettingsButtonEnabled(value && NativeUILayerSupported);
      }
      enableSettingsButton = value;
    }
  }
  [SerializeField]
  private bool enableSettingsButton = true;

  public bool TapIsTrigger = true;

  public float NeckModelScale {
    get {
      return neckModelScale;
    }
    set {
      value = Mathf.Clamp01(value);
      if (!Mathf.Approximately(value, neckModelScale) && device != null) {
        device.SetNeckModelScale(value);
      }
      neckModelScale = value;
    }
  }
  [SerializeField]
  private float neckModelScale = 0.0f;

  public bool AutoDriftCorrection {
    get {
      return autoDriftCorrection;
    }
    set {
      if (value != autoDriftCorrection && device != null) {
        device.SetAutoDriftCorrectionEnabled(value);
      }
      autoDriftCorrection = value;
    }
  }
  [SerializeField]
  private bool autoDriftCorrection = true;

#if UNITY_IOS
  public bool SyncWithCardboardApp {
    get {
      return syncWithCardboardApp;
    }
    set {
      if (value && value != syncWithCardboardApp) {
        Debug.LogWarning("Remember to enable iCloud capability in Xcode, "
            + "and set the 'iCloud Documents' checkbox. "
            + "Not doing this may cause the app to crash if the user tries to sync.");
      }
      syncWithCardboardApp = value;
    }
  }
  [SerializeField]
  private bool syncWithCardboardApp = false;
#endif

#if UNITY_EDITOR
  // Mock settings for in-editor emulation of Cardboard while playing.
  public bool autoUntiltHead = true;

  // Whether to perform distortion correction in the editor.
  public bool simulateDistortionCorrection = true;

  // Use unity remote as the input source.
  [HideInInspector]
  public bool UseUnityRemoteInput = false;

  public CardboardProfile.ScreenSizes ScreenSize {
    get {
      return screenSize;
    }
    set {
      if (value != screenSize) {
        screenSize = value;
        device.UpdateScreenData();
      }
    }
  }
  [SerializeField]
  private CardboardProfile.ScreenSizes screenSize = CardboardProfile.ScreenSizes.Nexus5;

  public CardboardProfile.DeviceTypes DeviceType {
    get {
      return deviceType;
    }
    set {
      if (value != deviceType) {
        deviceType = value;
        device.UpdateScreenData();
      }
    }
  }
  [SerializeField]
  public CardboardProfile.DeviceTypes deviceType = CardboardProfile.DeviceTypes.CardboardJun2014;
#endif

  // The VR device that will be providing input data.
  private static BaseVRDevice device;

  public bool NativeDistortionCorrectionSupported { get; private set; }

  public bool NativeUILayerSupported { get; private set; }

  // The texture that Unity renders the scene to. This is sent to the VR device,
  // which renders it to screen, correcting for lens distortion.
  public RenderTexture StereoScreen {
    get {
      // Don't need it except for distortion correction.
      if (!distortionCorrection || !vrModeEnabled) {
        return null;
      }
      if (stereoScreen == null && NativeDistortionCorrectionSupported) {
        StereoScreen = CreateStereoScreen();  // Note: use set{}
      }
      return stereoScreen;
    }
    set {
      if (value == stereoScreen) {
        return;
      }
      if (!NativeDistortionCorrectionSupported && value != null) {
        Debug.LogError("Can't set StereoScreen: native distortion correction is not supported.");
        return;
      }
      if (stereoScreen != null) {
        stereoScreen.Release();
      }
      stereoScreen = value;
      if (stereoScreen != null && !stereoScreen.IsCreated()) {
        stereoScreen.Create();
      }
      if (device != null) {
        device.SetStereoScreen(stereoScreen);
      }
    }
  }
  private static RenderTexture stereoScreen = null;

  public bool UseDistortionEffect {
    get {
      return !NativeDistortionCorrectionSupported && distortionCorrection && vrModeEnabled
          && SystemInfo.supportsRenderTextures;
    }
  }

  // Describes the current device, including phone screen.
  public CardboardProfile Profile {
    get {
      return device.Profile;
    }
  }

  // Distinguish the stereo eyes.
  public enum Eye {
    Left,
    Right,
    Center
  }

  // When asking for project, viewport, etc, whether to assume viewing through
  // the lenses.
  public enum Distortion {
    Distorted,   // Viewing through the lenses
    Undistorted  // No lenses
  }

  // The transformation of head from origin in the tracking system.
  public Pose3D HeadPose {
    get {
      return device.GetHeadPose();
    }
  }

  // The transformation from head to eye.
  public Pose3D EyePose(Eye eye) {
    return device.GetEyePose(eye);
  }

  // The projection matrix for a given eye.
  public Matrix4x4 Projection(Eye eye, Distortion distortion = Distortion.Distorted) {
    return device.GetProjection(eye, distortion);
  }

  // The screen-space rectangle each eye should render into.
  public Rect Viewport(Eye eye, Distortion distortion = Distortion.Distorted) {
    return device.GetViewport(eye, distortion);
  }

  // The distance range from the viewer in user-space meters where objects
  // may be viewed comfortably in stereo.  If the center of interest falls
  // outside this range, the stereo eye separation should be adjusted to
  // keep the onscreen disparity within the limits set by this range.
  public Vector2 ComfortableViewingRange {
    get {
      return defaultComfortableViewingRange;
    }
  }
  private readonly Vector2 defaultComfortableViewingRange = new Vector2(1.0f, 100000.0f);

  private void InitDevice() {
    if (device != null) {
      device.Destroy();
    }
    device = BaseVRDevice.GetDevice();
    device.Init();

    List diagnostics = new List();
    NativeDistortionCorrectionSupported = device.SupportsNativeDistortionCorrection(diagnostics);
    if (diagnostics.Count > 0) {
      Debug.LogWarning("Built-in distortion correction disabled. Causes: ["
                       + String.Join("; ", diagnostics.ToArray()) + "]");
    }
    diagnostics.Clear();
    NativeUILayerSupported = device.SupportsNativeUILayer(diagnostics);
    if (diagnostics.Count > 0) {
      Debug.LogWarning("Built-in UI layer disabled. Causes: ["
                       + String.Join("; ", diagnostics.ToArray()) + "]");
    }

    device.SetVRModeEnabled(vrModeEnabled);
    device.SetDistortionCorrectionEnabled(distortionCorrection
        && NativeDistortionCorrectionSupported);
    device.SetAlignmentMarkerEnabled(enableAlignmentMarker
        && NativeUILayerSupported);
    device.SetSettingsButtonEnabled(enableSettingsButton
        && NativeUILayerSupported);
    device.SetNeckModelScale(neckModelScale);
    device.SetAutoDriftCorrectionEnabled(autoDriftCorrection);

    device.UpdateScreenData();
  }

  // NOTE: Each scene load causes an OnDestroy of the current SDK, followed
  // by and Awake of a new one.  That should not cause the underlying native
  // code to hiccup.  Exception: developer may call Application.DontDestroyOnLoad
  // on the SDK if they want it to survive across scene loads.
  void Awake() {
    if (sdk == null) {
      sdk = this;
    }
    if (sdk != this) {
      Debug.LogWarning("Cardboard SDK object should be a singleton.");
      enabled = false;
      return;
    }
#if UNITY_IOS
    Application.targetFrameRate = 60;
#endif
    InitDevice();
    AddDummyCamera();
    StereoScreen = null;
  }

  public event Action OnTrigger;

  public event Action OnTilt;

  public bool Triggered { get; private set; }

  public bool Tilted { get; private set; }

  private bool updated = false;

  private CardboardUILayer uiLayer = null;

  public void UpdateState() {
    if (!updated) {
      device.UpdateState();
      if (TapIsTrigger) {
        if (Input.GetMouseButtonUp(0)) {
          device.triggered = true;
        }
        if (Input.GetKeyUp(KeyCode.Escape)) {
          device.tilted = true;
        }
      }
      updated = true;
    }
  }

  private void DispatchEvents() {
    Triggered = device.triggered;
    Tilted = device.tilted;
    device.triggered = false;
    device.tilted = false;

    if (Tilted) {
      if (OnTilt != null) {
        OnTilt();
      }
    }
    if (Triggered) {
      if (OnTrigger != null) {
        OnTrigger();
      }
    }
  }

  private void AddDummyCamera() {
    var go = gameObject;
    if (go.GetComponent()) {
      go = new GameObject("CardboardDummy");
      go.transform.parent = gameObject.transform;
    }
    var cam = go.AddComponent();
    cam.clearFlags = CameraClearFlags.SolidColor;
    cam.backgroundColor = Color.black;
    cam.cullingMask = 0;
    cam.useOcclusionCulling = false;
    cam.depth = -100;
  }

  IEnumerator EndOfFrame() {
    while (true) {
      yield return new WaitForEndOfFrame();
      UpdateState();
      device.PostRender(vrModeEnabled);
      if (vrModeEnabled && !NativeUILayerSupported) {
        if (uiLayer == null) {
          uiLayer = new CardboardUILayer();
        }
        uiLayer.Draw();
      }
      DispatchEvents();
      updated = false;
    }
  }

  // Return a StereoScreen with sensible default values.
  public RenderTexture CreateStereoScreen() {
    return device.CreateStereoScreen();
  }

  // Reset the tracker so that the user's current direction becomes forward.
  public void Recenter() {
    device.Recenter();
  }

  // Set the screen coordinates of the mouse/touch event.
  public void SetTouchCoordinates(int x, int y) {
    device.SetTouchCoordinates(x, y);
  }

  void OnEnable() {
    device.OnPause(false);
    StartCoroutine("EndOfFrame");
  }

  void OnDisable() {
    StopCoroutine("EndOfFrame");
    device.OnPause(true);
  }

  void OnApplicationPause(bool pause) {
    device.OnPause(pause);
  }

  void OnApplicationFocus(bool focus) {
    device.OnFocus(focus);
  }

  void OnLevelWasLoaded(int level) {
    device.Reset();
  }

  void OnDestroy() {
    if (device != null) {
      device.Destroy();
    }
    if (sdk == this) {
      sdk = null;
    }
  }

  void OnApplicationQuit() {
    device.OnApplicationQuit();
  }

  //********* OBSOLETE ACCESSORS *********

  [System.Obsolete("Use DistortionCorrection instead.")]
  public bool nativeDistortionCorrection {
    get { return DistortionCorrection; }
    set { DistortionCorrection = value; }
  }

  [System.Obsolete("InCardboard is deprecated.")]
  public bool InCardboard { get { return true; } }

  [System.Obsolete("Use Triggered instead.")]
  public bool CardboardTriggered { get { return Triggered; } }

  [System.Obsolete("Use HeadPose instead.")]
  public Matrix4x4 HeadView { get { return HeadPose.Matrix; } }

  [System.Obsolete("Use HeadPose instead.")]
  public Quaternion HeadRotation { get { return HeadPose.Orientation; } }

  [System.Obsolete("Use HeadPose instead.")]
  public Vector3 HeadPosition { get { return HeadPose.Position; } }

  [System.Obsolete("Use EyePose() instead.")]
  public Matrix4x4 EyeView(Eye eye) {
    return EyePose(eye).Matrix;
  }

  [System.Obsolete("Use EyePose() instead.")]
  public Vector3 EyeOffset(Eye eye) {
    return EyePose(eye).Position;
  }

  [System.Obsolete("Use Projection() instead.")]
  public Matrix4x4 UndistortedProjection(Eye eye) {
    return Projection(eye, Distortion.Undistorted);
  }

  [System.Obsolete("Use Viewport() instead.")]
  public Rect EyeRect(Eye eye) {
    return Viewport(eye, Distortion.Distorted);
  }

  [System.Obsolete("Use ComfortableViewingRange instead.")]
  public float MinimumComfortDistance { get { return ComfortableViewingRange.x; } }

  [System.Obsolete("Use ComfortableViewingRange instead.")]
  public float MaximumComfortDistance { get { return ComfortableViewingRange.y; } }
}

上面的源代码理解起来比较麻烦,我解析的时候会把变量定义等一些顺序调换一下
注释部分

The Cardboard object communicates with the head-mounted display in order to:Query the device for viewing parameters  Retrieve the latest head tracking data  Provide the rendered scene to the device for distortion correction 翻译/*Cardboard物体与头盔显示器进行交互以获得设备上的一些参数,返回最近头部跟踪数据,为被渲染的场景提供失真校正*/

定义了一个Cardboard类型的静态公有变量SDK,主要用来获取游戏中的Cardboard.cs脚本,可以看到它是只读的,即SDK提供了一个共有的访问接口,在任何时候它都只有一个实例。 // The singleton instance of the Cardboard class. 翻译/*Cardboard类的单件实例*/---关于单件模式,编程用的还是挺多的。

BaseVRDevice类型的私有静态变量device,BaseVRDevice也是插件当中定义的一个类,注意!!它与Cardboard类不同,它不是脚本类!!这个变量用的比较多  // The VR device that will be providing input data. 翻译/*VR设备(在这里直接理解为手机吧!)提供输入的数据*/

公有bool类型的变量DistortionCorrection,默认为true。DistortionCorrection我把它翻译为失真校正(暂且这样翻译吧),读属性不说了,写属性加了个if语句,想说明假如设备接入了并且支持失真校正功能,才能修改它的值,不然一直为true。(这个变量是控制扭曲的,开或者关的效果如下,差距应该一目了然)

写给VR手游开发小白的教程:(二)UnityVR插件CardboardSDKForUnity解析(一)_第4张图片

写给VR手游开发小白的教程:(二)UnityVR插件CardboardSDKForUnity解析(一)_第5张图片

同上还有VRModeEnabled(VR模式的使能,开了就是分屏),EnableAlignmentMarker(就是下图黄圈中间那根线,关了,线就没了),EnableSettingsButton(就是下图绿圈内的设置按钮,同样也是关了就没了),AutoDriftCorrection(这个属性还没搞懂先放着),NeckModelScale(这个是专业术语,应该是颈部的微调吧...猜的,值在0-1之间,调整的话视角会有微小的变化,但可以忽略不计)

写给VR手游开发小白的教程:(二)UnityVR插件CardboardSDKForUnity解析(一)_第6张图片

后面从#if UNITY_IOS到#endif中间的部分是预编译部分,根据ios,unity_editor,android选择性的编译,可以根据自己的平台选择性的看。我们现在使用unity编辑器在运行,所以他编译的是#if UNITY_EDITOR到#endif中间的这部分。

RenderTexture类型的公有变量StereoScreen,渲染纹理是一种即时更新的纹理。//The texture that Unity renders the scene to. This is sent to the VR device,which renders it to screen, correcting for lens distortion. 翻译/*Unity渲染场景产生的纹理,它被传输到VR设备上,在屏幕上被绘制产生正确的透镜弯曲效果*/这个变量的读属性当vr模式或者distortionCorrection关闭时,为null,写属性也只有在支持distortionCorrection的时候可以使用,总的来说,可以把它看作产生弯曲效果的一个工具或者一个中间量。

bool类型只读属性的UseDistortionEffect,这个量是用来判断透镜效果是否开启的量,后面会用到。

CardboardProfile类型的公有变量Profile,也是只读的。 // Describes the current device, including phone screen. 翻译/*描述当前设备,包括手机屏幕*/

公有枚举类型Eye。 // Distinguish the stereo eyes. 翻译/*看立体效果时用来区分左右眼*/

公有枚举类型Distortion。这个量主要控制在某些特殊情况下是否需要透镜预览,在下面会用到

Pose3D类型的公有量HeadPose。  // The transformation of head from origin in the tracking system.  翻译/*在跟踪在系统中,头部距离起始点的信息*/由于这里Pose3D也是插件自定义的类,后续需要分析Pose3D这个类才能知道信息是如何传递的。同理还有变量EyePose。

返回Matrix4x4类型的方法Projection(Eye eye, Distortion distortion = Distortion.Distorted)返回结果是device.GetProjection(eye, distortion);从两个参数推断这个方法是要根据眼睛计算返回一个投影矩阵,在Unity圣典中阐述过Matrix4x4类型的投影矩阵,关于投影矩阵,涉及到计算机图形学的东西,我目前也正在学习,给一个链接大家可以学习一下。

http://blog.csdn.net/yanwei2016/article/details/7326180

Viewport(Eye eye, Distortion distortion = Distortion.Distorted)方法返回视口矩形,应该是处理在屏幕上的位置的一个方法

Vector2类型的变量ComfortableViewingRange,只读,控制我们去看空间中物体的一个最舒服的距离范围,这个量不需要去配置,理解就行。

私有方法InitDevice()用于初始化设备,因为涉及到BaseVRDevice类,我们以后再去解析其细节。

私有方法AddDummyCamera()用来添加一个黑色背景,当然背景也可以自己更换。

一些琐碎的定义先到这里



/********************************************************分割线********************************************************************/

NOTE: Each scene load causes an OnDestroy of the current SDK, followed by and Awake of a new one.  That should not cause the underlying native code to hiccup.  Exception: developer may call Application.DontDestroyOnLoad on the SDK if they want it to survive across scene loads.

翻译/*说明:每一个新场景被加载会导致现在sdk的destroy,然后产生新的sdk,这会发生一些问题(这句话实在不会翻译了),所以开发者们在切换场景的时候如果希望sdk依然存活,可以使用Application.DontDestroyOnLoad*/。这里其实可以间接的说明SDK是采用的单件模式,因为我们无法用new()去创建,而且自始而终最多只有一个实例。

比较关键的awake函数来了

  void Awake() {
    if (sdk == null) {
      sdk = this;
    }
    if (sdk != this) {
      Debug.LogWarning("Cardboard SDK object should be a singleton.");
      enabled = false;
      return;
    }
#if UNITY_IOS
    Application.targetFrameRate = 60;
#endif
    InitDevice();
    AddDummyCamera();
    StereoScreen = null;
  }相信上面的语句根据我之前的解释大家都能看懂了

这个类中没有Update()所以我认为更新可能放在BaseVRDevice类型的device里面了。

我先码到这里(太累了),后面接着此篇,另外会解析BaseVRDevice这个类(很重要!!),还有CardboardProfile这个类。

总结:这一章还只是把每个类的功能和大致框架整理了一下,并没有涉及到很多细节的东西,也没有涉及到原理,注意我们现在还在顶端的父物体Cardboard物体上分析,后面当涉及到原理的的时候,就要开始看下层的Head,和它的子物体几个camera了。

你可能感兴趣的:(Unity3D移动端)