第十五章
通常,在屏幕上任意位置點擊,點擊之後,計算出拾取射線,然後對場景中的每個物體進行遍歷,並檢測其是否與該射線相交,與射線相交的物體即爲用戶所拾取的物體
拾取步驟:
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;
};