У меня отлично работает преобразование 2D/3D, как сделать перспективу

Хотя контекст этого вопроса касается создания 2D / 3D-игры, проблема, с которой я столкнулся, сводится к некоторой математике. Хотя это 2,5-мерный мир, давайте притворимся, что это всего лишь 2-мерный мир для этого вопроса.

// xa: x-accent, the x coordinate of the projection
// mapP: a coordinate on a map which need to be projected
// _Dist_ values are constants for the projection, choosing them correctly will result in i.e. an isometric projection 
xa = mapP.x * xDistX + mapP.y * xDistY; 
ya = mapP.x * yDistX + mapP.y * yDistY;

xDistX и yDistX определяют угол оси x, а xDistY и yDistY определяют угол оси y на проекции (а также размер сетки, но для простоты предположим, что это 1 пиксель).

x-axis-angle = atan(yDistX/xDistX)
y-axis-angle = atan(yDistY/yDistY)

такая "нормальная" система координат

--------------- x
|
|
|
|
|
y

has values like this:
xDistX = 1;
yDistX = 0;
xDistY = 0;
YDistY = 1;

Таким образом, каждый шаг в направлении x приведет к проекции на 1 пиксель вправо на 0 пикселей вниз. Каждый шаг в направлении y проекции приведет к 0 шагам вправо и 1 пикселю вниз. При правильном выборе xDistX, yDistX, xDistY, yDistY вы можете спроецировать любую триметрическую или диметрическую систему (поэтому я выбрал эту).

Пока все хорошо, когда это нарисовано, все оказывается в порядке. Если «моя система» и мышление понятны, давайте перейдем к перспективе. Я хотел добавить некоторую перспективу к этой сетке, поэтому я добавил несколько дополнительных элементов, например:

camera = new MapPoint(60, 60);
dx = mapP.x - camera.x; // delta x
dy = mapP.y - camera.y; // delta y
dist = Math.sqrt(dx * dx + dy * dy); // dist is the distance to the camera, Pythagoras etc.. all objects must be in front of the camera

fac = 1 - dist / 100; // this formula determines the amount of perspective

xa = fac * (mapP.x * xDistX  + mapP.y * xDistY) ;
ya = fac * (mapP.x * yDistX + mapP.y * yDistY );

Теперь самое сложное... что, если вы получили точку (xa,ya) на проекции и хотите вычислить исходную точку (x,y). Для первого случая (без перспективы) я нашел обратную функцию, но как это сделать для формулы с перспективой. Майские математические способности не совсем подходят для решения этой задачи.

(Я смутно помню, что давным-давно mathematica могла создать обратную функцию для некоторых особых случаев... может ли это решить эту проблему? Может кто-нибудь попробовать?)


person jdv145    schedule 21.05.2010    source источник
comment
Что такое fac? Какова величина перспективы? Какое отношение имеет 100 к чему-либо? Предназначен ли fac для реализации кажущегося сжатия объектов из-за перспективы. Это делает объект на расстоянии 100 нулевым размером, так что это не имеет смысла?   -  person sigfpe    schedule 21.05.2010
comment
fac означает фактор, как в факторе перспективы, который можно настроить на тяжелую или маленькую перспективу. Рисование сетки с помощью этой функции дало то, что выглядело достаточно хорошо. Объекты всегда должны располагаться на сетке перед камерой. Это означает, что объект никогда не может иметь расстояние больше, чем sqrt (60 ^ 2 + 60 ^ 2) 85. Причина, по которой я выбрал 100, заключается в том, что он больше 85 и дает мне нужный вид. Моя функция неверна, но визуальный результат выглядел хорошо, хотя перспектива немного преувеличена... img188.imageshack.us/img188/5154/gridpu.png   -  person jdv145    schedule 22.05.2010


Ответы (2)


Функция, которую вы определили, не имеет обратной. В качестве примера, как уже указал пользователь 207422, все, что находится на расстоянии 100 единиц от камеры, будет сопоставлено с (xa, ya) = (0,0), поэтому обратное значение не определено однозначно.

Что еще более важно, это не то, как вы рассчитываете перспективу. Как правило, коэффициент масштабирования перспективы определяется как viewdist/zdist, где zdist — это перпендикулярное расстояние от камеры до объекта, а viewdist — это константа, представляющая собой расстояние от камеры до гипотетического экрана, на который все проецируется. (См. диаграмму здесь, но не стесняйтесь игнорировать все остальное на этой странице.) Коэффициент масштабирования, который вы используете в своем примере, не имеет такого же поведения.

Вот попытка преобразовать ваш код в правильный расчет перспективы (обратите внимание, я не упрощаю до 2D; перспектива — это проецирование трех измерений на два, попытка упростить проблему до 2D бессмысленна):

camera = new MapPoint(60, 60, 10);
camera_z = camera.x*zDistX + camera.y*zDistY + camera.z*zDistz;

// viewdist is the distance from the viewer's eye to the screen in
// "world units". You'll have to fiddle with this, probably.
viewdist = 10.0;

xa = mapP.x*xDistX + mapP.y*xDistY + mapP.z*xDistZ;
ya = mapP.x*yDistX + mapP.y*yDistY + mapP.z*yDistZ;
za = mapP.x*zDistX + mapP.y*zDistY + mapP.z*zDistZ;

zdist = camera_z - za;
scaling_factor = viewdist / zdist;
xa *= scaling_factor;
ya *= scaling_factor;

Вы собираетесь вернуть из этой функции только xa и ya; za предназначен только для расчета перспективы. Я предполагаю, что «направление za» указывает за пределы экрана, поэтому, если ось X предварительной проекции указывает на зрителя, тогда zDistX должно быть положительным и наоборот, и аналогично для zDistY. Для триметрической проекции у вас, вероятно, будут xDistZ==0, yDistZ<0 и zDistZ==0. Это сделало бы ось z до проекции направленной прямо вверх после проекции.

А теперь плохие новости: у этой функции нет и обратной. Любая точка (xa,ya) есть образ бесконечного числа точек (x,y,z). Но! Если вы предполагаете, что z = 0, то вы можете решить для x и y, что, возможно, достаточно хорошо.

Для этого вам придется заняться линейной алгеброй. Вычислить camera_x и camera_y аналогично camera_z. Это координаты камеры после трансформации. Точка на экране имеет посттрансформационные координаты (xa,ya,camera_z-viewdist). Проведите линию через эти две точки и вычислите, где пересекается плоскость, натянутая векторами (xDistX, yDistX, zDistX) и (xDistY, yDistY, zDistY). Другими словами, вам нужно решить уравнения:

x*xDistX + y*xDistY == s*camera_x + (1-s)*xa
x*yDistX + y*yDistY == s*camera_y + (1-s)*ya
x*zDistX + y*zDistY == s*camera_z + (1-s)*(camera_z - viewdist)

Это не красиво, но это сработает.

person Peter Milley    schedule 22.05.2010

Я думаю, что с вашим постом я могу решить проблему. Тем не менее, для уточнения некоторых вопросов:

Решать задачу в 2d действительно бесполезно, но это было сделано только для того, чтобы облегчить понимание задачи (для меня и для читателей здесь). Моя программа на самом деле дает идеальную 3D-проекцию (я проверил это с 3D-изображениями, визуализированными с помощью блендера). Однако я упустил кое-что об обратной функции. Обратная функция предназначена только для координат от 0..camera.x * 0,5 до 0.. camera.y*0,5. Итак, в моем примере от 0 до 30. Но даже тогда я сомневаюсь в своей функции.

В моей проекции ось z всегда направлена ​​прямо вверх, поэтому для расчета высоты объекта я использовал только угол обзора. Но так как вы на самом деле не можете летать или прыгать в небо, все имеет только 2-ю точку. Это также означает, что когда вы пытаетесь решить x и y, z действительно равен 0.

Я знаю, что не у каждой функции есть обратная, а у некоторых функций есть, но только для определенной области. Моей основной мыслью во всем этом было... если я могу нарисовать сетку с помощью функции... каждая точка на этой сетке сопоставляется ровно с одной точкой карты. Я могу прочитать координаты x и y, поэтому, если бы у меня была правильная функция, я мог бы вычислить обратную. Но нет лучшей замены, чем хорошая твердая математика, и я очень рад, что вы нашли время, чтобы дать очень полезный ответ :).

person jdv145    schedule 22.05.2010
comment
Просто для справки в будущем, обычно это было бы в редактировании вашего вопроса или, возможно, в комментариях. - person Joren; 22.05.2010