所以我一直在尝试学习 3D 渲染的工作原理。我尝试编写一个脚本,目标是在 3D 空间中旋转平面(2D)正方形。我首先在标准化空间 (-1, 1) 中定义一个正方形。请注意,只有 x 和 y 是标准化的。
class Vec3: # 3D VECTOR def __init__(self, x, y, z): self.x = x self.y = y self.z = z s = 1 p1 = Vec3(-s, -s, -s) p2 = Vec3(s, -s, -s) p3 = Vec3(s, s, -s) p4 = Vec3(-s, s, -s)
然后将这些点翻译到屏幕上:
p1.z += 6 p2.z += 6 p3.z += 6 p4.z += 6
此后的所有操作都在应用程序循环内完成。我使用以下函数将点缩放到屏幕上并应用投影:
class Transform: # IT TRANSFORMS THE X AND Y FROM NORMALISED SPACE TO SCREEN SPACE WITH PROJECTION APPLIED def worldSpaceTransform(self, vec3, w, h): if vec3.z == 0: vec3.z = 0.001 zInverse = 1/ vec3.z xTransformed = ((vec3.x * zInverse) + 1) * (w/2) yTransformed = ((-vec3.y * zInverse) + 1) * (h/2) xTransformed = str(xTransformed)[:6] yTransformed = str(yTransformed)[:6] return Vec2(float(xTransformed), float(yTransformed))
像这样:
# TRANSLATING THE SQUARE SHEET INTO THE SCREEN SPACE point1 = transform.worldSpaceTransform(p1, SCREENWIDTH, SCREENHEIGHT) point2 = transform.worldSpaceTransform(p2, SCREENWIDTH, SCREENHEIGHT) point3 = transform.worldSpaceTransform(p3, SCREENWIDTH, SCREENHEIGHT) point4 = transform.worldSpaceTransform(p4, SCREENWIDTH, SCREENHEIGHT)
并得出以下几点:
# STORING THE POINTS TO A TUPLE SO IT CAN BE DRAWN USING pygame.draw.lines points = ((point1.x, point1.y), (point2.x, point2.y), (point2.x, point2.y), (point3.x, point3.y), (point3.x, point3.y), (point4.x, point4.y), (point4.x, point4.y), (point1.x, point1.y)) pygame.draw.lines(D, (0, 0, 0), False, points)
到目前为止一切都正常(我认为),因为它绘制了一个正方形,正如它应该的那样。
现在开始旋转。我尝试了所有轴的旋转,但都不起作用,但为了具体起见,我将讨论 x 轴。以下是旋转类。我从维基百科复制了旋转矩阵。我不完全确定它们是如何工作的,所以我也不知道它是否与我上面描述的系统兼容。
def multVecMatrix(vec3, mat3): # MULTIPLIES A Vec3 OBJECT WITH Mat3 OBJECT AND RETURNS A NEW Vec3 ? x = vec3.x * mat3.matrix[0][0] + vec3.y * mat3.matrix[0][1] + vec3.z * mat3.matrix[0][2] y = vec3.x * mat3.matrix[1][0] + vec3.y * mat3.matrix[1][1] + vec3.z * mat3.matrix[1][2] z = vec3.x * mat3.matrix[2][0] + vec3.y * mat3.matrix[2][1] + vec3.z * mat3.matrix[2][2] return Vec3(x, y, z) class Rotation: def rotateX(self, theta): # ROTATION MATRIX IN X AXIS ?? sinTheta = sin(theta) cosTheta = cos(theta) m = Mat3() m.matrix = [[1, 0, 0], [0, cosTheta, sinTheta], [0, -sinTheta, cosTheta]] return m def rotate(self, vec3, theta, axis=None): # ROTATES A Vec3 BY GIVEN THETA AND AXIS ?? if axis == "x": return multVecMatrix(vec3, self.rotateX(theta)) if axis == "y": return multVecMatrix(vec3, self.rotateY(theta)) if axis == "z": return multVecMatrix(vec3, self.rotateZ(theta))
在将屏幕填充为白色之后以及将点从标准空间缩放到屏幕空间之前,它会像这样被调用。
# screen is filled with white color # ROTATING THE POINTS AROUND X AXIS ????? p1.x = rotation.rotate(p1, thetax, axis='x').x p1.y = rotation.rotate(p1, thetay, axis='x').y p1.z = rotation.rotate(p1, thetax, axis='x').z p2.x = rotation.rotate(p2, thetax, axis='x').x p2.y = rotation.rotate(p2, thetay, axis='x').y p2.z = rotation.rotate(p2, thetax, axis='x').z p3.x = rotation.rotate(p3, thetax, axis='x').x p3.y = rotation.rotate(p3, thetay, axis='x').y p3.z = rotation.rotate(p3, thetax, axis='x').z p4.x = rotation.rotate(p4, thetax, axis='x').x p4.y = rotation.rotate(p4, thetay, axis='x').y p4.z = rotation.rotate(p4, thetax, axis='x').z # then the points are translated into world space
应用旋转后,它看起来像是在移动并绕着 x 轴旋转,但没有旋转。我希望它旋转但保持在原位。我做错了什么?
完整的复制粘贴代码供参考:
import pygame from math import sin, cos, radians pygame.init() ### PYGAME STUFF ###################################### SCREENWIDTH = 600 SCREENHEIGHT = 600 D = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT)) pygame.display.set_caption("PRESS SPACE TO ROTATE AROUND X") ######### MATH FUNCTIONS AND CLASSES #################### class Mat3: # 3X3 MATRIX INITIALIZED WITH ALL 0's def __init__(self): self.matrix = [[0 for i in range(3)], [0 for i in range(3)], [0 for i in range(3)]] class Vec2: # 2D VECTOR def __init__(self, x, y): self.x = x self.y = y class Vec3: # 3D VECTOR def __init__(self, x, y, z): self.x = x self.y = y self.z = z def multVecMatrix(vec3, mat3): # MULTIPLIES A Vec3 OBJECT WITH Mat3 OBJECT AND RETURNS A NEW Vec3 x = vec3.x * mat3.matrix[0][0] + vec3.y * mat3.matrix[0][1] + vec3.z * mat3.matrix[0][2] y = vec3.x * mat3.matrix[1][0] + vec3.y * mat3.matrix[1][1] + vec3.z * mat3.matrix[1][2] z = vec3.x * mat3.matrix[2][0] + vec3.y * mat3.matrix[2][1] + vec3.z * mat3.matrix[1][2] return Vec3(x, y, z) class Transform: # IT TRANSFORMS THE X AND Y FROM NORMALIZED SPACE TO SCREEN SPACE WITH PROJECTION APPLIED def worldSpaceTransform(self, vec3, w, h): if vec3.z == 0: vec3.z = 0.001 zInverse = 1/ vec3.z xTransformed = ((vec3.x * zInverse) + 1) * (w/2) yTransformed = ((-vec3.y * zInverse) + 1) * (h/2) xTransformed = str(xTransformed)[:6] yTransformed = str(yTransformed)[:6] return Vec2(float(xTransformed), float(yTransformed)) class Rotation: def rotateX(self, theta): # ROTATION MATRIX IN X AXIS sinTheta = sin(theta) cosTheta = cos(theta) m = Mat3() m.matrix = [[1, 0, 0], [0, cosTheta, sinTheta], [0, -sinTheta, cosTheta]] return m def rotate(self, vec3, theta, axis=None): # ROTATES A Vec3 BY GIVEN THETA AND AXIS if axis == "x": return multVecMatrix(vec3, self.rotateX(theta)) if axis == "y": return multVecMatrix(vec3, self.rotateY(theta)) if axis == "z": return multVecMatrix(vec3, self.rotateZ(theta)) transform = Transform() rotation = Rotation() # ASSIGNING 4 Vec3's FOR 4 SIDES OF SQUARE IN NORMALIZED SPACE s = 1 p1 = Vec3(-s, -s, -s) p2 = Vec3(s, -s, -s) p3 = Vec3(s, s, -s) p4 = Vec3(-s, s, -s) # TRANSLATING THE POINTS OF THE CUBE A LITTLE BIT INTO THE SCREEN p1.z += 6 p2.z += 6 p3.z += 6 p4.z += 6 # ASSIGNING THE ROTATION ANGLES thetax = 0 # APPLICATION LOOP while True: pygame.event.get() D.fill((255, 255, 255)) # ROTATING THE POINTS AROUND X AXIS p1.x = rotation.rotate(p1, thetax, axis='x').x p1.y = rotation.rotate(p1, thetax, axis='x').y p1.z = rotation.rotate(p1, thetax, axis='x').z p2.x = rotation.rotate(p2, thetax, axis='x').x p2.y = rotation.rotate(p2, thetax, axis='x').y p2.z = rotation.rotate(p2, thetax, axis='x').z p3.x = rotation.rotate(p3, thetax, axis='x').x p3.y = rotation.rotate(p3, thetax, axis='x').y p3.z = rotation.rotate(p3, thetax, axis='x').z p4.x = rotation.rotate(p4, thetax, axis='x').x p4.y = rotation.rotate(p4, thetax, axis='x').y p4.z = rotation.rotate(p4, thetax, axis='x').z # TRANSLATING THE SQUARE SHEET INTO THE SCREEN SPACE point1 = transform.worldSpaceTransform(p1, SCREENWIDTH, SCREENHEIGHT) point2 = transform.worldSpaceTransform(p2, SCREENWIDTH, SCREENHEIGHT) point3 = transform.worldSpaceTransform(p3, SCREENWIDTH, SCREENHEIGHT) point4 = transform.worldSpaceTransform(p4, SCREENWIDTH, SCREENHEIGHT) # STORING THE POINTS TO A TUPLE SO IT CAN BE DRAWN USING pygame.draw.lines points = ((point1.x, point1.y), (point2.x, point2.y), (point2.x, point2.y), (point3.x, point3.y), (point3.x, point3.y), (point4.x, point4.y), (point4.x, point4.y), (point1.x, point1.y)) keys = pygame.key.get_pressed() # ROTATE X ? if keys[pygame.K_SPACE]: thetax -= 0.005 pygame.draw.lines(D, (0, 0, 0), False, points) pygame.display.flip()
从你的代码中,我看到你已经非常接近正确的实现了3D旋转。你使用的是旋转矩阵,并且尝试将每个点旋转到新的位置。不过,问题可能出在旋转的处理上以及如何使用 theta 角度。
theta
旋转角度单位:在你的代码中,旋转的角度 thetax 没有明确指出是以什么单位为基础的。如果你没有明确转换角度,theta 可能是以度为单位,但在Python的 math.sin() 和 math.cos() 函数中,它们期望的角度是以弧度为单位的。因此,你需要确保将 theta 转换为弧度。
thetax
math.sin()
math.cos()
旋转应用的顺序:在你的代码中,你多次调用 rotation.rotate() 来分别更新每个轴上的坐标,应该是没有问题的。不过你只需要旋转 x 轴而不是 x 和 y。因此,你不需要分别更新 p1.x、p1.y 和 p1.z,你只需要旋转 p1 作为一个整体。
rotation.rotate()
x
y
p1.x
p1.y
p1.z
p1
我将会修改你的代码来解决这些问题:
import pygame from math import sin, cos, radians pygame.init() ### PYGAME STUFF ###################################### SCREENWIDTH = 600 SCREENHEIGHT = 600 D = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT)) pygame.display.set_caption("PRESS SPACE TO ROTATE AROUND X") ######### MATH FUNCTIONS AND CLASSES #################### class Mat3: # 3X3 MATRIX INITIALIZED WITH ALL 0's def __init__(self): self.matrix = [[0 for i in range(3)], [0 for i in range(3)], [0 for i in range(3)]] class Vec2: # 2D VECTOR def __init__(self, x, y): self.x = x self.y = y class Vec3: # 3D VECTOR def __init__(self, x, y, z): self.x = x self.y = y self.z = z def multVecMatrix(vec3, mat3): # MULTIPLIES A Vec3 OBJECT WITH Mat3 OBJECT AND RETURNS A NEW Vec3 x = vec3.x * mat3.matrix[0][0] + vec3.y * mat3.matrix[0][1] + vec3.z * mat3.matrix[0][2] y = vec3.x * mat3.matrix[1][0] + vec3.y * mat3.matrix[1][1] + vec3.z * mat3.matrix[1][2] z = vec3.x * mat3.matrix[2][0] + vec3.y * mat3.matrix[2][1] + vec3.z * mat3.matrix[1][2] return Vec3(x, y, z) class Transform: # IT TRANSFORMS THE X AND Y FROM NORMALIZED SPACE TO SCREEN SPACE WITH PROJECTION APPLIED def worldSpaceTransform(self, vec3, w, h): if vec3.z == 0: vec3.z = 0.001 zInverse = 1 / vec3.z xTransformed = ((vec3.x * zInverse) + 1) * (w / 2) yTransformed = ((-vec3.y * zInverse) + 1) * (h / 2) xTransformed = str(xTransformed)[:6] yTransformed = str(yTransformed)[:6] return Vec2(float(xTransformed), float(yTransformed)) class Rotation: def rotateX(self, theta): # ROTATION MATRIX IN X AXIS sinTheta = sin(theta) cosTheta = cos(theta) m = Mat3() m.matrix = [[1, 0, 0], [0, cosTheta, sinTheta], [0, -sinTheta, cosTheta]] return m def rotate(self, vec3, theta, axis=None): # ROTATES A Vec3 BY GIVEN THETA AND AXIS if axis == "x": return multVecMatrix(vec3, self.rotateX(theta)) if axis == "y": return multVecMatrix(vec3, self.rotateY(theta)) if axis == "z": return multVecMatrix(vec3, self.rotateZ(theta)) transform = Transform() rotation = Rotation() # ASSIGNING 4 Vec3's FOR 4 SIDES OF SQUARE IN NORMALIZED SPACE s = 1 p1 = Vec3(-s, -s, -s) p2 = Vec3(s, -s, -s) p3 = Vec3(s, s, -s) p4 = Vec3(-s, s, -s) # TRANSLATING THE POINTS OF THE CUBE A LITTLE BIT INTO THE SCREEN p1.z += 6 p2.z += 6 p3.z += 6 p4.z += 6 # ASSIGNING THE ROTATION ANGLES (in degrees) thetax = 0 # APPLICATION LOOP while True: pygame.event.get() D.fill((255, 255, 255)) # Convert degrees to radians (ensure theta is in radians) thetax_rad = radians(thetax) # ROTATING THE POINTS AROUND X AXIS (all at once) p1 = rotation.rotate(p1, thetax_rad, axis='x') p2 = rotation.rotate(p2, thetax_rad, axis='x') p3 = rotation.rotate(p3, thetax_rad, axis='x') p4 = rotation.rotate(p4, thetax_rad, axis='x') # TRANSLATING THE SQUARE SHEET INTO THE SCREEN SPACE point1 = transform.worldSpaceTransform(p1, SCREENWIDTH, SCREENHEIGHT) point2 = transform.worldSpaceTransform(p2, SCREENWIDTH, SCREENHEIGHT) point3 = transform.worldSpaceTransform(p3, SCREENWIDTH, SCREENHEIGHT) point4 = transform.worldSpaceTransform(p4, SCREENWIDTH, SCREENHEIGHT) # STORING THE POINTS TO A TUPLE SO IT CAN BE DRAWN USING pygame.draw.lines points = ((point1.x, point1.y), (point2.x, point2.y), (point2.x, point2.y), (point3.x, point3.y), (point3.x, point3.y), (point4.x, point4.y), (point4.x, point4.y), (point1.x, point1.y)) keys = pygame.key.get_pressed() # ROTATE X ? if keys[pygame.K_SPACE]: thetax -= 0.005 # Decrease theta to rotate counter-clockwise pygame.draw.lines(D, (0, 0, 0), False, points) pygame.display.flip()
radians()
rotateX
sin(theta)
cos(theta)
rotate
Vec3
z
按下 空格键 后,你应该看到正方形围绕 x 轴旋转,而不是平移。旋转时会围绕其中心旋转,并且图形将保持在视野内。