const D3DXMATRIX* pmatProj = g_Camera.GetProjMatrix();
POINT ptCursor;
GetCursorPos( &ptCursor );
ScreenToClient( DXUTGetHWND(), &ptCursor );
// Compute the vector of the Pick ray in screen space
D3DXVECTOR3 v;
v.x = ( ( ( 2.0f * ptCursor.x ) / pd3dsdBackBuffer->Width ) - 1 ) / pmatProj->_11;
v.y = -( ( ( 2.0f * ptCursor.y ) / pd3dsdBackBuffer->Height ) - 1 ) / pmatProj->_22;
v.z = 1.0f;
// Get the inverse view matrix
const D3DXMATRIX matView = *g_Camera.GetViewMatrix();
const D3DXMATRIX matWorld = *g_Camera.GetWorldMatrix();
D3DXMATRIX mWorldView = matWorld * matView;
D3DXMATRIX m;
D3DXMatrixInverse( &m, NULL, &mWorldView );
// Transform the screen space Pick ray into 3D space
vPickRayDir.x = v.x * m._11 + v.y * m._21 + v.z * m._31;
vPickRayDir.y = v.x * m._12 + v.y * m._22 + v.z * m._32;
vPickRayDir.z = v.x * m._13 + v.y * m._23 + v.z * m._33;
vPickRayOrig.x = m._41;
vPickRayOrig.y = m._42;
vPickRayOrig.z = m._43;
先回顾一些知识。1)DirectX和OpenGL中矩阵向量区别,详见这里 2)齐次坐标,3D变换,详见这里
3 ) 透视投影变换推导请看这里。4) view与world的区别。
从Normalized device space 到 window space的具体变换公式是
xw,yw是屏幕坐标, 也就是ptCursor.x,ptCursor.y。
xnd,ynd是做完投影归一化后得到的坐标。
x, y是窗口左下角的坐标。在本例Pick例中x = y = 0。
现在来看
v.x = ( ( ( 2.0f * ptCursor.x ) / pd3dsdBackBuffer->Width ) - 1 ) / pmatProj->_11;
v.y = -( ( ( 2.0f * ptCursor.y ) / pd3dsdBackBuffer->Height ) - 1 ) / pmatProj->_22;
( ( ( 2.0f * ptCursor.x ) / pd3dsdBackBuffer->Width ) - 1 ),-( ( ( 2.0f * ptCursor.y ) / pd3dsdBackBuffer->Height ) - 1 )是将屏幕坐标变换到归一化的投影坐标空间,这个可以理解了。
( 这个归一化的空间是个axis-aligned cube. 在OpenGL中是[-1,-1,-1], [1, 1, 1]分别作为cube的minimum corner and maximum corner。在DirectX中是[-1,-1, 0],[1, 1, 0]。详情请查看Realtime Rendering 3rd version )
现在还差/pmatProj->_11, /pmatProj->_22暂时无法理解。
OpenGL中透视矩阵为
DirectX中是以上矩阵的转置。
不论是从Realtime rendering 3rd version上还是某大牛的具体推导,都是以上矩阵形式。 那为什么会是/pmatProj->_11, /pmatProj->_22?
百思不得其解以后,我调试进入相应代码位置,发现这个pmatProj矩阵不同于以上所谓的投影矩阵,它是从函数
D3DXMatrixPerspectiveFovLH( &m_mProj, fFOV, fAspect, fNearPlane, fFarPlane )返回的。
m_mProj这个矩阵只有_11,_22, _33,_34, _43, 不为0,其他元素都为0。
回头一想,肯定是由于right = -left, top = -bottom, 所以_30 = 0, _31 = 0.
所以/pmatProj->_11, /pmatProj->_22之后就是投影之前的x, y坐标了。
最后的变换是将射线变换到局部空间,
// Get the inverse view matrix
const D3DXMATRIX matView = *g_Camera.GetViewMatrix();
const D3DXMATRIX matWorld = *g_Camera.GetWorldMatrix();
D3DXMATRIX mWorldView = matWorld * matView;
D3DXMATRIX m;
D3DXMatrixInverse( &m, NULL, &mWorldView );
// Transform the screen space Pick ray into 3D space
vPickRayDir.x = v.x * m._11 + v.y * m._21 + v.z * m._31;
vPickRayDir.y = v.x * m._12 + v.y * m._22 + v.z * m._32;
vPickRayDir.z = v.x * m._13 + v.y * m._23 + v.z * m._33;
vPickRayOrig.x = m._41;
vPickRayOrig.y = m._42;
vPickRayOrig.z = m._43;
最后一个问题是为什么vPickRayOrig只跟 m 有关系而跟拾取点无关呢?希望有大牛来帮忙解答一下。
今天看到OpenGPU坛子上有朋友也发了这个问题而且有好多朋友已经回答了。
原来view 空间其实应该是从视点发出来的一些斜线。
http://www.opengpu.org/bbs/forum.php?mod=viewthread&tid=15692