ShadowBox.java 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. package eu.tankernn.gameEngine.renderEngine.shadows;
  2. import org.lwjgl.opengl.Display;
  3. import org.lwjgl.util.vector.Matrix4f;
  4. import org.lwjgl.util.vector.Vector3f;
  5. import org.lwjgl.util.vector.Vector4f;
  6. import eu.tankernn.gameEngine.entities.Camera;
  7. import eu.tankernn.gameEngine.settings.Settings;
  8. /**
  9. * Represents the 3D cuboidal area of the world in which objects will cast
  10. * shadows (basically represents the orthographic projection area for the shadow
  11. * render pass). It is updated each frame to optimise the area, making it as
  12. * small as possible (to allow for optimal shadow map resolution) while not
  13. * being too small to avoid objects not having shadows when they should.
  14. * Everything inside the cuboidal area represented by this object will be
  15. * rendered to the shadow map in the shadow render pass. Everything outside the
  16. * area won't be.
  17. *
  18. * @author Karl
  19. *
  20. */
  21. public class ShadowBox {
  22. private static final float OFFSET = 10;
  23. private static final Vector4f UP = new Vector4f(0, 1, 0, 0);
  24. private static final Vector4f FORWARD = new Vector4f(0, 0, -1, 0);
  25. public static final float SHADOW_DISTANCE = 100;
  26. private float minX, maxX;
  27. private float minY, maxY;
  28. private float minZ, maxZ;
  29. private Matrix4f lightViewMatrix;
  30. private Camera cam;
  31. private float farHeight, farWidth, nearHeight, nearWidth;
  32. /**
  33. * Creates a new shadow box and calculates some initial values relating to
  34. * the camera's view frustum, namely the width and height of the near plane
  35. * and (possibly adjusted) far plane.
  36. *
  37. * @param lightViewMatrix
  38. * - basically the "view matrix" of the light. Can be used to
  39. * transform a point from world space into "light" space (i.e.
  40. * changes a point's coordinates from being in relation to the
  41. * world's axis to being in terms of the light's local axis).
  42. * @param camera
  43. * - the in-game camera.
  44. */
  45. protected ShadowBox(Matrix4f lightViewMatrix, Camera camera) {
  46. this.lightViewMatrix = lightViewMatrix;
  47. this.cam = camera;
  48. calculateWidthsAndHeights();
  49. }
  50. /**
  51. * Updates the bounds of the shadow box based on the light direction and the
  52. * camera's view frustum, to make sure that the box covers the smallest area
  53. * possible while still ensuring that everything inside the camera's view
  54. * (within a certain range) will cast shadows.
  55. */
  56. protected void update() {
  57. Matrix4f rotation = calculateCameraRotationMatrix();
  58. Vector3f forwardVector = new Vector3f(Matrix4f.transform(rotation, FORWARD, null));
  59. Vector3f toFar = new Vector3f(forwardVector);
  60. toFar.scale(SHADOW_DISTANCE);
  61. Vector3f toNear = new Vector3f(forwardVector);
  62. toNear.scale(Settings.NEAR_PLANE);
  63. Vector3f centerNear = Vector3f.add(toNear, cam.getPosition(), null);
  64. Vector3f centerFar = Vector3f.add(toFar, cam.getPosition(), null);
  65. Vector4f[] points = calculateFrustumVertices(rotation, forwardVector, centerNear,
  66. centerFar);
  67. boolean first = true;
  68. for (Vector4f point : points) {
  69. if (first) {
  70. minX = point.x;
  71. maxX = point.x;
  72. minY = point.y;
  73. maxY = point.y;
  74. minZ = point.z;
  75. maxZ = point.z;
  76. first = false;
  77. continue;
  78. }
  79. if (point.x > maxX) {
  80. maxX = point.x;
  81. } else if (point.x < minX) {
  82. minX = point.x;
  83. }
  84. if (point.y > maxY) {
  85. maxY = point.y;
  86. } else if (point.y < minY) {
  87. minY = point.y;
  88. }
  89. if (point.z > maxZ) {
  90. maxZ = point.z;
  91. } else if (point.z < minZ) {
  92. minZ = point.z;
  93. }
  94. }
  95. maxZ += OFFSET;
  96. }
  97. /**
  98. * Calculates the center of the "view cuboid" in light space first, and then
  99. * converts this to world space using the inverse light's view matrix.
  100. *
  101. * @return The center of the "view cuboid" in world space.
  102. */
  103. protected Vector3f getCenter() {
  104. float x = (minX + maxX) / 2f;
  105. float y = (minY + maxY) / 2f;
  106. float z = (minZ + maxZ) / 2f;
  107. Vector4f cen = new Vector4f(x, y, z, 1);
  108. Matrix4f invertedLight = new Matrix4f();
  109. Matrix4f.invert(lightViewMatrix, invertedLight);
  110. return new Vector3f(Matrix4f.transform(invertedLight, cen, null));
  111. }
  112. /**
  113. * @return The width of the "view cuboid" (orthographic projection area).
  114. */
  115. protected float getWidth() {
  116. return maxX - minX;
  117. }
  118. /**
  119. * @return The height of the "view cuboid" (orthographic projection area).
  120. */
  121. protected float getHeight() {
  122. return maxY - minY;
  123. }
  124. /**
  125. * @return The length of the "view cuboid" (orthographic projection area).
  126. */
  127. protected float getLength() {
  128. return maxZ - minZ;
  129. }
  130. /**
  131. * Calculates the position of the vertex at each corner of the view frustum
  132. * in light space (8 vertices in total, so this returns 8 positions).
  133. *
  134. * @param rotation
  135. * - camera's rotation.
  136. * @param forwardVector
  137. * - the direction that the camera is aiming, and thus the
  138. * direction of the frustum.
  139. * @param centerNear
  140. * - the center point of the frustum's near plane.
  141. * @param centerFar
  142. * - the center point of the frustum's (possibly adjusted) far
  143. * plane.
  144. * @return The positions of the vertices of the frustum in light space.
  145. */
  146. private Vector4f[] calculateFrustumVertices(Matrix4f rotation, Vector3f forwardVector,
  147. Vector3f centerNear, Vector3f centerFar) {
  148. Vector3f upVector = new Vector3f(Matrix4f.transform(rotation, UP, null));
  149. Vector3f rightVector = Vector3f.cross(forwardVector, upVector, null);
  150. Vector3f downVector = new Vector3f(-upVector.x, -upVector.y, -upVector.z);
  151. Vector3f leftVector = new Vector3f(-rightVector.x, -rightVector.y, -rightVector.z);
  152. Vector3f farTop = Vector3f.add(centerFar, new Vector3f(upVector.x * farHeight,
  153. upVector.y * farHeight, upVector.z * farHeight), null);
  154. Vector3f farBottom = Vector3f.add(centerFar, new Vector3f(downVector.x * farHeight,
  155. downVector.y * farHeight, downVector.z * farHeight), null);
  156. Vector3f nearTop = Vector3f.add(centerNear, new Vector3f(upVector.x * nearHeight,
  157. upVector.y * nearHeight, upVector.z * nearHeight), null);
  158. Vector3f nearBottom = Vector3f.add(centerNear, new Vector3f(downVector.x * nearHeight,
  159. downVector.y * nearHeight, downVector.z * nearHeight), null);
  160. Vector4f[] points = new Vector4f[8];
  161. points[0] = calculateLightSpaceFrustumCorner(farTop, rightVector, farWidth);
  162. points[1] = calculateLightSpaceFrustumCorner(farTop, leftVector, farWidth);
  163. points[2] = calculateLightSpaceFrustumCorner(farBottom, rightVector, farWidth);
  164. points[3] = calculateLightSpaceFrustumCorner(farBottom, leftVector, farWidth);
  165. points[4] = calculateLightSpaceFrustumCorner(nearTop, rightVector, nearWidth);
  166. points[5] = calculateLightSpaceFrustumCorner(nearTop, leftVector, nearWidth);
  167. points[6] = calculateLightSpaceFrustumCorner(nearBottom, rightVector, nearWidth);
  168. points[7] = calculateLightSpaceFrustumCorner(nearBottom, leftVector, nearWidth);
  169. return points;
  170. }
  171. /**
  172. * Calculates one of the corner vertices of the view frustum in world space
  173. * and converts it to light space.
  174. *
  175. * @param startPoint
  176. * - the starting center point on the view frustum.
  177. * @param direction
  178. * - the direction of the corner from the start point.
  179. * @param width
  180. * - the distance of the corner from the start point.
  181. * @return - The relevant corner vertex of the view frustum in light space.
  182. */
  183. private Vector4f calculateLightSpaceFrustumCorner(Vector3f startPoint, Vector3f direction,
  184. float width) {
  185. Vector3f point = Vector3f.add(startPoint,
  186. new Vector3f(direction.x * width, direction.y * width, direction.z * width), null);
  187. Vector4f point4f = new Vector4f(point.x, point.y, point.z, 1f);
  188. Matrix4f.transform(lightViewMatrix, point4f, point4f);
  189. return point4f;
  190. }
  191. /**
  192. * @return The rotation of the camera represented as a matrix.
  193. */
  194. private Matrix4f calculateCameraRotationMatrix() {
  195. Matrix4f rotation = new Matrix4f();
  196. rotation.rotate((float) Math.toRadians(-cam.getYaw()), new Vector3f(0, 1, 0));
  197. rotation.rotate((float) Math.toRadians(-cam.getPitch()), new Vector3f(1, 0, 0));
  198. return rotation;
  199. }
  200. /**
  201. * Calculates the width and height of the near and far planes of the
  202. * camera's view frustum. However, this doesn't have to use the "actual" far
  203. * plane of the view frustum. It can use a shortened view frustum if desired
  204. * by bringing the far-plane closer, which would increase shadow resolution
  205. * but means that distant objects wouldn't cast shadows.
  206. */
  207. private void calculateWidthsAndHeights() {
  208. farWidth = (float) (SHADOW_DISTANCE * Math.tan(Math.toRadians(Settings.FOV)));
  209. nearWidth = (float) (Settings.NEAR_PLANE
  210. * Math.tan(Math.toRadians(Settings.FOV)));
  211. farHeight = farWidth / getAspectRatio();
  212. nearHeight = nearWidth / getAspectRatio();
  213. }
  214. /**
  215. * @return The aspect ratio of the display (width:height ratio).
  216. */
  217. private float getAspectRatio() {
  218. return (float) Display.getWidth() / (float) Display.getHeight();
  219. }
  220. }