Как применить матрицу преобразования?
Я пытаюсь получить двухмерные экранные координаты точки в трехмерном пространстве, т.е. я знаю местоположение камеры, ее панорамирование, наклон и поворот, и у меня есть трехмерные координаты x,y,z точки, которую я хочу проецировать.
У меня возникают трудности с пониманием матриц трансформации / проекции, и я надеялся, что некоторые умные люди здесь могут мне помочь;)
Вот мой тестовый код, который я сгенерировал до сих пор:
public class TransformTest {
public static void main(String[] args) {
// set up a world point (Point to Project)
double[] wp = {100, 100, 1};
// set up the projection centre (Camera Location)
double[] pc = {90, 90, 1};
double roll = 0;
double tilt = 0;
double pan = 0;
// translate the point
vSub(wp, pc, wp);
// create roll matrix
double[][] rollMat = {
{1, 0, 0},
{0, Math.cos(roll), -Math.sin(roll)},
{0, Math.sin(roll), Math.cos(roll)},
};
// create tilt matrix
double[][] tiltMat = {
{Math.cos(tilt), 0, Math.sin(tilt)},
{0, 1, 0},
{-Math.sin(tilt), 0, Math.cos(tilt)},
};
// create pan matrix
double[][] panMat = {
{Math.cos(pan), -Math.sin(pan), 0},
{Math.sin(pan), Math.cos(pan), 0},
{0, 0, 1},
};
// roll it
mvMul(rollMat, wp, wp);
// tilt it
mvMul(tiltMat, wp, wp);
// pan it
mvMul(panMat, wp, wp);
}
public static void vAdd(double[] a, double[] b, double[] c) {
for (int i=0; i<a.length; i++) {
c[i] = a[i] + b[i];
}
}
public static void vSub(double[] a, double[] b, double[] c) {
for (int i=0; i<a.length; i++) {
c[i] = a[i] - b[i];
}
}
public static void mvMul(double[][] m, double[] v, double[] w) {
// How to multiply matrices?
} }
По сути, мне нужно получить координаты 2D XY для данного экрана, где точка 3D пересекается. Я не уверен, как использовать матрицы крена, наклона и панорамирования для преобразования мировой точки (wp).
Любая помощь в этом с благодарностью!
3 ответа
Это сложный материал. Пожалуйста, прочитайте книгу на эту тему, чтобы получить все математические и мелкие детали. Если вы планируете долго играть с этим, вам нужно знать эти вещи. Этот ответ только для того, чтобы вы могли намочить ноги и взломать.
Умножающиеся матрицы
Обо всем по порядку. Умножение матриц - дело достаточно простое.
Допустим, у вас есть матрицы A, B и C, где AB = C. Допустим, вы хотите выяснить значение матрицы C в строке 3, столбце 2.
- Возьмите третий ряд A и второй столбец B. Теперь у вас должно быть одинаковое количество значений из A и B. (Если у вас нет матричного умножения, оно не определено для этих двух матриц. Вы не можете сделать это.) Если оба являются матрицами 4×4, у вас должно быть 4 значения из A (строка 3) и 4 значения из B (колонка 2).
- Умножьте каждое значение A на каждое значение B. Вы должны получить 4 новых значения.
- Добавьте эти значения.
Теперь у вас есть значение матрицы C в строке 3, столбец 2. Конечно, задача состоит в том, чтобы сделать это программно.
/* AB = C
Row-major ordering
a[0][0] a[0][2] a[0][3]...
a[1][0] a[1][4] ...
a[2][0] ...
...*/
public static mmMul(double[][] a, double[][] b, double[][] c) {
c_height = b.length; // Height of b
c_width = a[0].length; // Width of a
common_side = a.length; // Height of a, width of b
for (int i = 0; i < c_height; i++) {
for (int j = 0; j < c_width; j++) {
// Ready to calculate value of c[i][j]
c[i][j] = 0;
// Iterate through ith row of a, jth col of b in lockstep
for (int k = 0; k < common_side; k++) {
c[i][j] += a[i][k] * b[k][j];
}
}
}
}
Однородные координаты
У вас есть 3D-координаты. Допустим, у вас есть (5, 2, 1). Это декартовы координаты. Давайте назовем их (x, y, z).
Однородные координаты означают, что вы пишете дополнительную 1 в конце ваших декартовых координат. (5, 2, 1) становится (5, 2, 1, 1). Давайте назовем их (x, y, z, w).
Всякий раз, когда вы делаете преобразование, которое делает w ≠ 1, вы делите каждый компонент ваших координат на w. Это меняет ваши x, y и z, и снова делает w = 1. (В этом нет никакого вреда, даже если ваша трансформация не изменится на w. Она просто делит все на 1, что ничего не делает.)
Есть несколько классных вещей, которые вы можете сделать с однородными координатами, даже если математика за ними не имеет смысла. Именно в этот момент я прошу вас снова взглянуть на совет в верхней части этого ответа.
Преобразование точки
Я буду использовать терминологию OpenGL и подходы в этом и следующих разделах. Если что-то неясно или кажется противоречащим вашим целям (потому что мне это кажется неопределенно домашним заданием:P), пожалуйста, оставьте комментарий.
Я также начну с предположения, что ваши матрицы крена, наклона и панорамирования правильные.
Если вы хотите преобразовать точку, используя матрицу преобразования, вы умножаете эту матрицу вправо на вектор столбцов, представляющий вашу точку. Скажем, вы хотите перевести (5, 2, 1) с помощью некоторой матрицы преобразования А. Сначала вы определяете v = [5, 2, 1, 1] T. (Я пишу [ x, y, z, w ] T с маленькой буквой T, чтобы обозначить, что вы должны написать ее как вектор-столбец.)
// Your point in 3D
double v[4][5] = {{5}, {2}, {1}, {1}}
В этом случае Av = v 1, где v 1 - ваша преобразованная точка. Сделайте это умножение как матричное умножение, где A равно 4×4, а v равно 4×1. В итоге вы получите матрицу 4 × 1 (которая является еще одним вектором столбца).
// Transforming a single point with a roll
double v_1[4][6];
mmMul(rollMat, v, v_1);
Теперь, если вам нужно применить несколько матриц преобразования, сначала объедините их в одну матрицу преобразования. Сделайте это, умножив матрицы вместе в том порядке, в котором вы хотите их применить.
Программно следует начинать с матрицы тождеств и умножать вправо каждую матрицу преобразования. Пусть I 4 будет единичной матрицей 4×4, и пусть A 1, A 2, A 3,... будут вашими матрицами преобразования. Пусть ваша финальная матрица преобразования будет финальной
Финал ← Я 4
Финал ← Финал А 1
Финал ← Финал А 2
Финал ← Финал А 3
Обратите внимание, что я использую эту стрелку для представления назначения. Когда вы реализуете это, убедитесь, что не перезаписали A final, пока вы все еще используете его в расчете умножения матриц! Сделать копию.
// A composite transformation matrix (roll, then tilt)
double a_final[4][4] =
{
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1}
}; // the 4 x 4 identity matrix
double a_final_copy[4][4];
mCopy(a_final, a_final_copy); // make a copy of a_final
mmMul(rollMat, a_final_copy, a_final);
mCopy(a_final, a_final_copy); // update the copy
mmMul(tiltMat, a_final_copy, a_final);
Наконец, сделайте то же умножение, что и выше: финальное v = v 1
// Use the above matrix to transform v
mmMul(a_final, v, v_1);
От начала до конца
Преобразования камеры должны быть представлены в виде матрицы просмотра. Выполните операцию A view v = v 1 здесь. (v представляет ваши мировые координаты в виде вектора столбца 4 × 1, а финал - ваш вид A).
// World coordinates to eye coordinates
// A_view is a_final from above
mmMult(a_view, v_world, v_view);
Проекционные преобразования описывают перспективное преобразование. Это то, что делает ближние объекты больше, а дальние - меньше. Это выполняется после преобразования камеры. Если вам пока не нужна перспектива, просто используйте матрицу тождественности для матрицы проекции. В любом случае, выполните A v 1 = v 2 здесь.
// Eye coordinates to clip coordinates
// If you don't care about perspective, SKIP THIS STEP
mmMult(a_projection, v_view, v_eye);
Далее вам нужно сделать перспективное разделение. Это углубляется в однородные координаты, которые я еще не описал. В любом случае, разделите каждый компонент v 2 на последний компонент v 2. Если v 2 = [ x, y, z, w ] T, то разделите каждый компонент на w (включая сам w). В итоге вы должны получить w = 1. (Если ваша матрица проекции - это единичная матрица, как я описал ранее, этот шаг ничего не должен делать.)
// Clip coordinates to normalized device coordinates
// If you skipped the previous step, SKIP THIS STEP
for (int i = 0; i < 4; i++) {
v_ndc[i] = v_eye[i] / v[3];
}
Наконец, возьмите свой v 2. Первые две координаты - это ваши координаты x и y. Третий - это z, который вы можете выбросить. (Позже, когда вы станете очень продвинутым, вы можете использовать это значение z, чтобы выяснить, какая точка находится перед или позади какой-либо другой точки.) И в этот момент последний компонент имеет значение w = 1, поэтому вам не нужно это вообще больше.
x = v_ndc[0]
y = v_ndc[1]
z = v_ndc[2] // unused; your screen is 2D
Если вы пропустили шаги разделения перспективы и перспективы, используйте v_view
вместо v_ndc
выше.
Это очень похоже на набор систем координат OpenGL. Разница в том, что вы начинаете с мировых координат, тогда как OpenGL начинается с координат объекта. Разница заключается в следующем:
- Вы начинаете с мировых координат
- OpenGL начинается с координат объекта
- Вы используете матрицу вида для преобразования мировых координат в координаты глаза.
- OpenGL использует матрицу ModelView для преобразования координат объекта в координаты глаза
С этого момента все то же самое.
Масштаб этого слишком велик, чтобы получить хороший ответ здесь: я бы рекомендовал прочитать хороший справочник по теме. Мне всегда нравились Фоли и ВанДам...
Я разместил здесь код, который делает многое из того, что вам нужно.
Он содержит реализации Java OpenGL gluPerspective()
а также gluLookAt()
функции:
Camera camera = new Camera();
Point3d eye = new Point3d(3, 4, 8);
Point3d center = new Point3d(0, 0, 0);
Vector3d up = new Vector3d(0, 1, 0);
camera.perspective(60.0, 1.6, 0.1, 20); // vertical fov, aspect ratio, znear, zfar
camera.lookAt(eye, center, up);
Чтобы использовать project()
функция там, используйте:
void plot(Camera camera, Point4d p) {
Point4d q = Camera.project(p);
float x = q.x / q.w;
float y = q.y / q.w;
...
}
x
а также y
возвращаемые значения попадают в диапазон -0,5 ... 0,5