2014-01-04 4 views
1

Я создаю 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(); 
} 
} 

ответ

0

Я считаю, что ваша проблема в том, что вы пренебрегли модель камеры позы. Представьте себе куб, который где-то плавает в комнате. Понял? Теперь в вашем воображении нарисуйте декартову систему координат, жестко прикрепленную к кубу, с осью x и z в горизонтальной плоскости, а ось y направлена ​​к потолку. Поскольку камера - это то, что движется в вашем примере кода, вы также можете выбрать визуализацию системы координат куба как жестко привязанного к комнате, если хотите.

Теперь, фотоаппарат перемещается, глядя на куб под разными углами. Представьте себе отдельную декартову систему координат, жестко привязанную к ней. Он имеет ось x, которая указывает на правую сторону камеры, ось y, указывающую верхнюю часть, и ось z, которая указывает на обратную сторону (то есть к вашему лицу, когда вы смотрите через видоискатель) , Когда вы ходите по кубу, удерживая камеру, она должна вращать относительно оси y, чтобы не допустить столкновения с кубом. Предполагая, что вы держите камеру вертикально, ось y камеры всегда будет оставаться параллельной оси y куба, но соотношение между двумя осями x и z будет меняться со временем.

Эта связь между тремя осями двух декартовых координат может рассматриваться как «ориентация» или «поза» камеры, и вы в настоящее время не моделируете ее. Чтобы моделировать позу, вам нужна матрица 3X3. Если вы не знаете много о математике вращения координат, я бы рекомендовал вам изучить here и here и here.

Я добавил модель позы как матрицу вращения 3X3 в свой класс Camera и обновил класс 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]); 
    } 
} 
} 
+0

Да, я упростил вещи. Действительно ясный ответ, спасибо. – marc

Смежные вопросы