Может ли кто-нибудь объяснить (причины) последствия использования colum vs row Major для умножения / объединения?

11

Я пытаюсь научиться создавать матрицы представлений и проекций и продолжаю сталкиваться с трудностями в моей реализации из-за путаницы в двух стандартах на матрицы.
Я знаю, как умножить матрицу, и я вижу, что транспонирование перед умножением полностью изменило бы результат, поэтому необходимо умножать в другом порядке.

То , что я не понимаю , хотя это , Что означало только «нотационная конвенция» - из статей здесь и здесь авторы , по всей видимости утверждают , что это не делает никакой разницы в том , как матрица хранится или передается на GPU, но на втором страница этой матрицы явно не эквивалентна тому, как она будет размещена в памяти для строки-мажора; и если я смотрю на заселенную матрице в моей программе я вижу компоненты перевода , занимающие 4 - й, 8 - й и 12 - й элементы.

Учитывая это:

«постмножение с матрицами основных столбцов дает тот же результат, что и предварительное умножение с матрицами основных строк».

Почему в следующем фрагменте кода:

        Matrix4 r = t3 * t2 * t1;
        Matrix4 r2 = t1.Transpose() * t2.Transpose() * t3.Transpose();

Имеет ли г = г2! И почему POS3 = поз для! :

        Vector4 pos = wvpM * new Vector4(0f, 15f, 15f, 1);
        Vector4 pos3 = wvpM.Transpose() * new Vector4(0f, 15f, 15f, 1);

Меняется ли процесс умножения в зависимости от того, являются ли матрицы мажорами строк или столбцов , или это просто порядок (для эквивалентного эффекта?)

Одна вещь, которая не помогает этому стать более понятной, это то, что при предоставлении DirectX основная WVP-матрица моего столбца успешно используется для преобразования вершин с помощью вызова HLSL: mul (vector, matrix), что должно привести к тому, что вектор будет обрабатываться как row-major , так как же может работать матрица основных столбцов, предоставляемая моей математической библиотекой?

sebf
источник

Ответы:

11

если я смотрю на заполненную матрицу в моей программе, я вижу компоненты перевода, занимающие 4-й, 8-й и 12-й элементы.

Прежде чем я начну, важно понять: это означает, что ваши матрицы являются основными рядами . Поэтому вы отвечаете на этот вопрос:

Основная WVP-матрица моего столбца успешно используется для преобразования вершин с помощью вызова HLSL: mul (вектор, матрица), что должно привести к тому, что вектор будет рассматриваться как основной-ряд, так как же может работать основная матрица столбца, предоставляемая моей математической библиотекой?

это довольно просто: ваши матрицы являются мажорными строками.

Так много людей используют мажорные строки или транспонированные матрицы, что забывают, что матрицы не ориентированы таким образом естественным образом. Таким образом, они видят матрицу перевода следующим образом:

1 0 0 0
0 1 0 0
0 0 1 0
x y z 1

Это транспонированная матрица перевода . Это не то , что нормальный перевод матрица выглядит. Перевод идет в 4-й столбце , а не четвертая строка. Иногда, вы даже можете увидеть это в учебниках, что это полная фигня.

Легко узнать, является ли матрица в массиве строкой или столбцом. Если это главная строка, то перевод сохраняется в 3, 7 и 11-м индексах. Если это главный столбец, то перевод сохраняется в 12, 13 и 14-м индексах. Нулевые базовые показатели конечно.

Ваше заблуждение связано с убеждением, что вы используете матрицы с основными столбцами, тогда как на самом деле вы используете матрицы с основными рядами.

Утверждение, что столбец строки по сравнению с основным является Notational конвенции только совсем верно. Механика умножения матриц и умножения матриц / векторов одинакова независимо от соглашения.

Какие изменения смысл результатов.

Матрица 4х4 - это всего лишь сетка чисел 4х4. Это не обязательно относится к изменению системы координат. Однако после того, как вы присвоите значение определенной матрице, вам теперь нужно знать, что в ней хранится и как ее использовать.

Возьмите матрицу перевода, которую я показал вам выше. Это действительная матрица. Вы можете хранить эту матрицу в float[16]в одном из двух способов:

float row_major_t[16] =    {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1};
float column_major_t[16] = {1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1};

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

1 0 0 x
0 1 0 y
0 0 1 z
0 0 0 1

Давайте посмотрим на то, как они хранятся:

float row_major[16] =    {1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1};
float column_major[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1};

Обратите внимание, что column_majorэто точно так же, как row_major_t. Итак, если мы возьмем правильную матрицу перевода и сохраним ее как главную-колонку, это будет то же самое, что транспонировать эту матрицу и сохранить ее как главную-строку.

Это то, что подразумевается под «условным обозначением». На самом деле существует два набора соглашений: хранение памяти и транспонирование. Память хранится в столбце по отношению к основной строке, а транспонирование нормальное и транспонированное

Если у вас есть матрица, которая была сгенерирована в основном порядке строк, вы можете получить тот же эффект, транспонируя основной столбец, эквивалентный этой матрице. И наоборот.

Умножение матриц можно выполнить только одним способом: при заданных двух матрицах в определенном порядке вы умножаете определенные значения вместе и сохраняете результаты. Теперь, A*B != B*Aно фактический исходный код для A*Bтакой же, как код для B*A. Они оба запускают один и тот же код для вычисления вывода.

Код умножения матриц не заботится о том, хранятся ли матрицы в главном или главном порядке строк.

Этого нельзя сказать о умножении вектора на матрицу. И вот почему.

Вектор / матричное умножение - это ложь; это не может быть сделано. Тем не менее, вы можете умножить матрицу на другую матрицу. Таким образом, если вы делаете вид, что вектор является матрицей, тогда вы можете эффективно выполнять умножение вектора на матрицу, просто выполняя умножение матрицы на матрицу.

Вектор 4D можно рассматривать как вектор-столбец или вектор-строку. Таким образом, вектор 4D можно рассматривать как матрицу 4x1 (помните: в матричной нотации счетчик строк идет первым) или матрица 1x4.

Но вот в чем дело: учитывая две матрицы A и B, A*Bопределяется только в том случае, если количество столбцов в A равно количеству строк в B. Следовательно, если A является нашей матрицей 4x4, B должна быть матрицей с 4 строками. в этом. Следовательно, вы не можете выполнить A*x, где x - это вектор-строка . Точно так же вы не можете выполнить, x*Aгде x - вектор-столбец.

Из-за этого большинство математических математических библиотек делают это предположение: если вы умножаете вектор на матрицу, вы действительно хотите выполнить умножение, которое действительно работает , а не то, которое не имеет смысла.

Определим для любого четырехмерного вектора x следующее. Cдолжна быть матричной векторной формой столбца xи Rдолжна быть матричной векторной формой строки x. Учитывая это, для любой матрицы 4x4 A A*Cпредставляет матрицу, умножающую A на вектор-столбец x. И R*Aпредставляет матрицу, умножающую вектор-строку xна А.

Но если мы посмотрим на это с использованием строгой математической математики, мы увидим, что они не эквивалентны . R*A не может быть таким же, как A*C. Это потому, что вектор-строка - это не то же самое, что вектор-столбец. Они не одна и та же матрица, поэтому они не дают одинаковых результатов.

Тем не менее, они связаны друг с другом. Это правда R != C. Однако верно также и то , где T - операция транспонирования. Две матрицы являются транспонированными друг для друга.R = CT

Вот забавный факт. Так как векторы рассматриваются как матрицы, они тоже имеют столбец против ряда-главного вопроса для хранения. Проблема заключается в том, что они оба выглядят одинаково . Массив с плавающей точкой одинаков, поэтому вы не можете определить разницу между R и C, просто взглянув на данные. Только способ отличить это от того, как они используются.

Если у вас есть какая - либо две матрицы А и В, и А хранятся в виде строки-основная и B в качестве столбцов, умножая их является совершенно бессмысленным . Вы получаете ерунду как результат. Ну не совсем. Математически, что вы получаете эквивалент делать . Или ; они математически идентичны.AT*BA*BT

Следовательно, умножение матриц имеет смысл, только если две матрицы (и помните: умножение вектора / матрицы - это просто умножение матриц) хранятся в одном и том же главном порядке.

Итак, является ли вектор-столбец мажор или мажор строки? Это не так, и ни один, как было сказано ранее. Это основной столбец, только когда он используется в качестве матрицы столбцов, и это основной ряд, когда он используется в качестве матрицы строк.

Поэтому, если у вас есть матрица A , которая не является столбец основных, x*Aсредств ... ничего. Ну, опять же , это значит , но это не то , что вы действительно хотели. Кроме того , это транспонированная умножение , если это строка-мажор.x*ATA*xA

Таким образом, порядок вектора / матричного умножения делает изменения, в зависимости от вашего основного упорядочения данных (и вы используете ли транспонированные матрицы).

Почему в следующем фрагменте кода делает г! = R2

Потому что ваш код сломан и глючит. Математически . Если вы не получите этот результат, то либо ваш тест равенства неправильно (вопросы точности с плавающей точкой) или ваш матричный код умножения нарушается.A * (B * C) == (CT * BT) * AT

почему POS3! = поз для

Потому что это не имеет смысла. Единственный способ быть правдой будет, если . И это верно только для симметричных матриц.A * t == AT * tA == AT

Николь Болас
источник
@Nicol, все начинает щёлкать. Возникла путаница из-за несоответствия между тем, что я видел, и тем, что, как я думал, должно быть, так как моя библиотека (взята из Axiom) объявляет мажор столбца (и все порядки умножения и т. Д. Соответствуют этому), но структура памяти является строкой -много (судя по индексам трансляции и тому факту, что HLSL работает корректно с использованием нетранспонированной матрицы); Однако теперь я вижу, как это не конфликтует. Большое спасибо!
sebf
2
Я почти дал вам -1 за то, что вы говорите что-то вроде «Это не то, на что похожа нормальная матрица перевода» и «что является полным мусором». Затем вы продолжаете и приятно объясняете, почему они полностью эквивалентны и, следовательно, ни один не является более «естественным», чем другой. Почему бы вам просто не убрать эту маленькую чепуху с самого начала? Остальная часть вашего ответа на самом деле довольно хорошая. (Кроме того , для интересующихся: steve.hollasch.net/cgindex/math/matrix/column-vec.html )
Имре
2
@imre: Потому что это не чепуха. Соглашения важны, так как сложно иметь два соглашения. Математики уже давно договорились о матрицах . «Транспонированные матрицы» (названные потому, что они транспонированы из стандарта) являются нарушением этого соглашения. Поскольку они эквивалентны, они не дают никакой реальной выгоды для пользователя. А так как они разные и ими можно злоупотреблять, это создает путаницу. Или, другими словами, если бы транспонированные матрицы не существовали, ОП никогда бы об этом не спросила. И поэтому это альтернативное соглашение создает путаницу.
Николь Болас
1
@Nicol: Матрица, имеющая перевод в 12-13-14, все еще может быть главной строкой - если мы затем используем с ней векторы строки (и умножаем на vM). Смотрите DirectX. ИЛИ его можно рассматривать как главный столбец, используемый с векторами столбцов (Mv, OpenGL). Это действительно то же самое. И наоборот, если матрица имеет перевод в 3-7-11, то ее можно рассматривать как матрицу мажорной строки с векторами столбцов, ИЛИ мажорную строку с векторами строк. Версия 12-13-14 действительно более распространена, но, на мой взгляд, 1) это не совсем стандарт, и 2) называть ее столбцом-мажором может ввести в заблуждение, поскольку это не обязательно так.
Имре
1
@imre: это стандарт. Спросите любого действительного обученного математика, куда идет перевод, и он скажет вам, что он идет в четвертой колонке. Математики изобрели матрицы; именно они устанавливают соглашения.
Николь Болас
3

Здесь работают два разных варианта соглашения. Во-первых, используете ли вы векторы строк или векторы столбцов, и матрицы для этих соглашений являются транспонированными друг для друга.

Другой вопрос заключается в том, храните ли вы матрицы в памяти в порядке основной строки или основной столбца. Обратите внимание, что «строка-майор» и «столбец-мажор» не являются правильными терминами для обсуждения соглашения о векторе-строке / векторе-столбце ... даже при том, что многие люди используют их как таковые. Макеты памяти основной строки и основной строки также различаются по транспонированию.

OpenGL использует соглашение о векторном столбце и порядок хранения основных столбцов, а D3D использует соглашение о векторе строк и основной порядок хранения строк (хорошо - по крайней мере, D3DX, математическая библиотека, делает), поэтому две транспонирования отменяются, и получается одна и та же схема памяти работает как для OpenGL, так и для D3D. То есть один и тот же список из 16 чисел, хранящихся последовательно в памяти, будет работать одинаково в обоих API.

Это может означать то, что люди говорят, что «неважно, как матрица хранится или передается в графический процессор».

Что касается фрагментов кода, r! = R2, потому что правило для транспонирования продукта: (ABC) ^ T = C ^ TB ^ TA ^ T. Транспозиция распределяется по умножению с рефералом порядка. Так что в вашем случае вы должны получить r.Transpose () == r2, а не r == r2.

Аналогично, pos! = Pos3, потому что вы транспонировали, но не меняли порядок умножения. Вы должны получить wpvM * localPos == localPos * wvpM.Tranpose (). Вектор автоматически интерпретируется как вектор строки при умножении на левой стороне матрицы, и как вектор столбца при умножении на правой стороне матрицы. Кроме этого, нет никаких изменений в том, как выполняется умножение.

И наконец: re: «Моя WVP-матрица столбца успешно используется для преобразования вершин с помощью вызова HLSL: mul (vector, matrix),« я не уверен в этом, но, возможно, возникла путаница / ошибка, из-за которой матрица вышла из математическая библиотека уже транспонирована.

Натан Рид
источник
1

В трехмерной графике вы используете матрицу для преобразования как вектора, так и точек. Учитывая тот факт, что вы говорите о матрице перевода, я буду говорить только о точках (вы не можете перевести вектор с матрицей или, говоря лучше, вы можете получить тот же вектор).

В матричном умножении номер столбца первой матрицы должен быть равен номеру строки второй (вы можете умножить матрицу тревожности на mxk).

Точка (или вектор) представлена ​​3 компонентами (x, y, z) и может рассматриваться как строка или столбец:

Колум (размер 3 х 1):

| Х |

| У |

| Г |

или же

строка (размер 1 X 3):

| Х, у, г |

Вы можете выбрать предпочтительное соглашение, это просто соглашение. Давайте назовем это T матрицей перевода. Если вы выбираете первое соглашение, чтобы умножить точку p для матрицы, вам нужно использовать умножение после записи:

T * v (размерность 3x3 * 3x1)

в противном случае:

v * T (размерность 1x3 * 3x3)

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

Если вы используете всегда один и тот же конвенции, нет никакой разницы. Это не значит, что матрица другого соглашения будет иметь одно и то же представление в памяти, но при преобразовании точки с двумя различными соглашениями вы получите одну и ту же преобразованную точку:

p2 = B * A * p1; // первое соглашение

р3 = р1 * A * B; // второе соглашение

p2 == p3;

гейзенбаг
источник
1

Я вижу, что компоненты перевода, занимающие 4-й, 8-й и 12-й элементы, означают, что ваши матрицы ошибочны.

Компоненты перевода всегда указываются в виде записей № 13, № 14 и № 15 матрицы преобразования ( считая самый первый элемент массива как элемент № 1 ).

Матрица преобразования основных строк выглядит следующим образом:

[ 2 2 2 1 ]   R00 R01 R02 0  
              R10 R11 R12 0 
              R20 R21 R22 0 
              t.x t.y t.z 1 

Колонна из основной матрицы преобразования выглядит следующим образом:

 R00 R01 R02 t.x   2  
 R10 R11 R12 t.y   2 
 R20 R21 R22 t.z   2 
  0   0   0   1    1 

Роу основные матрицы указано спускаясь строки .

Объявление строки основную матрицу выше в виде линейного массива, я бы написать:

ROW_MAJOR = { R00, R01, R02, 0,  // row 1 // very intuitive
              R10, R11, R12, 0,  // row 2
              R20, R21, R22, 0,  // row 3
              t.x, t.y, t.z, 1 } ; // row 4

Это кажется очень естественным. Поскольку обратите внимание, на английском языке написано "row-major" - матрица появляется в тексте выше точно так же, как и в математике.

А вот и точка замешательства.

Основные матрицы столбцов указываются по столбцам

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

    COLUMN_MAJOR = {R00, R10, R20, 0, // COLUMN # 1 // очень нелогично
                     R01, R11, R21, 0,
                     R02, R12, R22, 0,
                     tx, ty, tz, 1};

Обратите внимание, что это совершенно нелогично! Основные матрицы столбцов имеют свои записи, указанные вниз по столбцам при инициализации линейного массива, поэтому первая строка

COLUMN_MAJOR = { R00, R10, R20, 0,

Определяет первый столбец матрицы:

 R00
 R10
 R20
  0 

и не первый ряд , как вы могли бы поверить в простом расположении текста. Вы должны мысленно транспонировать основную матрицу столбца, когда видите ее в коде, потому что первые 4 указанных элемента фактически описывают первый столбец. Я полагаю, именно поэтому многие люди предпочитают матрицы строк в коде (GO DIRECT3D !! кашель.)

Таким образом, компоненты перевода всегда имеют индексы линейного массива № 13, № 14 и № 15 (где первый элемент № 1), независимо от того, используете ли вы мажор строки или мажор столбца.

Что случилось с вашим кодом и почему он работает?

Что происходит в вашем коде, так это то, что у вас есть мажорная матрица столбца, но вы поместили компоненты перевода в неправильное место. Когда вы перемещаете матрицу, запись № 4 переходит к записи № 13, записи № 8 - № 13 и записи № 12 - № 15. И там у вас есть это.

bobobobo
источник
0

Проще говоря, причина разницы в том, что умножение матриц не является коммутативным . При регулярном умножении чисел, если A * B = C, то из этого следует, что B * A также = C. Это не относится к матрицам. Вот почему выбираются либо основные ряды, либо основные столбцы.

Почему это не имеет значения, так это то, что в современном API (а я специально говорю о шейдерах здесь) вы можете выбрать свое собственное соглашение и умножить матрицы в правильном порядке для этого соглашения в своем собственном шейдерном коде. API больше не навязывает вам одно или другое.

Максимус Минимус
источник