美文网首页Unity教程合集Unity技术VR/AR分享
写给VR手游开发小白的教程:(二)UnityVR插件Cardb

写给VR手游开发小白的教程:(二)UnityVR插件Cardb

作者: 你的头好大 | 来源:发表于2017-08-30 09:41 被阅读19次

    现在我们已经有了开发环境,还没安装环境的小伙伴可以看上一篇:
    (一)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下的场景

    Paste_Image.png

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

    Paste_Image.png

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

    Paste_Image.png
    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<Cardboard>();
    }
    if (sdk == null) {
    Debug.Log("Creating Cardboard object");
    var go = new GameObject("Cardboard");
    sdk = go.AddComponent<Cardboard>();
    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<string> diagnostics = new List<string>();
    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<Camera>()) {
    go = new GameObject("CardboardDummy");
    go.transform.parent = gameObject.transform;
    }
    var cam = go.AddComponent<Camera>();
    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; } }
    }
    

    来自CODE的代码片Cardboard.cs

    上面的源代码理解起来比较麻烦,我解析的时候会把变量定义等一些顺序调换一下注释部分
    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。(这个变量是控制扭曲的,开或者关的效果如下,差距应该一目了然)


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

    相关文章

      网友评论

        本文标题: 写给VR手游开发小白的教程:(二)UnityVR插件Cardb

        本文链接:https://www.haomeiwen.com/subject/xqyzdxtx.html