对OpenGLES中的空间变换的理解

        今天翻看以前用Cocos2d-x写的VR小游戏的代码,有一个模块功能是判断固定在屏幕中心的一条射线是否触碰到了其他3D物体。这套代码原来是从学长写的游戏中直接扒出来的,没有细细研究,今天正好看到了,就借此总结一下在OpenGLES中空间的转换。

        在OpenGLES中,物体会在五个空间中变换,如图所示:

coordinate_systems

(图片来自网络)


        1.物体空间:

        物体空间指的就是一个3D物体最开始所在的地方,也就是说当我们把一个正方体导入3DMax或其他建模软件中时,它的中心总是在(0,0,0)点,它的一个顶点可能是(1,1,1)这个坐标就是它在自己物体空间的坐标。

        2.世界空间

        如果直接将物体转换到世界空间当中,它们一定都会集中在(0,0,0)这个点。为了能让它们摆放在不同的位置,从而引进了世界空间这个概念,具体的做法就是需要将物体空间坐标系中每个点的坐标乘以模型矩阵(Model Matrix)。

        模型矩阵是一种变化矩阵,将物体坐标系中的每个顶点都乘上模型矩阵,就可以实现平移、旋转、缩放等操作,从而将其从物体空间转换到世界空间当中。

        3.观察空间

        现在所有的物体都已经加在进了场景,而且在世界空间中井井有条的摆放好了,是不是还少了点什么~没错,没有摄像机呀,只有在加入一个摄像机才能最终让你看到,不然摆放好的东西就没有任何意义了。嗯~这时候就需要再将处于世界空间中的物体乘上一个观察矩阵(View Matrix),这时就将世界空间中的物体转换到了观察空间中了。

        4.剪裁空间

        在OpenGLES中,最终成现在用户眼前的画面一定是你所构建的世界的一部分,也就是说由于摄像机视野,位置的不同,最终会有一部分物体会被剔除出画面,剪裁空间要做的就是这部分工作——将落在剪裁空间之外的点剔除掉。

        为了将物体从观察空间转换到剪裁空间,我们需要使用观察矩阵乘上投影矩阵(Project Matrix),将最终入选的点筛选出来(如果只是图元的一部分超出了剪裁体积,OpenGLES则会将其重构)。投影矩阵创建的观察箱被称为平截头体,投影矩阵还有一个功能就是将3D的物体投影到2D的屏幕上,其原理很像我们的视网膜成像。

        在将顶点变换到了剪裁空间中之后,最终的处理就是执行透视除法(关于这个概念,我觉得简书的这个作者讲得很到位了:http://www.jianshu.com/p/7e701d7bfd79),我的理解就是将物体按照近大远小(透视投影)或者远近大小一样(正交投影)的形式成现在设备上。

        正交投影:正交投影的平截头体类似于一个长方体,也就是说,观察空间的物体无论距离摄像机远近投影到画面的大小都是一样的,这适合一些2D游戏或是非真实的绘制。

orthographic projection frustum(图片来源网络)

        透视投影:透视投影则是更加贴近现实生活的一种投影,简单的来说就是近大远小的效果。这种效果的实现源于透视投影矩阵投影矩阵将给定的平截头体范围映射到裁剪空间,除此之外还修改了每个顶点坐标的w值,从而使得离观察者越远的顶点坐标w分量越大。被变换到裁剪空间的坐标都会在-w到w的范围之间(这个w可以类比现实生活中投影机到屏幕的距离)。这也就可以解释顶点着色器中gl_Position的值为啥是vec4了~(因为它要根据最后一个w算大小呀~当然透视除法和剪裁都是OpenGLES自己处理的)。

        5.视口空间

        最后一步就是将已经将3D场景转换成为2D的画面对应转换到屏幕上,如果手机是1920X1080的分辨率就是将画面转换成为2073600个像素点,在OpenGLES中我们使用的是glViewPort这个函数设置视口。

       最后,我们总结一下:一个顶点转换到我们的屏幕上要经历过MVP这三个矩阵,值得一提的是,在线性代数中一个左矩阵乘一个顶点,乘和右乘结果完全不同,所以相乘的方向是逆序的如下图所示。


        洋洋洒洒总结了这么多,在电脑前从6点多一下子坐到了快10点啦,希望这些能够对大家理解有所帮助,也希望哪里有不对的地方,和我多多交流讨论~

        对了我先前说的那个Cocos的模块,就是先获得屏幕的中间点的坐标,然后得到一个屏幕中点的近距离点与远距离点,再乘unproject也就是投影矩阵的逆矩阵,把这个线段从剪裁空间转换到观察空间,然后归一化得到一个方向向量,最后使用API判断这个方向的射线是否触碰到了菜单的包围盒,从而进一步判断的。

下面是这部分的代码:

Size size = Director::getInstance()->getWinSize(); //获取屏幕的尺寸
Vec3 nearP(size.width / 2, size.height / 2, 0.0); //近距离点
Vec3 farP(size.width / 2, size.height / 2, 1.0); //远距离点
CameraControl::mycamera->unproject(size, &nearP, &nearP); //转换为摄像机下的坐标(屏幕坐标)
CameraControl::mycamera->unproject(size, &farP, &farP);
Vec3 direction; //方向向量
Vec3::subtract(farP, nearP, &direction); //获取方向向量
direction.normalize(); //归一化
Ray ray; //声明射线
ray._origin = nearP; //射线的起点
ray._direction = direction; //射线的方向矢量


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值