VR的原理和实现
一、 VR内容制作
VR内容场景的呈现分为两种情况:实景拍摄与3D建模场景制作。其中,3d建模场景制作又包含了“可以在VR里行走”和“不能在VR里行走”两种情况。
1.1全景拍摄的流程
第一步 拿到制作需求后,设计师进行头脑风暴思考场景内容,场景切换路径,界面里的文案交互逻辑,输出策划文档。
第二步 摄制团队在实景进行视频或全景拍摄,输出全景视频或全景图。
第三步 设计师进行视频剪辑或全景图拼接,及后期处理输出全景视频或全景图。
第四步 设计师制作交互动画及VR里的2d界面输出交互动画png序列,2d界面元素切图。
第五步 程序员写代码实现交互逻辑输出可交互的VR内容。
第六步 程序员设计师进入VR场景进行逻辑测试并不断完善内容。
1.2 3D建模场景制作的流程
第一步 设计师进行头脑风暴思考场景内容,场景切换路径,界面里的文案交互逻辑,输出策划文档。
第二步 设计师用草图或草模表现场景输出场景示意。
第三步 3d模型师根据场景示意图进行建模输出3d模型。
后面几步同1.1
全景拍摄需要用到全景摄像头到实地进行拍摄,3D建模则一般使用unity3D,UE等软件进行3D建模来构建虚拟场景,并在虚拟场景中设置全景镜头
1.3 全景素材的后期处理
不管是拍摄或者建模,得到的图片是分离的,比如6张照片or 12张照片等,这些照片涵盖了一个场景中的所有内容。
后期处理的第一步就是使用全景合成软件,对这些分离的素材进行合成,使之成为一张360度的全景图片
合成在网上有许多现成的软件,比如Kolor Autopano Video Pro,kolor autopano giga,这两个软件就可以完成素材的合成以及拼接剪辑,制成一个VR的360全景视频
具体步骤可以参考:
http://www.vmovier.com/48630?from=post_hotcomment
1.4 VR播放器的简单原理
上述简单说明了一个VR视频是如何制作的,但是一个VR视频如果不使用特殊的播放器,那么播放出来的效果看上去是一个拉伸的长条形图片拼成的视频。
之所以Vr视频能让人有身处其中的感觉,是因为观看时人的视角处于VR视频空间中,而不是看到一个二维平面,因此就需要将本身的全景二维图,投影到一个立体的空间——立方体or球形。
简单球面纹理映射讲解
实现球面纹理映射有两种方法,一种是使用顶点的法向量来生成纹理坐标,另一个是使用顶点的位置向量来生成纹理坐标。
想象一个球体,球面上每一个点的法向量,可以是从球心起到该点的线段距离。球上每一点都可以视作一个无限小的平面。
Ps:法向量——垂直于平面的向量,叫做该平面的法向量
问题的本质是根据球面上每个点的法向量坐标生成对应的纹理坐标,请看下图,下图中外部的方框表示二维纹理坐标,其范围是(u,v)min = (0,0), (u,v)max = (1,1),中间的圆形表示球面法向量坐标,其x,y分量的范围是(x,y)min = (-1,-1), (x,y)max = (1,1)。
所以问题的本质变成了两组坐标的映射,也即将区间(x,y)min - (x,y)max映射到区间(u,v)min - (u,v)max。这里我们使用反正弦函数y = acrsin(x)来实现。先看一下它的函数图象。
由这个图象知,它的定义域x = (-1,1),值域是y = (-pi / 2, pi / 2)。我们稍作变型,得到下面两个公式。正好完成了由(-1, 1)到(0, 1)的映射。这里tu表示纹理的x坐标,tv表示纹理的y坐标,Nx表示顶点法向量的x轴分量,Ny表示顶点法向量的y轴分量。
tu = arcsin(Nx) / PI + 0.5
tv = arcsin(Ny) / PI + 0.5
二、 VR app实现研究
2.1 VR开源播放器
在网上找到了一个完整的PC上的VR开源播放器——VRPlayer 代码为VS工程,使用C#实现,支持windows平台。源码链接如下:
http://vrplayer.codeplex.com/SourceControl/latest
另外一套开源VR,就是google的 vr sdk。包括它的cardboard daydream,覆盖平台Android、IOS、以及Web。
覆盖安卓和IOS的实现是cardboard和daydream,Web的实现则是VR View。
安卓sdk使用java NDK部分为C++,VR view的实现目测是js。
Google vr有一个主页,链接如下:
https://developers.google.com/vr/
2.2 google cardboard demo源码研究
在google vr sdk for android中 有一个cardboard 的sample——treasurehunt。
这是一个小游戏,玩家通过寻找空间中的立方体并吃掉它来进行游戏。
水平所限,只能简单的说下看懂的部分
跳过前面的头文件和全局变量声明,函数定义从91行开始,持续100行左右,应该是功能函数的一些定义,包括
矩阵乘法MatrixMul、
矩阵变幻到openGL数组 MatrixToGLArray、
矩阵元素乘法 MatrixVectorMul
其中这个函数PerspectiveMatrixFromView line 135
看函数名字似乎是将视图变为一个凸透镜的视图。在后续的一个DrawEye函数中有调用。
ModulateRect 看参数应该是创建一个符合宽和高的立方体模型,就是被玩家吃的那个立方体
CalculatePixelSpaceRect 看函数名是和像素什么的有关。。计算空间立方体的像素啥的吧,个人认为和VR生成关系不大
CheckGLError openGL报错检查函数
接下来是一个类定义TreasureHuntRenderer 看名称是该游戏的渲染作用的类
忽略其中的声音部分
简单的功能和判断函数如下:
OnTriggerEvent 触发事件,玩家视线看到立方体后,立方体着色 ,然后消失。
DrawCube 玩家视线看到立方体后,立方体着色
IsLookingAtObject 判断玩家视线是否算是看到立方体
OnPause 暂停游戏
OnResume 取消暂停游戏
HideObject 使立方体消失
一系列draw函数应该是比较关键的部分
drawFrame->drawEye->drawFloor,drawCube
这一块代码就看的不是很明白,关键的VR映射部分应该就是这里实现的
DrawFrame中
这里获取头部姿势,然后通过头部的姿势获取眼睛的一个目视范围吧?
接下来DrawEye
根据上个函数中的参数left_eye_view_matrix/right,视野范围4*4矩阵,确定灯光的范围和能看到的地板的范围吧,并把地板也着色。
InitializeGl初始化各种颜色,位置参数
其实能形成VR效果的关键应该就是在这些draw函数的参数上。应该是通过一些公式,将图像映射成了玩家眼中的立体空间。
关键的参数变量数据结构定义:
第一个应该是数据的值域范围矩阵,第二个则是对象的空间参数,xyz向量以及一个w标量。
OpenGL知识补充:
通常我们使用 4 维向量 (x, y, z, w) 表示在3D空间中的一个点,最后一维 w 表示齐次坐标。齐次坐标的含义是两条平行线在投影平面的无穷远处相交于一点,但在 Matrix 中没有表示无穷大,所以增加了齐次坐标这一维。
3D空间的物体投影到2D平面上时,就需要使用到齐次坐标,因此我们需要使用 4 × 4 的 Matrix 来表示变换。在编程语言中,这样的 Matrix 可用大小为 16 的一维数组或4 × 4 的二维数组来表示。由于矩阵乘法不满足乘法交换律,用数组表示 Matrix 又分为两种形式:行主序和列主序,它们在本质上是等价的,只不过是一个是右乘(行主序,矩阵放右边)和一个是左乘(列主序,矩阵放左边)。OpenGL 使用列主序矩阵,即列矩阵,因此我们总是倒过来算的(左乘矩阵,变换效果是按从右向左的顺序进行): 投影矩阵 × 视图矩阵 × 模型矩阵 × 3D位置。
后来找到的上面矩阵变幻和数据结构定义的原理: