第十五章
通常,在屏幕上任意位置点击,点击之后,计算出拾取射线,然后对场景中的每个物体进行遍历,并检测其是否与该射线相交,与射线相交的物体即为用户所拾取的物体
拾取步骤:
1 给定所点击的屏幕点s,求出它在投影窗口中的对应点p
2 计算拾取射线,即自坐标原点发出且通过点p的那条射线
3 将拾取射线和物体模型变换至同一坐标系中
4 进行物体/射线的相交判断,相交物体即为用户所拾取的屏幕对象
屏幕到投影窗口看的变换
将屏幕点到投影窗口中,视口变换(viewport transformaion)矩阵为:
2/Width 0 0 0
0 -Height/2 0 0
[ 0 0 MaxZ-MinZ 0 ]
X+Width/2 Y+Height/2 MinZ 1
对投影窗口中的点p=(px,py,pz)实施视口变换,就得到了屏幕点s = (sx,sy)
sx = px(Width/2) + X + Width/2
sy = py(Height/2) + Y + Height/2
拾取变换之后,z坐标并不作为2D图像的一部分进行存储,而是被保存在深度缓存中
已知的是屏幕点s,求点p的位置,得:
px = (2sx -2X-Width)/Width
py = (-2sy + 2Y + Height)/Height
假定视口中的X和Y成员都为0,得到
px = 2*sx/Width -1
py = 2*sy/Width +1
pz = 1
按照定义,投影窗口与平面z=1重合,所以,pz =1
由于投影矩阵已对投影窗口中的点进行了比例变换以模拟不同的视场,即呈现近大远小的效果,为了求出缩放之前该点的位置,必须对该点做一次比例变换的逆运算,设P为投影矩阵,由项P00和P11是该变换矩阵中对应于x和y坐标的比例系数,所以有:
px = (2x/viewporWidth -1)(1/P00)
py = (2y/viewporHeight +1)(1/P11)
pz = 1
拾取射线计算,射线参数方程p(t) = p0+tu来表示,p0是射线的起点,它描述了射线位置,u是一个描述了射线方向的向量
射线的起点与坐标原点重合,所以p0=(0,0,0),如果射线经过了投影窗口中的p点,则方向向量u:
u = p-p0 = (px,py,1) - (0,0,0) = p
该函数用于在给定屏幕坐标系中选定的x和y坐标的条件下,计算观察坐标系中拾取的射线:
d3d::Ray CalcPickingRay(int x,int y)
{
float px = 0.0f;
float py = 0.0f;
D3DVIEWPORT9 vp;
Device->GetViewport(&vp);
D3DXMATRIX proj;
Device->GetTransfrom(D3DTS_PROJECTION,&proj);
px = (((2.0f*x)/vp.Width)-1.0f)/proj(0,0);
py = (((-2.0f*y)/vp.Height)+1.0f)/proj(1,1);
d3d::Ray ray;
ray._origin = D3DXVECTOR3(0.0f,0.0f,0.0f);
ray._direction = D3DXVECTOR3(px,py,1.0f);
return ray;
}
Ray结构:
struct Ray
{
D3DXVECTOR3 _origin;
D3DXVECTOR3 _direction;
};
为了进行射线/物体相交测试,射线和物体必须位于同一坐标系中,我们并不打算将所有的物体变换至观察坐标系中,这是因为将射线变换至世界坐标系甚至某个物体的局部坐标系往往更容易
借助变换矩阵对其起点p0和方向u分别进行变换,就实现了射线p(t)=p0+tu的变换,注意,起点是按照点来变换的,而方向是按照向量来变换的
该函数用于对射线进行变换
void TransformRay(d3d::Ray* ray,D3DXMATRIX * T)
{
//transform the ray's origin ,w = 1
D3DXVec3TransformCoord(
&ray->_origin,
&ray->_origin,
T);
// transfrom the ray's direction ,w = 0
D3DXVec3TransformNormal(
&ray->_direction,
&ray->_direction,
T);
// normalize the direction
D3DXVec3Normalize(&ray->_direction,&ray->_direction);
}
D3DXVec3TransformCoord和D3DXVec3TransformNormal均以3D向量作为其参数,注意,D3DXVec3TransformCoord参数第4个分量应理解为w = 1,而是用D3DXVec3TransformNormal时,参数第4个分量应理解为w = 0 ,所以,用D3DXVec3TransformCoord来实现点变换,用D3DXVec3TransformNormal实现向量变换
射线/物体相交判定:用外接球去近似表示每个物体,然后进行射线/外接球的相交测试
注意,射线可能会与多个物体相交,但只有距离摄像机最近的那个物体被拾取,因为距离摄像机较近的物体遮挡了位于其后的物体
给定一个球的圆心点c和半径r,可用隐式方程(implicit equation)来测试点p是否在求面上
|| p -c || - r = 0
如果p满足该方程,称p在球面上
为了判定球体与射线p(t) = p0 + tu是否相交,可将射线方程的参数方程代入隐式的球面方程中,并解出满足球面方程的参数t,这样就求出对应交点的那个参数t。
将射线参数方程代入球面方程,的:
||p(t) -c|| -r = 0
||p0 + tu -c || -r = 0
导出一个二次方程(quadratic equation):
At*t +Bt +C = 0;
其中,A=u*u,B=2(u*(p0-c)),C = (p0-c)*(p0-c)-r*r,若u是单位向量,则A为1
假定u为单位向量,则可解出t0和t1:
t0 = (-B +squrt(B*B-4AC))/2A, t1 = (-B -squrt(B*B-4AC))/2A
下面函数如果射线与球体相交,则返回true,否则返回false
bool RaySphereIntTest(d3d::Ray* ray,d3d::BoundingSphere* sphere)
{
D3DXVECTOR3 v = ray ->_origin-sphere->_center;
float b = 2.0f * D3DXVec3Dot(&ray->_direction,&v);
float c = D3DXVec3Dot(&v,&v) - (sphere->_radius * sphere->_radius) ;
// find the discriminant
float discriminant = (b*b) - (4.0f*c);
// test for imaginary number
float discriminant = (b*b) - (4.0f*c);
// test for imaginary number
if(discriminant < 0.0f)
return false ;
discriminant = sqrtf(discriminant )
float s0 = (-b + discriminant) /2.0f;
float s1 = (-b - discriminant) /2.0f;
//if a solution is >= 0,then we intersected the sphere
if (s0 >= 0.0f || s1 >= 0.0f)
return true;
return false;
}
struct BoundingSphere
{
BoundingSphere();
D3DXVECTOR3 _center;
float _radius;
};