UE4 MVP 坐标转换
局部的模型点从局部空间(相对空间)到世界空间以及到屏幕空间。
ViewProject
视图投影矩阵。
ULocalPlayer* const LP = Player ? Player->GetLocalPlayer() : nullptr;
if (LP && LP->ViewportClient)
{
// get the projection data
FSceneViewProjectionData ProjectionData;
if (LP->GetProjectionData(LP->ViewportClient->Viewport, eSSP_FULL, /*out*/ ProjectionData))
{
FMatrix const InvViewProjMatrix = ProjectionData.ComputeViewProjectionMatrix().InverseFast();
}
}
DeprojectScreenToWorld
屏幕空间到世界空间。
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
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
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.5)
const FVector4 RayStartProjectionSpace = FVector4(ScreenSpaceX, ScreenSpaceY, 1.0f, 1.0f);
const FVector4 RayEndProjectionSpace = FVector4(ScreenSpaceX, ScreenSpaceY, 0.5f, 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).
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;
}
ProjectWorldToScreen
世界空间到屏幕空间。
- 通过
ViewProjectionMatrix
矩阵把点从世界位置转为视口下,然后再从视口点转为近剪裁面上的点
FPlane Result = ViewProjectionMatrix.TransformFVector4(FVector4(WorldPosition, 1.f));
- 通过投射除法映射到NDC空间[-1.1]
// the result of this will be x and y coords in -1..1 projection space
const float RHW = 1.0f / Result.W;
FPlane PosInScreenSpace = FPlane(Result.X * RHW, Result.Y * RHW, Result.Z * RHW, Result.W);
- 通过半兰伯特手法把[-1.1]映射到[0,1]
// Move from projection space to normalized 0..1 UI space
const float NormalizedX = ( PosInScreenSpace.X / 2.f ) + 0.5f;
const float NormalizedY = 1.f - ( PosInScreenSpace.Y / 2.f ) - 0.5f;
- 通过和屏幕宽高相乘获得屏幕的位置
FVector2D RayStartViewRectSpace(
( NormalizedX * (float)ViewRect.Width() ),
( NormalizedY * (float)ViewRect.Height() )
);
bool FSceneView::ProjectWorldToScreen(const FVector& WorldPosition, const FIntRect& ViewRect, const FMatrix& ViewProjectionMatrix, FVector2D& out_ScreenPos)
{
FPlane Result = ViewProjectionMatrix.TransformFVector4(FVector4(WorldPosition, 1.f));
if ( Result.W > 0.0f )
{
// the result of this will be x and y coords in -1..1 projection space
const float RHW = 1.0f / Result.W;
FPlane PosInScreenSpace = FPlane(Result.X * RHW, Result.Y * RHW, Result.Z * RHW, Result.W);
// Move from projection space to normalized 0..1 UI space
const float NormalizedX = ( PosInScreenSpace.X / 2.f ) + 0.5f;
const float NormalizedY = 1.f - ( PosInScreenSpace.Y / 2.f ) - 0.5f;
FVector2D RayStartViewRectSpace(
( NormalizedX * (float)ViewRect.Width() ),
( NormalizedY * (float)ViewRect.Height() )
);
out_ScreenPos = RayStartViewRectSpace + FVector2D(static_cast<float>(ViewRect.Min.X), static_cast<float>(ViewRect.Min.Y));
return true;
}
return false;
}