Skip to content

Instantly share code, notes, and snippets.

@connorjak
Last active February 5, 2024 21:32
Show Gist options
  • Save connorjak/a01f22c7e2c595c7716d9645594d5fa6 to your computer and use it in GitHub Desktop.
Save connorjak/a01f22c7e2c595c7716d9645594d5fa6 to your computer and use it in GitHub Desktop.
Copy pasted source code from UE 4.27.2 that shows math details of how camera perspective matrices are calculated.
// COPY PASTED FROM UNREAL 4.27.2 SOURCE CODE
// Copyright Epic Games, Inc. All Rights Reserved.
//...
FMatrix FMinimalViewInfo::CalculateProjectionMatrix() const
{
FMatrix ProjectionMatrix;
if (ProjectionMode == ECameraProjectionMode::Orthographic)
{
const float YScale = 1.0f / AspectRatio;
const float HalfOrthoWidth = OrthoWidth / 2.0f;
const float ScaledOrthoHeight = OrthoWidth / 2.0f * YScale;
const float NearPlane = OrthoNearClipPlane;
const float FarPlane = OrthoFarClipPlane;
const float ZScale = 1.0f / (FarPlane - NearPlane);
const float ZOffset = -NearPlane;
ProjectionMatrix = FReversedZOrthoMatrix(
HalfOrthoWidth,
ScaledOrthoHeight,
ZScale,
ZOffset
);
}
else
{
// Avoid divide by zero in the projection matrix calculation by clamping FOV
ProjectionMatrix = FReversedZPerspectiveMatrix(
FMath::Max(0.001f, FOV) * (float)PI / 360.0f,
AspectRatio,
1.0f,
GNearClippingPlane );
}
if (!OffCenterProjectionOffset.IsZero())
{
const float Left = -1.0f + OffCenterProjectionOffset.X;
const float Right = Left + 2.0f;
const float Bottom = -1.0f + OffCenterProjectionOffset.Y;
const float Top = Bottom + 2.0f;
ProjectionMatrix.M[2][0] = (Left + Right) / (Left - Right);
ProjectionMatrix.M[2][1] = (Bottom + Top) / (Bottom - Top);
}
return ProjectionMatrix;
}
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()));
InOutProjectionData.ProjectionMatrix = ViewInfo.CalculateProjectionMatrix();
}
else
{
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
const bool bMaintainXFOV =
((SizeX > SizeY) && (AspectRatioAxisConstraint == AspectRatio_MajorAxisFOV)) ||
(AspectRatioAxisConstraint == AspectRatio_MaintainXFOV) ||
(ViewInfo.ProjectionMode == ECameraProjectionMode::Orthographic);
if (bMaintainXFOV)
{
// 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;
}
float MatrixHalfFOV;
if (!bMaintainXFOV && ViewInfo.AspectRatio != 0.f && !CVarUseLegacyMaintainYFOV.GetValueOnGameThread())
{
// The view-info FOV is horizontal. But if we have a different aspect ratio constraint, we need to
// adjust this FOV value using the aspect ratio it was computed with, so we that we can compute the
// complementary FOV value (with the *effective* aspect ratio) correctly.
const float HalfXFOV = FMath::DegreesToRadians(FMath::Max(0.001f, ViewInfo.FOV) / 2.f);
const float HalfYFOV = FMath::Atan(FMath::Tan(HalfXFOV) / ViewInfo.AspectRatio);
MatrixHalfFOV = HalfYFOV;
}
else
{
// Avoid divide by zero in the projection matrix calculation by clamping FOV.
// Note the division by 360 instead of 180 because we want the half-FOV.
MatrixHalfFOV = FMath::Max(0.001f, ViewInfo.FOV) * (float)PI / 360.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(
MatrixHalfFOV,
MatrixHalfFOV,
XAxisMultiplier,
YAxisMultiplier,
GNearClippingPlane,
GNearClippingPlane
);
}
}
if (!ViewInfo.OffCenterProjectionOffset.IsZero())
{
const float Left = -1.0f + ViewInfo.OffCenterProjectionOffset.X;
const float Right = Left + 2.0f;
const float Bottom = -1.0f + ViewInfo.OffCenterProjectionOffset.Y;
const float Top = Bottom + 2.0f;
InOutProjectionData.ProjectionMatrix.M[2][0] = (Left + Right) / (Left - Right);
InOutProjectionData.ProjectionMatrix.M[2][1] = (Bottom + Top) / (Bottom - Top);
}
}
//...
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreTypes.h"
#include "Math/UnrealMathUtility.h"
#include "Math/Plane.h"
#include "Math/Matrix.h"
class FPerspectiveMatrix
: public FMatrix
{
public:
// Note: the value of this must match the mirror in Common.usf!
#define Z_PRECISION 0.0f
/**
* Constructor
*
* @param HalfFOVX Half FOV in the X axis
* @param HalfFOVY Half FOV in the Y axis
* @param MultFOVX multiplier on the X axis
* @param MultFOVY multiplier on the y axis
* @param MinZ distance to the near Z plane
* @param MaxZ distance to the far Z plane
*/
FPerspectiveMatrix(float HalfFOVX, float HalfFOVY, float MultFOVX, float MultFOVY, float MinZ, float MaxZ);
/**
* Constructor
*
* @param HalfFOV half Field of View in the Y direction
* @param Width view space width
* @param Height view space height
* @param MinZ distance to the near Z plane
* @param MaxZ distance to the far Z plane
* @note that the FOV you pass in is actually half the FOV, unlike most perspective matrix functions (D3DXMatrixPerspectiveFovLH).
*/
FPerspectiveMatrix(float HalfFOV, float Width, float Height, float MinZ, float MaxZ);
/**
* Constructor
*
* @param HalfFOV half Field of View in the Y direction
* @param Width view space width
* @param Height view space height
* @param MinZ distance to the near Z plane
* @note that the FOV you pass in is actually half the FOV, unlike most perspective matrix functions (D3DXMatrixPerspectiveFovLH).
*/
FPerspectiveMatrix(float HalfFOV, float Width, float Height, float MinZ);
};
class FReversedZPerspectiveMatrix : public FMatrix
{
public:
FReversedZPerspectiveMatrix(float HalfFOVX, float HalfFOVY, float MultFOVX, float MultFOVY, float MinZ, float MaxZ);
FReversedZPerspectiveMatrix(float HalfFOV, float Width, float Height, float MinZ, float MaxZ);
FReversedZPerspectiveMatrix(float HalfFOV, float Width, float Height, float MinZ);
};
#ifdef _MSC_VER
#pragma warning (push)
// Disable possible division by 0 warning
#pragma warning (disable : 4723)
#endif
FORCEINLINE FPerspectiveMatrix::FPerspectiveMatrix(float HalfFOVX, float HalfFOVY, float MultFOVX, float MultFOVY, float MinZ, float MaxZ)
: FMatrix(
FPlane(MultFOVX / FMath::Tan(HalfFOVX), 0.0f, 0.0f, 0.0f),
FPlane(0.0f, MultFOVY / FMath::Tan(HalfFOVY), 0.0f, 0.0f),
FPlane(0.0f, 0.0f, ((MinZ == MaxZ) ? (1.0f - Z_PRECISION) : MaxZ / (MaxZ - MinZ)), 1.0f),
FPlane(0.0f, 0.0f, -MinZ * ((MinZ == MaxZ) ? (1.0f - Z_PRECISION) : MaxZ / (MaxZ - MinZ)), 0.0f)
)
{ }
FORCEINLINE FPerspectiveMatrix::FPerspectiveMatrix(float HalfFOV, float Width, float Height, float MinZ, float MaxZ)
: FMatrix(
FPlane(1.0f / FMath::Tan(HalfFOV), 0.0f, 0.0f, 0.0f),
FPlane(0.0f, Width / FMath::Tan(HalfFOV) / Height, 0.0f, 0.0f),
FPlane(0.0f, 0.0f, ((MinZ == MaxZ) ? (1.0f - Z_PRECISION) : MaxZ / (MaxZ - MinZ)), 1.0f),
FPlane(0.0f, 0.0f, -MinZ * ((MinZ == MaxZ) ? (1.0f - Z_PRECISION) : MaxZ / (MaxZ - MinZ)), 0.0f)
)
{ }
FORCEINLINE FPerspectiveMatrix::FPerspectiveMatrix(float HalfFOV, float Width, float Height, float MinZ)
: FMatrix(
FPlane(1.0f / FMath::Tan(HalfFOV), 0.0f, 0.0f, 0.0f),
FPlane(0.0f, Width / FMath::Tan(HalfFOV) / Height, 0.0f, 0.0f),
FPlane(0.0f, 0.0f, (1.0f - Z_PRECISION), 1.0f),
FPlane(0.0f, 0.0f, -MinZ * (1.0f - Z_PRECISION), 0.0f)
)
{ }
FORCEINLINE FReversedZPerspectiveMatrix::FReversedZPerspectiveMatrix(float HalfFOVX, float HalfFOVY, float MultFOVX, float MultFOVY, float MinZ, float MaxZ)
: FMatrix(
FPlane(MultFOVX / FMath::Tan(HalfFOVX), 0.0f, 0.0f, 0.0f),
FPlane(0.0f, MultFOVY / FMath::Tan(HalfFOVY), 0.0f, 0.0f),
FPlane(0.0f, 0.0f, ((MinZ == MaxZ) ? 0.0f : MinZ / (MinZ - MaxZ)), 1.0f),
FPlane(0.0f, 0.0f, ((MinZ == MaxZ) ? MinZ : -MaxZ * MinZ / (MinZ - MaxZ)), 0.0f)
)
{ }
FORCEINLINE FReversedZPerspectiveMatrix::FReversedZPerspectiveMatrix(float HalfFOV, float Width, float Height, float MinZ, float MaxZ)
: FMatrix(
FPlane(1.0f / FMath::Tan(HalfFOV), 0.0f, 0.0f, 0.0f),
FPlane(0.0f, Width / FMath::Tan(HalfFOV) / Height, 0.0f, 0.0f),
FPlane(0.0f, 0.0f, ((MinZ == MaxZ) ? 0.0f : MinZ / (MinZ - MaxZ)), 1.0f),
FPlane(0.0f, 0.0f, ((MinZ == MaxZ) ? MinZ : -MaxZ * MinZ / (MinZ - MaxZ)), 0.0f)
)
{ }
FORCEINLINE FReversedZPerspectiveMatrix::FReversedZPerspectiveMatrix(float HalfFOV, float Width, float Height, float MinZ)
: FMatrix(
FPlane(1.0f / FMath::Tan(HalfFOV), 0.0f, 0.0f, 0.0f),
FPlane(0.0f, Width / FMath::Tan(HalfFOV) / Height, 0.0f, 0.0f),
FPlane(0.0f, 0.0f, 0.0f, 1.0f),
FPlane(0.0f, 0.0f, MinZ, 0.0f)
)
{ }
#ifdef _MSC_VER
#pragma warning (pop)
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment