我正在构建一个玩具3D渲染器,我还有一个尚未确定的问题。我有一个指向空间某一点的相机。相机具有框架和给定的焦距。我想在相机的框架上投射任意一点。像往常一样,X和Y坐标分开处理。图像显示了我如何计算X.我使用三角形的余弦定理:给定三个三角形长度,我首先找到角度,然后使用相机的焦距得到X.
图像:
这同样适用于Y坐标。对我来说它看起来很干净,但是结果并不像预期的那样:我在空间中设置了8个点作为立方体顶点,并且我将相机设置为围绕原点旋转。当相机移动时,立方体会变形严重。
关键方法:
private void project(double[][] points3D, int[][] points2D) {
double x;
double y;
double angle;
double camToPoint2;
double camToCenter2;
double centerToPoint2;
double[] camToCenter;
double[] centerToPoint;
for(int i = 0; i < points3D.length; i++) {
// x's projection
camToCenter = new double[] {center[0]-camera.position[0], center[2]-camera.position[2]};
centerToPoint = new double[] {points3D[i][0]-center[0], points3D[i][2]-center[2]};
camToCenter2 = camToCenter[0]*camToCenter[0] + camToCenter[1]*camToCenter[1];
centerToPoint2 = centerToPoint[0]*centerToPoint[0] + centerToPoint[1]*centerToPoint[1];
camToPoint2 = (points3D[i][0]-camera.position[0])*(points3D[i][0]-camera.position[0]) +
(points3D[i][2]-camera.position[2])*(points3D[i][2]-camera.position[2]);
angle = Math.acos((camToCenter2 + camToPoint2 - centerToPoint2) /
(2 * Math.sqrt(camToCenter2) * Math.sqrt(camToPoint2)));
x = camera.focalLength * Math.tan(angle);
// check if x lies to the left or right of the frame's center
x = camToCenter[0]*centerToPoint[1] + camToCenter[1]*centerToPoint[0] < 0 ? -x : x;
// reescale
points2D[i][0] = (int) (screenW * (0.5 * camera.frame[0] + x) / camera.frame[0]);
// y's projection
camToCenter = new double[] {center[1]-camera.position[1], center[2]-camera.position[2]};
centerToPoint = new double[] {points3D[i][1]-center[1], points3D[i][2]-center[2]};
camToCenter2 = camToCenter[0]*camToCenter[0] + camToCenter[1]*camToCenter[1];
centerToPoint2 = centerToPoint[0]*centerToPoint[0] + centerToPoint[1]*centerToPoint[1];
camToPoint2 = (points3D[i][1]-camera.position[1])*(points3D[i][1]-camera.position[1]) +
(points3D[i][2]-camera.position[2])*(points3D[i][2]-camera.position[2]);
angle = Math.acos((camToCenter2 + camToPoint2 - centerToPoint2) /
(2 * Math.sqrt(camToCenter2) * Math.sqrt(camToPoint2)));
y = camera.focalLength * Math.tan(angle);
// check if y lies to the left or right of the frame's center
y = camToCenter[0]*centerToPoint[1] + camToCenter[1]*centerToPoint[0] < 0 ? -y : y;
// reescale
points2D[i][1] = (int) (screenH * (0.5 * camera.frame[1] + y) / camera.frame[1]);
}
}
代码是上面解释的精确翻译。唯一的附加操作是注释:点积用于检查要投影的点是否位于相机框架中心的左侧或右侧。这将在此处讨论Determining if one 2D vector is to the right or left of another。关于错误可能出现的任何线索?在这里,我粘贴测试代码所需的内容。
Main.java
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
Universe universe = new Universe();
JFrame frame = new JFrame("3D Projection");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(universe);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
universe.loop();
}
}
Camera.java
public class Camera {
// both measures in meters
public final double focalLength = 50e-3;
public final double[] frame = {36e-3, 24e-3};
public double[] position;
public Camera(double x, double y, double z) {
position = new double[] {x, y, z};
}
}
Universe.java
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
public class Universe extends JPanel {
private int screenW;
private int screenH;
private int[][] points2D;
private double[] center;
private double[][] points3D;
private Camera camera;
public Universe() {
screenW = 864;
screenH = 576;
setPreferredSize(new Dimension(screenW, screenH));
points2D = new int[8][2];
center = new double[] {0, 0, 0};
camera = new Camera(0, 0, 10);
points3D = new double[][] {{1, 1, 1},
{1, 1, -1},
{1, -1, 1},
{1, -1, -1},
{-1, 1, 1},
{-1, 1, -1},
{-1, -1, 1},
{-1, -1, -1}};
}
public void paint(Graphics g) {
g.setColor(new Color(0, 0, 0));
g.fillRect(0, 0, screenW, screenH);
g.setColor(new Color(255, 255, 255));
g.drawLine(points2D[0][0], points2D[0][1], points2D[1][0], points2D[1][1]);
g.drawLine(points2D[2][0], points2D[2][1], points2D[3][0], points2D[3][1]);
g.drawLine(points2D[4][0], points2D[4][1], points2D[5][0], points2D[5][1]);
g.drawLine(points2D[6][0], points2D[6][1], points2D[7][0], points2D[7][1]);
g.drawLine(points2D[1][0], points2D[1][1], points2D[5][0], points2D[5][1]);
g.drawLine(points2D[0][0], points2D[0][1], points2D[4][0], points2D[4][1]);
g.drawLine(points2D[3][0], points2D[3][1], points2D[7][0], points2D[7][1]);
g.drawLine(points2D[2][0], points2D[2][1], points2D[6][0], points2D[6][1]);
g.drawLine(points2D[0][0], points2D[0][1], points2D[2][0], points2D[2][1]);
g.drawLine(points2D[1][0], points2D[1][1], points2D[3][0], points2D[3][1]);
g.drawLine(points2D[5][0], points2D[5][1], points2D[7][0], points2D[7][1]);
g.drawLine(points2D[4][0], points2D[4][1], points2D[6][0], points2D[6][1]);
}
public void loop() {
double t = 0;
double dt = 0.02;
while(true) {
try {
Thread.sleep(50);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
camera.position[0] = 10 * Math.sin(t % (2 * Math.PI));
camera.position[2] = 10 * Math.cos(t % (2 * Math.PI));
project(points3D, points2D);
repaint();
t += dt;
}
}
private void project(double[][] points3D, int[][] points2D) {
double x;
double y;
double angle;
double camToPoint2;
double camToCenter2;
double centerToPoint2;
double[] camToCenter;
double[] centerToPoint;
for(int i = 0; i < points3D.length; i++) {
// x's projection
camToCenter = new double[] {center[0]-camera.position[0], center[2]-camera.position[2]};
centerToPoint = new double[] {points3D[i][0]-center[0], points3D[i][2]-center[2]};
camToCenter2 = camToCenter[0]*camToCenter[0] + camToCenter[1]*camToCenter[1];
centerToPoint2 = centerToPoint[0]*centerToPoint[0] + centerToPoint[1]*centerToPoint[1];
camToPoint2 = (points3D[i][0]-camera.position[0])*(points3D[i][0]-camera.position[0]) +
(points3D[i][2]-camera.position[2])*(points3D[i][2]-camera.position[2]);
angle = Math.acos((camToCenter2 + camToPoint2 - centerToPoint2) /
(2 * Math.sqrt(camToCenter2) * Math.sqrt(camToPoint2)));
System.out.print(angle * (360/(2*Math.PI)) + " ");
x = camera.focalLength * Math.tan(angle);
x = camToCenter[0]*centerToPoint[1] + camToCenter[1]*centerToPoint[0] < 0 ? -x : x;
points2D[i][0] = (int) (screenW * (0.5 * camera.frame[0] + x) / camera.frame[0]);
// y's projection
camToCenter = new double[] {center[1]-camera.position[1], center[2]-camera.position[2]};
centerToPoint = new double[] {points3D[i][1]-center[1], points3D[i][2]-center[2]};
camToCenter2 = camToCenter[0]*camToCenter[0] + camToCenter[1]*camToCenter[1];
centerToPoint2 = centerToPoint[0]*centerToPoint[0] + centerToPoint[1]*centerToPoint[1];
camToPoint2 = (points3D[i][1]-camera.position[1])*(points3D[i][1]-camera.position[1]) +
(points3D[i][2]-camera.position[2])*(points3D[i][2]-camera.position[2]);
angle = Math.acos((camToCenter2 + camToPoint2 - centerToPoint2) /
(2 * Math.sqrt(camToCenter2) * Math.sqrt(camToPoint2)));
System.out.println(angle * (360/(2*Math.PI)));
y = camera.focalLength * Math.tan(angle);
y = camToCenter[0]*centerToPoint[1] + camToCenter[1]*centerToPoint[0] < 0 ? -y : y;
points2D[i][1] = (int) (screenH * (0.5 * camera.frame[1] + y) / camera.frame[1]);
}
System.out.println();
}
}
答案 0 :(得分:0)
我相信你的问题是你忽略了模特相机姿势。想象一个立方体漂浮在房间里的立方体。得到它了?现在在您的脑海中,绘制一个刚性连接到立方体的笛卡尔坐标系,其中x轴和z轴在水平面上,y轴指向天花板。由于相机是在您的示例代码中移动的东西,您可以同样选择将立方体的坐标系可视化为刚性连接到房间,如果您愿意的话。
现在,想象一下相机四处移动,从不同角度观察立方体。想象一下,一个独立的笛卡尔坐标系也与之紧密相连。它的x轴指向相机的右侧,y轴指向顶部,z轴指向背面(即,当您通过取景器观察时朝向您的脸部) 。当您在拿着相机的立方体周围走动时,它必须围绕y轴旋转以保持面向立方体。假设您将摄像机竖直握住,摄像机的y轴将始终与立方体的y轴平行,但两个x轴和z轴之间的关系将随时间而变化。
在两个笛卡尔坐标系的三个轴之间的这种关系可以被认为是相机的“方向”或“姿势”,并且您当前没有对其进行建模。为了模拟姿势,您需要一个3X3矩阵。如果您对坐标框架旋转的数学知之甚少,我建议您研究here和here以及here。
我已将姿势模型添加为Camera
类中的3X3旋转矩阵,并更新了Universe
类以便能够利用它。 Main
类保持不变。新的Camera
课程就在这里:
public class Camera {
// both measures in meters
public final double focalLength = 50e-3;
public final double[] frame = {36e-3, 24e-3};
public double[] position;
// The rotation vector gives the unit vector directions, in the coordinate
// frame of the object, of each of the axes of the camera's coordinate
// frames
public double[][] rotation;
public Camera(double[] pos, double[][] rot) {
position = pos;
rotation = rot;
}
}
并且新的Universe
类在这里:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
public class Universe extends JPanel {
private int screenW;
private int screenH;
private int[][] points2D;
private double[] center;
private double[][] points3D;
private Camera camera;
public Universe() {
screenW = 864;
screenH = 576;
setPreferredSize(new Dimension(screenW, screenH));
points2D = new int[8][2];
center = new double[] {0, 0, 0};
// Initialize the camera object with "placeholder" values, just to
// reserve space in memory for two suitably sized arrays
double[] initpos = new double[] {0, 0, 10};
double[][] initrot = new double[][] {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
camera = new Camera(initpos, initrot);
points3D = new double[][] {{1, 1, 1},
{1, 1, -1},
{1, -1, 1},
{1, -1, -1},
{-1, 1, 1},
{-1, 1, -1},
{-1, -1, 1},
{-1, -1, -1}};
}
public void paint(Graphics g) {
g.setColor(new Color(0, 0, 0));
g.fillRect(0, 0, screenW, screenH);
g.setColor(new Color(255, 255, 255));
g.drawLine(points2D[0][0], points2D[0][1], points2D[1][0], points2D[1][1]);
g.drawLine(points2D[2][0], points2D[2][1], points2D[3][0], points2D[3][1]);
g.drawLine(points2D[4][0], points2D[4][1], points2D[5][0], points2D[5][1]);
g.drawLine(points2D[6][0], points2D[6][1], points2D[7][0], points2D[7][1]);
g.drawLine(points2D[1][0], points2D[1][1], points2D[5][0], points2D[5][1]);
g.drawLine(points2D[0][0], points2D[0][1], points2D[4][0], points2D[4][1]);
g.drawLine(points2D[3][0], points2D[3][1], points2D[7][0], points2D[7][1]);
g.drawLine(points2D[2][0], points2D[2][1], points2D[6][0], points2D[6][1]);
g.drawLine(points2D[0][0], points2D[0][1], points2D[2][0], points2D[2][1]);
g.drawLine(points2D[1][0], points2D[1][1], points2D[3][0], points2D[3][1]);
g.drawLine(points2D[5][0], points2D[5][1], points2D[7][0], points2D[7][1]);
g.drawLine(points2D[4][0], points2D[4][1], points2D[6][0], points2D[6][1]);
}
public void loop() {
double t = 0;
double dt = 0.02;
while(true) {
try {
Thread.sleep(50);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
camera.position[0] = 10 * Math.sin(t % (2 * Math.PI));
camera.position[1] = 0;
camera.position[2] = 10 * Math.cos(t % (2 * Math.PI));
// The x unit vector of the camera plane coordinate frame, expressed
// in the cube's coordinate frame
camera.rotation[0][0] = Math.cos(t % (2 * Math.PI));
camera.rotation[0][1] = 0;
camera.rotation[0][2] = -Math.sin(t % (2 * Math.PI));
// The y unit vector of the camera plane coordinate frame, expressed
// in the cube's coordinate frame
camera.rotation[1][0] = 0;
camera.rotation[1][1] = 1;
camera.rotation[1][2] = 0;
// Ditto, z unit vector
camera.rotation[2][0] = Math.sin(t % (2 * Math.PI));
camera.rotation[2][1] = 0;
camera.rotation[2][2] = Math.cos(t % (2 * Math.PI));
project(points3D, points2D);
repaint();
t += dt;
}
}
private void project(double[][] points3D, int[][] points2D) {;
for(int i = 0; i < points3D.length; i++) {
double[] camToPoint = new double[3];
double[] rotPoint = new double[3];
// You may visualize this operation as "shifting" the vertices of the
// cube to some new translational offset within an unrotated camera
// coordinate frame.
for(int j = 0; j < 3; j++) {
camToPoint[j] = points3D[i][j] - camera.position[j];
}
// Picture this operation as "rotating" the camera by the correct
// amount so that it will always be facing the cube, no matter what
// the current absolute position of the camera is within the cube's
// coordinate frame. If you don't do this, then the cube will pan
// across your view and back around behind the camera much like the
// sun rotating through the sky over the course of one complete day/
// night cycle.
rotPoint = new double[] {0, 0, 0};
for(int j = 0; j< 3; j++) {
for(int k = 0; k < 3; k++) {
rotPoint[j] += camera.rotation[j][k] * camToPoint[k];
}
}
// Project the cube onto the camera plane.
points2D[i][0] = (int) (screenW * (0.5 * camera.frame[0] +
camera.focalLength * rotPoint[0] /
rotPoint[2]) / camera.frame[0]);
points2D[i][1] = (int) (screenH * (0.5 * camera.frame[1] +
camera.focalLength * rotPoint[1] /
rotPoint[2]) / camera.frame[1]);
}
}
}