Zion插件分析——相机

发表于2016-10-31
评论0 2k浏览
  Zion插件中对相机的设计更多地参考了SteamVR中相机的模式。为了兼容各个平台,设计了一个相机虚基类,具体到不同的VR平台,各自派生实现。oculus的实现最为简单,它无需我们手动渲染,因为Unity的内置VR可以对其自动渲染。

一、继承关系图


  可以看到,目前有Oculus,SteamVR以及暴风魔镜三个平台的定制相机。拜Unity的VR支持所赐,Oculus的结构最为简单,只是实现了Setup方法,并无其他内容。

二、ZionCameraBase.cs
  相机虚基类,渲染流程中的部分流程可以定制,具体的VR平台需要派生并实现。
 

  如果需要手动渲染,那么必须要实现GetRenderTexture方法,直接使用基类的此方法会有错误提示。

?
1
2
3
4
if (ZionVR.instance.renderManually)
{
    Debug.LogError("this method must be implemented for manually render");
}

三、 ZionCameraSteamVR.cs
  SteamVR相机的具体实现脚本。在ZionVR中,如果检测到HTC Vive的连接,那么外界调用CreateCamera时,实例化的就是该类。
字段
  _sceneTexture:RenderTexture,共享渲染纹理
  sceneResolutionScale:渲染纹理宽高缩放比
  渲染纹理 Render Texture
  渲染纹理是一种特殊的纹理类型,它会在在运行时创建和更新。使用方法是,首先创建一个新的渲染纹理并且要指定一个摄像机进行渲染。然后你可以像使用常规的纹理一样,在材质中使用此渲染纹理。在Unity Standard Assets中,Water的预置体就是一个使用渲染纹理来实时进行反射和折射的例子。
  例如,给场景中的MainCamera指定一个渲染纹理,而让场景中某个物体的材质使用该纹理,那么相机中的画面,就会实时地渲染到物体上去。
 

方法
  ZionCameraSteamVR ()
  构造方法
  SubmitImage(Zion.EEyeType eye)
  重写方法。调用SteamVR_Utils中的方法,提交渲染后的图像到底层显示,提交顺序是由从左到右。
  

  EEyeType是枚举类型,所以直接用位运算判断是左还是右即可。
  EarySubmit()
  重写方法,基类中默认是返回false,即后置提交。但是SteamVR平台要求前置提交,所以在此返回true。
  Setup(GameObject gameObject)
  重写方法。对相机进行相关设置。核心功能为:
1、获取gameObject上的Camera组件camera。
2、获取ZionVR中保存的全局参数。
3、使用vr参数,对camera进行设置,设置完成后,禁用之,我们不希望Unity帮我们自动渲染,随后会手动对相机进行渲染。
4、获取父对象的Camera组件headCam,把camera的hdr、renderingPath复制给它,headCam会在PC端同步VR头盔看到的图像(如果 ZionVR中的needCompanionWindow为true)。
  GetRenderTexture(Zion.EEyeType eye, bool hdr)
重写方法。如果是手动渲染,那必须重写。创建/更新共享渲染纹理,保存在全局字段_sceneTexture里。流程为
1、获取ZionVR中保存的参数,宽和高。
2、根据宽高值、hdr以及sceneResolutionScale,得到新建渲染纹理所需要的参数,分辨率等。![] (http://oeryukwnd.bkt.clouddn.com/QQ%E6%88%AA%E5%9B%BE20161011165110.png)
3、如果渲染纹理不为null,即已经生成过,但是参数发生了变化,那么销毁之,并置_sceneTexture=null;。
4、用最新的参数新建渲染纹理。
5、返回最新的共享渲染纹理_sceneTexture。

四、ZionCameraOculusVR.cs
  Oculus相机的具体实现,Unity会自动在底层将它渲染好,无需我们手动渲染,所以内容很简单。但是这样一来反而会遇到一些问题。
  在Oculus的底层,会将标记了“MainCamera”的对象当成追踪对象,对其进行位移处理,以及相机渲染。而在我们的[CameraRig]里,Camera(head)和Camera(eye)的标记都是MainCamera,而我们的机制是,只认为Camera(head)是可追踪对象,只对它进行位移处理,而Camera(eye)作为子对象会跟随移动。那么这样一来,当连接Oculus头盔时,就会发生混淆,头显的移动会出现偏差。
 
 

  本类中只重写了一个方法,就是Setup()。它的作用就是处理上面说到的问题。
  Setup(GameObject gameObject)
1、获取Camera(eye)的相机camera。
2、如果camera标记了“MainCamera”,那么取消标记,设它的tag为“Untagged”。
3、找到camera的父亲(Camera(head))上的相机组件headCam,如果没有就添加一个。
4、将headCam的Tag设为“MainCamera”。
5、获取ZionVR的实例vr及其参数。
6、将camera和vr的参数复制到headCam中去,并保证其可用(enabled为true)。

五、ZionCameraMojing.cs
  魔镜相机的具体实现
字段
 · screenNum: 共享渲染纹理的数量,最多6个(即三对)
 · stereoScreen: 共享渲染纹理数组,初始化6个(即三对)
 · leftTextureId: 当前左眼图像纹理id
 · rightTextureId: 当前右眼图像纹理id
方法
 · ZionCameraMojing ()
构造方法
 · SubmitImage(Zion.EEyeType eye)
  提交渲染好的图像到底层显示,在右眼渲染后与左眼一起提交。提交图像的时候,会通过leftTextureId和rightTextureId指针来得到共享渲染纹理的资源(也就是提交两副图像),交给底层显示。
  MojingSDK.SetTextureID (leftTextureId, rightTextureId);
 · EarySubmit()
  返回false,即渲染后再提交图像
 · Setup(GameObject gameObject)
  魔镜中,对相机的Setup和SteamVR一样,手动渲染,复制参数等。但是在SteamVR里,渲染纹理的创建放在GetRenderTexture里进行,且只需要一个渲染纹理。而魔镜平台,要在Setup时成对新建渲染纹理,存储在纹理数组里,GetRenderTexture里只根据index来取。
  首先,要根据条件,来设置数组的大小(存放几对渲染纹理)

?
1
2
3
4
5
6
7
8
9
10
11
12
// TODO 处理不需要畸变的情况
// 创建渲染纹理
if (MojingSDK.Unity_IsEnableATW())
{
// 在需要进行帧预测的情况下,会多预测两帧
  screenNum = 6;
}
else
{
  // 不进行预测的情况下,只需要左右眼两个纹理
  screenNum = 2;
}

  然后,直接从底层获取分辨率

?
1
int size = MojingSDK.Unity_GetTextureSize();

  最后遍历数组,新建纹理,设置参数等

?
1
new RenderTexture(size, size, 24, RenderTextureFormat.Default);

 · GetRenderTexture(Zion.EEyeType eye,bool hdr)
  在MoJingSDK中,参考MojingEye.cs中的方法SetTargetTex(Mojing.Eye eye),根据眼睛的类型,index,从渲染纹理数组stereoScreen中取出相应的渲染纹理。并且取到纹理的指针,交给leftTextureId和rightTextureId引用。
  index默认为0,当

?
1
MojingSDK.Unity_IsATW_ON()==true

  index就要从MojingSDK底层来取

?
1
2
// 帧预测情况下,从底层获取当前可用(未被占用)的纹理索引
index = MojingSDK.Unity_ATW_GetModelFrameIndex();

  如果是左眼,那么取偶数索引纹理

?
1
2
texture = stereoScreen [index * 2];
leftTextureId = (int)texture.GetNativeTexturePtr();

  如果是右眼,那么取奇数索引纹理

?
1
2
texture = stereoScreen[index * 2 + 1];
rightTextureId = (int)texture.GetNativeTexturePtr();

  数组里是一对一对来存放渲染纹理的,自然是按照左、右、左、右的顺序,所以偶数为左眼,奇数为右眼。

六、ZionCamera.cs
  真正绑定在VR相机下的脚本。其功能与各VR平台的相机脚本相同,对比各平台的相机,将共性的部分,直接放在ZionCamera.cs中,而需要各自区分实现的代码,就由ZionCameraBase的实现类来自己实现,在ZionCamera中调用,这样就可以根据设备的类型,组合出完整的相机脚本。
 

字段
 · blitMaterial: Material材质,
 · cameraImpl: 各平台相机的具体实现
 · flip: 图像反转脚本
 · values: Hashtable,用来存放脚本中的字段及其值,仅用于ForceLast()方法
方法
 · Awake()
1、获取相机下的ZionCameraFlip脚本。
2、确保ZionCamera.cs在所有组件之后,使得总是可以对图像做最后的处理
3、查看Unity是否支持VR,并设置到ZionVR.cs中去。
  Unity支持VR的选项在Edit->Project Settings->Player->PlayerSettings->Other Settings
 

?
1
ZionVR.instance.useNativeVRSupport = UnityEngine.VR.VRSettings.enabled;

  可以看到现在Unity是支持Oculus的自动渲染的,所以使用Oculus设备使,勾选此项,ZionVR.cs中的

?
1
renderManually = false;

  那么随后,就不会对相机进行手动渲染。
 · ForceLast()
  保证当前脚本是当前对象最后一个组件,如果不是,就记录当前脚本的字段值,在values中临时存放,然后删除自身,重新添加,再还原values中的字段值。
 · OnEnable()
1、获取当前设备对应的相机实现类,令cameraImpl引用之。
2、调用cameraImpl的Setup()方法,配置Camera组件的参数等。
3、如果使用Unity支持VR,不手动渲染的话,就禁用自身。
4、在Assets里找到名为“Zion_Blit”的Shader,用它新建一个材质。
5、将ZionCamera.cs添加到ZionRender.cs中的相机列表。
 · OnDisable()
  自身被禁用时,从ZionRender的相机列表移除自己。
 · OnPreRender()
  在Unity的渲染流程中,此方法会在相机开始渲染场景之前回调。
  这里主要对图像翻转脚本进行处理。

?
1
2
3
4
5
6
7
8
9
10
11
if (flip)
{
   // 需要对图像进行翻转的条件是:
   // 1、手动渲染模式
   // 2、顶层相机(即最后渲染,只需要将最后渲染出来的图像进行翻转即可)
   // 3、非OpenGL图形库(通常就是针对Windows上的Direct3D而言,Direct3D与OpenGL在Y方向的定义是反的,所以需要翻转)
   flip.enabled = (ZionVR.instance.renderManually && ZionRender.Top() == this && !SystemInfo.graphicsDeviceVersion.StartsWith("OpenGL"));
   // TODO Mojing都需要翻转?还是和图形系统相关?
   flip.enabled |= (ZionVR.instance.vrType == EVRType.Mojing);
}
OnRenderImage(RenderTexture src, RenderTexture dest)

  在Unity的渲染流程中,此方法会在相机完成场景的渲染后回调。此时渲染既然已经完成,就可以将图像提交到底层,在VR眼镜/头显中显示。
  Unity引擎里对渲染后期处理效果很多,如Bloom、运动模糊、景深等效果。实现过程是在作用的摄像机上加脚本并实现OnRenderImage方法,Graphics.Blit(source, destination, material);使用材质material的shader处理帧缓存的数据,再拷贝回屏幕帧缓存。
  根据cameraImpl.EarySubmit()返回的值,决定是前置提交还是后置提交,图像的提交只需调用

?
1
cameraImpl.SubmitImage(ZionVR.instance.monoscopic ? EEyeType.Both : ZionRender.eye);

  如果是前置提交,那就先调用SubmitImage,再后期处理效果;否则就等后期处理效果结束后再提交图像。
腾讯GAD游戏程序交流群:484290331Gad游戏开发核心用户群

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引