赞
踩
UE4 的透视投影矩阵比较特殊。以下分析Game模式和Editor模式下每一帧投影矩阵和视图矩阵的相关处理过程。
Tick
UGameViewportClient::Draw
ULocalPlayer::CalcSceneView
ULocalPlayer::GetProjectionData
FMinimalViewInfo::CalculateProjectionMatrixGivenView
void UGameViewportClient::Draw(FViewport* InViewport, FCanvas* SceneCanvas){
//
FSceneView* View = LocalPlayer->CalcSceneView(&ViewFamily, ViewLocation, ViewRotation, InViewport, &GameViewDrawer, PassType);
//
}
FSceneView* ULocalPlayer::CalcSceneView( class FSceneViewFamily* ViewFamily, FVector& OutViewLocation, FRotator& OutViewRotation, FViewport* Viewport, class FViewElementDrawer* ViewDrawer, EStereoscopicPass StereoPass) { if ((PlayerController == NULL) || (Size.X <= 0.f) || (Size.Y <= 0.f) || (Viewport == NULL)) { return NULL; } FSceneViewInitOptions ViewInitOptions; // get the projection data if (GetProjectionData(Viewport, StereoPass, /*inout*/ ViewInitOptions) == false) { // Return NULL if this we didn't get back the info we needed return NULL; } // return if we have an invalid view rect if (!ViewInitOptions.IsValidViewRectangle()) { return NULL; } // Get the viewpoint...technically doing this twice // but it makes GetProjectionData better FMinimalViewInfo ViewInfo; GetViewPoint(ViewInfo, StereoPass); OutViewLocation = ViewInfo.Location; OutViewRotation = ViewInfo.Rotation; if (PlayerController->PlayerCameraManager != NULL) { // Apply screen fade effect to screen. if (PlayerController->PlayerCameraManager->bEnableFading) { ViewInitOptions.OverlayColor = PlayerController->PlayerCameraManager->FadeColor; ViewInitOptions.OverlayColor.A = FMath::Clamp(PlayerController->PlayerCameraManager->FadeAmount,0.0f,1.0f); } // Do color scaling if desired. if (PlayerController->PlayerCameraManager->bEnableColorScaling) { ViewInitOptions.ColorScale = FLinearColor( PlayerController->PlayerCameraManager->ColorScale.X, PlayerController->PlayerCameraManager->ColorScale.Y, PlayerController->PlayerCameraManager->ColorScale.Z ); } // Was there a camera cut this frame? ViewInitOptions.bInCameraCut = PlayerController->PlayerCameraManager->bGameCameraCutThisFrame; } check(PlayerController && PlayerController->GetWorld()); // Fill out the rest of the view init options ViewInitOptions.ViewFamily = ViewFamily; ViewInitOptions.SceneViewStateInterface = ((StereoPass != eSSP_RIGHT_EYE) ? ViewState.GetReference() : StereoViewState.GetReference()); ViewInitOptions.ViewActor = PlayerController->GetViewTarget(); ViewInitOptions.ViewElementDrawer = ViewDrawer; ViewInitOptions.BackgroundColor = FLinearColor::Black; ViewInitOptions.LODDistanceFactor = PlayerController->LocalPlayerCachedLODDistanceFactor; ViewInitOptions.StereoPass = StereoPass; ViewInitOptions.WorldToMetersScale = PlayerController->GetWorldSettings()->WorldToMeters; ViewInitOptions.CursorPos = Viewport->HasMouseCapture() ? FIntPoint(-1, -1) : FIntPoint(Viewport->GetMouseX(), Viewport->GetMouseY()); ViewInitOptions.bOriginOffsetThisFrame = PlayerController->GetWorld()->bOriginOffsetThisFrame; ViewInitOptions.bUseFieldOfViewForLOD = ViewInfo.bUseFieldOfViewForLOD; PlayerController->BuildHiddenComponentList(OutViewLocation, /*out*/ ViewInitOptions.HiddenPrimitives); FSceneView* const View = new FSceneView(ViewInitOptions); View->ViewLocation = OutViewLocation; View->ViewRotation = OutViewRotation; //@TODO: SPLITSCREEN: This call will have an issue with splitscreen, as the show flags are shared across the view family EngineShowFlagOrthographicOverride(View->IsPerspectiveProjection(), ViewFamily->EngineShowFlags); ViewFamily->Views.Add(View); { View->StartFinalPostprocessSettings(OutViewLocation); // CameraAnim override if (PlayerController->PlayerCameraManager) { TArray<FPostProcessSettings> const* CameraAnimPPSettings; TArray<float> const* CameraAnimPPBlendWeights; PlayerController->PlayerCameraManager->GetCachedPostProcessBlends(CameraAnimPPSettings, CameraAnimPPBlendWeights); for (int32 PPIdx = 0; PPIdx < CameraAnimPPBlendWeights->Num(); ++PPIdx) { View->OverridePostProcessSettings( (*CameraAnimPPSettings)[PPIdx], (*CameraAnimPPBlendWeights)[PPIdx]); } } // CAMERA OVERRIDE // NOTE: Matinee works through this channel View->OverridePostProcessSettings(ViewInfo.PostProcessSettings, ViewInfo.PostProcessBlendWeight); View->EndFinalPostprocessSettings(ViewInitOptions); } for (int ViewExt = 0; ViewExt < ViewFamily->ViewExtensions.Num(); ViewExt++) { ViewFamily->ViewExtensions[ViewExt]->SetupView(*ViewFamily, *View); } return View; }
bool ULocalPlayer::GetProjectionData(FViewport* Viewport, EStereoscopicPass StereoPass, FSceneViewProjectionData& ProjectionData) const { // If the actor if ((Viewport == NULL) || (PlayerController == NULL) || (Viewport->GetSizeXY().X == 0) || (Viewport->GetSizeXY().Y == 0)) { return false; } int32 X = FMath::TruncToInt(Origin.X * Viewport->GetSizeXY().X); int32 Y = FMath::TruncToInt(Origin.Y * Viewport->GetSizeXY().Y); uint32 SizeX = FMath::TruncToInt(Size.X * Viewport->GetSizeXY().X); uint32 SizeY = FMath::TruncToInt(Size.Y * Viewport->GetSizeXY().Y); #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // We expect some size to avoid problems with the view rect manipulation if(SizeX > 50 && SizeY > 50) { int32 Value = CVarViewportTest.GetValueOnGameThread(); if(Value) { int InsetX = SizeX / 4; int InsetY = SizeY / 4; // this allows to test various typical view port situations (todo: split screen) switch(Value) { case 1: X += InsetX; Y += InsetY; SizeX -= InsetX * 2; SizeY -= InsetY * 2;break; case 2: Y += InsetY; SizeY -= InsetY * 2; break; case 3: X += InsetX; SizeX -= InsetX * 2; break; case 4: SizeX /= 2; SizeY /= 2; break; case 5: SizeX /= 2; SizeY /= 2; X += SizeX; break; case 6: SizeX /= 2; SizeY /= 2; Y += SizeY; break; case 7: SizeX /= 2; SizeY /= 2; X += SizeX; Y += SizeY; break; } } } #endif FIntRect UnconstrainedRectangle = FIntRect(X, Y, X+SizeX, Y+SizeY); ProjectionData.SetViewRectangle(UnconstrainedRectangle); // Get the viewpoint. FMinimalViewInfo ViewInfo; GetViewPoint(/*out*/ ViewInfo, StereoPass); // If stereo rendering is enabled, update the size and offset appropriately for this pass const bool bNeedStereo = (StereoPass != eSSP_FULL) && GEngine->IsStereoscopic3D(); if (bNeedStereo) { GEngine->StereoRenderingDevice->AdjustViewRect(StereoPass, X, Y, SizeX, SizeY); } // scale distances for cull distance purposes by the ratio of our current FOV to the default FOV PlayerController->LocalPlayerCachedLODDistanceFactor = ViewInfo.FOV / FMath::Max<float>(0.01f, (PlayerController->PlayerCameraManager != NULL) ? PlayerController->PlayerCameraManager->DefaultFOV : 90.f); FVector StereoViewLocation = ViewInfo.Location; if (bNeedStereo || (GEngine->HMDDevice.IsValid() && GEngine->HMDDevice->IsHeadTrackingAllowed())) { GEngine->StereoRenderingDevice->CalculateStereoViewOffset(StereoPass, ViewInfo.Rotation, GetWorld()->GetWorldSettings()->WorldToMeters, StereoViewLocation); } // Create the view matrix ProjectionData.ViewOrigin = StereoViewLocation; ProjectionData.ViewRotationMatrix = FInverseRotationMatrix(ViewInfo.Rotation) * FMatrix( FPlane(0, 0, 1, 0), FPlane(1, 0, 0, 0), FPlane(0, 1, 0, 0), FPlane(0, 0, 0, 1)); if (!bNeedStereo) { // Create the projection matrix (and possibly constrain the view rectangle) FMinimalViewInfo::CalculateProjectionMatrixGivenView(ViewInfo, AspectRatioAxisConstraint, ViewportClient->Viewport, /*inout*/ ProjectionData); } else { // Let the stereoscopic rendering device handle creating its own projection matrix, as needed ProjectionData.ProjectionMatrix = GEngine->StereoRenderingDevice->GetStereoProjectionMatrix(StereoPass, ViewInfo.FOV); // calculate the out rect ProjectionData.SetViewRectangle(FIntRect(X, Y, X + SizeX, Y + SizeY)); } return true; }
FMinimalViewInfo::CalculateProjectionMatrixGivenView(ViewInfo, AspectRatioAxisConstraint, ViewportClient->Viewport, /*inout*/ ProjectionData);
Tick
FEditorViewportClient::Draw
FEditorViewportClient::CalcSceneView
FMinimalViewInfo::CalculateProjectionMatrixGivenView
void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas){
FSceneView* View = CalcSceneView( &ViewFamily );
}
FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily) { FSceneViewInitOptions ViewInitOptions; FViewportCameraTransform& ViewTransform = GetViewTransform(); const ELevelViewportType EffectiveViewportType = GetViewportType(); const FVector& ViewLocation = ViewTransform.GetLocation(); const FRotator& ViewRotation = ViewTransform.GetRotation(); const FIntPoint ViewportSizeXY = Viewport->GetSizeXY(); FIntRect ViewRect = FIntRect(0, 0, ViewportSizeXY.X, ViewportSizeXY.Y); ViewInitOptions.SetViewRectangle(ViewRect); // no matter how we are drawn (forced or otherwise), reset our time here TimeForForceRedraw = 0.0; const bool bConstrainAspectRatio = bUseControllingActorViewInfo && ControllingActorViewInfo.bConstrainAspectRatio; const EAspectRatioAxisConstraint AspectRatioAxisConstraint = GetDefault<ULevelEditorViewportSettings>()->AspectRatioAxisConstraint; //视图矩阵 ViewInitOptions.ViewOrigin = ViewLocation; if (bUseControllingActorViewInfo) { ViewInitOptions.ViewRotationMatrix = FInverseRotationMatrix(ViewRotation) * FMatrix( FPlane(0, 0, 1, 0), FPlane(1, 0, 0, 0), FPlane(0, 1, 0, 0), FPlane(0, 0, 0, 1)); FMinimalViewInfo::CalculateProjectionMatrixGivenView(ControllingActorViewInfo, AspectRatioAxisConstraint, Viewport, /*inout*/ ViewInitOptions); } else { // if (EffectiveViewportType == LVT_Perspective) { if (bUsingOrbitCamera) { ViewInitOptions.ViewRotationMatrix = FTranslationMatrix(ViewLocation) * ViewTransform.ComputeOrbitMatrix(); } else { ViewInitOptions.ViewRotationMatrix = FInverseRotationMatrix(ViewRotation); } ViewInitOptions.ViewRotationMatrix = ViewInitOptions.ViewRotationMatrix * FMatrix( FPlane(0, 0, 1, 0), FPlane(1, 0, 0, 0), FPlane(0, 1, 0, 0), FPlane(0, 0, 0, 1)); float MinZ = GetNearClipPlane(); float MaxZ = MinZ; // Avoid zero ViewFOV's which cause divide by zero's in projection matrix float MatrixFOV = FMath::Max(0.001f, ViewFOV) * (float)PI / 360.0f; if (bConstrainAspectRatio) { if ((int32)ERHIZBuffer::IsInverted != 0) { ViewInitOptions.ProjectionMatrix = FReversedZPerspectiveMatrix( MatrixFOV, MatrixFOV, 1.0f, AspectRatio, MinZ, MaxZ ); } else { ViewInitOptions.ProjectionMatrix = FPerspectiveMatrix( MatrixFOV, MatrixFOV, 1.0f, AspectRatio, MinZ, MaxZ ); } } else { float XAxisMultiplier; float YAxisMultiplier; if (((ViewportSizeXY.X > ViewportSizeXY.Y) && (AspectRatioAxisConstraint == AspectRatio_MajorAxisFOV)) || (AspectRatioAxisConstraint == AspectRatio_MaintainXFOV)) { //if the viewport is wider than it is tall XAxisMultiplier = 1.0f; YAxisMultiplier = ViewportSizeXY.X / (float)ViewportSizeXY.Y; } else { //if the viewport is taller than it is wide XAxisMultiplier = ViewportSizeXY.Y / (float)ViewportSizeXY.X; YAxisMultiplier = 1.0f; } if ((int32)ERHIZBuffer::IsInverted != 0) { ViewInitOptions.ProjectionMatrix = FReversedZPerspectiveMatrix( MatrixFOV, MatrixFOV, XAxisMultiplier, YAxisMultiplier, MinZ, MaxZ ); } else { ViewInitOptions.ProjectionMatrix = FPerspectiveMatrix( MatrixFOV, MatrixFOV, XAxisMultiplier, YAxisMultiplier, MinZ, MaxZ ); } } } else { static_assert((int32)ERHIZBuffer::IsInverted != 0, "Check all the Rotation Matrix transformations!"); float ZScale = 0.5f / HALF_WORLD_MAX; float ZOffset = HALF_WORLD_MAX; //The divisor for the matrix needs to match the translation code. const float Zoom = GetOrthoUnitsPerPixel(Viewport); float OrthoWidth = Zoom * ViewportSizeXY.X / 2.0f; float OrthoHeight = Zoom * ViewportSizeXY.Y / 2.0f; if (EffectiveViewportType == LVT_OrthoXY) { ViewInitOptions.ViewRotationMatrix = FMatrix( FPlane(1, 0, 0, 0), FPlane(0, -1, 0, 0), FPlane(0, 0, -1, 0), FPlane(0, 0, -ViewLocation.Z, 1)); } else if (EffectiveViewportType == LVT_OrthoXZ) { ViewInitOptions.ViewRotationMatrix = FMatrix( FPlane(1, 0, 0, 0), FPlane(0, 0, -1, 0), FPlane(0, 1, 0, 0), FPlane(0, 0, -ViewLocation.Y, 1)); } else if (EffectiveViewportType == LVT_OrthoYZ) { ViewInitOptions.ViewRotationMatrix = FMatrix( FPlane(0, 0, 1, 0), FPlane(1, 0, 0, 0), FPlane(0, 1, 0, 0), FPlane(0, 0, ViewLocation.X, 1)); } else if (EffectiveViewportType == LVT_OrthoNegativeXY) { ViewInitOptions.ViewRotationMatrix = FMatrix( FPlane(-1, 0, 0, 0), FPlane(0, -1, 0, 0), FPlane(0, 0, 1, 0), FPlane(0, 0, -ViewLocation.Z, 1)); } else if (EffectiveViewportType == LVT_OrthoNegativeXZ) { ViewInitOptions.ViewRotationMatrix = FMatrix( FPlane(-1, 0, 0, 0), FPlane(0, 0, 1, 0), FPlane(0, 1, 0, 0), FPlane(0, 0, -ViewLocation.Y, 1)); } else if (EffectiveViewportType == LVT_OrthoNegativeYZ) { ViewInitOptions.ViewRotationMatrix = FMatrix( FPlane(0, 0, -1, 0), FPlane(-1, 0, 0, 0), FPlane(0, 1, 0, 0), FPlane(0, 0, ViewLocation.X, 1)); } else { // Unknown viewport type check(false); } ViewInitOptions.ProjectionMatrix = FReversedZOrthoMatrix( OrthoWidth, OrthoHeight, ZScale, ZOffset ); } if (bConstrainAspectRatio) { ViewInitOptions.SetConstrainedViewRectangle(Viewport->CalculateViewExtents(AspectRatio, ViewRect)); } } ViewInitOptions.ViewFamily = ViewFamily; ViewInitOptions.SceneViewStateInterface = ViewState.GetReference(); ViewInitOptions.ViewElementDrawer = this; ViewInitOptions.BackgroundColor = GetBackgroundColor(); ViewInitOptions.EditorViewBitflag = (uint64)1 << ViewIndex, // send the bit for this view - each actor will check it's visibility bits against this // for ortho views to steal perspective view origin ViewInitOptions.OverrideLODViewOrigin = FVector::ZeroVector; ViewInitOptions.bUseFauxOrthoViewPos = true; if (bUseControllingActorViewInfo) { ViewInitOptions.bUseFieldOfViewForLOD = ControllingActorViewInfo.bUseFieldOfViewForLOD; } ViewInitOptions.OverrideFarClippingPlaneDistance = FarPlane; ViewInitOptions.CursorPos = CurrentMousePos; FSceneView* View = new FSceneView(ViewInitOptions); View->SubduedSelectionOutlineColor = GEngine->GetSubduedSelectionOutlineColor(); ViewFamily->Views.Add(View); View->StartFinalPostprocessSettings(ViewLocation); OverridePostProcessSettings( *View ); View->EndFinalPostprocessSettings(ViewInitOptions); return View; }
以上最主要的还是这个函数:
FMinimalViewInfo::CalculateProjectionMatrixGivenView(ControllingActorViewInfo, AspectRatioAxisConstraint, Viewport, /*inout*/ ViewInitOptions);
void FMinimalViewInfo::CalculateProjectionMatrixGivenView(const FMinimalViewInfo& ViewInfo, TEnumAsByte<enum EAspectRatioAxisConstraint> AspectRatioAxisConstraint, FViewport* Viewport, FSceneViewProjectionData& InOutProjectionData) { // Create the projection matrix (and possibly constrain the view rectangle) if (ViewInfo.bConstrainAspectRatio) { // Enforce a particular aspect ratio for the render of the scene. // Results in black bars at top/bottom etc. InOutProjectionData.SetConstrainedViewRectangle(Viewport->CalculateViewExtents(ViewInfo.AspectRatio, InOutProjectionData.GetViewRect())); // if (ViewInfo.ProjectionMode == ECameraProjectionMode::Orthographic) { const float YScale = 1.0f / ViewInfo.AspectRatio; const float OrthoWidth = ViewInfo.OrthoWidth / 2.0f; const float OrthoHeight = ViewInfo.OrthoWidth / 2.0f * YScale; const float NearPlane = ViewInfo.OrthoNearClipPlane; const float FarPlane = ViewInfo.OrthoFarClipPlane; const float ZScale = 1.0f / (FarPlane - NearPlane); const float ZOffset = -NearPlane; InOutProjectionData.ProjectionMatrix = FReversedZOrthoMatrix( OrthoWidth, OrthoHeight, ZScale, ZOffset ); } else { // Avoid divide by zero in the projection matrix calculation by clamping FOV InOutProjectionData.ProjectionMatrix = FReversedZPerspectiveMatrix( FMath::Max(0.001f, ViewInfo.FOV) * (float)PI / 360.0f, ViewInfo.AspectRatio, 1.0f, GNearClippingPlane ); } } else { // Avoid divide by zero in the projection matrix calculation by clamping FOV float MatrixFOV = FMath::Max(0.001f, ViewInfo.FOV) * (float)PI / 360.0f; float XAxisMultiplier; float YAxisMultiplier; const FIntRect& ViewRect = InOutProjectionData.GetViewRect(); const int32 SizeX = ViewRect.Width(); const int32 SizeY = ViewRect.Height(); // if x is bigger, and we're respecting x or major axis, AND mobile isn't forcing us to be Y axis aligned if (((SizeX > SizeY) && (AspectRatioAxisConstraint == AspectRatio_MajorAxisFOV)) || (AspectRatioAxisConstraint == AspectRatio_MaintainXFOV) || (ViewInfo.ProjectionMode == ECameraProjectionMode::Orthographic)) { //if the viewport is wider than it is tall XAxisMultiplier = 1.0f; YAxisMultiplier = SizeX / (float)SizeY; } else { //if the viewport is taller than it is wide XAxisMultiplier = SizeY / (float)SizeX; YAxisMultiplier = 1.0f; } if (ViewInfo.ProjectionMode == ECameraProjectionMode::Orthographic) { const float OrthoWidth = ViewInfo.OrthoWidth / 2.0f * XAxisMultiplier; const float OrthoHeight = (ViewInfo.OrthoWidth / 2.0f) / YAxisMultiplier; const float NearPlane = ViewInfo.OrthoNearClipPlane; const float FarPlane = ViewInfo.OrthoFarClipPlane; const float ZScale = 1.0f / (FarPlane - NearPlane); const float ZOffset = -NearPlane; InOutProjectionData.ProjectionMatrix = FReversedZOrthoMatrix( OrthoWidth, OrthoHeight, ZScale, ZOffset ); } else { InOutProjectionData.ProjectionMatrix = FReversedZPerspectiveMatrix( MatrixFOV, MatrixFOV, XAxisMultiplier, YAxisMultiplier, GNearClippingPlane, GNearClippingPlane ); } } }
UE4 的投影矩阵比较特殊,近剪裁和远剪裁同为一个值:
FReversedZPerspectiveMatrix(
MatrixFOV,
MatrixFOV,
XAxisMultiplier,
YAxisMultiplier,
GNearClippingPlane,
GNearClippingPlane
);
如果想自定义投影矩阵,具体请参考链接,通过继承ULocalPlayer
重写CalcSceneView
函数。
FSceneView * UOffAxisLocalPlayer::CalcSceneView(FSceneViewFamily * ViewFamily, FVector &amp;OutViewLocation, FRotator &amp;OutViewRotation, FViewport * Viewport, FViewElementDrawer * ViewDrawer, EStereoscopicPass StereoPass) { FSceneView* View = ULocalPlayer::CalcSceneView(ViewFamily, OutViewLocation, OutViewRotation, Viewport, ViewDrawer, StereoPass); if (View) { FMatrix CurrentMatrix = View->ViewMatrices.GetProjectionMatrix(); float FOV = FMath::DegreesToRadians(60.0f); FMatrix ProjectionMatrix = FReversedZPerspectiveMatrix(FOV, 16.0f, 9.0f, GNearClippingPlane); View->(ProjectionMatrix); } return View; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。