Zion插件分析——相机
发表于2016-10-31
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游戏程序交流群:484290331