笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。
射线在现实生活中使用的非常广泛,现实生活中经常使用各种射线检测金属表面的瑕疵。游戏开发或者说虚拟实现开发中也经常使用射线拾取,
比如游戏中拾取掉落物品,点击某个建筑物升级,以及AI中的寻路可以从角色身上发出一条射线用于检测前方的物体是否与角色发生碰撞。
也可以用于角色的透明处理以及墙体的透明处理,以及虚拟仿真中的导弹追踪目标的射线检测。下面给读者展现一下使用射线检测实现的案例效果图,
利用射线检测到的墙体透明处理。
利用射线检测导弹追踪目标实现的案例效果图:
开发者在使用射线拾取时, 可以直接调用引擎提供的射线接口即可,大家在使用接口时,是否想过射线检测它内部是如何实现的?我在CSDN的
公开课《算法与游戏实战技术》中有关这个课程的讲解,为了帮助大家理解其算法原理,在此通过博文的方式再给读者介绍一下其原理作为
课程的一个补充。
在介绍射线拾取之前,建议读者先看看我讲解的《算法与游戏实战技术》中的固定流水线课程或者是我已写的博文:游戏核心之固定流水线。
下面把固定流水线流程图再给读者展示一下:
上图是固定流水线的基本变换,这个变换与我们的射线检测是有关系的,以射线检测为例结合着固定流水线给读者介绍介绍实现原理:
要求是我们点击屏幕选中游戏场景中的物体,为了实现这个操作引擎内部实现了很多的运算,接下来逐步给读者分解其中涉及的运算,
首先点击屏幕对应的是屏幕坐标,需要先将屏幕坐标转化为视口坐标,这个视口坐标是被归一化后的坐标也就是我们在Unity或者虚幻
引擎看到的Viewport视口大小都是(0,0,1,1),如下图所示:
我们点击屏幕的坐标转化到标准的视口坐标后,下一步需要转化到摄像机的投影坐标中,投影坐标换算对应的是投影矩阵,转换后的视口坐标与
投影矩阵相乘即可得到投影坐标,取得投影坐标后,我们还是无法使用射线拾取,因为我们拾取的物体是在世界坐标系中的,接下来需将投影坐标
继续转换到观察坐标中,观察坐标换算对应的是观察矩阵,观察坐标距离世界坐标最近,最后一步就是从观察坐标转化到世界坐标中。这样射线拾
取就可以在世界坐标系中进行了,游戏场景中的所有物体都是在世界坐标系中的。这样整个步骤就完成了,效果如下图所示:
接下来开始实际操作,用户点击第一幅图显示的是熊猫在摄像机的观察坐标系中的坐标假设为(View.x,View.y,View.z),第二幅图熊猫在标准化
的视口中的投影坐标为(Proj.x,Proj.y,Proj.z),第三幅图的熊猫在的屏幕坐标为(Screen.x, Screen.y),下面按照固定流水线的逆推演进行。
观察坐标系中的熊猫显示效果图:
投影坐标系中的熊猫所在位置坐标效果图:
游戏中的熊猫所在屏幕位置效果图:
下面开始解答点击屏幕后在各个坐标系中坐标转换,假设第三幅图的程序窗口宽为ScreenW,高为ScreenH。因为我们要把屏幕坐标转化成标准化的视口坐标中也就
是(0,0,1,1)中。公式如下:
Proj.x =(Screen.x - ScreenW/2) / ScreenW * 2; (1)
Proj.y =(Screen.y - ScreenH/2) / ScreenH * 2; (2)
Proj.z =0;
这个公式的计算是右上角为(0,0)点,如果使用Unity3D引擎换算,Unity的原点在屏幕的中间。应该另当别论了,方法是一样的。
得到投影坐标后,接下来要做的是把该点坐标从投影空间转换到观察空间也就是相机空间。
正常的固定流水线计算公式是:
(Proj.x,Proj.y,Proj.z) = (View.x, View.y, View.z) * ProjMatrix(投影矩阵)
换算一下可以得到:
View.x =Proj.x/ProjMatrix.m11;
View.y =Proj.y/ProjMatrix.m22;
View.z =Zn;
射线的出发点是(0,0,0)射线上的另外一点是(View.x,View.y, Zn)这样射线的方向矢量DirView在观察空间中的表示就可以确定了
(View.x - 0, View.y - 0,View.z - 0),计算到这一步还没结束,将其带入(1),(2)公式中:
DirView.x= (2 * Screen.x/ScreenW -1)/ProjMatrix.m11;
DirView.y= (2 * Screen.y/ScreenH -1)/ProjMatrix.m22;
DirView.z= 1;
下一步转换DirView到世界坐标空间中,由于最终的运算要在世界坐标空间中进行,所以我们还需把矢量DirView从观察空间转换为世界坐标
空间中的矢量DirWorld。根据固定流水线的矩阵运算公式:
DirView= DirWorld * ViewMatrix;
换算一下可以得到:
DirWorld= DirView * inverse_ViewMatrix,其中inverse_ViewMatrix为ViewMatrix的逆矩阵。
方向有了后,射线的原点同样也要转换到世界坐标系中,其在世界坐标系中的表示如下所示:
OWorld =(inverse_ViewMatrix.m41, inverse_ViewMatrix.m42, inverse_ViewMatrix.m43,1)。到此为止,就可以通过射线与三角面进行
判断检测了,其实在游戏中我们通过射线与物体碰撞盒进行碰撞检测即可,射线检测终于完成了。
总结:
以上就是关于射线拾取的整个流程,这个都是在引擎内部实现的,其实给读者介绍这些也非常有助于去研究一些开源引擎的源代码。
因为现在引擎都封装的比较好,开发者只需要调用接口实现其功能,但是很多情况下,项目需要自己去实现该功能,作为开发者的我们必须
要懂得其原理,这样不论自己使用什么语言实现就非常简单多了。