Я создаю 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();
}
}
Да, я упростил вещи. Действительно ясный ответ, спасибо. – marc