3
0

ViewTests.cpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Atom/RPI.Public/View.h>
  9. #include <AzCore/UnitTest/TestTypes.h>
  10. #include <Common/RPITestFixture.h>
  11. namespace UnitTest
  12. {
  13. /*
  14. * Helper function to create a view with a given vertical field of view and aspect ratio
  15. */
  16. AZ::RPI::ViewPtr CreateView(float fovY, float aspectRatio)
  17. {
  18. using namespace AZ;
  19. using namespace RPI;
  20. ViewPtr view = View::CreateView(AZ::Name("TestView"), RPI::View::UsageCamera);
  21. AZ::Matrix4x4 worldToView = AZ::Matrix4x4::CreateIdentity();
  22. const float nearDist = 0.1f;
  23. const float farDist = 100.0f;
  24. AZ::Matrix4x4 viewToClip = AZ::Matrix4x4::CreateProjection(fovY, aspectRatio, nearDist, farDist);
  25. view->SetWorldToViewMatrix(worldToView);
  26. view->SetViewToClipMatrix(viewToClip);
  27. return view;
  28. }
  29. /*
  30. * Helper function to do a sanity check on CalculateSphereAreaInClipSpace
  31. * Given a fovY and aspect ratio it creates a view.
  32. * It then computes how far away the sphere of the given radius would have to be such that the edge of
  33. * the sphere horizon would touch the top and bottom edges of the view
  34. * It then uses CalculateSphereAreaInClipSpace with this distance and checks the answer agrees
  35. */
  36. void TestCalculateSphereAreaInClipSpaceWithSphereFillingYDimension(float fovY, float aspectRatio, float sphereRadius)
  37. {
  38. using namespace AZ;
  39. using namespace RPI;
  40. ViewPtr view = CreateView(fovY, aspectRatio);
  41. // Sphere distance from camera is radius/sin(fov/2)
  42. // At this distance the horizon of the sphere should be touching the edge of the viewport vertically
  43. // So the visible area of the sphere would be a circle the diameter of the viewport height
  44. // So the coverage percentage would be 0.5f * 0.5f * pi (radius will be half screen height)
  45. float sinHalfFovY = sin(fovY * 0.5f);
  46. float dist = sphereRadius / sinHalfFovY;
  47. AZ::Vector3 center(0.0f, 0.0f, -dist);
  48. float coverage = view->CalculateSphereAreaInClipSpace(center, sphereRadius);
  49. const float expectedCoverage = 0.5f * 0.5f * AZ::Constants::Pi;
  50. const float allowedEpsilon = 0.001f;
  51. float diff = fabsf(coverage - expectedCoverage);
  52. EXPECT_TRUE(diff < allowedEpsilon);
  53. }
  54. /*
  55. * Helper function to do a sanity check on CalculateSphereAreaInClipSpace
  56. * This uses a calculation that computes the projected radius of a sphere when the camera is looking directly at the sphere
  57. */
  58. void TestCalculateSphereAreaInClipSpaceVsProjectedRadius(float fovY, float aspectRatio, const AZ::Vector3& sphereCenter, float sphereRadius)
  59. {
  60. using namespace AZ;
  61. using namespace RPI;
  62. ViewPtr view = CreateView(fovY, aspectRatio);
  63. float coverage = view->CalculateSphereAreaInClipSpace(sphereCenter, sphereRadius);
  64. // This computes the projected radius of a sphere using the calculation described here:
  65. // https://stackoverflow.com/questions/21648630/radius-of-projected-sphere-in-screen-space
  66. float radiusSq = sphereRadius * sphereRadius;
  67. AZ::Vector3 distanceVector = sphereCenter;
  68. float distanceSq = distanceVector.GetLengthSq();
  69. float tanHalfFovY = tan(fovY * 0.5f);
  70. float cotHalfFovY = 1.0f / tanHalfFovY; // this is actually just the same as view->GetViewToClipMatrix().GetElement(1, 1)
  71. float sqrtDistanceSqMinusRadiusSq = sqrt(distanceSq - radiusSq);
  72. float projectedRadius = cotHalfFovY * sphereRadius / sqrtDistanceSqMinusRadiusSq;
  73. // projectedRadius is a percentage of half of the view height. To get as percentage of view height we halve it
  74. float prAsAPercentOfViewHeight = projectedRadius * 0.5f;
  75. float prSq = prAsAPercentOfViewHeight * prAsAPercentOfViewHeight;
  76. float expectedArea = prSq * AZ::Constants::Pi;
  77. const float allowedEpsilon = 0.0001f;
  78. float diff = fabsf(coverage - expectedArea);
  79. EXPECT_TRUE(diff < allowedEpsilon);
  80. }
  81. class ViewTests
  82. : public RPITestFixture
  83. {
  84. };
  85. TEST_F(ViewTests, SphereCoverageSpecialCases)
  86. {
  87. using namespace AZ;
  88. using namespace RPI;
  89. // Square view, 90 field of view
  90. {
  91. const float fovY = Constants::Pi * 0.5f; // 90 degrees
  92. const float aspectRatio = 1.0f;
  93. ViewPtr view = CreateView(fovY, aspectRatio);
  94. // Sphere in front of camera but touching camera origin
  95. // This is treated as a special case and should return 1.0
  96. {
  97. AZ::Vector3 center(0.0f, 0.0f, -1.0f);
  98. float radius = 1.0f;
  99. float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
  100. EXPECT_EQ(coverage, 1.0f);
  101. }
  102. // Sphere at camera origin
  103. // This is treated as a special case and should return 1.0
  104. {
  105. AZ::Vector3 center(0.0f, 0.0f, 0.0f);
  106. float radius = 1.0f;
  107. float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
  108. EXPECT_EQ(coverage, 1.0f);
  109. }
  110. // Sphere fully behind the camera origin
  111. // This is treated as a special case and should return 0.0
  112. {
  113. AZ::Vector3 center(0.0f, 0.0f, 1.1f);
  114. float radius = 1.0f;
  115. float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
  116. EXPECT_EQ(coverage, 0.0f);
  117. }
  118. // Sphere behind camera but touching camera origin
  119. // This is treated as a special case and should return 1.0
  120. {
  121. AZ::Vector3 center(0.0f, 0.0f, 1.0f);
  122. float radius = 1.0f;
  123. float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
  124. EXPECT_EQ(coverage, 1.0f);
  125. }
  126. // Camera inside sphere, sphere center in front of camera
  127. // This is treated as a special case and should return 1.0
  128. {
  129. AZ::Vector3 center(0.0f, 0.0f, -0.75f);
  130. float radius = 1.0f;
  131. float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
  132. EXPECT_EQ(coverage, 1.0f);
  133. }
  134. // Camera inside sphere, sphere center behind camera
  135. // This is treated as a special case and should return 1.0
  136. {
  137. AZ::Vector3 center(0.0f, 0.0f, 0.5f);
  138. float radius = 1.0f;
  139. float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
  140. EXPECT_EQ(coverage, 1.0f);
  141. }
  142. // Sphere radius zero
  143. // This is treated as a special case and should return 0.0
  144. {
  145. AZ::Vector3 center(0.0f, 0.0f, -10.0f);
  146. float radius = 0.0f;
  147. float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
  148. EXPECT_EQ(coverage, 0.0f);
  149. }
  150. // Sphere radius less than zero
  151. // This is treated as a special case and should return 0.0
  152. {
  153. AZ::Vector3 center(0.0f, 0.0f, -10.0f);
  154. float radius = -1.0f;
  155. float coverage = view->CalculateSphereAreaInClipSpace(center, radius);
  156. EXPECT_EQ(coverage, 0.0f);
  157. }
  158. }
  159. }
  160. TEST_F(ViewTests, SphereCoverageFillY)
  161. {
  162. using namespace AZ;
  163. using namespace RPI;
  164. // Square view, 90 field of view, radius 1
  165. {
  166. const float fovY = AZ::DegToRad(90.0f);
  167. const float aspectRatio = 1.0f;
  168. const float sphereRadius = 1.0f;
  169. TestCalculateSphereAreaInClipSpaceWithSphereFillingYDimension(fovY, aspectRatio, sphereRadius);
  170. }
  171. // Rectangular view, 60 field of view, radius 5
  172. {
  173. const float fovY = AZ::DegToRad(60.0f);
  174. const float aspectRatio = 1.5f;
  175. const float sphereRadius = 5.0f;
  176. TestCalculateSphereAreaInClipSpaceWithSphereFillingYDimension(fovY, aspectRatio, sphereRadius);
  177. }
  178. }
  179. TEST_F(ViewTests, SphereCoverageVsProjectedRadius)
  180. {
  181. using namespace AZ;
  182. using namespace RPI;
  183. // Square view, 90 field of view, radius 1, distance 3
  184. {
  185. const float fovY = AZ::DegToRad(90.0f);
  186. const float aspectRatio = 1.0f;
  187. AZ::Vector3 sphereCenter(0.0f, 0.0f, -3.0f);
  188. float sphereRadius = 1.0f;
  189. TestCalculateSphereAreaInClipSpaceVsProjectedRadius(fovY, aspectRatio, sphereCenter, sphereRadius);
  190. }
  191. // Rectangular view, 60 field of view, radius 4, distance 20
  192. {
  193. const float fovY = AZ::DegToRad(60.0f);
  194. const float aspectRatio = 1.5f;
  195. AZ::Vector3 sphereCenter(0.0f, 0.0f, -20.0f);
  196. float sphereRadius = 4.0f;
  197. TestCalculateSphereAreaInClipSpaceVsProjectedRadius(fovY, aspectRatio, sphereCenter, sphereRadius);
  198. }
  199. // Rectangular view, 70 field of view, radius 1, distance 30
  200. {
  201. const float fovY = AZ::DegToRad(70.0f);
  202. const float aspectRatio = 1.5f;
  203. AZ::Vector3 sphereCenter(0.0f, 0.0f, -30.0f);
  204. float sphereRadius = 0.05f;
  205. TestCalculateSphereAreaInClipSpaceVsProjectedRadius(fovY, aspectRatio, sphereCenter, sphereRadius);
  206. }
  207. }
  208. }