VR
虚拟现实(Virtual Reality)技术是一种可以创建和体验虚拟世界的计算机仿真系统,它利用计算机生成一种模拟环境,是一种多源信息融合的、交互式的三维动态视景和实体行为的系统仿真, 使用户沉浸到该环境中。
VR产品
Google VR for Android
Google VR SDK同时支持DayDream和CardBoard。 包含了一些用于创建App的简单API和支持DayDream手机、DayDream控制器的复杂API。同时涵盖了Android、iOS、Unity三大平台。
SDK下载
sdk地址:
git clone https://github.com/googlevr/gvr-android-sdk.git
运行官网项目
在使用之前,我们先运行下官网的项目。导入gvr-android-sdk到Android Studio, 同步的过程中需要下载很多库所以会比较耗时,另外可能出现build失败的情况,这时可以尝试使用本地的gradle来编译。
gradle版本最好为最新版本。
项目展示了印加文明遗迹马丘比丘的全景图(Panorama):
在真机上运行时,会有一个Cardboard选项。点击上图红色框中的按钮即可进入Cardboard模式。
Demo代码的主要逻辑就是加载一张全景图放入VrPanoramaView中。
panoOptions = new Options(); panoOptions.inputType = Options.TYPE_STEREO_OVER_UNDER;//图像类型为立体图像 istr = assetManager.open("andes.jpg");//加载assets目录下的全景图 panoWidgetView.loadImageFromBitmap(BitmapFactory.decodeStream(istr), panoOptions);
全景图片andes是由两张图片组成,上面一张是给左眼看,下面一张是给右眼看。
Demo的声音的实现是加载assets目录下的congo.mp4视频到VrVideoView中。
videoWidgetView.loadVideoFromAsset("congo.mp4", options);
congo.mp4的视频内容也是分为上下部分,上面给左眼看,下面给右眼看。
treasurehunt
treasurehunt展示了一个简单到离谱的寻宝游戏,当vr世界中矩形变成金黄色时,点击手机屏幕或者使用Daydream的控制器,即可完成寻宝。游戏还伴有音效。
controllerclient
接收DayDream控制器输入示例
videoplayer
使用Asynchronous Reprojection播放视频示例
全景图(PanoramaActivity)
1. 配置build.gradle
//修改minSDK为19 minSdkVersion 19 //添加依赖 compile 'com.google.vr:sdk-panowidget:1.20.0'
2. 配置AndroidManifest.xml
由于全景图占内存较大,当加载多张全景图时可能存在内存溢出的情况,所以这里开启largeHeap。
<application android:largeHeap="true"></application>
3. 加载全景图
private class LoadPanoramaImageTask extends AsyncTask<Void, Void, Bitmap> { @Override protected Bitmap doInBackground(Void... params) { try { //加载assets目录下的全景图片 AssetManager assetManager = getAssets(); InputStream open = assetManager.open("andes.jpg"); return BitmapFactory.decodeStream(open); } catch (IOException e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Bitmap bitmap) { VrPanoramaView.Options options = new VrPanoramaView.Options(); //图片类型为立体图像 options.inputType = VrPanoramaView.Options.TYPE_STEREO_OVER_UNDER; mVrPanoramaView.loadImageFromBitmap(bitmap, options); } }
4. 生命周期管理
@Override protected void onPause() { mVrPanoramaView.pauseRendering(); super.onPause(); } @Override protected void onResume() { super.onResume(); mVrPanoramaView.resumeRendering(); } @Override protected void onDestroy() { // Destroy the widget and free memory. mVrPanoramaView.shutdown(); // The background task has a 5 second timeout so it can potentially stay alive for 5 seconds // after the activity is destroyed unless it is explicitly cancelled. if (mLoadPanoramaImageTask != null) { mLoadPanoramaImageTask.cancel(true); } super.onDestroy(); }
5. 事件监听
mVrPanoramaView.setEventListener(mVrPanoramaEventListener); private VrPanoramaEventListener mVrPanoramaEventListener = new VrPanoramaEventListener() { /** * 点击回调 */ @Override public void onClick() { super.onClick(); } /** * 加载数据成功回调 */ @Override public void onLoadSuccess() { super.onLoadSuccess(); } /** * 加载数据失败回调 */ @Override public void onLoadError(String errorMessage) { super.onLoadError(errorMessage); } };
360 视频
1. 配置build.gradle
//添加依赖// minSdkVersion 19 //已经配置则忽略 compile 'com.google.vr:sdk-videowidget:1.20.0'
2. 配置AndroidManifest.xml (已经配置则忽略)
<application android:largeHeap="true"></application>
3. 加载视频
VrVideoView.Options options = new VrVideoView.Options(); //视频类型为立体视频 options.inputType = VrVideoView.Options.TYPE_STEREO_OVER_UNDER; try { mVrVideoView.loadVideoFromAsset("congo.mp4", options); } catch (IOException e) { e.printStackTrace(); }
4. 生命周期管理
@Override protected void onPause() { super.onPause(); // Prevent the view from rendering continuously when in the background. mVrVideoView.pauseRendering(); // If the video is playing when onPause() is called, the default behavior will be to pause // the video and keep it paused when onResume() is called. isPaused = true; } @Override protected void onResume() { super.onResume(); mVrVideoView.resumeRendering(); } @Override protected void onDestroy() { // Destroy the widget and free memory. mVrVideoView.shutdown(); super.onDestroy(); }
5. 事件监听
private VrVideoEventListener mVrEventListener = new VrVideoEventListener() { @Override public void onLoadError(String errorMessage) { Toast.makeText(VrVideoActivity.this, "onLoadError", Toast.LENGTH_SHORT).show(); } @Override public void onLoadSuccess() { Toast.makeText(VrVideoActivity.this, "onLoadSuccess", Toast.LENGTH_SHORT).show(); } @Override public void onNewFrame() { } @Override public void onCompletion() { Toast.makeText(VrVideoActivity.this, "onCompletion", Toast.LENGTH_SHORT).show(); mVrVideoView.seekTo(0);//播放结束后重新开始播放 } @Override public void onClick() { togglePause();//点击暂停或者播放 } };
Demo
本demo实现一个全景图控制显示的效果。这里主要演示下VrPanoramaView控件的使用。
初始化VrPanoramaView
private void initPanoramaView() { mVrPanoramaView = (VrPanoramaView) findViewById(R.id.vr_panorama_view); //mVrPanoramaView.setDisplayMode(VrWidgetView.DisplayMode.FULLSCREEN_MONO);//全屏模式,弹出一个全屏的Dialog mVrPanoramaView.setInfoButtonEnabled(false);//隐藏信息按钮 mVrPanoramaView.setStereoModeButtonEnabled(false);//隐藏cardboard按钮 mVrPanoramaView.setFullscreenButtonEnabled(false);//隐藏全屏按钮 mUrl = getIntent().getStringExtra("url"); OkGo.get(mUrl).cacheKey(mUrl).tag(mUrl).execute(new BitmapCallback() { @Override public void onSuccess(Bitmap bitmap, Call call, Response response) { VrPanoramaView.Options options = new VrPanoramaView.Options(); //设置图片类型为单通道图片 options.inputType = VrPanoramaView.Options.TYPE_MONO; mVrPanoramaView.loadImageFromBitmap(bitmap, options); } }); }
初始化MediaPlayer
/** * 如果有音乐数据则播放音乐 */ private void initMediaPlayer() { String mp3 = getIntent().getStringExtra("mp3"); if (mp3 != null) { mMediaPlayer = new MediaPlayer(); try { mMediaPlayer.setDataSource(this, Uri.parse(mp3)); mMediaPlayer.setOnPreparedListener(mOnPreparedListener); mMediaPlayer.prepareAsync(); } catch (IOException e) { e.printStackTrace(); } } }
生命周期管理
@Override protected void onResume() { super.onResume(); mVrPanoramaView.resumeRendering(); } @Override protected void onPause() { super.onPause(); mVrPanoramaView.pauseRendering(); if (mMediaPlayer != null) { mMediaPlayer.pause(); } } @Override protected void onDestroy() { super.onDestroy(); mVrPanoramaView.shutdown(); OkGo.getInstance().cancelTag(mUrl);//取消请求 if (mMediaPlayer != null) { mMediaPlayer.release(); mMediaPlayer = null; } }
VR View
VR View允许开发者在网站或app中嵌入360度全景多媒体文件(图片和视频)。这个技术主要是提供给传统的开发者,可以通过在app中添加动态内容来提升用户体验。比如旅行或房地产类型的app,可以让用户足不出户就体验到虚拟场景。VR View同时支持web和Native app。
VR View同时支持单声道和立体声的图片和视频,但是图片和视频的存储格式必须为equirectangular-panoramic格式,这是一种常见的摄像头尺寸支持的格式。
VR view是客户端的显示技术,那么我们怎么去拍摄符合VR view要求的内容呢?
真实世界中的拍摄
Cardboard Camera App ,用这个app可以和方便的拍摄360照片,拍摄完成后需要通过conversion tool来创建一个立体声的360照片。
Ricoh Theta 一个非常流行的,相对廉价的用来拍摄单声道相片和视频的设备。
1,CG 拍摄
VR view的图片不仅仅限制在真实世界中拍摄。CGI(计算机合成图像)可以为所有的东西生成360度全景照片和视频,最流行拍摄解决方案如下:
360 Panorama Capture for Unity Unity插件
Unreal 虚幻引擎
Domemaster3D for Maya
Renderman 一个开源库
Rendering Omnidirectional Stereo Content
下面用伪代码展示如何用VrPanoramaView和VrVideoView展示360度图片和视频。
<com.google.vr.sdk.widgets.pano.VrPanoramaView android:id="@+id/pano_view" android:layout_margin="5dip" android:layout_width="match_parent" android:scrollbars="@null" android:layout_height="250dip"/> <com.google.vr.sdk.widgets.video.VrVideoView android:id="@+id/video_view" android:layout_width="match_parent" android:scrollbars="@null" android:layout_height="250dip"/>
//展示全景图片 panoWidgetView = (VrPanoramaView) findViewById(R.id.pano_view); //图片加载结果回调 private class ActivityEventListener extends VrPanoramaEventListener { /** * Called by pano widget on the UI thread when it's done loading the image. */ @Override public void onLoadSuccess() { loadImageSuccessful = true; } /** * Called by pano widget on the UI thread on any asynchronous error. */ @Override public void onLoadError(String errorMessage) { loadImageSuccessful = false; Toast.makeText( SimpleVrPanoramaActivity.this, "Error loading pano: " + errorMessage, Toast.LENGTH_LONG) .show(); Log.e(TAG, "Error loading pano: " + errorMessage); } } //设置图片加载监听 panoWidgetView.setEventListener(new ActivityEventListener()); //加载图像的Bitmap数据 istr是一个文件输入流 //VrPanoramaView.Options是用来配置图片格式的 // TYPE_MONO 包含单一圆柱的全景图像 // TYPE_STEREO_OVER_UNDER 图像包含了两个大小相等、垂直相交的圆柱全景图像。上图展示给左眼,下图展示给右眼。 panoWidgetView.loadImageFromBitmap(BitmapFactory.decodeStream(istr), panoOptions); //展示全景视频 videoWidgetView = (VrVideoView) findViewById(R.id.video_view); videoWidgetView.setEventListener(new ActivityEventListener()); //VrVideoEventListener同时对视频加载和操作的回调 private class ActivityEventListener extends VrVideoEventListener { /** * Called by video widget on the UI thread when it's done loading the video. */ @Override public void onLoadSuccess() { Log.i(TAG, "Sucessfully loaded video " + videoWidgetView.getDuration()); loadVideoStatus = LOAD_VIDEO_STATUS_SUCCESS; seekBar.setMax((int) videoWidgetView.getDuration()); updateStatusText(); } /** * Called by video widget on the UI thread on any asynchronous error. */ @Override public void onLoadError(String errorMessage) { // An error here is normally due to being unable to decode the video format. loadVideoStatus = LOAD_VIDEO_STATUS_ERROR; Toast.makeText( SimpleVrVideoActivity.this, "Error loading video: " + errorMessage, Toast.LENGTH_LONG) .show(); Log.e(TAG, "Error loading video: " + errorMessage); } @Override public void onClick() { togglePause(); } /** * Update the UI every frame. */ @Override public void onNewFrame() { updateStatusText(); seekBar.setProgress((int) videoWidgetView.getCurrentPosition()); } /** * Make the video play in a loop. This method could also be used to move to the next video in * a playlist. */ @Override public void onCompletion() { videoWidgetView.seekTo(0); } } //支持两种加载方式,从asset文件夹中或从一个uri中读取视频数据 //VrVideoView.Options同样是用来配置视频格式的 videoWidgetView.loadVideoFromAsset("congo.mp4", options); 或 videoWidgetView.loadVideo(fileInformation[0].first, fileInformation[0].second) //除此外还有一些控制渲染、播放的方法: videoWidgetView.pauseRendering(); videoWidgetView.resumeRendering(); videoWidgetView.shutdown(); videoWidgetView.playVideo(); videoWidgetView.pauseVideo(); //shutdown()一定要在onDestroy()时调用,用来释放占用内存。
附:VRDemo