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

上一章讲了Cardboard这个类,做一个总结,这个类总就是给一些私有的变量去提供了共有的接口,以供用户去修改他们的值。这些量大多都是用户级别的变量,多是控制一些功能的使能,例如是否开启VR模式等。还有一个重要作用,类实现了自身的单例化,如下:

public class Singleton
{
    private static Singleton _instance = null;
    //将构造函数设为private,防止客户代码通过new实例化对象
    private Singleton()
    {
    }
    public static Singleton CreateInstance()
    {
            if(_instance == null)
            _instance = new Singleton();
        return _instance;
    }
}

这就是单件模式一个重要的例子,用户永远只能通过自定义的公有方法去实例化或者返回已经实例化的对象。

单件模式,有两个好处:

第一个是资源共享,例如在这个demo中,我们只希望VR Mode Enabled这个变量只有一个,外部共享的调用它。如果它有两个,这样就会导致难以处理一些请求和资源的损耗。

第二个是信息交互会更加简便,就像为什么Unity里面永远只有一个Main Camera,而我们去find它的时候不需要遍历的去搜索判断它的名字,道理是一样的。

我们还简单介绍了以下几项属性的功能(其中Editor Mock Settings是Unity编辑器下运行的设置,一般不会做改动的,也对手机上运行没什么影响)



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


好了,接下来进入这次的主题,先上图


这是VRDevice文件夹下所有类的继承关系,可以看到所有类都继承于同一个基类,BaseVRDevice


BaseVRDevice的介绍

// Represents a vr device that this plugin interacts with. 代表了与这个插件交互的VR设备

这个类里面应该定义了作为VR设备必须要实现的一些方法,但我们不必关注是怎样实现的,这些方法会在子类里面根据不同的平台(ios,android)重写,给出详细的实现步骤。

我们把重要的和不重要的归个类

重要:

public virtual RenderTexture CreateStereoScreen()

//Creating new default cardboard screen texture.为Cardboard屏幕生成一个新的默认RenderTexture,RenderTexture是一个实时更新的纹理,动态的,上节说过


public Pose3D GetHeadPose()

protected MutablePose3D headPose = new MutablePose3D();

//两个一起讲,headPose是变量,记录了头部的位置信息,注意是protected的,GetHeadPose()得到头部的位置,返回的是headPose,这个方法是public的


public Pose3D GetEyePose(Cardboard.Eye eye)

protected MutablePose3D leftEyePose = new MutablePose3D();

protected MutablePose3D rightEyePose = new MutablePose3D();

//同理,眼睛位置


public Matrix4x4 GetProjection(Cardboard.Eye eye,Cardboard.Distortion distortion = Cardboard.Distortion.Distorted)

protected Matrix4x4 leftEyeDistortedProjection;

protected Matrix4x4 rightEyeDistortedProjection;

protected Matrix4x4 leftEyeUndistortedProjection;

protected Matrix4x4 rightEyeUndistortedProjection;

//根据眼睛类型(左右眼)和是否应用透镜弯曲效果来返回一个Projection(投影),这个投影是Matrix4x4类型的,Unity官方对投影矩阵的解释是 如果你改变这个矩阵,相机的渲染不再基于它的fieldOfView更新,直到调用ResetProjectionMatrix.只有当真正需要一个非标准的投影时,才使用自定义投影。


public Rect GetViewport(Cardboard.Eye eye,Cardboard.Distortion distortion = Cardboard.Distortion.Distorted)

protected Rect leftEyeDistortedViewport;

protected Rect rightEyeDistortedViewport;

protected Rect leftEyeUndistortedViewport;

protected Rect rightEyeUndistortedViewport;

//根据眼睛类型(左右眼)和是否应用透镜弯曲效果来返回一个Viewport(视口),这个视口是Rect 类型的,Rect类型很简单,里面存着(x,y,w,h)四个量,而每个量分别代表的是如下图,而一个camera呈现的画面,它在屏幕上占多大的地,就是以视口决定的。




public abstract void UpdateState();

public abstract void UpdateScreenData();

public abstract void Recenter();

public abstract void PostRender(bool vrMode);

//又来四个抽象方法,从字面意思上看,是状态更新,屏幕数据更新,重新定位中心,完成渲染后


public virtual void OnPause(bool pause)

//当停止了,就不再UpdateScreenData()

public virtual void OnFocus(bool focus)

public virtual void Reset()

public virtual void OnApplicationQuit()

public virtual void Destroy()

//由字面意思就可以理解


protected void ComputeEyesFromProfile()

//这个函数很关键!!!官方打的注释是// Compute left eye matrices from screen and device params,意思就是根据设备的参数计算左眼的观测信息


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


这一部分对ComputeEyesFromProfile()函数做一个详细介绍

首先这个方法中用到的数据都是来源于CardboardProfile这个类,这个类中记录了Cardboard设备的各项参数

第一句话建立了Matrix4x4 leftEyeView这个量,然后把一个4*4的单位矩阵赋给他

第二句话涉及到了CardboardProfile里的Lenses,想要解释清楚这部分,需要画三张图




Lenses里面一些量标注如上图。

还有MaxFOV这个量

public struct MaxFOV {
    public float outer;  // Towards the side of the screen.
    public float inner;  // Towards the center line of the screen.
    public float upper;  // Towards the top of the screen.
    public float lower;  // Towards the bottom of the screen.
  }

这个量表示我们视野的最大值,是用角度来定义的,如outer这个量就是俯视图中,∠ABC的大小,inner就是∠CBD的大小了

接着第二句话leftEyeView[0, 3] = -Profile.device.lenses.separation / 2,这就是一个赋值语句,给第4列的第一个元素赋了值

第三句话leftEyePose.Set(leftEyeView),从这句话可以看出了leftEyeView这个矩阵里面是存储了眼睛的位置信息的,后续看了Set()函数,矩阵的第四列存了Position,第二第三列存了Orientation朝向信息。

再看后面 float[] rect = new float[4];
Profile.GetLeftEyeVisibleTanAngles(ref rect);这两句话,新建了一个数组,在一个方法GetLeftEyeVisibleTanAngles中修改了数组里的元素的值

public void GetLeftEyeVisibleTanAngles(ref float[] result) {
    // Tan-angles from the max FOV.
    float fovLeft = (float) Math.Tan(-device.maxFOV.outer * Math.PI / 180);
    float fovTop = (float) Math.Tan(device.maxFOV.upper * Math.PI / 180);
    float fovRight = (float) Math.Tan(device.maxFOV.inner * Math.PI / 180);
    float fovBottom = (float) Math.Tan(-device.maxFOV.lower * Math.PI / 180);
    // Viewport size.
    float halfWidth = screen.width / 4;
    float halfHeight = screen.height / 2;
    // Viewport center, measured from left lens position.
    float centerX = device.lenses.separation / 2 - halfWidth;
    float centerY = -VerticalLensOffset;
    float centerZ = device.lenses.screenDistance;
    // Tan-angles of the viewport edges, as seen through the lens.
    float screenLeft = device.distortion.distort((centerX - halfWidth) / centerZ);
    float screenTop = device.distortion.distort((centerY + halfHeight) / centerZ);
    float screenRight = device.distortion.distort((centerX + halfWidth) / centerZ);
    float screenBottom = device.distortion.distort((centerY - halfWidth) / centerZ);
    // Compare the two sets of tan-angles and take the value closer to zero on each side.
    result[0] = Math.Max(fovLeft, screenLeft);
    result[1] = Math.Min(fovTop, screenTop);
    result[2] = Math.Min(fovRight, screenRight);
    result[3] = Math.Max(fovBottom, screenBottom);
  }
看一下GetLeftEyeVisibleTanAngles这个函数,先是把FOV角度转换成了正切值,再取到了屏幕相对于左眼的正切角,相比取了一个最小值,可以这样理解,如果手机屏幕比正常视野大了,那么我们看不全整个屏幕,为了有更好的体验,需要以视野作为标准,当视野比屏幕大了,当然是以手机屏幕作为标准更好一些。


以上函数完成了以后,rect的值产生了变化,接下来就需要应用这些值,leftEyeDistortedProjection = MakeProjection(rect[0], rect[1], rect[2], rect[3], 1, 1000);计算投影矩阵,因为上一步,我们做了distort变换,可以把distort理解为经过了透镜的光线折射,所以投影矩阵的结果应该也是distort的,camera呈现的画面就会是这样


当无透镜的时候Profile.GetLeftEyeNoLensTanAngles(ref rect);
leftEyeUndistortedProjection = MakeProjection(rect[0], rect[1], rect[2], rect[3], 1, 1000);会做一个inverse distort,即distort的逆变换,得到的自然是无透镜的情况


直到这里,我们说的都是Camera的Projection,他跟viewport是有区别的,后面执行的  

leftEyeUndistortedViewport = Profile.GetLeftEyeVisibleScreenRect(rect);
leftEyeDistortedViewport = leftEyeUndistortedViewport;

处理的是camera的viewport,即在屏幕上划定一个矩形,camera捕捉到的画面都将在矩形中显示,这在游戏分屏技术中用的很多(比如两个玩家对抗,在一个屏幕中显示两个画面),当然viewport的划定与是否需要distort无关,这样leftEyeDistortedViewport与leftEyeUndistortedViewport 应该是一个值。


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


不重要:

public virtual bool SupportsNativeDistortionCorrection(List<string> diagnostics)

//一个用来判断是否支持NativeDistortionCorrection的方法,需要满足两个条件,支持RenderTexture和Unity4.5版本以上,才能支持透镜扭曲的效果


public virtual bool SupportsNativeUILayer(List<string> diagnostics)

//同理,但是这个NativeUILayer仅需要Unity4.5以上版本一个条件


public abstract void SetDistortionCorrectionEnabled(bool enabled);

public abstract void SetVRModeEnabled(bool enabled);

public abstract void SetAlignmentMarkerEnabled(bool enabled);

public abstract void SetSettingsButtonEnabled(bool enabled);

public abstract void SetNeckModelScale(float scale);

public abstract void SetAutoDriftCorrectionEnabled(bool enabled);

public abstract void SetStereoScreen(RenderTexture stereoScreen);

//一堆抽象方法,后面会重写的,现在先放着吧


public abstract void UpdateState();

public abstract void UpdateScreenData();

public abstract void Recenter();

public abstract void PostRender(bool vrMode);

//又来四个抽象方法,从字面意思上看,是状态更新,屏幕数据更新,重新定位中心,完成渲染后


public virtual void OnPause(bool pause)

//当停止了,就不再UpdateScreenData()

public virtual void OnFocus(bool focus)

public virtual void Reset()

public virtual void OnApplicationQuit()

public virtual void Destroy()

//由字面意思就可以理解


OK!!BaseVRDevice就看到这里,这个类中还是有比较关键的函数的,后面会继续看子类的代码,有些地方比如怎样去计算Projection的4*4的矩阵,投影为什么要用4*4矩阵可能涉及到数学的东西了,我也不太明白,只能是看懂了以后再作介绍。

你可能感兴趣的:(Android开发,插件,unity3d,手游,虚拟现实)