这一节我介绍的主要内容有
- SteamVR渲染机制
用Unity做游戏开发的核心之一就是图形渲染,做VR开发当然也是一样,在这一节,我们就来看看SteamVR的图形渲染原理。SteamVR_Render.cs是SteamVR图形渲染的核心,该类位于Scripts文件夹中。接下来我来详细分析一下这个类。
下面是该脚本的OnEnable()方法
void OnEnable()
{
StartCoroutine("RenderLoop"); //启动渲染
SteamVR_Utils.Event.Listen("input_focus", OnInputFocus); //输入监听
SteamVR_Utils.Event.Listen("Quit", OnQuit); //退出监听
SteamVR_Utils.Event.Listen("RequestScreenshot", OnRequestScreenshot); //截屏监听
var vr = SteamVR.instance; //获取SteamVR实例,用来判断头显设备是否安装好
if (vr == null)
{
enabled = false;
return;
}
/*
public enum EVRScreenshotType
{
None = 0,
Mono = 1,
Stereo = 2,
Cubemap = 3,
MonoPanorama = 4,
StereoPanorama = 5,
}
EVRScreenshotType.StereoPanorama为默认的截屏类型
*/
var types = new EVRScreenshotType[] { EVRScreenshotType.StereoPanorama};
OpenVR.Screenshots.HookScreenshot(types); //初始化截屏,设置截屏类型
}
可以看到里面最主要的方法是StartCoroutine(“RenderLoop”),它启动了渲染循环,我们再来分析这个方法
private IEnumerator RenderLoop()
{
while (true) //死循环用来不断的进行渲染
{
yield return new WaitForEndOfFrame(); //等待所有相机和GUI都渲染完
if (pauseRendering) //渲染暂停,用来实现VR运行时暂停的需求
continue;
var compositor = OpenVR.Compositor; //获取合成器,合成器的作用用来简化图像显示,是一个核心类
if (compositor != null)
{
if (!compositor.CanRenderScene())
continue;
compositor.SetTrackingSpace(trackingSpace); //设置跟踪控件类型,默认为站姿
/*这段if end的代码主要Unity5.0及以上版本中,通过GetLastPoses()不断的获取设备位置,然后通过SteamVR_Utils.Event.Send方法发送通知,头显和手柄中的监听器这个事件,从而不断的显示界面中更新位置*/
#if (UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0)
SteamVR_Utils.QueueEventOnRenderThread(SteamVR.Unity.k_nRenderEventID_WaitGetPoses);
SteamVR.Unity.EventWriteString("[UnityMain] GetNativeTexturePtr - Begin");
SteamVR_Camera.GetSceneTexture(cameras[0].GetComponent<Camera>().hdr).GetNativeTexturePtr();
SteamVR.Unity.EventWriteString("[UnityMain] GetNativeTexturePtr - End");
compositor.GetLastPoses(poses, gamePoses);
SteamVR_Utils.Event.Send("new_poses", poses);
SteamVR_Utils.Event.Send("new_poses_applied");
#endif
}
var overlay = SteamVR_Overlay.instance;
if (overlay != null)
overlay.UpdateOverlay(); //更新overlay
RenderExternalCamera(); //渲染外部相机
#if (UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0)
var vr = SteamVR.instance;
RenderEye(vr, EVREye.Eye_Left); //开始渲染左右眼
RenderEye(vr, EVREye.Eye_Right);
// Move cameras back to head position so they can be tracked reliably
//还原相机位置,在上面渲染左右时对相机位置进行了调整,所以这儿要还原
foreach (var c in cameras)
{
c.transform.localPosition = Vector3.zero;
c.transform.localRotation = Quaternion.identity;
}
if (cameraMask != null)
cameraMask.Clear();
#endif
}
}
总结一下这个方法里面的渲染流程①等待相机和GUI的渲染完成->②设置跟踪空间->③获取设备位置,通知更新->④渲染外部相机->⑤渲染左右眼
看完了RenderLoop中的循环,我们再来看看左右眼画面的渲染,也就是我们在VR头盔中看到的画面。
void RenderEye(SteamVR vr, EVREye eye)
{
int i = (int)eye;
SteamVR_Render.eye = eye;
if (cameraMask != null)
cameraMask.Set(vr, eye);
foreach (var c in cameras)
{
/*左右眼的视差使我们看到的世界有了立体的效果,VR的成像原理也是这样,左右两块镜片显示的画面是有些微差别的,所以这儿对左右眼中的画面进行了位置的修改*/
c.transform.localPosition = vr.eyes[i].pos;
c.transform.localRotation = vr.eyes[i].rot;
// Update position to keep from getting culled
cameraMask.transform.position = c.transform.position;
var camera = c.GetComponent<Camera>();
//将SteamVR_Camera中的纹理设置为实际渲染相机的纹理
camera.targetTexture = SteamVR_Camera.GetSceneTexture(camera.hdr);
int cullingMask = camera.cullingMask;
if (eye == EVREye.Eye_Left)
{
//渲染左眼时把右眼特有的mask去掉,加上左眼特有的mask
camera.cullingMask &= ~rightMask;
camera.cullingMask |= leftMask;
}
else
{
//渲染右眼时把左眼特有的mask去掉,加上右眼特有的mask
camera.cullingMask &= ~leftMask;
camera.cullingMask |= rightMask;
}
//手动调用相机的Render方法
camera.Render();
camera.cullingMask = cullingMask;
}
}
到这里,我们就知道原来VIVE是将一个相机上的图像变形后分别显示再左右眼的显示屏上的。也知道了SteamVR的渲染流程。所以我们也就可以实现自由绘制左右眼的显示内容,控制VR渲染流程这些功能了。