在openGL中实现RayPicking

*原创文章,转载请注明出处*

 

在openGL中实现RayPicking

 

看过D3D入门龙书的朋友肯定知道,第十五章讲picking的时候,是利用拾取射线和包围球的的交叉测试来完成拾取的。但是在OpenGL中,我们知道是利用OpenGL渲染管线的特点,在拾取模式中采用拾取矩阵,然后检查selectbuffer的内容来判断拾取的。这两种方法各有各的好处。在进行OpenGl编程的时候,有时会遇到需要使用拾取射线的时候,下面我就讲一下在OpenGL中如何利用拾取射线来实现picking技术。在你阅读这篇文章之前,首先要明白openGl的渲染管线各个阶段的变换过程,如果你还不清楚,请参考我的另一篇文章《OpenGL渲染管线和坐标变换》。

 

先来看看picking的时候要进行的步骤,首先我们在屏幕上点击的时候会得到一点屏幕坐标p0,开始的时候我们可以把深度值初始化位1,于是有p1。我们点击屏幕上一点的时候并没有深度值Z,所以这里可以设置1。然后用视口变换(viewport transformation)得到在正规化viewport空间中的坐标p2,这里我为什么要用-1在后面我会详细说明。这个p3就是相机坐标系中的坐标了。现在就可以开始生成拾取射线了,利用直线的参数方程P4,这里p5就是射线的出发点,t是参数,v是直线的方向。我们一般认为射线是从相机坐标的原点出发的,所以p6,那么用p7就得到了直线的方向,当然要正规化一下。有了这些信息还够,因为现在物体和射线不在一个坐标中,我们要picking的物体在世界坐标中,而拾取射线是在相机坐标里计算的。所以这里可以把物体转换到相机坐标,也可以把射线转换到世界坐标。为了和龙书上对应,这里我们将射线转换到世界坐标。然后在世界坐标里进行包围球和该射线的测试就可以了。下面我一步一步来详细说明。

 

 

 

1. 屏幕坐标到视口空间坐标的变换(Screen coordinate to viewport coordinate)

 

也许这里叫视口坐标有点不准确,反正它就是一个物体的顶点被投影后的坐标。因为被投影后的物体坐标是被正规化的,它在区间p8之间。一个800*600大小的屏幕上的坐标p0要如何转换到-1到1之间的坐标p2呢?

 

 

p1

在上面的图中我们可以找到一个关系,

 

p9

 

其中的ScreenWidth和ScreenHeight分别是窗口的宽和高。比如我们800*600窗口的正中点击了一下,屏幕坐标为(400,300),带入上面公式即可得到投影后坐标为(0,0)。

 

 

 

 

2. 投影坐标到相机坐标系坐标(Viewport coordinate to Carmera coordinate)

 

由于投影后的坐标是经过投影矩阵处理过的,它的大小已经被缩放了。所以这里我们要的到投影矩阵中关于x成分和y成分的比例,在openGL中我们可以通过 glGetDoublev(GL_PROJECTION_MATRIX,m)这个函数来得到当前投影矩阵的数据,m是用来保存这些数据的地址。由于OpenGL的矩阵是以列的形式来保存的,所以对于x成分和y成分的就分别为m[0]和m[5],用上面得到的坐标p2分别除以这两个值。

 

 

p10

 

这里是p11就是窗口上一点对应的在相机坐标系中的坐标了,由于要计算射线的方向,就要加上z值。在这篇文章开始的时候我就提到了,射线的起点就是相机坐标的原点,但是还需要一个点来决定射线的方向。OpenGL由于使用右手坐标系,Z轴是朝向外的,Z轴的方向关系到我们射线的方向,如果Z为正那么射线是朝向屏幕外,如果Z为负,那么射线是朝向屏幕里,这里就要根据时的需要改变射线的方向。如果相机是朝向Z轴负方向的,那么这里Z要为负数才可以。后面的例子程序中相机是朝向负方向的,所以这里我们把Z值设定为-1。于是有

 

p10

 

有了p11我们就可以生成拾取射线,只需要2个变量即可。一个用来保存射线的方向,一个用来保存射线的起点。

 

 

 

 

3. 把射线从相机坐标转换到世界坐标(Carmera coordinate to world coordinate)

 

 

 

 

如果使用了函数gluLookAt的话,那么我们需要把相机坐标系转换到世界坐标系。如果没有设置过相机位置,那么不用转换,因为默认状态时相机坐标和世界坐标重合的。该函数的第一个参数是表示相机的位置,如果相机没有转动之类的,那么只要使用表示相机位置的参数就可以了。

 

P12

 

这里x,y,z就分别表示相机在世界坐标中的位置。如果相机有转动,那么该矩阵的前三列就要有变化,具体怎么变化请各位读者自己想想,呵呵。这里我就不详细说明了。有了从相机坐标系到世界坐标系的变换矩阵,那么用这个矩阵p13乘以射线的方向和射线的起点就可以把射线转换到世界坐标了。要注意的是,这里射线的起点和方向要用其次坐标系来表示。

 

14

 

这样我们就把射线转换到世界坐标了。下面就是交叉测试了。

 

 

 

 

4. 世界坐标中的射线和球的交叉测试

 

 

 

 

先来看看下面的图

 

p15

 

如果绿色包围球圆心坐标为C(a,b,c),半径为R的话,那么球的方程可以表示为:

 

p16

 

P表示球上的点,如果P点在球上的话,那么P点到球的距离肯定等于球的半径R。于是有上面的等式成立。射线上t1时刻和t2时刻表示和球相交的点,如果射线和球相交,那么这2个点肯定也满足球的方程,于是t时刻射线上的点带入到球方程中,可以得到

 

p17

 

展开上式整理一下,得到二元一次方程组,可以写成下面的形式

 

p18

 

其中

p19

 

 

这里u是射线的方向,p20是射线的起点,c是球心坐标。如果射线和球相交,那么上面的方程组就有1个或2个解,当然射线和球相切的时候有2个相同的解。现在根据球根公式就可以判断射线和包围球是否相交了。解出t1,t2分别为:

 

 

p21

 

这里没有A = 1,是因为u已经单位化了。

 

 

 

在OpengL中实现picking的过程就完了,其实不难。只要熟悉渲染管线,那么这些都很好理解。下面是源代码,需要的同学可以参考。注意,代码中的Gvector,Gvector3,GMatrix是我自己的数学类,表示向量和矩阵的操作。同学们可以自己实现。例子程序运行如下。

 

 

p21

 

 

程序代码

 

 

 

 

*原创文章,转载请注明出处*

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张赐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值