You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

添加透视后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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 22:15:53