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

Processing(Java)中3D投影轴反转问题求助

3D相机旋转时立方体投影翻转问题求助

背景介绍

我数学基础一般,最近尝试将线性代数知识和编程技能结合,用Processing(Java)开发一款小型3D游戏。目前已经实现了立方体的三维旋转功能,但在添加相机视角时遇到了问题。

问题描述

参考维基百科的3D投影矩阵实现相机视角后,旋转相机时,投影的立方体出现横竖翻转现象(视频0:14处可见该问题)。调整显示面参数e的取值后,问题仍未解决,希望了解问题原因及修复方案。

相关代码

import java.util.Arrays;

PVector[] points;
PVector cam, cam_angle, point;
float angle = 0.0;

void setup() {
  //fullScreen();  
  size(1000,400);
  noCursor();

  cam = point = new PVector(0, 0, -1000);
  cam_angle = new PVector(0,0,0);

  points = new PVector[] {
    new PVector(-100, -100, -100),
    new PVector(-100, -100, 100),
    new PVector(-100, 100, -100),
    new PVector(-100, 100, 100),
    new PVector( 100, -100, -100),
    new PVector( 100, -100, 100),
    new PVector( 100, 100, -100),
    new PVector( 100, 100, 100),
  };
}

PVector Rotate2d(PVector p, float a) {
  float[][] m2 = {
    {cos(a), -sin(a)},
    {sin(a), cos(a)}
  };

  float[][] rotated = matmul(m2, new float[][] {
    { p.x },
    { p.y }
    });

  return new PVector(rotated[0][0], rotated[1][0]);
}

PVector Rotate3d(PVector p, float[][] m2) {
  float[][] rotated = matmul(m2, new float[][] {
    { p.x },
    { p.y },
    { p.z }
    });

  return new PVector(rotated[0][0], rotated[1][0], rotated[2][0]);
}

PVector Rotate3d_x(PVector p, float a) {
  return Rotate3d(p,
    new float[][] {
    {1, 0, 0},
    {0, cos(a), -sin(a)},
    {0, sin(a), cos(a)}
    });
};

PVector Rotate3d_y(PVector p, float a) {
  return Rotate3d(p,
    new float[][] {
    {cos(a), 0, sin(a)},
    {0, 1, 0},
    {-sin(a), 0, cos(a)}
    });
}

PVector Rotate3d_z(PVector p, float a) {
  return Rotate3d(p,
    new float[][] {
    {cos(a), -sin(a), 0},
    {sin(a), cos(a), 0},
    {0, 0, 1}
    });
}

PVector Rotate3d(PVector p, PVector a) {
  return Rotate3d_z( Rotate3d_y(Rotate3d_x(p, a.x), a.y), a.z );
}

PVector applyPerspective(PVector p) {
   PVector c = cam;
   PVector co = cam_angle;
   PVector e =  new PVector(0, 0, 100);
  // c = camera position
  // co = camera orientation / camera rotation
  // e = displays surface pos relative to camera pinhole c

  // dx, dy, dz
  float[][] dxyz = matmul(
    matmul(new float[][]{ 
    {1, 0, 0},
    {0, cos(co.x), sin(co.x)},
    {0, -sin(co.x), cos(co.x)}
    }, new float[][]{ 
      {cos(co.y), 0, -sin(co.y)},
      {0, 1, 0},
      {sin(co.y), 0, cos(co.y)}
    }),

    matmul(new float[][]{ 
      {cos(co.z), sin(co.z), 0},
      {-sin(co.z), cos(co.z), 0},
      {0, 0, 1}
    }, new float[][]{ 
      {p.x - c.x},
      {p.y - c.y},
      {p.z - c.z},
    }));

  PVector d = new PVector(dxyz[0][0], dxyz[1][0], dxyz[2][0]);

  return new PVector((e.z/d.z)*d.x+e.x, (e.z/d.z)*d.y+e.y);
}

// Matrixmultiplikation
float[][] matmul(float[][] m1, float[][] m2) {
  int cols_m1 = m1.length,
    rows_m1 = m1[0].length;

  int cols_m2 = m2.length,
    rows_m2 = m2[0].length;

  try {
    if (rows_m1 != cols_m2) throw new Exception("Rows of m1 must match Columns of m2!");
  }
  catch(Exception e) {
    println(e);
  }

  float[][] res = new float[cols_m2][rows_m2];

  for (int c=0; c < cols_m1; c++) {
    for (int r2=0; r2 < rows_m2; r2++) {
      float sum = 0;
      float[] buf = new float[rows_m1];

      // Multiply rows of m1 with columns of m2 and store in buf
      for (int r=0; r < rows_m1; r++) {
        buf[r] = m1[c][r]* m2[r][r2];
      }

      // Add up all entries into sum
      for (float entry : buf) {
        sum += entry;
      }

      res[c][r2] = sum;
    }
  }

  return res;
}

void draw() {
  cam_angle = new PVector(0.01*(mouseY-width/2), 0.01*(mouseX-height/2), 0);
  
  background(255);
  translate(width/2, height/2);
  strokeWeight(1);
  fill(0);

  PVector[] points_projected = new PVector[points.length];

  for (int i=0; i < points.length; i++) {
    points_projected[i] =   applyPerspective(points[i]);
  }

  for (int i=0; i < points_projected.length; i++) {    
    for (int a=0; a < points_projected.length; a++) {
      // Alle Punkte verbinden
      line(points_projected[i].x, points_projected[i].y, points_projected[a].x, points_projected[a].y);
    }
  }  
}

void keyPressed() {
  if (key == 'w') {
    cam.add(Rotate3d(new PVector(0, 0, -20), cam_angle));
  }
  
  if (key == 'a') {
    cam.add(Rotate3d(new PVector(20, 0, 0), cam_angle));
  }
  
  if (key == 's') {
    cam.add(Rotate3d(new PVector(0, 0, 20), cam_angle));
  }
  
  if (key == 'd') {
   cam.add(Rotate3d(new PVector(-20, 0, 0), cam_angle));
  }  
}

已尝试的操作

调整显示面参数e的取值:

// e = displays surface position relative to camera pinhole c
PVector e = new PVector(0, 0, 1000);

但问题未解决。


问题原因及修复方案

1. 鼠标角度映射维度错误

原因:draw()中cam_angle的计算将mouseY与width/2做差,mouseX与height/2做差,属于维度不匹配(高度轴对应高度值,宽度轴对应宽度值),导致Y轴旋转方向异常,引发翻转。
修复:修改cam_angle的计算逻辑:

cam_angle = new PVector(0.01*(mouseY - height/2), 0.01*(mouseX - width/2), 0);

2. 相机旋转矩阵符号与顺序错误

原因:applyPerspective中的X轴、Y轴旋转矩阵符号不符合右手坐标系的相机空间转换规则,且矩阵乘法顺序颠倒,导致坐标旋转后出现翻转。
修复:替换为正确的旋转矩阵,并调整乘法顺序:

float[][] dxyz = matmul(
    matmul(new float[][]{ 
      {cos(co.z), -sin(co.z), 0},
      {sin(co.z), cos(co.z), 0},
      {0, 0, 1}
    }, new float[][]{ 
      {cos(co.y), 0, sin(co.y)},
      {0, 1, 0},
      {-sin(co.y), 0, cos(co.y)}
    }),
    matmul(new float[][]{ 
      {1, 0, 0},
      {0, cos(co.x), -sin(co.x)},
      {0, sin(co.x), cos(co.x)}
    }, new float[][]{ 
      {p.x - c.x},
      {p.y - c.y},
      {p.z - c.z},
    })
);

该顺序对应先对平移后的点应用X轴旋转,再Y轴、Z轴旋转,符合世界坐标到相机坐标的转换逻辑,同时矩阵符号匹配右手坐标系,避免翻转。

3. 额外验证:旋转函数一致性

确保Rotate3d函数的旋转顺序(X→Y→Z)与相机转换中的矩阵顺序一致,避免因旋转顺序冲突导致的异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 15:55:35