Чи може хтось пояснити (причини) наслідки колум проти головного рядка у множенні / конкатенації?


11

Я намагаюся навчитися будувати матриці перегляду та проекції, і постійно досягаю труднощів у моєму впровадженні через мій плутанини щодо двох стандартів для матриць.
Я знаю, як множити матрицю, і я можу бачити, що перенесення перед множенням повністю змінить результат, отже, необхідність множення в іншому порядку.

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

Враховуючи, що:

"після множення з матрицями основних стовпців дає такий самий результат, що і попереднє множення з матрицями основних рядків."

Чому в наступному фрагменті коду:

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

Чи r! = R2 і чому pos3! = Pos для :

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

Чи змінюється процес множення залежно від того, чи є матриці основними рядками чи стовпцями , чи це лише порядок (для еквівалентного ефекту?)

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



Відповіді:


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-му індексах. Нульові базові показники, звичайно.

Ваша плутанина випливає з того, що ви вважаєте, що ви використовуєте матриці основних стовпців, коли ви фактично використовуєте основні рядки.

Заява про те, що рядки проти головного стовпця є лише умовною умовою, є повністю правдивою. Механіка множення матриць та множення матриць / векторів однакова незалежно від умовності.

Те, що змінюється, означає зміст результатів.

Зрештою, матриця 4x4 - це лише сітка чисел 4x4. Він не повинен посилатися на зміну системи координат. Однак, як тільки ви присвоїли значення певній матриці, тепер вам потрібно знати, що зберігається в ній і як нею користуватися.

Візьміть матрицю перекладу, яку я вам показав вище. Це дійсна матриця. Ви можете зберігати матрицю 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 - стовпець-вектор.

Через це більшість матричних математичних бібліотек роблять це припущення: якщо ви множите векторний раз на матрицю, ви дійсно маєте на увазі зробити множення, яке насправді працює , а не те, яке не має сенсу.

Давайте визначимо для будь-якого 4D вектора x наступне. Cмає бути матричною формою стовпців-векторних xта Rмає бути матричною формою векторних рядків x. З огляду на це, для будь-якої матриці 4х4 А A*Cявляє собою матрицю, що множує A на стовпець-вектор x. І R*Aявляє матрицю, помножуючи вектор-рядок xна A.

Але якщо ми подивимось на це, використовуючи сувору матричну математику, то побачимо, що вони не є рівнозначними . R*A не може бути таким же, як A*C. Це тому, що вектор-рядок - це не те саме, що вектор-стовпчик. Вони не одна і та ж матриця, тому вони не дають однакових результатів.

Однак вони пов’язані між собою. Це правда, що R != C. Однак вірно також, що де T - операція транспонування. Дві матриці переносяться одна в одну.R = CT

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

Якщо у вас є дві матриці A і B, а A зберігається як основні рядки, а B як стовпчики, множення їх абсолютно безглуздо . Ви отримуєте дурниці в результаті. Ну не дуже. Математично те, що ви отримуєте, є еквівалентом занять . Або ; вони математично однакові.AT*BA*BT

Отже, множення матриці має сенс лише в тому випадку, якщо дві матриці (і пам’ятайте: множення вектор / матриця - це просто матричне множення) зберігаються в одному і тому ж великому порядку.

Отже, чи векторний стовпчик-мажор чи рядок-мажор? Це і те, і інше, як було сказано раніше. Він є основним стовпцем лише тоді, коли він використовується як матриця стовпців, і він є основним рядком, коли він використовується як матриця рядків.

Тому, якщо у вас є матриця A, яка є головною колоною, це x*Aозначає ... нічого. Ну, знову ж таки, це означає , але це не те, що ви насправді хотіли. Точно так само робить перенесене множення, якщо воно є основним рядком.x*ATA*xA

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

Чому в наступному фрагменті коду робиться r! = R2

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

чому pos3! = pos for

Тому що це не має сенсу. Єдиним способом бути правдою було б, якби . І це справедливо лише для симетричних матриць.A * t == AT * tA == AT


@Nicol, Все починає клацати зараз. Виникла плутанина через відключення між тим, що я бачив, і тим, що я вважав, що мені повинно бути, оскільки моя бібліотека (взята з Аксіоми) оголошує основний стовпець (і всі порядки множення тощо відповідають цьому), але макет пам'яті є рядком -major (судячи з індексів перекладу та факту, що 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) називати його стовпчиком-майором може бути оманливим, оскільки це не обов'язково.
imre

1
@imre: Це стандартно. Запитайте будь-якого фактично навченого математика, куди йде переклад, і він скаже вам, що він йде в четвертому стовпчику. Математики винайшли матриці; саме вони встановлюють конвенції.
Нікол Болас

3

Тут працюють два різні варіанти конвенції. Одне полягає в тому, чи використовуєте ви вектори рядків або вектори стовпців, а матриці цих конвенцій переносять одна одну.

Інша справа, чи зберігаєте ви матриці в пам'яті в порядку рядка-основного чи стовпця-основного. Зауважте, що "рядок-мажор" та "стовпчик-мажор" не є правильними умовами для обговорення конвенції вектор-рядок / стовпець-вектор ... навіть якщо багато людей зловживають ними як такими. Макети пам’яті головного рядка та основного стовпця відрізняються і транспозицією.

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

Це може означати те, що люди говорять про те, що "це не має значення для того, як матриця зберігається чи передається в GPU".

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

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

Нарешті, re: "моя основна WVP матриця стовпців успішно використовується для перетворення вершин за допомогою виклику HLSL: mul (вектор, матриця)," я в цьому не впевнений, але, можливо, плутанина / помилка спричинила появу матриці з вже перенесена математична бібліотека.


1

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

При множенні матриці кількість стовпців першої матриці має бути рівним кількості рядків другої (ви можете помножити матрицю anxm на mxk).

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

колон (розмірність 3 X 1):

| х |

| у |

| z |

або

рядок (розмірність 1 X 3):

| х, у, з |

Ви можете вибрати кращу конвенцію, це просто конвенція. Назвемо це T матрицею перекладу. Якщо ви вибираєте першу умову, для множення точки p для матриці вам потрібно використовувати пост множення:

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

інакше:

v * T (розмір 1х3 * 3х3)

Як видається, автори стверджують, що це не має значення для того, як матриця зберігається чи передається в GPU

Якщо ви завжди використовуєте одну і ту ж умову, це не має ніякого значення. Це не означає, що матриця різної конвенції матиме однакове представлення пам'яті, але що перетворюючи точку з двома різними умовами, ви отримаєте ту саму перетворену точку:

p2 = B * A * p1; // перша конвенція

p3 = p1 * 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

Це здається дуже природним. Оскільки зауважте, англійською мовою написано "рядок-мажор" - матриця з'являється в тексті вище саме так, як це буде в математиці.

І ось суть плутанини.

Основні матриці стовпців задаються вниз по стовпцях

Це означає, щоб вказати матрицю перетворення стовпця як лінійний масив у коді, вам потрібно буде написати:

    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. І ось у вас це є.


0

Простіше кажучи, причина різниці полягає в тому, що множення матриць не є комутативним . При регулярному множенні чисел, якщо A * B = C, то випливає, що B * A також = C. Це не так з матрицями. Ось чому вибирайте або основні рядки, або основні стовпці.

Чому це не важливо, це те, що в сучасному API (і я тут конкретно говорю шейдери), ви можете вибирати власну конвенцію і множити свої матриці в правильному порядку для цієї конвенції у власному коді шейдера. API більше не застосовує те чи інше до вас.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.