Pull to refresh

Математика с фиксированной точкой в Marmalade SDK

Reading time 4 min
Views 4.6K
Не так давно на хабре был пост «Погружаемся в 3D с помощью Marmalade SDK», который оставил у меня достаточно много вопросов. В первую очередь это касалось магических шестнадцатиричных чисел, которые передавались в функции, т.е. вычислений с фиксированной точкой. В интернете достаточно плохо описана эта тема, поэтому пришлось экспериментировать. Если интересно — добро пожаловать под кат.

Первая часть. Координаты в стиле integer

Имея многолетний опыт в коммерческом геймдеве, я быстро забросил в майе на сцену тор (в начало координат) и экспортировал его плагином мармелада. Далее последовал более увлекательный вопрос — какие магические числа integer установить матрице вида и перспективной матрице. Ответ зависит в первую очередь от того, как же загружается модель и каким образом float координаты майи преобразуются в integer значения. Конечно же я вначале подумал про макрос IW_FIXED_FROM_FLOAT, который кстати, переводит 1.f аж в 0x1000 (это знание полезно для понимания что такое нормализированный вектор, т.е. вектор с длиной 0x1000). Я не буду вас мучить и рассказывать про свои потуги, расскажу результат. При импорте никаких подобных макросов не применяется, а просто происходит приведение флоата к целому, т.е. просто обрезается всё, что после запятой. Поэтому возвращаемся в майю и делаем тор побольше… еще побольше, нет еще больше, я же хочу офигенной точности. Ну и сделал я десятки тысяч. А не тут то было! Всё обломала (точнее обрезала) перспективная матрица. Оказывается, IwGxSetFarZNearZ хоть и принимает в качестве аргументов int32, но допускает максимальный zFar только 0xffff, накладывая ограничения на размер объектов. Итак, как же выбрать баланс между достаточной точностью и ограничением zFar? По моему мнению, оптимальный размер объектов будет в диапазоне от 100 до 1000 (можно от 1000 до 10000 для закрытых помещений). Это неплохо согласуется с размерностью майи, где единица измерения сантиметр. Делаем, к примеру, человека 180 см и это будет достаточной точностью для большинства сцен. Конечно, детальное лицо замоделить не выйдет с точностью в 1 см, но к счастью (или к сожалению) на мобильных платформах на данный момент не придется видеть в сцене близко лицо человека и одновременно корабль за триста метров от нас.

Вторая часть Марлезонского балета. Углы в стиле integer

С углами всё проще. Так же как и для координат, есть соответствующий макрос IW_ANGLE_FROM_DEGREES для перевода углов в целые числа. Сразу возникает вопрос где могут проявиться проблемы при использовании целых для матриц поворота. Как показал простой тест, анимация из майи экспортируется без проблем, все повороты в флоате преобразуются в соответствующие матрицы с целочисленными значениями. Единственное место где надо не забывать преобразовывать вручную — при передаче матрицы в шейдер. Кстати, поддержки шейдеров в мармеладе никакой, всё нужно писать самому.

Третья часть. Критика (необязательная к прочтению)

И в заключение хочется немного раскритиковать пост, упомянутый мной в самом начале статьи. В целом я благодарен автору за вдохновение к исследованиям и написанию этой статьи.
Но всё же нельзя постить маленький кусочек программы вызывающий тонны вопросов, да еще и с припиской «Код весьма прост даже для неискушённого программиста». Хотя может я не совсем неискушенный программист. Первые же строки вызвали вопросы
	CIwSVec3 dd(0xFF, 0xFF, 0xFF);
	IwGxSetLightDirn(1, &dd);

Почему все компоненты вектора 0xFF, а не просто (1,1,1)? Это что-то осознанное или издержки копипаста? Может вектор должен быть нормализован? Кстати, последняя буква n в названии функции как бы намекает… читаем описание и действительно — вектор нужно передавать нормализованный. Очевидно, что вектор (0xFF, 0xFF, 0xFF) не нормализован, выше я уже упоминал, что единичка в фиксированной системе мармелада — это 0x1000. Длина же вектора из примера явно меньше. Добавляем нормализацию — и вуаля, освещение начинает правильно работать. Кстати, я не зря экпортировал из майи тор, а не коробку — усилия по созданию одинаковы, а на торе сразу видны проблемы освещения, точности координат и т.п. Вот как изменилось освещение после исправления этой досадной ошибки.


Чуть далее по тексту идет мега комментарий, после которого я чуть под стол не сполз
Так же следует обратить особое внимание на функцию IwGxSetPerspMul(...). Это своего рода установка степени эффекта «рыбьего глаза». Если оставить этот параметр по умолчанию, то ваши сцены по краям экрана будут создавать впечатление просмотра через дырку унитаза донышко от бутылки.

Математический подход автора поражает воображение. Метод научного тыка во всей красе.
Справка описывает единственный параметр этой функции как «The distance from the camera to the viewing plane». И чего не FOV — угол обзора по горизонтали, как задают все белые люди? Всемогущий гугл подсказал что же мармелад имеет ввиду под «viewing plane», а значит имеем относительно простую формулу для перевода FOV в это расстояние:
    int32 perspMul =  IwGxGetDeviceWidth() / (2*tan(fov/2));
    IwGxSetPerspMul(perspMul);

Немного пораскинув мозгами, у меня появилось предположение почему не FOV — мобильные устройства, в отличие от ПК, могут иметь как портретный, так и пейзажный вид. Причем гироскоп может переключать его в реальном времени. Такой подход избавляет нас от необходимости вручную обрабатывать изменение вида и менять каждый раз матрицу перспективы.

Идем далее…
		CIwMat view = CIwMat::g_Identity;
		view.t.z = -80;
		view.t.y = 80;
		view.t.x = -60;
		view.LookAt(view.GetTrans(), CIwVec3(0, 0, 0), -CIwVec3::g_AxisY);

Никакого толкового объяснения от автора, хотя сразу бросается в глаза некая избыточность кода. Почему не написать просто
view.LookAt(CIwVec3(-60,80,-80), CIwVec3(0, 0, 0), -CIwVec3::g_AxisY);

Я написал. Но так не работает. Справка поведала, что ф-ия LookAt не меняет позицию, а создает только матрицу поворота. Т.е. компоненту «translate» нужно устанавливать отдельно. Здесь хочется кинуть большой камень в огород разработчиков мармелада — если называете функцию LookAt, то извольте сделать её работу аналогично D3DXMatrixLookAt и gluLookAt — мы же привыкли черт возьми. Уверен, что не я один долго не понимал почему она не работает. Действительно, чего читать справку, когда уже много лет знаешь эту функцию :)
P.S. Немножко сумбурно вышло, так что если есть вопросы — задавайте. Я не специалист по мармеладу, но всё же постараюсь ответить.

Update: забыл написать, что для умножения, деления, тригонометрических функций и прочего нужно применять соответствующие макросы IW_FIXED_MUL (IW_FIXED_MUL_SAFE) IW_FIXED_DIV (IW_FIXED_DIV_SAFE), IW_GEOM_COS, IW_GEOM_SIN etc. Safe версии макросов используют для вычислений int64, чтобы избежать переполнения во время вычислений. Хотя, результат умножения, конечно, может всё равно переполнить буфер. Спасибо хабраюзеру zigmar за напоминание.
Tags:
Hubs:
+15
Comments 9
Comments Comments 9

Articles