如何基于D3DX将2D屏幕坐标逆转换为3D世界坐标?
Great question! Let’s start with a critical detail you need to wrap your head around first: a single 2D screen point corresponds to an infinite number of 3D world points. Every point along a straight ray from your camera through the screen pixel will project to that same 2D coordinate. To get a specific 3D position, you’ll either need the depth value of the point (from the z-buffer, for example) or to intersect this ray with a plane/object in your scene.
Step 1: Recap the Forward Transformation
Looking at your D3DXVec3TransformCoordImpl code, the forward process boils down to two key steps:
- Multiply the 3D world coordinate (represented as a homogeneous vector
(x,y,z,1)) by the combined view-projection matrix. - Perform perspective division by the resulting
wcomponent (yournormvariable) to get normalized device coordinates (NDC), wherex,y ∈ [-1, 1]andz ∈ [0, 1](standard for Direct3D).
Reversing this means undoing these steps in reverse order.
Step 2: Convert Screen Coordinates to NDC
If your pScreen variable is already in NDC, skip this step. If it’s in pixel coordinates (e.g., ranging from (0,0) to (screenWidth-1, screenHeight-1)), convert it first:
float screenX = pScreen.x; float screenY = pScreen.y; float screenZ = pScreen.z; // This is the depth value from your forward transform (0-1 range) float ndcX = (2.0f * screenX) / screenWidth - 1.0f; float ndcY = 1.0f - (2.0f * screenY) / screenHeight; float ndcZ = screenZ;
Step 3: Compute the Inverse View-Projection Matrix
The core of reversing the transform lies in using the inverse of your viewProjectionMatrix—this matrix undoes the combined effect of the camera view and projection transforms:
D3DXMATRIX invViewProj; // Calculate the inverse of the view-projection matrix if (D3DXMatrixInverse(&invViewProj, nullptr, &viewProjectionMatrix) == NULL) { // Handle inversion failure (e.g., singular matrix) D3DXVECTOR3 defaultWorld(0.0f, 0.0f, 0.0f); return defaultWorld; }
Step 4: Transform Back to 3D World Coordinates
We’ll use a homogeneous NDC vector (ndcX, ndcY, ndcZ, 1) and multiply it by the inverse matrix, then perform perspective division again to get the final 3D world coordinate:
// Create a homogeneous NDC vector D3DXVECTOR4 ndcHomogeneous(ndcX, ndcY, ndcZ, 1.0f); // Transform by the inverse view-projection matrix D3DXVECTOR4 worldHomogeneous; D3DXVec4Transform(&worldHomogeneous, &ndcHomogeneous, &invViewProj); // Undo perspective division to get the 3D world coordinate D3DXVECTOR3 pWorld; if (worldHomogeneous.w != 0.0f) { pWorld.x = worldHomogeneous.x / worldHomogeneous.w; pWorld.y = worldHomogeneous.y / worldHomogeneous.w; pWorld.z = worldHomogeneous.z / worldHomogeneous.w; } else { // Handle division by zero (rare if matrix inversion succeeded) pWorld = D3DXVECTOR3(0.0f, 0.0f, 0.0f); }
Step 5: Handling Cases Without Depth (Camera Ray)
If you don’t have the screenZ depth value, you can compute the camera ray that passes through the screen point. This ray can then be used to intersect with objects in your scene to find the corresponding 3D point:
- Get Camera Position: Transform the origin
(0,0,0)with the inverse view matrix to get the camera’s world position:D3DXMATRIX invViewMatrix; D3DXMatrixInverse(&invViewMatrix, nullptr, &renderer->viewMatrix); D3DXVECTOR4 cameraHomogeneous(0.0f, 0.0f, 0.0f, 1.0f); D3DXVec4Transform(&cameraHomogeneous, &cameraHomogeneous, &invViewMatrix); D3DXVECTOR3 cameraPos(cameraHomogeneous.x / cameraHomogeneous.w, cameraHomogeneous.y / cameraHomogeneous.w, cameraHomogeneous.z / cameraHomogeneous.w); - Calculate Ray Direction: Transform a far-plane NDC point
(ndcX, ndcY, 1.0f)to world space, then subtract the camera position and normalize the result:
Now you have a ray starting atD3DXVECTOR4 farNdcHomogeneous(ndcX, ndcY, 1.0f, 1.0f); D3DXVECTOR4 farWorldHomogeneous; D3DXVec4Transform(&farWorldHomogeneous, &farNdcHomogeneous, &invViewProj); D3DXVECTOR3 farWorld(farWorldHomogeneous.x / farWorldHomogeneous.w, farWorldHomogeneous.y / farWorldHomogeneous.w, farWorldHomogeneous.z / farWorldHomogeneous.w); D3DXVECTOR3 rayDir = farWorld - cameraPos; D3DXVec3Normalize(&rayDir, &rayDir);cameraPoswith directionrayDir—any point along this ray will project to your original 2D screen point.
Why This Works
Your forward transform maps world space to NDC via the view-projection matrix, then applies perspective division to get screen coordinates. Reversing this requires:
- Converting screen coordinates back to NDC
- Using the inverse matrix to map NDC back to homogeneous world space
- Undoing perspective division by dividing by the
wcomponent of the resulting homogeneous vector
内容的提问来源于stack exchange,提问作者Bob A

