我编辑了这个问题,因为我对我的代码之前的“错误”有错误的解释。 之前我以为我在 FBO 或 MVP 矩阵中犯了一个错误,因为我的阴影贴图完全是空白的,但实际上我做的一切都是正确的,深度贴图仅从 0-1 的距离渲染事物,关联黑白像素屏幕上。 我真傻。
我缩小了整个场景并重新定位了“太阳”,这样我现在就可以看到阴影了。 我的问题是如何“扩展”此视图,以便不仅可以看到远至 1 个单位的对象,而且可以看到我的视图。
我的阴影贴图是黑白的,与我遵循的教程中的红色和黑色相反。这是怎么回事?
最后,“太阳”的视图不会随着我的移动而更新。预计它会跟随角色,更新视图和可见阴影,但视图在“太阳”的位置保持静止。
这是我的整个 Shadow Box 类,因为这可能是问题所在:
编辑:这可能不是问题,但你仍然可以看看它。
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()
这也是我的矩阵的创建方式:
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
模型矩阵绝对正确。
...
self.__projection_view_matrix = self.__projection_matrix * self.__light_view_matrix
...
mvp_matrix = self.__projection_view_matrix * model_matrix
我想我会添加矩阵计算的输出,以便您可以验证它们:
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]
每个实例的
mvp_matrix
看起来像这样,因为它们没有旋转和缩放,只有一个位置。