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

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()

关键修改说明

  1. 分离原始点集:新增self.original_points保存初始坐标,每帧基于原始点计算旋转,避免累积修改。
  2. 修正旋转矩阵:将X/Y/Z轴旋转矩阵调整为适配行向量右乘的形式(转置原列向量矩阵),确保旋转方向正确。
  3. 修复投影矩阵:修正宽高比的计算方向(宽/高),调整X轴缩放因子为self.f / self.aspect_ratio,符合透视投影逻辑。
  4. 增大Z轴坐标:将点的Z值改为100/102,远离近裁剪面,避免投影异常。
  5. 优化事件处理:替换列表推导式的退出逻辑,代码更清晰规范。

内容的提问来源于stack exchange,提问作者dan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 22:14:57