Войти
Urho3DФорумДЕЛИТЕСЬ КОДОМ И ТУТОРИАЛАМИ

Умножения матриц и векторов в движке и в шейдерах

#0
17:29, 3 ноя. 2016

Небольшие изыскания (извиняюсь за сумбур, писал заметку для себя, пока ковырялся, но решил выложить, вдруг кому-то будет полезно)

Произведение матриц

Матрицы умножаются строка на столбец (исторически так сложилось). Значит, ширина первой матрицы должна быть равна высоте второй матрицы.
А результирующая матрица имеет размеры (высота первой матрицы) x (ширина второй матрицы). Порядок умножения важен!

Для визуального запоминания):

            |- - - -|                    |- - - -|   |-|   |-|
|- - - -| * |- - - -| = |- - - -|        |- - - -| * |-| = |-|
            |- - - -|                    |- - - -|   |-|   |-|
            |- - - -|                    |- - - -|   |-|   |-|

Хранение матриц в памяти

На бумаге матрица может выглядеть так:

|11 12 13 14|
|21 22 23 24|
|31 32 33 34|
|41 42 43 44|

Однако в памяти есть только последовательность значений. И матрица может храниться двумя способами: "row-major order" и "column-major order".
Row-major order = обычный двумерный массив Си = по строкам: первая строка, вторая строка, ... = 11 12 13 14 21 22 23 24 ...
Column-major order = по столбцам: первый столбец, второй столбец,... = 11 21 31 41 12 22 32 42 ...

Матрица-столбец и матрица-строка в памяти выглядят абсолютно одинаково. Для транспонирования вектора не нужно делать ничего.
Для вектора Row-major order и Column-major order идентичны - есть просто 4 значения.

|-|
|-|    |- - - -|
|-|
|-|
В движке Urho3D
матрицы хранятся по строкам "row-major order". Вектор умножается на матрицу справа и является матрицей-столбцом.
|- - - -|   |-|   |-|
|- - - -| * |-| = |-|
|- - - -|   |-|   |-|
|- - - -|   |-|   |-|
Для модели сначала нужно изменить размер, потом прокрутить и лишь потом сдвинуть. В ином порядке получится ерунда.
Еще одно подтвержение важности правильного порядка умножения матриц.

Матрица масштабирования * вектор = скалированный вектор.
Матрица вращения * (матрица масштабирования * вектор) = скалированный и повернутый вектор.
Матрица перемещения * (Матрица вращения * (матрица масштабирования * вектор)) = трансформированный вектор.

Скобки можно опустить:
Матрица перемещения * матрица вращения * матрица масштабирования * исходный вектор = трансформированный вектор.

Если предварительно перемножить матрицы:
матрица трансформаций * исходный вектор = трансформированный вектор.

В матрице трансформаций 4x4 последнняя строка (0 0 0 1) и заранее известна. Да и умножать на нули и единицу - лишняя трата времени.
Поэтому в движке матрицы трансформаций хранятся в виде Matrix3x4. А Матрицы 4x4 используются только при проецировании вектора.

Matrix3x4 * Vector3 = Vector3. Но это только для оптимизации вычислений.
С математической точки зрения умножать Matrix3x4 на Matrix3x1 вообще нельзя.
Было бы верным Matrix3x4 * Vector4 = Matrix3x4 * Matrix4x1 = Matrix3x1 = Vector3
Тут Vector4 это Vector3 с добавленной единицей в последнем элементе.

В OpenGL
матрицы хранятся ПО СТОБЦАМ "column-major order", а не по строкам. И это влияет на математику при умножении матриц в шейдере.

То есть OpenGL ожидает элементы матрицы в другом порядке, чем они хранятся в движке Urho3D. И умножение матриц в шейдере исходит их этого.
Фактически перед передачей в шейдер матрицы нужно транспонировать. Но это лишняя трата процессороного времени.
Матрицы из движка передаются блоком данным как есть,
а в шейдере эти матрицы считаются транспонированными (вектор при транспонировании в памяти вообще не меняется). Раз все данные
в шейдер передаются транспонированными, то меняется и порядок умножения.

Транспонированный вектор-столбец * транспонированная матрица трансформаций = транспонированный трансформированный вектор (который в памяти идентичен нетранспонированному)

            |- - - -|
|- - - -| * |- - - -| = |- - - -|
            |- - - -|
            |- - - -|

Исходный вектор тр * матр масштаб тр * матр вращ тр * матр перемещ тр = трансформированный вектор транспонированный

В отличии от движка Urho3D матрица трансформаций в шейдер передается как Matrix4x4 (uniform mat4 cModel), а не Matrix3x4.
Перед передачей восстанавливается последняя строка (0, 0, 0, 1).
void Graphics::SetShaderParameter(StringHash param, const Matrix3x4& matrix).

В DirectX

В директХ матрицы трансформаций Matrix3x4 передаются прямым копированием и хранятся в uniform float4x3 cModel
(т.е. в шейдере блок данных интерепретируется как транспонированная матрица).

flot4 * float4x3 = float3 (mat1x4 * mat4x3 = mat1x3) - работает нормально и математически верно
#define GetWorldPos(modelMatrix) mul(iPos, modelMatrix)

            |- - -|
|- - - -| * |- - -| = |- - -|
            |- - -|
            |- - -|

Вектор так же как и в OpenGL умножается на матрицу слева.

Исходный вектор * матр масштаб * матр вращ * матр перемещ = трансформированный вектор
Исходный вектор * матрица трансформаций = трансформированный вектор

Почему-то в интернете много инфы, что DirectX хранит матрицу по строкам,
но в https://msdn.microsoft.com/en-us/library/windows/desktop/bb509634(v=vs.85).aspx#Matrix_Ordering написано

Matrix packing order for uniform parameters is set to column-major by default.

Может какие-то старые версии использовали row-major order (или библиотечка DX (CPU) использовала его), а в HLSL (GPU) ожидается передача по столбцам.


#1
19:37, 3 ноя. 2016

Нужно больше газа веспен кода в качестве примера как на GL / так и на DX ) Ну и в целом - отличная заметка, думаю пригодиться ) След. наверное про написание простейшего шейдера ?

#2
19:45, 3 ноя. 2016

ну я заметку для себя писал, как простейший шейдер написать я и так знаю))

#3
20:23, 3 ноя. 2016

это понятно что знаешь, но на тот случай, если вдруг - забудешь ))

#4
22:13, 3 ноя. 2016

Полезная инфа, спасибо.

#5
0:56, 21 дек. 2016

1vanK Для чего нужна координата w в системе координат x y z w? Почему у нас матрица 4x4 а не 4x3? Ведь мы бы могли хранить матрицу поворота 3x3 и ниже неё координаты xyz. Зачем нам 4x4?

#6
1:12, 21 дек. 2016

у матрицы переноса дельта в четвертом столбце, матрицей 3x3 никак не обойтись
для матрицы проекции необходимые данные записываются в 4й строке, поэтому до 4x3 урезать не получится

ну а w - потому что https://ru.wikipedia.org/wiki/Однородные_координаты
а вообще так на пальцах не пояснить, тут надо углубляться в математику, откуда вообще берутся эти матрицы трансформаций
например http://pmg.org.ru/basic3d/index.html -> http://kappasoft.narod.ru/info/3d/3d.htm http://pmg.org.ru/basic3d/math.htm

#7
1:20, 21 дек. 2016

Я то знаю ответ )) это на собеседованиях часто спрашивают.

Поэтому в движке матрицы трансформаций хранятся в виде Matrix3x4. А Матрицы 4x4 используются только при проецировании вектора.

Если ты упомянул это, так раскрой тему до конца )
#8
1:26, 21 дек. 2016

извини, я не трудоустраиваюсь, чтоб ты меня экзаменовал, впредь подумаю, отвечать ли на твои вопросы xD

#9
1:54, 21 дек. 2016

f1ufx_, по ходу  устроил масштабное анкетирование, спросил о графике работы форумчан, даже не поленился пройтись по многим темам для поиска "жертвы")) Нелегко нынче работодателям...

#10
1:56, 21 дек. 2016

Ох, ну тебе же придётся когда-то трудоустраиваться..

Говоря о матрицах, важно упомянуть Аффинные преобразования - это такие, при которых параллельные линии остаются параллельными, перпендикулярные - перпендикулярными. Записывается оно как то так:

Изображение

Чтобы записать это в матричном виде, матрицу-строку [x,y] нужно умножить на матрицу столбец [a,b,c]. Но количество элементов должно быть одинаково. Поэтому матрицу-строку добили единицей [x,y,1].

#11
1:57, 21 дек. 2016

а может это просто привычка ) вдруг он большой начальник )

#12
2:01, 21 дек. 2016

> Ох, ну тебе же придётся когда-то трудоустраиваться..

ну может и придется, но с моей точки зрения это выглядит, что я хочу помочь чем могу, а меня троллят)

Urho3DФорумДЕЛИТЕСЬ КОДОМ И ТУТОРИАЛАМИ

Тема в архиве.