| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <Atom/RPI.Public/View.h>
- #include <AzCore/UnitTest/TestTypes.h>
- #include <Common/RPITestFixture.h>
- namespace UnitTest
- {
- /*
- * Helper function to create a view with a given vertical field of view and aspect ratio
- */
- AZ::RPI::ViewPtr CreateView(float fovY, float aspectRatio)
- {
- using namespace AZ;
- using namespace RPI;
- ViewPtr view = View::CreateView(AZ::Name("TestView"), RPI::View::UsageCamera);
- AZ::Matrix4x4 worldToView = AZ::Matrix4x4::CreateIdentity();
- const float nearDist = 0.1f;
- const float farDist = 100.0f;
- AZ::Matrix4x4 viewToClip = AZ::Matrix4x4::CreateProjection(fovY, aspectRatio, nearDist, farDist);
- view->SetWorldToViewMatrix(worldToView);
- view->SetViewToClipMatrix(viewToClip);
- return view;
- }
- /*
- * Helper function to do a sanity check on CalculateSphereAreaInClipSpace
- * Given a fovY and aspect ratio it creates a view.
- * It then computes how far away the sphere of the given radius would have to be such that the edge of
- * the sphere horizon would touch the top and bottom edges of the view
- * It then uses CalculateSphereAreaInClipSpace with this distance and checks the answer agrees
- */
- void TestCalculateSphereAreaInClipSpaceWithSphereFillingYDimension(float fovY, float aspectRatio, float sphereRadius)
- {
- using namespace AZ;
- using namespace RPI;
- ViewPtr view = CreateView(fovY, aspectRatio);
- // Sphere distance from camera is radius/sin(fov/2)
- // At this distance the horizon of the sphere should be touching the edge of the viewport vertically
- // So the visible area of the sphere would be a circle the diameter of the viewport height
- // So the coverage percentage would be 0.5f * 0.5f * pi (radius will be half screen height)
- float sinHalfFovY = sin(fovY * 0.5f);
- float dist = sphereRadius / sinHalfFovY;
- AZ::Vector3 center(0.0f, 0.0f, -dist);
- float coverage = view->CalculateSphereAreaInClipSpace(center, sphereRadius);
- const float expectedCoverage = 0.5f * 0.5f * AZ::Constants::Pi;
- const float allowedEpsilon = 0.001f;
- float diff = fabsf(coverage - expectedCoverage);
- EXPECT_TRUE(diff < allowedEpsilon);
- }
- /*
- * Helper function to do a sanity check on CalculateSphereAreaInClipSpace
- * This uses a calculation that computes the projected radius of a sphere when the camera is looking directly at the sphere
- */
- void TestCalculateSphereAreaInClipSpaceVsProjectedRadius(float fovY, float aspectRatio, const AZ::Vector3& sphereCenter, float sphereRadius)
- {
- using namespace AZ;
- using namespace RPI;
- ViewPtr view = CreateView(fovY, aspectRatio);
- float coverage = view->CalculateSphereAreaInClipSpace(sphereCenter, sphereRadius);
- // This computes the projected radius of a sphere using the calculation described here:
- // https://stackoverflow.com/questions/21648630/radius-of-projected-sphere-in-screen-space
- float radiusSq = sphereRadius * sphereRadius;
- AZ::Vector3 distanceVector = sphereCenter;
- float distanceSq = distanceVector.GetLengthSq();
- float tanHalfFovY = tan(fovY * 0.5f);
- float cotHalfFovY = 1.0f / tanHalfFovY; // this is actually just the same as view->GetViewToClipMatrix().GetElement(1, 1)
- float sqrtDistanceSqMinusRadiusSq = sqrt(distanceSq - radiusSq);
- float projectedRadius = cotHalfFovY * sphereRadius / sqrtDistanceSqMinusRadiusSq;
- // projectedRadius is a percentage of half of the view height. To get as percentage of view height we halve it
- float prAsAPercentOfViewHeight = projectedRadius * 0.5f;
- float prSq = prAsAPercentOfViewHeight * prAsAPercentOfViewHeight;
- float expectedArea = prSq * AZ::Constants::Pi;
- const float allowedEpsilon = 0.0001f;
- float diff = fabsf(coverage - expectedArea);
- EXPECT_TRUE(diff < allowedEpsilon);
- }
- class ViewTests
- : public RPITestFixture
- {
- };
- TEST_F(ViewTests, SphereCoverageSpecialCases)
- {
- using namespace AZ;
- using namespace RPI;
- // Square view, 90 field of view
- {
- const float fovY = Constants::Pi * 0.5f; // 90 degrees
- const float aspectRatio = 1.0f;
- ViewPtr view = CreateView(fovY, aspectRatio);
- // Sphere in front of camera but touching camera origin
- // This is treated as a special case and should return 1.0
- {
- AZ::Vector3 center(0.0f, 0.0f, -1.0f);
- float radius = 1.0f;
- float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
- EXPECT_EQ(coverage, 1.0f);
- }
- // Sphere at camera origin
- // This is treated as a special case and should return 1.0
- {
- AZ::Vector3 center(0.0f, 0.0f, 0.0f);
- float radius = 1.0f;
- float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
- EXPECT_EQ(coverage, 1.0f);
- }
- // Sphere fully behind the camera origin
- // This is treated as a special case and should return 0.0
- {
- AZ::Vector3 center(0.0f, 0.0f, 1.1f);
- float radius = 1.0f;
- float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
- EXPECT_EQ(coverage, 0.0f);
- }
- // Sphere behind camera but touching camera origin
- // This is treated as a special case and should return 1.0
- {
- AZ::Vector3 center(0.0f, 0.0f, 1.0f);
- float radius = 1.0f;
- float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
- EXPECT_EQ(coverage, 1.0f);
- }
- // Camera inside sphere, sphere center in front of camera
- // This is treated as a special case and should return 1.0
- {
- AZ::Vector3 center(0.0f, 0.0f, -0.75f);
- float radius = 1.0f;
- float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
- EXPECT_EQ(coverage, 1.0f);
- }
- // Camera inside sphere, sphere center behind camera
- // This is treated as a special case and should return 1.0
- {
- AZ::Vector3 center(0.0f, 0.0f, 0.5f);
- float radius = 1.0f;
- float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
- EXPECT_EQ(coverage, 1.0f);
- }
- // Sphere radius zero
- // This is treated as a special case and should return 0.0
- {
- AZ::Vector3 center(0.0f, 0.0f, -10.0f);
- float radius = 0.0f;
- float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
- EXPECT_EQ(coverage, 0.0f);
- }
- // Sphere radius less than zero
- // This is treated as a special case and should return 0.0
- {
- AZ::Vector3 center(0.0f, 0.0f, -10.0f);
- float radius = -1.0f;
- float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
- EXPECT_EQ(coverage, 0.0f);
- }
- }
- }
- TEST_F(ViewTests, SphereCoverageFillY)
- {
- using namespace AZ;
- using namespace RPI;
- // Square view, 90 field of view, radius 1
- {
- const float fovY = AZ::DegToRad(90.0f);
- const float aspectRatio = 1.0f;
- const float sphereRadius = 1.0f;
- TestCalculateSphereAreaInClipSpaceWithSphereFillingYDimension(fovY, aspectRatio, sphereRadius);
- }
- // Rectangular view, 60 field of view, radius 5
- {
- const float fovY = AZ::DegToRad(60.0f);
- const float aspectRatio = 1.5f;
- const float sphereRadius = 5.0f;
- TestCalculateSphereAreaInClipSpaceWithSphereFillingYDimension(fovY, aspectRatio, sphereRadius);
- }
- }
- TEST_F(ViewTests, SphereCoverageVsProjectedRadius)
- {
- using namespace AZ;
- using namespace RPI;
- // Square view, 90 field of view, radius 1, distance 3
- {
- const float fovY = AZ::DegToRad(90.0f);
- const float aspectRatio = 1.0f;
- AZ::Vector3 sphereCenter(0.0f, 0.0f, -3.0f);
- float sphereRadius = 1.0f;
- TestCalculateSphereAreaInClipSpaceVsProjectedRadius(fovY, aspectRatio, sphereCenter, sphereRadius);
- }
- // Rectangular view, 60 field of view, radius 4, distance 20
- {
- const float fovY = AZ::DegToRad(60.0f);
- const float aspectRatio = 1.5f;
- AZ::Vector3 sphereCenter(0.0f, 0.0f, -20.0f);
- float sphereRadius = 4.0f;
- TestCalculateSphereAreaInClipSpaceVsProjectedRadius(fovY, aspectRatio, sphereCenter, sphereRadius);
- }
- // Rectangular view, 70 field of view, radius 1, distance 30
- {
- const float fovY = AZ::DegToRad(70.0f);
- const float aspectRatio = 1.5f;
- AZ::Vector3 sphereCenter(0.0f, 0.0f, -30.0f);
- float sphereRadius = 0.05f;
- TestCalculateSphereAreaInClipSpaceVsProjectedRadius(fovY, aspectRatio, sphereCenter, sphereRadius);
- }
- }
- }
|