Плутанина між порядком матриці C ++ та OpenGL (великі рядки проти основних стовпців)


77

Я грунтовно плутаюся з визначеннями матриць. У мене є клас матриці, який містить, як float[16]я вважав, головний ряд, на основі таких спостережень:

float matrixA[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
float matrixB[4][4] = { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 }, { 12, 13, 14, 15 } };

matrixAі matrixBобидва мають однаковий лінійний макет в пам’яті (тобто всі числа в порядку). Відповідно до http://en.wikipedia.org/wiki/Row-major_order це вказує на розміщення основних рядків.

matrixA[0] == matrixB[0][0];
matrixA[3] == matrixB[0][3];
matrixA[4] == matrixB[1][0];
matrixA[7] == matrixB[1][3];

Отже, matrixB[0]= рядок 0, matrixB[1]= рядок 1 і т. Д. Знову ж таки, це вказує на розміщення великих рядків.

Моя проблема / плутанина виникає, коли я створюю матрицю перекладу, яка виглядає так:

1, 0, 0, transX
0, 1, 0, transY
0, 0, 1, transZ
0, 0, 0, 1

Що викладено в пам'яті як { 1, 0, 0, transX, 0, 1, 0, transY, 0, 0, 1, transZ, 0, 0, 0, 1 },.

Потім, коли я викликаю glUniformMatrix4fv , мені потрібно встановити прапорець транспонування на GL_FALSE, вказуючи, що він є основним стовпцем, інакше такі перетворення, як translate / scale тощо не застосовуються належним чином:

Якщо транспонування має значення GL_FALSE, передбачається, що кожна матриця подається у великому порядку стовпця. Якщо транспонування має значення GL_TRUE, передбачається, що кожна матриця подається у великому порядку рядка.

Чому мою матрицю, яка видається основною у рядку, потрібно передавати OpenGL як головну колонку?


Як ви визначаєте, що вам "потрібно встановити прапор транспонування GL_FALSE?" Як ти використовуєш форму?
Енджу більше не пишається SO

@Angew Мені потрібно встановити прапорець транспонування на GL_FALSE, інакше переклади / масштаби тощо не працюють, вони застосовують перекладені перетворення до подання.
Марк Інграм

1
Я трохи детальніше розробив цю тему тут. "Матриці не є перетвореннями": bit.ly/1cPINCm .
DuckMaestro

Про це є чудова стаття на Scratchapixel: scratchapixel.com/lessons/3d-basic-lessons/lesson-4-geometry/… .
user18490

12
Мене дуже дратує, коли люди приходять і голосують за питання (або відповідають) і не залишають жодних відгуків ...
Марк Інграм

Відповіді:


71

Позначення матриць, що використовується в документації opengl, не описує розміщення в пам'яті для матриць OpenGL

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

Матриці OpenGL мають такий же макет пам’яті, як і матриці Directx .

x.x x.y x.z 0
y.x y.y y.z 0
z.x z.y z.z 0
p.x p.y p.z 1

або

{ x.x x.y x.z 0 y.x y.y y.z 0 z.x z.y z.z 0 p.x p.y p.z 1 }
  • x, y, z - це 3-компонентні вектори, що описують матричну систему координат (локальна система координат усередині відносно глобальної системи координат).

  • p - 3-компонентний вектор, що описує початок матричної системи координат.

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

{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, transX, transY, transZ, 1 }.

Залиште це, а решта повинна бути легкою.

--- цитата зі старого запитання про opengl--


9.005 Чи є матриці OpenGL великими або основними рядковими?

Для програмування матриці OpenGL - це масиви з 16 значеннями з базовими векторами, розташованими по сусідству в пам'яті. Компоненти перекладу займають 13-й, 14-й та 15-й елементи 16-елементної матриці, де індекси нумеруються від 1 до 16, як описано в розділі 2.11.2 Специфікації OpenGL 2.1.

Велика колона проти мажорної суто є суто умовною умовою. Зверніть увагу, що помноження з матрицями великих стовпців дає той самий результат, що і попереднє перемноження з матрицями великих рядків. Специфікація OpenGL і Довідковий посібник OpenGL використовують позначення основних стовпців. Ви можете використовувати будь-які позначення, якщо це чітко зазначено.

На жаль, використання формату основного стовпця в специфікації та синій книзі призвело до нескінченної плутанини у програмістському співтоваристві OpenGL. Основні позначення в колонці свідчать про те, що матриці не закладені в пам'ять, як очікував би програміст.



2
Як ви маєте на увазі однаковий макет пам'яті? Враховуючи приклад у моєму оригінальному дописі, це вказує на те, що розміщення пам'яті моєї матриці є основним (оскільки саме так С викладає пам’ять у двовимірних масивах). То чому мені потрібно встановити прапор, щоб повідомляти OpenGL, що я використовую основний стовпець, коли макет пам'яті не збігається?
Марк Інграм

2
@MarkIngram: рядок / стовпець - це позначення, що використовується ТІЛЬКИ в документації. І в D3DMatrix, і в OpenGL матричні елементи перекладу займають однакові позиції в пам'яті - елементи з індексом / зміщенням 12, 13, 14 (або 13-й, 14-й і 15-й елементи масиву з 16 елементами).
SigTerm

2
@MarkIngram: Іншими словами, позначення матриць, що використовується в документації OpenGL, не описує розміщення матриць OpenGL в пам'яті.
SigTerm

8
Великі стовпці та рядки - про те, як матриця викладена в пам’яті. Подивитися. напр. книга Голуб - Матричні обчислення або стаття Вікі про мажор. Поширені запитання щодо OpenGL є неправильними або неоднозначними. Окреме питання, чи розглядаєте ви вектори як вектори рядків або стовпців, що також визначає, чи слід зберігати основні вектори у стовпцях або рядках. (Іноді для посилання на це використовується термін major-major, що додає плутанини.) Тип даних матриці GLSL є стовпцем major, і домовленість полягає в тому, щоб трактувати вектори як вектори стовпців. У пам’яті D3D і конвенція OpenGL виявляються однаковими.
darklon

5
Не могли б ви додати пояснення щодо того, що, на вашу думку, насправді означає стовпчик / рядок? Я взагалі не розумію зауваження "забути про ...". Згідно з Вікіпедією , ці терміни визначають розміщення пам'яті, яка тут є найважливішою. Чому я хотів би це забути?
Андреас Гафербург,

66

Підсумовуючи відповіді SigTerm і dsharlet: Звичайний спосіб перетворення вектора в GLSL - це помноження вектора на ліву матрицю перетворення:

mat4 T; vec4 v; vec4 v_transformed; 
v_transformed = T*v;

Для того, щоб це працювало, OpenGL очікує, що розмітка пам'яті Tбуде такою, як описано SigTerm,

{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, transX, transY, transZ, 1 }

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

v_transformed = v*T;

який дає лише правильний результат, якщо Tтранспонується, тобто має макет

{ 1, 0, 0, transX, 0, 1, 0, transY, 0, 0, 1, transZ, 0, 0, 0, 1 }

(тобто "основний ряд"). Оскільки ви вже надали правильний макет своєму шейдеру, а саме основний рядок, не потрібно було встановлювати transposeпрапор glUniform4v.


7
найкраща відповідь на мій погляд. Це чудовий приклад для студентів графіки. У математиці один з вектора горизонтальний, а інший вертикальний (інакше добуток не визначений). Я знаю, що під час програмування ми забуваємо про більшість математичних формалізмів XD
CoffeDeveloper

чому б не забути і про цей формалізм? З точки зору програмування це мало б набагато більше сенсу.
whossname

15

Ви маєте справу з двома окремими питаннями.

По-перше, ваші приклади стосуються макета пам'яті. Ваш масив [4] [4] є основним рядком, оскільки ви використовували домовленість, встановлену багатовимірними масивами C, щоб відповідати вашому лінійному масиву.

Друге питання - це питання домовленостей щодо того, як ви інтерпретуєте матриці у вашій програмі. glUniformMatrix4fv використовується для встановлення параметра шейдера. Чи обчислюється ваше перетворення для перетворення вектора рядків або векторних стовпців, залежить від того, як ви використовуєте матрицю у коді шейдера. Оскільки ви говорите, що вам потрібно використовувати вектори стовпців, я припускаю, що ваш шейдерний код використовує матрицю A і вектор стовпця x для обчислення x ' = A x .

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

Це посилання має кілька хороших подальших обговорень: http://steve.hollasch.net/cgindex/math/matrix/column-vec.html


Я не роблю нічого вишуканого з матрицями у своєму шейдері. Це виглядає так:gl_Position = vec4(v_xy, 0.0, 1.0) * v_ViewMatrix * v_ProjectionMatrix;
Марк Інграм

@MarkIngram, що може виглядати не вигадливо, але це важливо. Це говорить вам про те, як ви дотримуєтесь інтерпретації матриць перетворень.
dsharlet

1
Що це мені говорить (тобто, що таке конвенція, і як це мені показує)? :)
Марк Інграм

1
@MarkIngram повідомляє, що ви інтерпретуєте свої позиції як вектори рядків. Коли ви хочете перетворити вектор x матрицею A, ви обчислюєте x '^ T = x ^ T A. Це не стандарт у загальній математиці, а поширений в комп'ютерній графіці (на жаль, це призводить до великої плутанини).
dsharlet

2
Тож мені слід змінити порядок множення? Замість того vector x model x view x projection;, щоб projection x view x model x vector;?
Марк Інграм
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.