3D透视投影中X、Y轴旋转异常问题求助
问题分析与解决方案
核心问题点
- 旋转矩阵与向量乘法顺序不匹配:使用行向量右乘矩阵,但旋转矩阵是按列向量左乘设计的,导致X/Y轴旋转方向错误。
- 原始点集被直接修改:每帧绘制时直接更新
self.points,导致旋转累积叠加,运动轨迹异常。 - 投影矩阵参数错误:Y轴缩放因子误用了宽高比,第三行参数不符合标准透视投影公式。
- 点的Z轴坐标过小:Z值过近,接近近裁剪面,易引发投影计算异常。
修改后的代码
import pygame as pg import math as m import numpy as np class App: def __init__(self): pg.init() self.res = self.width, self.height = 1000, 700 self.hwidth, self.hheight = self.width / 2, self.height / 2 self.fps = 60 self.screen = pg.display.set_mode(self.res) self.clock = pg.time.Clock() self.fov = m.pi / 3 self.f = 1 / m.tan(self.fov/2) self.zf = 1000 self.zn = 0.1 self.aspect_ratio = self.width / self.height # 修正宽高比计算(宽/高) self.angle = 0.01 # 修正透视投影矩阵(适配行向量右乘) self.proj = np.array([ [self.f / self.aspect_ratio, 0, 0, 0], [0, self.f, 0, 0], [0, 0, self.zf / (self.zf - self.zn), 1], [0, 0, (-self.zf * self.zn) / (self.zf - self.zn), 0] ]) # 保留原始点集,避免每帧修改 self.original_points = [ np.array([100, 100, 100, 1]), np.array([100, -100, 100, 1]), np.array([-100, -100, 100, 1]), np.array([-100, 100, 100, 1]), np.array([100, 100, 102, 1]), np.array([100, -100, 102, 1]), np.array([-100, -100, 102, 1]), np.array([-100, 100, 102, 1]) ] def rotx(self): # 适配行向量右乘的X轴旋转矩阵(原列向量矩阵的转置) return np.array([ [1, 0, 0, 0], [0, m.cos(self.angle), -m.sin(self.angle), 0], [0, m.sin(self.angle), m.cos(self.angle), 0], [0, 0, 0, 1] ]) def roty(self): # 适配行向量右乘的Y轴旋转矩阵(原列向量矩阵的转置) return np.array([ [m.cos(self.angle), 0, m.sin(self.angle), 0], [0, 1, 0, 0], [-m.sin(self.angle), 0, m.cos(self.angle), 0], [0, 0, 0, 1] ]) def rotz(self): # Z轴旋转矩阵适配行向量右乘(原列向量矩阵的转置) return np.array([ [m.cos(self.angle), -m.sin(self.angle), 0, 0], [m.sin(self.angle), m.cos(self.angle), 0, 0], [0, 0, 1, 0], [0, 0, 0, 1] ]) def project(self, point): projected = point @ self.proj if projected[3] != 0: projected[0] /= projected[3] projected[1] /= projected[3] return (projected[0], projected[1]) else: return None def draw(self): self.screen.fill((255, 255, 255)) for point in self.original_points: # 基于原始点计算旋转后的临时点,不修改原始数据 rotated = point @ self.rotx() @ self.roty() @ self.rotz() projected = self.project(rotated) if projected is not None: x, y = projected[0], projected[1] pg.draw.circle(self.screen, (0), (x + self.hwidth, y + self.hheight), 3) def run(self): while True: self.draw() for event in pg.event.get(): if event.type == pg.QUIT: pg.quit() exit() pg.display.set_caption(f"{round(self.clock.get_fps())} FPS") pg.display.flip() self.clock.tick(self.fps) if __name__ == "__main__": app = App() app.run()
关键修改说明
- 分离原始点集:新增
self.original_points保存初始坐标,每帧基于原始点计算旋转,避免累积修改。 - 修正旋转矩阵:将X/Y/Z轴旋转矩阵调整为适配行向量右乘的形式(转置原列向量矩阵),确保旋转方向正确。
- 修复投影矩阵:修正宽高比的计算方向(宽/高),调整X轴缩放因子为
self.f / self.aspect_ratio,符合透视投影逻辑。 - 增大Z轴坐标:将点的Z值改为100/102,远离近裁剪面,避免投影异常。
- 优化事件处理:替换列表推导式的退出逻辑,代码更清晰规范。
内容的提问来源于stack exchange,提问作者dan
相关产品推荐
相关产品推荐

