相机投影

时间:2014-01-04 20:55:24

标签: java 3d rendering

我正在构建一个玩具3D渲染器,我还有一个尚未确定的问题。我有一个指向空间某一点的相机。相机具有框架和给定的焦距。我想在相机的框架上投射任意一点。像往常一样,X和Y坐标分开处理。图像显示了我如何计算X.我使用三角形的余弦定理:给定三个三角形长度,我首先找到角度,然后使用相机的焦距得到X.

图像: enter image description here

这同样适用于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();
}
}

1 个答案:

答案 0 :(得分:0)

我相信你的问题是你忽略了模特相机姿势。想象一个立方体漂浮在房间里的立方体。得到它了?现在在您的脑海中,绘制一个刚性连接到立方体的笛卡尔坐标系,其中x轴和z轴在水平面上,y轴指向天花板。由于相机是在您的示例代码中移动的东西,您可以同样选择将立方体的坐标系可视化为刚性连接到房间,如果您愿意的话。

现在,想象一下相机四处移动,从不同角度观察立方体。想象一下,一个独立的笛卡尔坐标系也与之紧密相连。它的x轴指向相机的右侧,y轴指向顶部,z轴指向背面(即,当您通过取景器观察时朝向您的脸部) 。当您在拿着相机的立方体周围走动时,它必须围绕y轴旋转以保持面向立方体。假设您将摄像机竖直握住,摄像机的y轴将始终与立方体的y轴平行,但两个x轴和z轴之间的关系将随时间而变化。

在两个笛卡尔坐标系的三个轴之间的这种关系可以被认为是相机的“方向”或“姿势”,并且您当前没有对其进行建模。为了模拟姿势,您需要一个3X3矩阵。如果您对坐标框架旋转的数学知之甚少,我建议您研究herehere以及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]);
    }
}
}