Add an Orthogonal Projection option to the Camera Gem (#2414)

* Add an Orthogonal Projection option to the Camera Gem

This adds a check-box to opt into an ortho projection along with a half-width parameter to adjust the size of the visible area. Includes some light tweaks to ensure debug rendering looks OK and that we generate a correct camera state for these non-perspective views.

Known issue: while in "Be this camera" mode in the Editor using an ortho projection manipulators aren't working correctly. This appears to be a downstream issue with CameraState consumers not actually checking the ortho flag.

Signed-off-by: nvsickle <nvsickle@amazon.com>

* Fix some typos

Signed-off-by: nvsickle <nvsickle@amazon.com>

* Account for reversed depth buffer

Signed-off-by: nvsickle <nvsickle@amazon.com>

* Clarify depth reversal for MakeOrthographicMatrixRH

Signed-off-by: nvsickle <nvsickle@amazon.com>
This commit is contained in:
Nicholas Van Sickle
2021-07-26 13:58:50 -07:00
committed by GitHub
parent e81f59d1e1
commit 7f84a4318c
12 changed files with 232 additions and 33 deletions
+12 -1
View File
@@ -463,7 +463,7 @@ void EditorViewportWidget::Update()
if (m_updateCameraPositionNextTick)
{
auto cameraState = m_renderViewport->GetCameraState();
auto cameraState = GetCameraState();
AZ::Matrix3x4 matrix;
matrix.SetBasisAndTranslation(cameraState.m_side, cameraState.m_forward, cameraState.m_up, cameraState.m_position);
auto m = AZMatrix3x4ToLYMatrix3x4(matrix);
@@ -1138,6 +1138,17 @@ void EditorViewportWidget::OnMenuSelectCurrentCamera()
AzFramework::CameraState EditorViewportWidget::GetCameraState()
{
if (m_viewEntityId.IsValid())
{
bool cameraStateAcquired = false;
AzFramework::CameraState cameraState;
Camera::EditorCameraViewRequestBus::BroadcastResult(cameraStateAcquired,
&Camera::EditorCameraViewRequestBus::Events::GetCameraState, cameraState);
if (cameraStateAcquired)
{
return cameraState;
}
}
return m_renderViewport->GetCameraState();
}
@@ -71,7 +71,7 @@ namespace AZ
return &out;
}
Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist)
Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist, bool reverseDepth)
{
AZ_Assert(right > left, "right should be greater than left");
// valid to have matrix invert top/bottom and far/near
@@ -83,6 +83,11 @@ namespace AZ
return nullptr;
}
if (reverseDepth)
{
AZStd::swap(nearDist, farDist);
}
out.SetRow(0, 2.f/(right - left), 0.f, 0.f, - (right + left) / (right - left) );
out.SetRow(1, 0.f, 2.f / (top - bottom), 0.f, - (top + bottom) / (top - bottom) );
out.SetRow(2, 0.f, 0.f, 1 / (nearDist - farDist), nearDist / (nearDist - farDist) );
@@ -57,8 +57,9 @@ namespace AZ
//! @param top The y coordinate of top view-plane
//! @param near Distance to the near view-plane. Must be no less than zero.
//! @param far Distance to the far view-plane. Must be greater than zero.
//! @param reverseDepth Set to true to reverse depth which means near distance maps to 1 and far distance maps to 0.
//! @return Pointer of the output matrix
Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist);
Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist, bool reverseDepth = false);
//! Transforms a position by a matrix. This function can be used with any generic cases which include projection matrices.
Vector3 MatrixTransformPosition(const Matrix4x4& matrix, const Vector3& inPosition);
@@ -63,6 +63,13 @@ namespace Camera
//! @return The camera frustum's height
virtual float GetFrustumHeight() = 0;
//! Gets whether or not the camera is using an orthographic projection.
//! @return True if the camera is using an orthographic projection, or false if the camera is using a perspective projection.
virtual bool IsOrthographic() = 0;
//! @return The half width of the orthographic projection, @see SetOrthographicHalfWidth.
virtual float GetOrthographicHalfWidth() = 0;
//! Sets the camera's field of view in degrees between 0 < fov < 180 degrees
//! @param fov The camera frustum's new field of view in degrees
virtual void SetFov(float fov)
@@ -95,6 +102,15 @@ namespace Camera
//! @param height The camera frustum's new height
virtual void SetFrustumHeight(float height) = 0;
//! Sets whether or not the camera should use an orthographic projection in place of a perspective projection.
//! @param orthographic If true, the camera will use an orthographic projection
virtual void SetOrthographic(bool orthographic) = 0;
//! Sets the half-width of the orthographic projection.
//! @params halfWidth Used to calculate the bounds of the projection while in orthographic mode.
//! The height is calculated automatically based on the aspect ratio.
virtual void SetOrthographicHalfWidth(float halfWidth) = 0;
//! Makes the camera the active view
virtual void MakeActiveView() = 0;
@@ -102,7 +102,7 @@ namespace Camera
using EditorCameraNotificationBus = AZ::EBus<EditorCameraNotifications>;
/**
* This bus is for requesting any camera-view-related changes
* This bus is for requesting any camera-view-related changes or information
*/
class EditorCameraViewRequests : public AZ::ComponentBus
{
@@ -115,6 +115,11 @@ namespace Camera
* Sets this camera as the active view in the scene, otherwise restores the default editor camera if it was already active
*/
virtual void ToggleCameraAsActiveView() = 0;
/**
* Gets the camera state associated with this view.
*/
virtual bool GetCameraState(AzFramework::CameraState& cameraState) = 0;
};
using EditorCameraViewRequestBus = AZ::EBus<EditorCameraViewRequests>;
@@ -92,12 +92,16 @@ namespace AZ
float GetFarClipDistance() override;
float GetFrustumWidth() override;
float GetFrustumHeight() override;
bool IsOrthographic() override;
float GetOrthographicHalfWidth() override;
void SetFovDegrees(float fov) override;
void SetFovRadians(float fov) override;
void SetNearClipDistance(float nearClipDistance) override;
void SetFarClipDistance(float farClipDistance) override;
void SetFrustumWidth(float width) override;
void SetFrustumHeight(float height) override;
void SetOrthographic(bool orthographic) override;
void SetOrthographicHalfWidth(float halfWidth) override;
void MakeActiveView() override;
// RPI::WindowContextNotificationBus overrides...
@@ -185,6 +185,15 @@ namespace AZ
return m_componentConfig.m_depthFar * tanf(m_componentConfig.m_fovY / 2) * 2;
}
bool CameraComponent::IsOrthographic()
{
return false;
}
float CameraComponent::GetOrthographicHalfWidth()
{
return 0.0f;
}
void CameraComponent::SetFovDegrees(float fov)
{
@@ -226,6 +235,16 @@ namespace AZ
UpdateViewToClipMatrix();
}
void CameraComponent::SetOrthographic(bool orthographic)
{
AZ_Assert(!orthographic, "DebugCamera does not support orthographic projection");
}
void CameraComponent::SetOrthographicHalfWidth([[maybe_unused]] float halfWidth)
{
AZ_Assert(false, "DebugCamera does not support orthographic projection");
}
void CameraComponent::MakeActiveView()
{
// do nothing
@@ -103,9 +103,15 @@ namespace Camera
->Event("SetNearClipDistance", &CameraRequestBus::Events::SetNearClipDistance)
->Event("SetFarClipDistance", &CameraRequestBus::Events::SetFarClipDistance)
->Event("MakeActiveView", &CameraRequestBus::Events::MakeActiveView)
->Event("IsOrthographic", &CameraRequestBus::Events::IsOrthographic)
->Event("SetOrthographic", &CameraRequestBus::Events::SetOrthographic)
->Event("GetOrthographicHalfWidth", &CameraRequestBus::Events::GetOrthographicHalfWidth)
->Event("SetOrthographicHalfWidth", &CameraRequestBus::Events::SetOrthographicHalfWidth)
->VirtualProperty("FieldOfView","GetFovDegrees","SetFovDegrees")
->VirtualProperty("NearClipDistance", "GetNearClipDistance", "SetNearClipDistance")
->VirtualProperty("FarClipDistance", "GetFarClipDistance", "SetFarClipDistance")
->VirtualProperty("Orthographic", "IsOrthographic", "SetOrthographic")
->VirtualProperty("OrthographicHalfWidth", "GetOrthographicHalfWidth", "SetOrthographicHalfWidth")
;
behaviorContext->Class<CameraComponent>()->RequestBus("CameraRequestBus");
@@ -24,7 +24,9 @@ namespace Camera
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<CameraComponentConfig, AZ::ComponentConfig>()
->Version(2)
->Version(3)
->Field("Orthographic", &CameraComponentConfig::m_orthographic)
->Field("Orthographic Half Width", &CameraComponentConfig::m_orthographicHalfWidth)
->Field("Field of View", &CameraComponentConfig::m_fov)
->Field("Near Clip Plane Distance", &CameraComponentConfig::m_nearClipDistance)
->Field("Far Clip Plane Distance", &CameraComponentConfig::m_farClipDistance)
@@ -42,25 +44,33 @@ namespace Camera
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_makeActiveViewOnActivation,
"Make active camera on activation?", "If true, this camera will become the active render camera when it activates")
->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_orthographic, "Orthographic",
"If set, this camera will use an orthographic projection instead of a perspective one. Objects will appear as the same size, regardless of distance from the camera.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_orthographicHalfWidth, "Orthographic Half-width", "The half-width used to calculate the orthographic projection. The height will be determined by the aspect ratio.")
->Attribute(AZ::Edit::Attributes::Visibility, &CameraComponentConfig::GetOrthographicParameterVisibility)
->Attribute(AZ::Edit::Attributes::Min, 0.001f)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::ValuesOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_fov, "Field of view", "Vertical field of view in degrees")
->Attribute(AZ::Edit::Attributes::Min, MIN_FOV)
->Attribute(AZ::Edit::Attributes::Suffix, " degrees")
->Attribute(AZ::Edit::Attributes::Step, 1.f)
->Attribute(AZ::Edit::Attributes::Max, AZ::RadToDeg(AZ::Constants::Pi) - 0.0001f) //We assert at fovs >= Pi so set the max for this field to be just under that
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshValues", 0x28e720d4))
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::ValuesOnly)
->Attribute(AZ::Edit::Attributes::Visibility, &CameraComponentConfig::GetPerspectiveParameterVisibility)
->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_nearClipDistance, "Near clip distance",
"Distance to the near clip plane of the view Frustum")
->Attribute(AZ::Edit::Attributes::Min, CAMERA_MIN_NEAR)
->Attribute(AZ::Edit::Attributes::Suffix, " m")
->Attribute(AZ::Edit::Attributes::Step, 0.1f)
->Attribute(AZ::Edit::Attributes::Max, &CameraComponentConfig::GetFarClipDistance)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues", 0xcbc2147c))
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_farClipDistance, "Far clip distance",
"Distance to the far clip plane of the view Frustum")
->Attribute(AZ::Edit::Attributes::Min, &CameraComponentConfig::GetNearClipDistance)
->Attribute(AZ::Edit::Attributes::Suffix, " m")
->Attribute(AZ::Edit::Attributes::Step, 10.f)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues", 0xcbc2147c))
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
;
}
}
@@ -81,6 +91,16 @@ namespace Camera
return AZ::EntityId(m_editorEntityId);
}
AZ::u32 CameraComponentConfig::GetPerspectiveParameterVisibility() const
{
return m_orthographic ? AZ::Edit::PropertyVisibility::Hide : AZ::Edit::PropertyVisibility::Show;
}
AZ::u32 CameraComponentConfig::GetOrthographicParameterVisibility() const
{
return m_orthographic ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
CameraComponentController::CameraComponentController(const CameraComponentConfig& config)
{
SetConfiguration(config);
@@ -289,6 +309,16 @@ namespace Camera
return m_config;
}
AZ::RPI::ViewportContextPtr CameraComponentController::GetViewportContext()
{
auto atomViewportRequests = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
if (m_atomCamera && atomViewportRequests)
{
return atomViewportRequests->GetDefaultViewportContext();
}
return nullptr;
}
AZ::EntityId CameraComponentController::GetCameras()
{
return m_entityId;
@@ -324,6 +354,16 @@ namespace Camera
return m_config.m_frustumHeight;
}
bool CameraComponentController::IsOrthographic()
{
return m_config.m_orthographic;
}
float CameraComponentController::GetOrthographicHalfWidth()
{
return m_config.m_orthographicHalfWidth;
}
void CameraComponentController::SetFovDegrees(float fov)
{
m_config.m_fov = AZ::GetClamp(fov, MinFoV, MaxFoV);
@@ -359,6 +399,18 @@ namespace Camera
UpdateCamera();
}
void CameraComponentController::SetOrthographic(bool orthographic)
{
m_config.m_orthographic = orthographic;
UpdateCamera();
}
void CameraComponentController::SetOrthographicHalfWidth(float halfWidth)
{
m_config.m_orthographicHalfWidth = halfWidth;
UpdateCamera();
}
void CameraComponentController::MakeActiveView()
{
// Set Legacy Cry view, if it exists
@@ -423,30 +475,38 @@ namespace Camera
m_view->SetCurrentParams(viewParams);
}
auto atomViewportRequests = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
if (m_atomCamera && atomViewportRequests)
if (auto viewportContext = GetViewportContext())
{
AZ::Matrix4x4 viewToClipMatrix;
float aspectRatio = m_view ? m_view->GetCamera().GetPixelAspectRatio() : 1.f;
auto viewportContext = atomViewportRequests->GetViewportContextByName(
atomViewportRequests->GetDefaultViewportContextName());
if (viewportContext)
if (!m_atomAuxGeom)
{
if (!m_atomAuxGeom)
{
SetupAtomAuxGeom(viewportContext);
}
auto windowSize = viewportContext->GetViewportSize();
aspectRatio = aznumeric_cast<float>(windowSize.m_width) / aznumeric_cast<float>(windowSize.m_height);
SetupAtomAuxGeom(viewportContext);
}
auto windowSize = viewportContext->GetViewportSize();
aspectRatio = aznumeric_cast<float>(windowSize.m_width) / aznumeric_cast<float>(windowSize.m_height);
// This assumes a reversed depth buffer, in line with other LY Atom integration
AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix,
AZ::DegToRad(m_config.m_fov),
aspectRatio,
m_config.m_nearClipDistance,
m_config.m_farClipDistance,
true);
if (m_config.m_orthographic)
{
AZ::MakeOrthographicMatrixRH(viewToClipMatrix,
-m_config.m_orthographicHalfWidth,
m_config.m_orthographicHalfWidth,
-m_config.m_orthographicHalfWidth / aspectRatio,
m_config.m_orthographicHalfWidth / aspectRatio,
m_config.m_nearClipDistance,
m_config.m_farClipDistance,
true);
}
else
{
AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix,
AZ::DegToRad(m_config.m_fov),
aspectRatio,
m_config.m_nearClipDistance,
m_config.m_farClipDistance,
true);
}
m_updatingTransformFromEntity = true;
m_atomCamera->SetViewToClipMatrix(viewToClipMatrix);
m_updatingTransformFromEntity = false;
@@ -40,6 +40,9 @@ namespace Camera
float GetNearClipDistance() const;
AZ::EntityId GetEditorEntityId() const;
AZ::u32 GetPerspectiveParameterVisibility() const;
AZ::u32 GetOrthographicParameterVisibility() const;
// Reflected members
float m_fov = DefaultFoV;
float m_nearClipDistance = DefaultNearPlaneDistance;
@@ -49,6 +52,8 @@ namespace Camera
bool m_specifyFrustumDimensions = false;
AZ::u64 m_editorEntityId = AZ::EntityId::InvalidEntityId;
bool m_makeActiveViewOnActivation = true;
bool m_orthographic = false;
float m_orthographicHalfWidth = 5.f;
};
class CameraComponentController
@@ -78,6 +83,7 @@ namespace Camera
void Deactivate();
void SetConfiguration(const CameraComponentConfig& config);
const CameraComponentConfig& GetConfiguration() const;
AZ::RPI::ViewportContextPtr GetViewportContext();
// CameraBus::Handler interface
AZ::EntityId GetCameras() override;
@@ -89,12 +95,17 @@ namespace Camera
float GetFarClipDistance() override;
float GetFrustumWidth() override;
float GetFrustumHeight() override;
bool IsOrthographic() override;
float GetOrthographicHalfWidth() override;
void SetFovDegrees(float fov) override;
void SetFovRadians(float fov) override;
void SetNearClipDistance(float nearClipDistance) override;
void SetFarClipDistance(float farClipDistance) override;
void SetFrustumWidth(float width) override;
void SetFrustumHeight(float height) override;
void SetOrthographic(bool orthographic) override;
void SetOrthographicHalfWidth(float halfWidth) override;
void MakeActiveView() override;
// AZ::TransformNotificationBus::Handler interface
@@ -17,6 +17,9 @@
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Atom/RPI.Public/ViewportContext.h>
#include <Atom/RPI.Public/View.h>
namespace Camera
{
namespace ClassConverters
@@ -155,6 +158,42 @@ namespace Camera
}
}
bool EditorCameraComponent::GetCameraState(AzFramework::CameraState& cameraState)
{
const CameraComponentConfig& config = m_controller.GetConfiguration();
AZ::RPI::ViewportContextPtr viewportContext = m_controller.GetViewportContext();
AZ::RPI::ViewPtr view = m_controller.GetView();
if (viewportContext == nullptr || view == nullptr)
{
return false;
}
AzFramework::SetCameraTransform(cameraState, view->GetCameraTransform());
{
const AzFramework::WindowSize viewportSize = viewportContext->GetViewportSize();
cameraState.m_viewportSize =
AZ::Vector2{aznumeric_cast<float>(viewportSize.m_width), aznumeric_cast<float>(viewportSize.m_height)};
}
if (config.m_orthographic)
{
cameraState.m_fovOrZoom = cameraState.m_viewportSize.GetX() / (config.m_orthographicHalfWidth * 2.0f);
cameraState.m_orthographic = true;
}
else
{
cameraState.m_fovOrZoom = config.m_fov;
cameraState.m_orthographic = false;
}
cameraState.m_nearClip = config.m_nearClipDistance;
cameraState.m_farClip = config.m_farClipDistance;
return true;
}
AZ::Crc32 EditorCameraComponent::OnPossessCameraButtonClicked()
{
AZ::EntityId currentViewEntity;
@@ -201,9 +240,20 @@ namespace Camera
const CameraComponentConfig& config = m_controller.GetConfiguration();
const float distance = config.m_farClipDistance * m_frustumViewPercentLength * 0.01f;
float tangent = static_cast<float>(tan(0.5f * AZ::DegToRad(config.m_fov)));
float height = distance * tangent;
float width = height * debugDisplay.GetAspectRatio();
float width;
float height;
if (config.m_orthographic)
{
width = config.m_orthographicHalfWidth;
height = width / debugDisplay.GetAspectRatio();
}
else
{
const float tangent = static_cast<float>(tan(0.5f * AZ::DegToRad(config.m_fov)));
height = distance * tangent;
width = height * debugDisplay.GetAspectRatio();
}
AZ::Vector3 farPoints[4];
farPoints[0] = AZ::Vector3( width, distance, height);
@@ -211,12 +261,21 @@ namespace Camera
farPoints[2] = AZ::Vector3(-width, distance, -height);
farPoints[3] = AZ::Vector3( width, distance, -height);
AZ::Vector3 start(0, 0, 0);
AZ::Vector3 nearPoints[4];
nearPoints[0] = farPoints[0].GetNormalizedSafe() * config.m_nearClipDistance;
nearPoints[1] = farPoints[1].GetNormalizedSafe() * config.m_nearClipDistance;
nearPoints[2] = farPoints[2].GetNormalizedSafe() * config.m_nearClipDistance;
nearPoints[3] = farPoints[3].GetNormalizedSafe() * config.m_nearClipDistance;
if (config.m_orthographic)
{
nearPoints[0] = AZ::Vector3( width, config.m_nearClipDistance, height);
nearPoints[1] = AZ::Vector3(-width, config.m_nearClipDistance, height);
nearPoints[2] = AZ::Vector3(-width, config.m_nearClipDistance, -height);
nearPoints[3] = AZ::Vector3( width, config.m_nearClipDistance, -height);
}
else
{
nearPoints[0] = farPoints[0].GetNormalizedSafe() * config.m_nearClipDistance;
nearPoints[1] = farPoints[1].GetNormalizedSafe() * config.m_nearClipDistance;
nearPoints[2] = farPoints[2].GetNormalizedSafe() * config.m_nearClipDistance;
nearPoints[3] = farPoints[3].GetNormalizedSafe() * config.m_nearClipDistance;
}
debugDisplay.PushMatrix(world);
debugDisplay.SetColor(m_frustumDrawColor.GetAsVector4());
@@ -58,7 +58,9 @@ namespace Camera
/// EditorCameraNotificationBus::Handler interface
void OnViewportViewEntityChanged(const AZ::EntityId& newViewId) override;
/// EditorCameraViewRequestBus::Handler interface
void ToggleCameraAsActiveView() override { OnPossessCameraButtonClicked(); }
bool GetCameraState(AzFramework::CameraState& cameraState) override;
protected:
void EditorDisplay(AzFramework::DebugDisplayRequests& displayInterface, const AZ::Transform& world);