Матрицы и заключение.
![](https://habrastorage.org/getpro/habr/upload_files/20f/007/0f9/20f0070f9a8e34887834943108de27fd.png)
Завершающий урок из цикла про линейную алгебру для 3D-приложений от Александра Паничева — ведущего разработчика логики в UNIGINE. В прошлом уроке мы разобрали углы Эйлера и кватернионы, а в этот раз поговорим о матрицах и подведем итоги.
Матрицы
Проще говоря, матрица в 3D-графике — это массив чисел, расположенных по строкам и столбцам:
![](https://habrastorage.org/getpro/habr/upload_files/ad6/60c/48f/ad660c48fe25411daf51cc17c4f80a78.png)
В 3D-приложениях используются матрицы либо размером 3х3 (матрицы вращения), либо 4х4 (матрица трансформации). Рассмотрим матрицу 4х4 (трансформацию).
В первых трех столбцах находится вращение (Rotation), которое представлено в виде трех ортогональных друг к другу векторов (ось вправо, ось вперед, ось вверх), а по диагонали масштабирование (Scale).
Почему так? Смотрите предыдущую лекцию. Все дело в самой природе комплексных чисел.
В четвертом столбце — смещение (Translation). А единица под смещением — это гомогенная координата. Если она равна 1 — позволяет сдвигать вектор перемножением матриц. А если равна 0 — Translation считается вектором направления.
![](https://habrastorage.org/getpro/habr/upload_files/bca/66b/945/bca66b9452182d91857739fcf53c63e1.png)
Как правило, для удобства работы все объекты в виртуальном мире формируются в определенные иерархии. Такие иерархии называются родительско-дочерними связями.
Например: пистолет в руке главного героя. «Пистолет» является дочерним к объекту «герой».
Когда мы выбираем объекты в 3D-редакторе, в инспекторе объектов справа вверху всегда видна локальная матрица трансформации (декомпозиция локальной матрицы). В ней Position, Rotation и Scale показываются относительно родителя. То есть родитель становится центром начала координат.
Родительско-дочерняя связь с точки зрения кода и матриц реализуется как:
world_B = world_A * local_B
local_B = inverse(world_A) * world_B
Где world_A
— глобальная (родительская) матрица трансформации, скажем, «героя».
local_b
— локальная (дочерняя) матрица, к примеру, «пистолета».
world_b
— это ее глобальная (мировая) матрица трансформации.
Узнать положение точки
(vec3_world_position_B)
относительно «глаз» объекта A:
vec3 relative_pos_B = inverse(world_A) * vec3_world_position_B;
Связь с камерой:
view = inverse(transform)
где transform
— матрица трансформации камеры,
view
— матрица, которая необходима для перевода вершины какой-либо модели в экранное пространство (читаем про тройку преобразований model * view * projection
).
Детерминант mat3 — это объем параллелепипеда из XYZ осей со знаком.
Ранг mat3 — проверка на «схлопывание» базиса в плоскость (2) или в линию (1).
Транспонирование — перевод столбцов в строки:
transpose(mat3) == inverse(mat3)
Если базисные вектора ортогональны и единичной длины (т.е., для любой матрицы вращения или в случае mat4 — если позиция нулевая).
Собираем матрицу в движке UNIGINE
Здесь и далее мы рассказываем про то, как работает UNIGINE изнутри.
Итак, есть два пути создания матриц:
![12 умножений, 15 сложений/вычитаний 12 умножений, 15 сложений/вычитаний](https://habrastorage.org/getpro/habr/upload_files/747/891/7a7/7478917a7c5e938912151d34b24d173b.png)
![8 умножений, 15 сложений/вычитаний 8 умножений, 15 сложений/вычитаний](https://habrastorage.org/getpro/habr/upload_files/d43/ac0/f9f/d43ac0f9fe701165669eb3f26d29a45e.png)
Все это быстрее, чем mat4(translate() * rotate() * scale())
!
Создание матрицы масштабирования
В матрицах есть набор таких методов, как setScale
(или setTranslate
). Их название часто путает — можно подумать, что они задают масштаб и смещение соответственно. Но на самом деле слово set
лишь означает, что мы инициализируем матрицу масштабирования (или смещения). То есть каждый метод set означает новую матрицу.
![](https://habrastorage.org/getpro/habr/upload_files/3c3/ec8/8af/3c3ec88afea6d1697891a7bb65ccd25b.png)
Создание матрицы вращения
![](https://habrastorage.org/getpro/habr/upload_files/e96/ea4/81e/e96ea481e9526e83a538305fba689f88.png)
![](https://habrastorage.org/getpro/habr/upload_files/fc7/eee/ae1/fc7eeeae149d0835e6c166c1f3418b79.png)
Углы Эйлера в матрицу и обратно
![В сумме по три sincos’а, но много лишних перемножений кватернионов (выглядит и правда не очень — постараемся исправить этот код в будущих релизах) В сумме по три sincos’а, но много лишних перемножений кватернионов (выглядит и правда не очень — постараемся исправить этот код в будущих релизах)](https://habrastorage.org/getpro/habr/upload_files/e52/b58/7f2/e52b587f2a003c0a148a38061a6b7775.png)
![2 atan2, 1 asin 2 atan2, 1 asin](https://habrastorage.org/getpro/habr/upload_files/1d9/7fb/3ed/1d97fb3ede17b4b55ec56588786ee59b.png)
Инвертирование
Без SSE: 140 умножений, 67 сложений! Через SSE: всего 35 _mm_mul_ps. Инвертирование кватернионов все равно проще:
![](https://habrastorage.org/getpro/habr/upload_files/f98/8c8/dc8/f988c8dc88edcabbc1d0d208b9e93206.png)
![](https://habrastorage.org/getpro/habr/upload_files/e7e/e4f/39a/e7ee4f39ad5495104edc189a6e7f27e7.png)
Заключение
Примечание редактора: на самом деле, текст снизу предназначался для первой части лекции, где не было еще кватернионов и матриц. Но, пример слишком хорош, чтобы от него отказываться, так что давайте на миг представим, что мы не читали все то, что написано выше. :)
Итак, просуммируем все то, что мы узнали в данной лекции. Рассмотрим задачу нахождения площади треугольника. Как ее лучше всего реализовать?
Как найти площадь треугольника? Вариант 1
![](https://habrastorage.org/getpro/habr/upload_files/a2d/32a/8c2/a2d32a8c29d4775f1825b6932161a6cc.png)
Как найти площадь треугольника? Вариант 2
![](https://habrastorage.org/getpro/habr/upload_files/3f6/dc2/2ef/3f6dc22ef6f1b4f672c408a0d2974895.png)
А почему?
![](https://habrastorage.org/getpro/habr/upload_files/c38/7d8/d1c/c387d8d1ce2403f994fefb8e007427a9.png)
Так, погодите-ка, а если немного оптимизнуть...
![](https://habrastorage.org/getpro/habr/upload_files/81a/51f/d56/81a51fd562b704bc371ac6301cf6bdd2.png)
А можно сделать еще лучше?
Пару слов про SIMD (SSE, AVX):
![](https://habrastorage.org/getpro/habr/upload_files/14f/065/b0f/14f065b0f4fe7329b769f47b0920b0cd.png)
Каков результат?
![](https://habrastorage.org/getpro/habr/upload_files/cd7/f80/b07/cd7f80b074cbb97f76bc3c7485997eb5.png)
Выводы:
Не используем тригонометрию (sin/cos/tan) там, где этого можно избежать. Эти операции реально медленные.
Если есть возможность использовать
length2()
вместоlength()
— не отказываемся от этого.По возможности производим операции над векторами, а не над компонентами векторов. SIMD — наш скрытый друг, товарищ и брат!
Ну и, самое важное: стараемся локализовать данные, с которыми работаем. Пишем cache-friendly код и все такое. Даже самая тяжелая тригонометрическая операция на порядок быстрее, чем простое взятие числа из оперативной памяти.
DimPal
Там где оптимизнули расчет площади треугольника просится двойку за скобку вынести:
(2*(A*B + B*C + C*A) + A*A + B*B + C*C) * .0625
или так:
(A*(2*B + A) + B*(2*C + B) + C*(2*A + C)) * .0625