3
0

ModelLodUtils.cpp 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  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/Model/ModelLodUtils.h>
  9. #include <Atom/RPI.Public/Model/Model.h>
  10. #include <Atom/RPI.Public/View.h>
  11. #include <Atom/RPI.Reflect/Model/ModelAsset.h>
  12. #include <AzCore/Math/Aabb.h>
  13. #include <AzCore/Math/Transform.h>
  14. #include <AzCore/Math/Vector2.h>
  15. namespace AZ
  16. {
  17. namespace RPI
  18. {
  19. namespace ModelLodUtils
  20. {
  21. ModelLodIndex SelectLod(const View* view, const Transform& entityTransform, const Model& model, ModelLodIndex lodOverride)
  22. {
  23. return SelectLod(view, entityTransform.GetTranslation(), model, lodOverride);
  24. }
  25. ModelLodIndex SelectLod(const View* view, const Vector3& position, const Model& model, ModelLodIndex lodOverride)
  26. {
  27. AZ_PROFILE_SCOPE(RPI, "ModelLodUtils: SelectLod");
  28. ModelLodIndex lodIndex;
  29. if (model.GetLodCount() == 1)
  30. {
  31. lodIndex = ModelLodIndex(0);
  32. }
  33. else if (lodOverride.IsNull())
  34. {
  35. /*
  36. Simple screen-space Lod determination algorithm.
  37. The idea here is simple. We take the bounding sphere of the model
  38. and project it into clip space. From there we measure the area of the
  39. ellipse in screen space and determine what percent of the total
  40. screen it takes up.
  41. With that percentage we can determine which Lod we want to use.
  42. */
  43. Aabb modelAabb = model.GetModelAsset()->GetAabb();
  44. modelAabb.Translate(position);
  45. Vector3 center;
  46. float radius;
  47. modelAabb.GetAsSphere(center, radius);
  48. // Projection of a sphere to screen space
  49. // Derived from https://www.iquilezles.org/www/articles/sphereproj/sphereproj.htm
  50. const Matrix4x4 worldToViewMatrix = view->GetWorldToViewMatrix();
  51. const Matrix4x4 viewToClipMatrix = view->GetViewToClipMatrix();
  52. lodIndex = ModelLodIndex(SelectLodFromBoundingSphere(center, static_cast<float>(radius), aznumeric_cast<uint8_t>(model.GetLodCount()), worldToViewMatrix, viewToClipMatrix));
  53. }
  54. else
  55. {
  56. lodIndex = lodOverride;
  57. }
  58. return lodIndex;
  59. }
  60. uint8_t SelectLodFromBoundingSphere(const Vector3 center, float radius, uint8_t numLods, const Matrix4x4& worldtoView, const Matrix4x4& viewToClip)
  61. {
  62. // Projection of a sphere to screen space
  63. // Derived from https://www.iquilezles.org/www/articles/sphereproj/sphereproj.htm
  64. const Vector3 cameraPosition = worldtoView.GetTranslation();
  65. const Vector3 cameraToCenter = cameraPosition - center;
  66. const float m00 = viewToClip.GetRow(0).GetX();
  67. const float m11 = viewToClip.GetRow(1).GetY();
  68. const float viewScale = AZStd::max(0.5f * m00, 0.5f * m11);
  69. const float cameraToCenterLength = cameraToCenter.GetLength();
  70. const float screenPercentage = (viewScale * radius) / AZStd::max(1.0f, cameraToCenterLength);
  71. uint8_t lodIndex;
  72. if (screenPercentage > 0.25f)
  73. {
  74. lodIndex = 0;
  75. }
  76. else if (screenPercentage > 0.075)
  77. {
  78. lodIndex = 1;
  79. }
  80. else
  81. {
  82. lodIndex = 2;
  83. }
  84. return AZStd::min<uint8_t>(lodIndex, numLods - (uint8_t)1);
  85. }
  86. float ApproxScreenPercentage(const Vector3& center, float radius, const Vector3& cameraPosition, float yScale, bool isPerspective)
  87. {
  88. if (isPerspective)
  89. { // view to clip matrix is perspective
  90. //Derivation:
  91. //let x = approxScreenPercentage (unknown)
  92. //let H = nearPlaneHeight
  93. //let N = nearPlaneDistance
  94. //yScale = cot(FovY/2) = 2*N/H (by the geometry)
  95. //therefore H = 2*N/yScale
  96. //let S = diameter projected onto near plane = x*H
  97. //let R = radius
  98. //let D = cameraToCenter
  99. //2*R/D = S/N (by like triangles)
  100. //2*R/D = (x*H)/N (substitute for S)
  101. //R/D = x/yScale (substitute for H, cancel the N's and the 2's)
  102. //x = yScale*R/D
  103. const Vector3 cameraToCenter = cameraPosition - center;
  104. const float cameraToCenterLength = cameraToCenter.GetLength();
  105. const float approxScreenPercentage = AZStd::GetMin(((yScale * radius) / cameraToCenterLength), 1.0f);
  106. return approxScreenPercentage;
  107. }
  108. else
  109. { // view to clip matrix is orthogonal.
  110. //Derivation:
  111. //let x = approxScreenPercentage (unknown)
  112. //let H = frustum height (top - bottom)
  113. //yScale = 2/(top - bottom) = 2/H
  114. //threfore H = 2/yScale
  115. //let R = radius
  116. //x = 2*R/H = yScale*R
  117. const float approxScreenPercentage = AZStd::GetMin((yScale * radius), 1.0f);
  118. return approxScreenPercentage;
  119. }
  120. }
  121. } // namespace ModelLodUtils
  122. } // namespace RPI
  123. } // namespace AZ