拾取
拾取
假设用户在屏幕上单击,击中的位置为点 s = (x, y) 。但是,仅给出点 s, 应用程序还是无法立即判断物体是否被选中。所以,针对这类问题,我们需要采用一项称为“拾取”的技术。
我们知道,物体和屏幕点 s 之间的一种关系是茶壶被投影到了一个包含了 s 的区域中。更准确地说,茶壶被投影到了投影窗口中一个包含点 p (点 p 位于投影窗口中)的区域中,其中点 p 对应于屏幕中的点 s 。
由上图中,我们看到,如果自坐标原点发出一条拾取射线,该射线将与那些其投影包围了点 p 的物体(即茶壶)相交。所以,一旦计算出来拾取射线,我们就可以对场景中的每个物体进行遍历,并逐个测试射线是否与其相交。与射线相交的物体即为用户所拾取的物体。
通常,我们可在屏幕上的任意位置单击。单击后,计算出拾取射线,然后再对场景中的每个物体进行遍历,并测试其是否与该射线相交。与射线相交的物体即为用户所拾取的物体。但是,射线也有可能不与任何物体相交。所以,我们可做出如下结论:如果拾取射线不与场景中的任意物体相交,则用户便没有拾取到任何物体,而是选中了屏幕中的背景或一些我们并不感兴趣的东西。
我们将拾取分解为以下 4 个步骤:
l 给定所单击的屏幕点 s ,求出它在投影窗口中的对应点 p 。
l 计算拾取射线。即自坐标原点发出且通过点 p 的那条射线。
l 将拾取射线和物体模型变换至同一坐标系中。
l 进行物体 / 射线的相交判断。相交的物体即为用户所拾取的屏幕对象。
一、 屏幕到投影窗口的变换
第一步需要将屏幕点转换到投影窗口中。视口变换矩阵为:
对投影窗口中的点 p = (px, py, pz) 实施视口变换,就得到了屏幕点 s = (sx, sy) ;
拾取变换之后, z 坐标并不作为 2D 图像的一部分进行存储,而是被保存在深度缓存中。
在前面的实例中,已知的是屏幕点 s ,我们需要求出点 p 的位置。解上述方程组,得:
假定视口的 X 和 Y 成员都为 0 (一般情况下均如此),这样我们就进一步得到:
按照定义,投影窗口与平面 z = 1 重合,所以 pz = 1 。
但是,由于投影矩阵已对投影窗口中的点进行了比例变换以模拟不同的视场,即呈现出近大远小的效果。为了反求出缩放之前该点的位置,我们必须对改点做一次比例变换的逆运算。设 p 为投影矩阵,由于项 p00 和 p11 是该变换矩阵中对应于 x 和 y 坐标的比例系数,所以有:
二、 拾取射线的计算
前面我们提到射线可以用参数方程 p (t) = p 0 + tu 来表示,其中 p 0 是射线的起点,它描述了射线的位置, u 是一个描述了射线方向的向量。
由前面的图可看出,射线的起点与坐标原点重合,所以 p 0 = (0, 0, 0) 。如果射线经过了投影窗口中的 p 点,则方向向量 u 由下式给出:
三、 对射线进行变换
为了进行射线 / 物体相交测试,射线和物体必须位于同一坐标系中。我们并不打算将所有的物体都变换至观察坐标系中,这是因为将射线变换至世界坐标系甚至某个物体的局部坐标系往往更容易。
借助变换矩阵对起点 p 0 和方向 u 分别进行变换,就实现了射线 p (t) = p 0 + tu 的变换。注意,起点是按照点来变换的,而方向是按照向量来变换的。
四、 射线 / 物体相交判定
由于我们是用三角形网格来表示物体的,容易想到的一种方法是对场景中的每个物体依次遍历其三角形单元列表,同时进行射线与当前面片的相交测试。如果某面片与射线相交,则射线一定命中该面片所属的物体。
但是,对场景中的每个面进行相交测试无疑会花费大量的计算时间。一种更快的方法是用外接球去近似表示每个物体,然后进行射线 / 外接球的相交测试,如果某一外接球与射线相交,则称外接球所对应的那个物体被拾取。
注意:拾取射线可能会与多个物体相交。但只有距离摄像机最近的那个物体被拾取,因为距离摄像机较近的物体遮挡了位于其后的物体。
给定一个球体的圆心点 c 和半径 r ,我们可用如下隐式方程来测试点 p 是否在球面上。
||p - c|| - r = 0;
其中,如果 p 满足上述方程,则称 p 在球面上。
为了判定一个球体与射线 p (t) = p 0 + tu 是否相交,我们可将射线的参数方程带入隐式的球面方程中,并解出满足球面方程的参数 t ,这样就可求出对应交点的那个参数 t 。
将射线参数方程代入球面方程,得:
||p (t) - c || - r = 0
||p 0 + tu - c || - r = 0
a) 射线未经过球体, t0 和 t1 都将为复数;
b) 射线位于球体前方, t0 和 t1 都将为负实数;
c) 射线的起始点位于球体内。 t0 和 t1 为异号的实数;
d) 射线与球体相交。 t0 和 t1 均为正实数;
e) 射线与球体相切,此时 t0 和 t1 相等且为正实数。
拾取
拾取
假设用户在屏幕上单击,击中的位置为点 s = (x, y) 。但是,仅给出点 s, 应用程序还是无法立即判断物体是否被选中。所以,针对这类问题,我们需要采用一项称为“拾取”的技术。
我们知道,物体和屏幕点 s 之间的一种关系是茶壶被投影到了一个包含了 s 的区域中。更准确地说,茶壶被投影到了投影窗口中一个包含点 p (点 p 位于投影窗口中)的区域中,其中点 p 对应于屏幕中的点 s 。
由上图中,我们看到,如果自坐标原点发出一条拾取射线,该射线将与那些其投影包围了点 p 的物体(即茶壶)相交。所以,一旦计算出来拾取射线,我们就可以对场景中的每个物体进行遍历,并逐个测试射线是否与其相交。与射线相交的物体即为用户所拾取的物体。
通常,我们可在屏幕上的任意位置单击。单击后,计算出拾取射线,然后再对场景中的每个物体进行遍历,并测试其是否与该射线相交。与射线相交的物体即为用户所拾取的物体。但是,射线也有可能不与任何物体相交。所以,我们可做出如下结论:如果拾取射线不与场景中的任意物体相交,则用户便没有拾取到任何物体,而是选中了屏幕中的背景或一些我们并不感兴趣的东西。
我们将拾取分解为以下 4 个步骤:
l 给定所单击的屏幕点 s ,求出它在投影窗口中的对应点 p 。
l 计算拾取射线。即自坐标原点发出且通过点 p 的那条射线。
l 将拾取射线和物体模型变换至同一坐标系中。
l 进行物体 / 射线的相交判断。相交的物体即为用户所拾取的屏幕对象。
一、 屏幕到投影窗口的变换
第一步需要将屏幕点转换到投影窗口中。视口变换矩阵为:
对投影窗口中的点 p = (px, py, pz) 实施视口变换,就得到了屏幕点 s = (sx, sy) ;
拾取变换之后, z 坐标并不作为 2D 图像的一部分进行存储,而是被保存在深度缓存中。
在前面的实例中,已知的是屏幕点 s ,我们需要求出点 p 的位置。解上述方程组,得:
假定视口的 X 和 Y 成员都为 0 (一般情况下均如此),这样我们就进一步得到:
按照定义,投影窗口与平面 z = 1 重合,所以 pz = 1 。
但是,由于投影矩阵已对投影窗口中的点进行了比例变换以模拟不同的视场,即呈现出近大远小的效果。为了反求出缩放之前该点的位置,我们必须对改点做一次比例变换的逆运算。设 p 为投影矩阵,由于项 p00 和 p11 是该变换矩阵中对应于 x 和 y 坐标的比例系数,所以有:
二、 拾取射线的计算
前面我们提到射线可以用参数方程 p (t) = p 0 + tu 来表示,其中 p 0 是射线的起点,它描述了射线的位置, u 是一个描述了射线方向的向量。
由前面的图可看出,射线的起点与坐标原点重合,所以 p 0 = (0, 0, 0) 。如果射线经过了投影窗口中的 p 点,则方向向量 u 由下式给出:
三、 对射线进行变换
为了进行射线 / 物体相交测试,射线和物体必须位于同一坐标系中。我们并不打算将所有的物体都变换至观察坐标系中,这是因为将射线变换至世界坐标系甚至某个物体的局部坐标系往往更容易。
借助变换矩阵对起点 p 0 和方向 u 分别进行变换,就实现了射线 p (t) = p 0 + tu 的变换。注意,起点是按照点来变换的,而方向是按照向量来变换的。
四、 射线 / 物体相交判定
由于我们是用三角形网格来表示物体的,容易想到的一种方法是对场景中的每个物体依次遍历其三角形单元列表,同时进行射线与当前面片的相交测试。如果某面片与射线相交,则射线一定命中该面片所属的物体。
但是,对场景中的每个面进行相交测试无疑会花费大量的计算时间。一种更快的方法是用外接球去近似表示每个物体,然后进行射线 / 外接球的相交测试,如果某一外接球与射线相交,则称外接球所对应的那个物体被拾取。
注意:拾取射线可能会与多个物体相交。但只有距离摄像机最近的那个物体被拾取,因为距离摄像机较近的物体遮挡了位于其后的物体。
给定一个球体的圆心点 c 和半径 r ,我们可用如下隐式方程来测试点 p 是否在球面上。
||p - c|| - r = 0;
其中,如果 p 满足上述方程,则称 p 在球面上。
为了判定一个球体与射线 p (t) = p 0 + tu 是否相交,我们可将射线的参数方程带入隐式的球面方程中,并解出满足球面方程的参数 t ,这样就可求出对应交点的那个参数 t 。
将射线参数方程代入球面方程,得:
||p (t) - c || - r = 0
||p 0 + tu - c || - r = 0
a) 射线未经过球体, t0 和 t1 都将为复数;
b) 射线位于球体前方, t0 和 t1 都将为负实数;
c) 射线的起始点位于球体内。 t0 和 t1 为异号的实数;
d) 射线与球体相交。 t0 和 t1 均为正实数;
e) 射线与球体相切,此时 t0 和 t1 相等且为正实数。