Google在2016年5月的I/O开发者大会上发布了DaydreamVR 平台,在Android N的SDK里也集成了部分Daydream相关的源码。
想做DaydreamAR开发的同学,得先配齐硬件:一台支持Daydream的手机,以及VR头盔。
Google官网(https://vr.google.com/daydream/phones/)上公布的目前已确认支持Daydream的手机有:Google Pixel、Pixel XL、联想的Moto Z、华为的Mate 9 Pro、中兴Axon 7和华硕 ZenFone AR手机。
Daydream要求显示的延时在20ms以内,delay过长会很容易导致眩晕。这个delay的定义是从人的头部移动开始到头显设备(HMD)的光学信号映射到人眼上面全部的等待时间(latency time)。
要达到20ms的要求,Daydrem手机对硬件要求也比较高:包括sensor、显示屏、CPU及GPU等。高通确认他们只在高通8996或8998平台上跑通过;另外,华为Mate9因为使用LCD屏,而不是OLED,所以也不在列表中。
不过,话说高通8996或8998的手机也还真不便宜,专门整一台也不是随便的事。好在Android手机升级到N之后也能跑Daydream APP,只是跑得不够好,要想跑得好,得打上google一堆patch,还有平台厂商发布的库。所以,特别有兴趣的同学得开始准备攒钱了。。。
SDK方面,Daydream为应用开发者分别提供了Unity、Unreal 4、Android和iOS平台的SDK。
相关资料:
Google Daydream开发者网址:https://vr.google.com/daydream/developers/
GoogleVR SDK for Android:git clonehttps://github.com/googlevr/gvr-android-sdk.git
SDK里附带了几个sample,看看里面附带的TreasureHunt代码,发现似乎和我们以前用OpenGL ES写的应用差不多嘛。
以前要画一个3D图,得先自己定义一个MySurfaceView类继承自GLSurfaceView,在这里可以重载一些和用户输入事件有关的方法,并设置自定义的渲染器Render,该渲染器执行真正的渲染任务。
如上图,自定义的渲染器需实现GLSurfaceView.Renderer的3个接口:
- onSurfaceCreated() :在开始渲染的时候被调用。
- onSurfaceChanged():该方法在surface大小改变时被调用。
- onDrawFrame():每帧更新时该方法都会被调用。
TreasureHunt 这个例子大致也是这样做的,同样自定义一个render来处理渲染任务,只是没有处理手柄的事件。不同的是,这个Render需要实现GvrView.StereoRenderer接口,还需要继承GvrActivity。
SDK定义一个继承自Activity的GvrActivity,它用来处理和手柄交互及渲染相关的事情。
GvrView.StereoRenderer和GLSurfaceView.Render很相似,只是多了3个接口:
- onNewFrame():画面更新时更新模型位置信息。
- onDrawEye():画面更新时需要分别画左、右眼内容;由于左、右眼的观察位置不同,调onNewFrame()后需要调两次onDrawEye(),下发实际的draw call。上面的onDrawFrame()相当于分成了两步:onNewFrame()和onDrawEye()。
- onRendererShutdown():应该是结束VR立体渲染模式时调用。
Talk is cheap, show me the code. 说了这么多,接下来就看看怎么把一个传统的APP改成Daydream APP吧。
手上有一个之前用openGL画的地月系,在Moto Z上跑起来是这样子:
下面动手开始修改:
1. AndroidManifest.xml
首先followGoogle 文档在AndroidManifest.xml中添加相关属性。(参考:https://developers.google.com/vr/daydream/guides/vr-manifest)
加上enableVrMode属性后,在启动该应用时,系统会自动切到Vr 模式。
- 一般需要强制VR应用为横屏模式,并且为全屏显示。
- VR APP需要运行在Android N上。
- android:configChanges="orientation|keyboardHidden|screenSize"并非必须指定。
- 如果应用支持Daydream或 Cardboard,则需指定相应的category。
2. build.gradle
在build.gradle中添加相应的依赖库。
目前最新的版本是1.20.0。
SDK中libraries列出了完整的列表:
我们这个例子比较简单,没有使用到audio、controller等模块,因此只添加了base这一个库。
3. MainActivity.java
接下来就是如何修改源码了。
1) 定义gvr_view
在layout中加上:
在MainActivity::onCreate()里会加载这个layout。
2) 修改MainActivity的继承关系
public class MainActivity extends GvrActivity implementsGvrView.StereoRenderer
3)在onNewFrame()中设置camera的视角位置。
camera视角信息对左右眼都是一样的,所以在onNewFrame()更新。这里设置固定的视角位置。
headTransform.getHeadView(headView,0);用来获取头盔的转向信息,然后传进vertex shader,使得旋转头盔时画面有移动的效果。后面并没有用到headView,所以画面一直在屏幕中间。
4)在onDrawEye()中发draw call
需要根据把左右眼观察位置分别计算:
上面projMatrix= eye.getPerspective(Z_NEAR,Z_FAR); 用来设置透视投影,这里只指定了观察点距近平面和远平面的距离,left、right、top、right参数封装在API里,应该是和屏幕宽高值相关。
这里视角位置要同时考虑camera和eye的信息。通过eye.getEyeView()获取眼睛的位置信息,然后乘以在onNewFrame()里设置的camera视角位置,再传给顶点渲染器作进一步计算。
代码大致就改完了,Moto Z上跑起来是这样:
呐,左右两幅图就出来了,这两幅图大致是一样的,稍微有些偏差,毕竟还得拼成3D呢,另外左右眼的视角位置也略有差别。当然这里不仅仅是同一幅图画两次这么简单,在帧率不够的时候,可以使用异步时间扭曲技术(Asynchronous Timewarp)来减少latency。举个栗子,画出一帧内容后,再根据最新的pose信息或者预测的pose,造出下一帧内容。在三星的Gear VR上就引进了这个技术。
附上源码,https://github.com/may627/VrEarthMoon
这里只作了个简单的例子,还有很多东西都没有考虑,比如头盔转动时移动画面、立体音效、和手柄的交互等,值得继续深入探究。