Post

UE5获取屏幕上的点在三维空间中的位置

方法

使用Deproject Screen To World方法

Desktop View Deproject Screen To World

  • 输入
    • Player: 当前视口所属的Player的Player Controller
    • Screen Position: 需要转换的屏幕坐标
  • 输出
    • World Position: 转换后的三维空间坐标
    • World Direction: 转换后,从当前视口摄像机向三维空间坐标方向的方向向量

原理

UE5 实现源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
bool UGameplayStatics::DeprojectScreenToWorld(APlayerController const* Player
					  , const FVector2D& ScreenPosition
					  , FVector& WorldPosition
					  , FVector& WorldDirection)
{  
    ULocalPlayer* const LP = Player ? Player->GetLocalPlayer() : nullptr;  
    if (LP && LP->ViewportClient)  
    {  
       // get the projection data  
       FSceneViewProjectionData ProjectionData;  
       if (LP->GetProjectionData(LP->ViewportClient->Viewport, /*out*/ ProjectionData))  
       {  
	      // 获得投影矩阵的逆矩阵,然后调用下一段代码中的函数
          FMatrix const InvViewProjMatrix = ProjectionData
	          .ComputeViewProjectionMatrix()
	          .InverseFast();  
          FSceneView::DeprojectScreenToWorld(ScreenPosition
	          , ProjectionData.GetConstrainedViewRect()
	          , InvViewProjMatrix
	          , /*out*/ WorldPosition
	          , /*out*/ WorldDirection);  
          return true;  
       }  
    }  
  
    // something went wrong, zero things and return false  
    WorldPosition = FVector::ZeroVector;  
    WorldDirection = FVector::ZeroVector;  
    return false;  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
void FSceneView::DeprojectScreenToWorld(const FVector2D& ScreenPos
				, const FIntRect& ViewRect
				, const FMatrix& InvViewProjMatrix
				, FVector& out_WorldOrigin
				, FVector& out_WorldDirection)
{  
    float PixelX = FMath::TruncToFloat(ScreenPos.X);  
    float PixelY = FMath::TruncToFloat(ScreenPos.Y);  
  
    // Get the eye position and direction of the mouse cursor in two stages (inverse transform projection, then inverse transform view).  
    // This avoids the numerical instability that occurs when a view matrix with large translation is composed with a projection matrix  
    // Get the pixel coordinates into 0..1 normalized coordinates within the constrained view rectangle
    // 翻译:首先将需要变换的点的坐标转换到(0, 0)到(1, 1)范围的平面空间中
    const float NormalizedX = (PixelX - ViewRect.Min.X) / ((float)ViewRect.Width());  
    const float NormalizedY = (PixelY - ViewRect.Min.Y) / ((float)ViewRect.Height());  
  
    // Get the pixel coordinates into -1..1 projection space 
    // 翻译:通过简单的数学运算将点转换到(-1, -1)到(1, 1)的投影空间中
    const float ScreenSpaceX = (NormalizedX - 0.5f) * 2.0f;  
    const float ScreenSpaceY = ((1.0f - NormalizedY) - 0.5f) * 2.0f;  
  
    // The start of the ray trace is defined to be at mousex,mousey,1 in projection space (z=1 is near, z=0 is far - this gives us better precision)  
    // To get the direction of the ray trace we need to use any z between the near and the far plane, so let's use (mousex, mousey, 0.01)
    // 翻译:这里UE设定的裁剪空间近平面和远平面分别为 z=1 和 z=0
    // 由于输出结果需要一个方向向量,所以首先需要两点确定一条直线,即下面的Start和End两个点
    // 为什么End点的Z不是0?
    const FVector4 RayStartProjectionSpace = FVector4(ScreenSpaceX, ScreenSpaceY, 1.0f, 1.0f);  
    const FVector4 RayEndProjectionSpace = FVector4(ScreenSpaceX, ScreenSpaceY, 0.01f, 1.0f);  
  
    // Projection (changing the W coordinate) is not handled by the FMatrix transforms that work with vectors, so multiplications  
    // by the projection matrix should use homogeneous coordinates (i.e. FPlane).
    // 翻译:这里使用投影矩阵的逆矩阵将裁切空间中的点转换到世界空间中
    // 由于投影矩阵的一些缩放会更改点的W值,这是我们不需要的,所以在下方再除以W才能得到最终结果
    const FVector4 HGRayStartWorldSpace = InvViewProjMatrix.TransformFVector4(RayStartProjectionSpace);  
    const FVector4 HGRayEndWorldSpace = InvViewProjMatrix.TransformFVector4(RayEndProjectionSpace);  
    FVector RayStartWorldSpace(HGRayStartWorldSpace.X
	    , HGRayStartWorldSpace.Y
	    , HGRayStartWorldSpace.Z);  
    FVector RayEndWorldSpace(HGRayEndWorldSpace.X
	    , HGRayEndWorldSpace.Y
	    , HGRayEndWorldSpace.Z);  
    // divide vectors by W to undo any projection and get the 3-space coordinate  
    if (HGRayStartWorldSpace.W != 0.0f)  
    {  
       RayStartWorldSpace /= HGRayStartWorldSpace.W;  
    }  
    if (HGRayEndWorldSpace.W != 0.0f)  
    {  
       RayEndWorldSpace /= HGRayEndWorldSpace.W;  
    }  
    const FVector RayDirWorldSpace = (RayEndWorldSpace - RayStartWorldSpace).GetSafeNormal();  
  
    // Finally, store the results in the outputs  
    out_WorldOrigin = RayStartWorldSpace;  
    out_WorldDirection = RayDirWorldSpace;  
}

要理解上面两段代码需要一些简单的图形学知识,总体来说,整个流程就是基本渲染流程中 Model-View-Projection 变换的逆变换

This post is licensed under CC BY 4.0 by the author.