添加透视后3D模型动画时发生形变的原因排查求助
问题:3D模型添加透视后动画播放时形变
我编写的Python代码原本运行正常,但为3D模型添加透视效果后,动画过程中模型出现形变。无法确定是数学计算还是代码逻辑问题,请求排查。代码如下:
import pygame import math import time pygame.init() white = (255,255,255) black = (0,0,0) screen = pygame.display.set_mode((800, 800)) triangle = [[90, 90, 0, 1], [180, 90, 0, 1], [180, 270, 0, 1], [120, 120, 80, 1]] edgeTable = [[0, 1], [1, 2], [2, 0], [0, 3], [2, 3], [1, 3]] Matrix = [[1, 0, 0, 0] , [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]] def Multiply(Matr, Vec): res = [0, 0, 0, 0] for i in range(4): for j in range(4): res[i]=res[i]+Matr[j][i]*Vec[j] return res def MatMultiply(M1, M2): res = [0, 0, 0, 0] for i in range(4): res[i] = Multiply(M1, M2[i]) return res def perspectiveMat(): d = 1000 PerspectiveMatRes = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 1/d], [0, 0, 0, 0]] return PerspectiveMatRes def RotZMat(angle): return [[math.cos(angle), math.sin(angle), 0, 0] , [-math.sin(angle), math.cos(angle), 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]] def RotXMat(angle): return [[1, 0, 0, 0], [0, math.cos(angle), math.sin(angle), 0] , [0, -math.sin(angle), math.cos(angle), 0], [0, 0, 0, 1]] def RotYMat(angle): return [[math.cos(angle), 0, math.sin(angle), 0], [0, 1, 0, 0], [-math.sin(angle), 0, math.cos(angle), 0], [0, 0, 0, 1]] def CopyMat(Mat): res = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] for i in range(4): for j in range(4): res[i][j]=Mat[i][j] return res def CopyVertices(Vert): res = [] for i in range(len(Vert)): res[i]=[] for j in range(4): res[i][j]=Vert[i][j] return res #angle = input(str("angle for x>> ")) angle = "1" angle = int(angle)*math.pi/180 run = True while run == True: for event in pygame.event.get(): if event.type == pygame.QUIT: run = False rotM = RotXMat(angle) resMat = MatMultiply(perspectiveMat(), rotM) cloud = triangle.copy() for i in range(len(triangle)): cloud[i] = Multiply(resMat, triangle[i]) screen.fill(white) for edge in edgeTable: pygame.draw.line(screen, black, (cloud[edge[0]][0]*cloud[edge[0]][3]+400, cloud[edge[0]][1]*cloud[edge[0]][3]+400),(cloud[edge[1]][0]*cloud[edge[1]][3]+400,cloud[edge[1]][1]*cloud[edge[1]][3]+400)) angle = angle + math.pi/180 time.sleep(0.04) pygame.display.flip() pygame.quit()
问题排查与修正
1. 矩阵乘法核心错误
Multiply函数中的矩阵-向量乘法索引完全颠倒:
# 错误写法 res[i] += Matr[j][i] * Vec[j] # 正确写法:矩阵第i行与向量做点积 res[i] += Matr[i][j] * Vec[j]
这会导致所有变换计算完全错误,是模型形变的最主要原因。
2. 透视矩阵与齐次坐标处理错误
- 透视矩阵定义不符合标准,且未执行齐次坐标归一化:标准透视投影需要用w分量除以x、y坐标得到屏幕坐标,你之前用
x*w的逻辑完全搞反,正确应为x/w。 - 修正后的透视矩阵:
def perspectiveMat(): d = 300 # 减小d值增强透视效果 return [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 1/d, 0]]
3. 变换顺序错误
3D变换的正确顺序是先旋转,再应用透视,你之前的矩阵乘法顺序会让透视变换被旋转影响,应先对顶点做旋转变换,再叠加透视。
4. 矩阵乘法函数实现错误
MatMultiply函数的4x4矩阵乘法逻辑完全错误,正确实现需要三重循环计算行列点积(修正后的代码中直接拆分变换步骤,可暂时不用此函数)。
修正后的完整代码
import pygame import math import time pygame.init() white = (255,255,255) black = (0,0,0) screen = pygame.display.set_mode((800, 800)) # 保留原始顶点,每次变换都基于原始数据 original_triangle = [[90, 90, 0, 1], [180, 90, 0, 1], [180, 270, 0, 1], [120, 120, 80, 1]] edgeTable = [[0, 1], [1, 2], [2, 0], [0, 3], [2, 3], [1, 3]] def Multiply(Matr, Vec): res = [0, 0, 0, 0] # 修正矩阵-向量乘法:矩阵第i行与向量做点积 for i in range(4): for j in range(4): res[i] += Matr[i][j] * Vec[j] return res def RotXMat(angle): return [[1, 0, 0, 0], [0, math.cos(angle), math.sin(angle), 0], [0, -math.sin(angle), math.cos(angle), 0], [0, 0, 0, 1]] def perspectiveMat(): d = 300 # 标准透视投影矩阵(相机位于z轴正方向,看向原点) return [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 1/d, 0]] angle = math.pi/180 run = True while run == True: for event in pygame.event.get(): if event.type == pygame.QUIT: run = False # 先旋转顶点,再应用透视 rotated_vertices = [Multiply(RotXMat(angle), v) for v in original_triangle] cloud = [] for v in rotated_vertices: persp_v = Multiply(perspectiveMat(), v) # 齐次坐标归一化:避免除以0 if persp_v[3] != 0: norm_x = persp_v[0] / persp_v[3] norm_y = persp_v[1] / persp_v[3] cloud.append([norm_x, norm_y, persp_v[2], persp_v[3]]) else: cloud.append(v) screen.fill(white) for edge in edgeTable: # 偏移到屏幕中心 pygame.draw.line(screen, black, (cloud[edge[0]][0] + 400, cloud[edge[0]][1] + 400), (cloud[edge[1]][0] + 400, cloud[edge[1]][1] + 400)) angle += math.pi/180 time.sleep(0.04) pygame.display.flip() pygame.quit()
内容的提问来源于stack exchange,提问作者Merlon
相关产品推荐
相关产品推荐

