I've edited this question as I got a wrong interpretation of what was previously 'wrong' with my code. Previously I thought I made a mistake in the FBO or the MVP-matrix as my shadow map was completely blank, but in reality I did everything right and the depth map renders things only from a distance of 0-1 correlating the black and white pixels on the screen. Silly me.
I've scaled my whole scene down and repositioned my 'sun' so that I can now see the shadows. My question is how I can 'extend' this view so that not only objects as far away as 1 unit can be seen but as far as my view goes.
Also my shadow map is in black and white opposed to the red and black of the tutorial I followed. What's up with that?
And lastly the view of the 'sun' doesn't update with me moving. Expected it would follow the character, updating the view and visible shadows, but the view stays stationary at the position of the 'sun'.
Here's my whole Shadow Box class as this is probably the problem with that:
Edit: It's probably not the problem, but you still can have a look at it.
class ShadowBox:
"""
Represents the 3D cuboidal area of the world in which objects will cast
shadows (basically represents the orthographic projection area for the shadow
render pass). It is updated each frame to optimise the area, making it as
small as possible (to allow for optimal shadow map resolution) while not
being too small to avoid objects not having shadows when they should.
Everything inside the cuboidal area represented by this object will be
rendered to the shadow map in the shadow render pass. Everything outside the
area won't be.
"""
__OFFSET = 10
__UP = vec4(0, 1, 0, 0)
__FORWARD = vec4(0, 0, -1, 0)
__SHADOW_DISTANCE = 100
def __init__(self, light_view_matrix: mat4, camera):
"""
Creates a new shadow box and calculates some initial values relating to
the camera's view frustum, namely the width and height of the near plane
and (possibly adjusted) far plane.
:param light_view_matrix:
basically the "view matrix" of the light. Can be used to transform a point from world space into "light" space
(i.e. changes a point's coordinates from being in relation to the world's axis to being in terms of the light's
local axis).
:param camera:
the in-game camera.
"""
self.__light_view_matrix = light_view_matrix
self.__cam = camera
self.__min_x = None
self.__max_x = None
self.__min_y = None
self.__max_y = None
self.__min_z = None
self.__max_z = None
self.__far_height = None
self.__far_width = None
self.__near_height = None
self.__near_width = None
self.calculate_widths_and_heights()
def update(self) -> None:
"""
Updates the bounds of the shadow box based on the light direction and the
camera's view frustum, to make sure that the box covers the smallest area
possible while still ensuring that everything inside the camera's view
(within a certain range) will cast shadows.
:return:
"""
rotation: mat4 = self.calculate_camera_rotation_matrix()
forward_vector = rotation * self.__FORWARD
forward_vector = vec3(forward_vector.x, forward_vector.y, forward_vector.z)
to_far = forward_vector * self.__SHADOW_DISTANCE
to_near = forward_vector * master_renderer.MasterRenderer.get_near_plane()
center_near = to_near + vec3(self.__cam.get_position())
center_far = to_far + vec3(self.__cam.get_position())
points: list[vec4] = self.calculate_frustum_vertices(rotation, forward_vector, center_near, center_far)
first = True
for point in points:
if first:
self.__min_x = point.x
self.__max_x = point.x
self.__min_y = point.y
self.__max_y = point.y
self.__min_z = point.z
self.__max_z = point.z
first = False
continue
if point.x > self.__max_x:
self.__max_x = point.x
elif point.x < self.__min_x:
self.__min_x = point.x
if point.y > self.__max_y:
self.__max_y = point.y
elif point.y < self.__min_y:
self.__min_y = point.y
if point.z > self.__max_z:
self.__max_z = point.z
elif point.z < self.__min_z:
self.__min_z = point.z
self.__max_z += self.__OFFSET
def get_center(self) -> vec3:
"""
Calculates the center of the "view cuboid" in light space first, and then
Converts this to world space using the inverse light's view matrix.
:return:
The center of the "view cuboid" in world space.
"""
x = (self.__min_x + self.__max_x) / 2.0
y = (self.__min_y + self.__max_y) / 2.0
z = (self.__min_z + self.__max_z) / 2.0
cen = vec4(x, y, z, 1)
inverted_light = self.__light_view_matrix.inverse()
center = inverted_light * cen
return vec3(center.x, center.y, center.z)
def get_width(self) -> float:
"""
:return: The width of the "view cuboid" (orthographic projection area).
"""
return self.__max_x - self.__min_x
def get_height(self) -> float:
"""
:return: The height of the "view cuboid" (orthographic projection area).
"""
return self.__max_y - self.__min_y
def get_length(self) -> float:
"""
:return: The height of the "view cuboid" (orthographic projection area).
"""
return self.__max_z - self.__min_z
def calculate_frustum_vertices(self, rotation: mat4, forward_vector: vec3, center_near: vec3,
center_far: vec3) -> list[vec4]:
"""
Calculates the position of the vertex at each corner of the view frustum
in light space (8 vertices in total, so this returns 8 positions).
:param rotation: camera's rotation.
:param forward_vector: the direction that the camera is aiming, and thus the direction of the frustum.
:param center_near: the center point of the frustum's near plane.
:param center_far: the center point of the frustum's (possibly adjusted) far plane.
:return: The positions of the vertices of the frustum in light space.
"""
up_vector = rotation * self.__UP
up_vector = vec3(up_vector.x, up_vector.y, up_vector.z)
right_vector = forward_vector.cross(up_vector)
down_vector = -up_vector
left_vector = -right_vector
far_top = center_far + up_vector * self.__far_height
far_bottom = center_far + down_vector * self.__far_height
near_top = center_near + up_vector * self.__near_height
near_bottom = center_near + down_vector * self.__near_height
points = [vec4()] * 8
points[0] = self.calculate_light_space_frustum_corner(far_top, right_vector, self.__far_width)
points[1] = self.calculate_light_space_frustum_corner(far_top, left_vector, self.__far_width)
points[2] = self.calculate_light_space_frustum_corner(far_bottom, right_vector, self.__far_width)
points[3] = self.calculate_light_space_frustum_corner(far_bottom, left_vector, self.__far_width)
points[4] = self.calculate_light_space_frustum_corner(near_top, right_vector, self.__near_width)
points[5] = self.calculate_light_space_frustum_corner(near_top, left_vector, self.__near_width)
points[6] = self.calculate_light_space_frustum_corner(near_bottom, right_vector, self.__near_width)
points[7] = self.calculate_light_space_frustum_corner(near_bottom, left_vector, self.__near_width)
return points
def calculate_light_space_frustum_corner(self, start_point: vec3, direction: vec3, width: float) -> vec4:
"""
Calculates one of the corner vertices of the view frustum in world space
and converts it to light space.
:param start_point: the starting center point on the view frustum.
:param direction: the direction of the corner from the start point.
:param width: the distance of the corner from the start point.
:return: The relevant corner vertex of the view frustum in light space.
"""
point = start_point + direction * width
point4f = vec4(point.x, point.y, point.z, 1)
point4f = self.__light_view_matrix * point4f
return point4f
def calculate_camera_rotation_matrix(self) -> mat4:
"""
:return: The rotation of the camera represented as a matrix.
"""
rotation = mat4(1)
rotation = rotation.rotate(radians(-self.__cam.get_yaw()), vec3(0, 1, 0))
rotation = rotation.rotate(radians(-self.__cam.get_pitch()), vec3(1, 0, 0))
return rotation
def calculate_widths_and_heights(self) -> None:
"""
Calculates the width and height of the near and far planes of the
camera's view frustum. However, this doesn't have to use the "actual" far
plane of the view frustum. It can use a shortened view frustum if desired
by bringing the far-plane closer, which would increase shadow resolution
but means that distant objects wouldn't cast shadows.
:return:
"""
# to prevent circular import
fov = master_renderer.MasterRenderer.get_fov()
near_plane = master_renderer.MasterRenderer.get_near_plane()
self.__far_width = self.__SHADOW_DISTANCE * tan(radians(fov))
self.__near_width = near_plane * tan(radians(fov))
self.__far_height = self.__far_width / self.get_aspect_ratio()
self.__near_height = self.__near_width / self.get_aspect_ratio()
@staticmethod
def get_aspect_ratio() -> float:
"""
:return: The aspect ratio of the display (width:height ratio).
"""
return DisplayManager.get_width() / DisplayManager.get_height()
Also here are how my matrices are created:
def update_light_view_matrix(self, direction: vec3, center: vec3) -> None:
"""
Updates the "view" matrix of the light. This creates a view matrix which
will line up the direction of the "view cuboid" with the direction of the
light. The light itself has no position, so the "view" matrix is centered
at the center of the "view cuboid". The created view matrix determines
where and how the "view cuboid" is positioned in the world. The size of
the view cuboid, however, is determined by the projection matrix.
:param direction: the light direction, and therefore the direction that the "view cuboid" should be pointing.
:param center: the center of the "view cuboid" in world space.
:return:
"""
direction = direction.normalize()
self.__light_view_matrix = mat4().identity()
pitch = math.acos(math.sqrt(direction.x**2 + direction.y**2))
self.__light_view_matrix = self.__light_view_matrix.rotate(pitch, vec3(1, 0, 0))
yaw = math.degrees(math.atan(direction.x / direction.z))
yaw = yaw - 180 if direction.z > 0 else yaw
self.__light_view_matrix = self.__light_view_matrix.rotate(-math.radians(yaw), vec3(0, 1, 0))
self.__light_view_matrix = self.__light_view_matrix.translate(-center)
def update_ortho_projection_matrix(self, width: float, height: float, length: float) -> None:
"""
Creates the orthographic projection matrix. This projection matrix
basically sets the width, length and height of the "view cuboid", based
on the values that were calculated in the ShadowBox class.
:param width: shadow box width.
:param height: shadow box height.
:param length: shadow box length.
:return:
"""
self.__projection_matrix = mat4()
self.__projection_matrix[(0, 0)] = 2.0 / width
self.__projection_matrix[(1, 1)] = 2.0 / height
self.__projection_matrix[(2, 2)] = -2.0 / length
self.__projection_matrix[(3, 3)] = 1
The model matrix is definetly correct.
...
self.__projection_view_matrix = self.__projection_matrix * self.__light_view_matrix
...
mvp_matrix = self.__projection_view_matrix * model_matrix
I think I'll add the outputs of the matrix calculations so you can verify them:
projection_matrix:
[ 0.0036, 0.0000, 0.0000, 0.0000]
[ 0.0000, 0.0069, 0.0000, 0.0000]
[ 0.0000, 0.0000, -0.0128, 0.0000]
[ 0.0000, 0.0000, 0.0000, 1.0000]
light_view_matrix:
[ -0.7071, 0.0000, -0.7071, -15.1670]
[ -0.3430, 0.8745, 0.3430, 22.1743]
[ 0.6183, 0.4851, -0.6183, -5.0441]
[ 0.0000, 0.0000, 0.0000, 1.0000]
projection_view_matrix:
[ -0.0026, 0.0000, -0.0026, -0.0552]
[ -0.0024, 0.0060, 0.0024, 0.1527]
[ -0.0079, -0.0062, 0.0079, 0.0643]
[ 0.0000, 0.0000, 0.0000, 1.0000]
mvp_matrix:
[ 1.0000, 0.0000, 0.0000, 575.0000]
[ 0.0000, 1.0000, 0.0000, -0.8157]
[ 0.0000, 0.0000, 1.0000, 792.0000]
[ 0.0000, 0.0000, 0.0000, 1.0000]
The mvp_matrix
for every instance looks something like this as they have no rotation and scaling, just a position.
from OpenGL Depth Map renders objects only 1 unit far away
No comments:
Post a Comment