Тепер ми знаємо як створювати об'єкти, розфарбовувати їх і накладати на них текстури, але вони все ще досить нудні, оскільки є статичними об'єктами. Ми можемо спробувати змусити їх рухатися змінюючи координати вершин для кожного кадру, але це досить муторно і вимагає процесорних обчислень. Є набагато більш зручний спосіб для здійснення трансформацій над об'єктом - це застосування матриць. Але це не означає, що ми зараз будемо розмовляти про кунг фу і штучний цифровий світ.
- Зміст
- Вектор
- Скалярні векторні дії
- Зворотний вектор
- Додавання і віднімання
- Довжина
- Множення вектора на вектор
- Скалярний твір
- Векторний твір
- Матриці
- Додавання і віднімання
- Множення матриці на скаляр
- Множення матриць
- Множення матриці на вектор
- Одинична матриця
- Матриця масштабування
- Матриця зсуву
- Гомогенні координати
- Матриця обертання
- Комбінування матриць
Зміст
Частина 1. Початок
- OpenGL
- Створення вікна
- Hello Window
- Hello Triangle
- Shaders
- Текстури
- Трансформації
- Системи координат
- Камера
Частина 2. Базове освітлення
- Кольори
- Основи освітлення
- Матеріали
- Текстурні карти
- Джерела світла
- Декілька джерел освітлення
Частина 3. Завантаження 3D-моделей
- Бібліотека Assimp
- Клас полігональної сітки Mesh
- Клас 3D-моделі
Частина 4. Просунуті можливості OpenGL
- Тест глибини
- Тест трафарету
- Змішування кольорів
- Відсікання граней
- Кадровий буфер
- Кубічні карти
- Просунута робота з даними
- Просунутий GLSL
- Геометричний шейдер
- Інстансинг
- Згладжування
Частина 5. Просунуте освітлення
- Просунуте освітлення. Модель Блінна-Фонга.
- Гамма-корекція
- Карти тіней
- Всенаправлені карти тіней
Матриці - це дуже потужні математичні конструкції, які спочатку лякають, але варто до них звикнути і вони відразу стануть вкрай корисними. Під час обговорення матриць потрібно також трохи заглибитися в математику. Також для більш схильних до математики читачів я залишу посилання на додаткові ресурси на цю тему.
Як би там не було, для повного розуміння трансформацій ми, по-перше, повинні розібратися з векторами. Основне завдання цієї глави - дати вам основні математичні знання, які нам знадобляться пізніше.
Вектор
У найпростішому визначенні, вектора - це не більше ніж напрямки. У векторі може бути напрямок і магнітуда (також іноді називається модулем або довжиною). Ви можете уявляти собі вектора в якості напрямків на карті скарбів: «Зробіть 10 кроків наліво, тепер 3 кроки на північ і тепер 5 кроків направо». У цьому прикладі «ліворуч» - це напрямок, а «10 кроків» - це довжина вектора. Напрямки на цій карті скарбів складаються з 3 векторів. Вектори можуть мати будь-яку розмірність, але найчастіше використовуються двокомпонентні і чотирьохкомпонентні вектори. Якщо вектор двокомпонентний, то він описує напрямок на площині (або на 2D графіці), якщо вектор трикомпонентний, то він описує напрямок у тривимірному світі.
Нижче ви можете бачити 3 вектори, кожен з яких представлений у вигляді (x, y) як стрілки на 2D графіці. Оскільки більш інтуїтивно представляти вектори в 2D (ніж у 3D), ви можете думати про 2D вектори, як про 3D вектори, але з нульовою z координатою. До тих пір, поки вектор описує напрямок - позиція вектора не змінює його значення. На графіку можна побачити, що вектори v і w однакові, хоча з позиції відрізняються:
Коли математики описують вектор, вони воліють використовувати символи нижнього регістру з невеликою чорточкою зверху. Приклад:
Оскільки вектори часто описують напрямок - то іноді їх важко уявити у вигляді позиції. Зазвичай ми візуалізуємо вектор наступним чином: ми встановлюємо центр в (0, 0, 0), а потім вказуємо напрямок, описаний точкою. Таким чином виходить позиційний вектор (також ми можемо взяти за центр іншу точку, а потім сказати «Цей вектор вказує на точку в просторі з цієї точки»). Позиційний вектор (3, 5) буде вказувати на точку (3, 5) на графі з підставою (0, 0). За допомогою векторів ми можемо описувати як напрямки так і позиції в двомірному і тривимірному просторах.
Також ми можемо виробляти над векторами деякі математичні дії.
Скалярні векторні дії
Скаляр - це одне число (або однокомпонентний вектор, якщо ви хочете продовжувати працювати з векторами). Під час додавання/віднімання/множення або ділення вектора на скаляр ми просто складаємо/віднімаємо/множимо або ділимо кожен елемент вектора на цей скаляр. Приклад:
Де замість додавання може бути віднімання, множення або ділення.
Зворотний вектор
Звернення (заперечення) вектора - це отримання вектора, чий напрямок протилежний вихідному. Зворотний вектор для вектора, що вказує на північний схід, буде вектор, що вказує на південний захід. Для обігу вектора ми просто множимо вектор на -1. Приклад:
Додавання і віднімання
Складення двох векторів проводиться покомпонентно. Приклад:
Візуально сума векторів v = (4,2) і k = (1,2) виглядає так:
Також як і зі звичайним додаванням і відніманням, віднімання векторів - це теж додавання, але зі зворотним другим вектором:
Віднімання векторів один з одного породжують вектор, який є різницею в позиціях операндів:
Довжина
Для отримання довжини (модуля) вектора ми використовуємо теорему Піфагора, які ви, можливо, пам'ятаєте зі школи. Вектор утворює трикутник, якщо представити його компоненти в якості сторін трикутника:
Оскільки довжина сторін (x, y) відома, і ми хочемо дізнатися довжину гіпотенузи - то ми робимо це наступним чином:
Де ||v|| - це довжина вектора v. Така формула легко розширюється в 3D добаленням z ^ 2. Приклад розрахунку довжини:
Обчислене значення: 4.47
Також існує спеціальний вид векторів, який називається одиничними векторами. Особливість таких векторів у тому, що їх довжина завжди дорівнює 1. Ми можемо перетворити будь-який вектор на одиничний поділом цього вектора на його довжину:
Такий вектор називається нормалізованим. Поодинокі вектори позначаються з невеликим дахом над буквою. З ними, також, простіше працювати, оскільки нам доводиться піклуватися тільки про напрямок такого вектора.
Множення вектора на вектор
Множення двох векторів виконується досить дивно. Нормальне множення не застосовне, оскільки воно не має візуального сенсу, але у нас є 2 специфічних підходи, з яких можна вибирати під час множення: перший - скалярний твір, який зображується як точка, а другий - векторний твір, який зображується як хрест.
Скалярний твір
Скалярний витвір двох векторів еквівалентний скалярному твору довжин цих векторів, помножений на косинус кута між ними. Якщо ця пропозиція збила вас з пантелику, то подивіться на формулу:
Де кут між векторами описаний як тета. Чому це може бути цікаво? Що ж, уявімо, якщо вектори v і k є одиничними векторами. Відповідно формула скорочується до:
Тепер скалярний твір визначає лише кут між двома векторами. Ви можливо пам'ятаєте, що функція cos стає 0, з кутом в 90 градусів ну і 1 з кутом 0. Це дозволяє легко перевіряти ортогональні вектори або паралельні один одному (ортогональність означає, що вектори прямокутні). Якщо хочете дізнатися більше про sin або cosine, то рекомендую відео Khan Academy про базову тригонометрію.
Ви також можете обчислити кут між двома непоодинокими векторами, але для цього вам доведеться розділити результат на довжини цих векторів, щоб залишитися тільки з cos.
То як же вважати скалярний твір? Скалярний твір - це множення компонентів векторів і подальше складання результатів. Приклад:
Для обчислення кута між векторами нам потрібно повернути функцію косинуса (cos ^ -1) в даному випадку - це 143.1 градуса. Таким чином ми ефективно вирахували кут між цими двома векторами. Скалярний твір дуже корисний під час роботи зі світлом.
Векторний твір
Векторний твір можливий тільки в тривимірному просторі і приймає на вхід два непараллельних вектори, а повертає вектор, який ортогональний вхідним. Якщо вхідні вектори ортогональні один одному, векторний твір створить 3 ортогональні вектори. Далі ви дізнаєтеся, чому це може бути корисно. Наступне зображення показує як це виглядає тривимірному просторі:
На відміну від інших операцій, векторний твір не дуже інтуїтивний без поглиблення в лінійну алгебру, так що краще просто запам'ятати формулу. Нижче представлено векторний твір між двома ортогональними векторами A і B.
Як ви можете бачити, в цій формулі не дуже багато сенсу. У будь-якому випадку після всіх цих кроків ви отримаєте вектор, який буде ортогоналений вхідним.
Матриці
Тепер, після того як ми обговорили майже все на рахунок векторів, настав час заглибитися в матриці. Зазвичай, матриця - це чотирикутні з набору чисел, символів та/або виразів. Ось приклад матриці 2х3:
Доступ до елементів матриці здійснюється за допомогою (i, j), де i - це рядок, а j - це стовпчик. Ось чому матриця вище називається 2х3 (3 стовпчики і 2 рядки). Така система - зворотна до тієї, що використовується в 2D графах (x, y). Для отримання значення 4 з матриці вище, ми повинні вказати індекс (2, 1) (другий рядок, перший стовпчик).
Матриці, за фактом, нічого більш ніж чотирикутні масиви математичних виразів. Вони також мають дуже приємний набір математичних властивостей і, також як і вектори, мають кілька операцій - додавання, віднімання і множення.
Додавання і віднімання
Додавання матриці зі скаляром виконується наступним чином:
Скаляр просто додається в усьому елементам матриці. Теж саме відбувається і при відніманні:
Додавання і віднімання між двома матрицями виконується поелементно. Таким чином операції додавання та віднімання можуть бути застосовані лише до матриць однакового розміру. Приклад:
Теж саме, тільки з відніманням:
Множення матриці на скаляр
Також як додавання і віднімання, множення матриці на скаляр проводиться множенням кожного елемента матриці на скаляр. Приклад:
Множення матриць
Множення матриць не дуже складне, але й не таке просте. Множення має кілька обмежень:
- Ви можете множити тільки матриці, де число стовпчиків першого збігається з числом рядків другої матриці.
- Множення матриць не комутативне. A * B != B * A.
Ось приклад множення двох матриць 2х2:
Зараз, можливо ви намагаєтеся зрозуміти, що ж тут взагалі відбувається? Множення матриць - це комбінація з нормального множення і додавання з використанням рядків лівої матриці зі стовпцями правої матриці. Наступне зображення має внести трохи ясності:
На початку ми беремо верхній рядок лівої матриці і лівий стовпчик правої матриці. Вибрані нами рядок і стовпчик визначає, який елемент результуючої матриці ми збираємося розрахувати. Якщо б ми взяли перший рядок лівої матриці, то ми збираємося працювати з верхнім рядком результуючої матриці, потім ми вибираємо стовпчик в правій матриці, він визначає те, з яким стовпчиком результуючої матриці ми працюємо. Для обчислення нижнього-правого елемента ми повинні вибрати нижній рядок лівої матриці і правий стовпчик правої матриці.
Для обчислення результуючого значення ми перемножуємо елементи рядка і стовпчика за допомогою звичайного множення. Результати множення потім складаються і ми отримуємо результат. Ось звідти і йде перше обмеження.
У результаті отримується матриця розміром (n, m), де n - кількість рядків у лівій матриці, а m - кількість стовпчиків у правій матриці.
Якщо у вас виникла проблема - то не хвилюйтеся. Просто продовжуйте обчислювати руками і повертайтеся до цього уроку, коли виникають складнощі. Незабаром множення матриць буде на автоматі.
Давайте закриємо питання множення матриць одним великим прикладом. Для перегляду алгоритму використано кольори. Для тренування спробуйте самі порахувати результат, а потім порівняти з результатом у прикладі.
Як ви можете бачити множення матриць досить муторний процес з великою кількістю місць, де можна помилитися. І ці проблеми лише зростають при збільшенні розмірів. Якщо ви все ще прагнете більше математичних властивостей матриць я вкрай рекомендую відео Khan Academy.
Множення матриці на вектор
Ми вже використовували вектори в минулих уроках. Ми використовували їх, щоб представляти позиції, кольори та текстурні координати. Тепер давайте трохи заглибимося в кролячу нору і розповімо, що вектор - це насправді просто Nx1 матриця, де N - це кількість компонентів вектора. Якщо ви трохи подумаєте про це - це має сенс. Вектор, прямо як матриці - масив чисел, але тільки з 1 колонкою. І як нам допоможе ця інформація? Що ж, якщо у нас є матриця MxN ми зможемо її помножити на Nx1 вектор, так як кількість стовпців матриці дорівнює кількості рядків вектора.
Але навіщо нам взагалі вміти множити матрицю на вектор? Досить багато різних 3D/2D трансформацій можна виконати, множачи матрицю на вектор, отримуючи змінений вектор. Якщо ви все ще не впевнені в тому, що повністю розумієте текст вище, то ось небагато прикладів:
Одинична матриця
В OpenGL зазвичай працюють з матрицями трансформації розмірами 4х4 з тієї причини, що більшість векторів має 4 компоненти. Найпростіша матриця трансформації яку можна обговорити - це одинична матриця. Одинична матриця - це NxN матриця, заповнена нулями, але з 1 по діагоналі. Як ми можете помітити ця матриця абсолютно не змінює вектор:
Вектор виглядає недоторканим. Це стає очевидно з правил множення: перший результуючий елемент - це кожен елемент першого рядка матриці, помножені на кожен елемент вектора. Оскільки кожен елемент рядка дорівнює 0, крім першого - то ми отримуємо 1 * 1 + 0 * 2 + 0 * 3 + 0 * 4 = 1. Також саме застосовується і до інших 3 елементів вектора.
Ви можете запитати, навіщо взагалі може знадобиться матриця трансформації, яка нічого не трансформує? Одинична матриця часто є відправною точкою для генерації інших матриць трансформації і якщо ми заглибимося в лінійну алгебру, це також дуже зручна матриця для доказу теорем і вирішення лінійних рівнянь.
Матриця масштабування
Коли ми масштабуємо вектор - ми збільшуємо довжину стрілки на величину масштабування, зберігаючи напрямок. Поки ми працюємо в 2 або 3 розмірностях ми можемо визначити масштабування вектором з 2 або 3 величин, кожна з яких масштабує одну з осей (x, y або z).
Давайте спробуємо масштабувати вектор v = (3,2). Ми будемо масштабувати вектор по осі x на 0.5, що зробить його в 2 рази вже; і масштабуємо вектор по осі y на 2, що збільшить висоту в 2 рази. Давайте подивимося як буде виглядати якщо ми масштабуємо вектор на (0.5, 2). Запишемо результат у вигляді s.
Пам'ятайте, що OpenGL часто працює в 3D-просторі, відповідно для 2D можна залишити Z координату, рівну 1. Операція масштабування, яку ми щойно виконали, є неоднорідною, оскільки величина масштабування для кожної осі розрізняється. Якби величина масштабування була б однаковою - то таке перетворення називається однорідним.
Давайте побудуємо матрицю трансформації яка виконає для нас масштабування. Ми вже побачили на одиничній матриці, що діагональний елемент буде помножений на відповідний елемент вектора. Що якщо ми замінимо одиниці в одиничній матриці на трійки? У такому випадку ми помножимо всі елементи вектора на це значення. Відповідно, якщо ми уявимо величини масштабування як (S1, S2, S3), то ми зможемо визначити матрицю масштабування для будь-якого вектора (x, y, z):
Зауважте, що 4 елементи вектора дорівнюють 1. Цей компонент позначається як w і буде потім використаний для інших завдань.
Матриця зсуву
Зсув - це процес додавання вектора до іншого для отримання нового вектора з іншою позицією, тобто зсув вектора на підставі вектора зсуву. Ми вже обговорювали складання векторів, тому для вас це не буде чимось новим.
Також як і з матрицею масштабування в матриці 4х4 є кілька позицій для виконання необхідних операцій, для зрушення - це верхні 3 елементи четвертої колонки. Якщо ми представимо вектор зрушення як (Tx, Ty, Tz) - то ми можемо визначити матрицю зрушення наступним чином:
Це працює, тому що всі значення вектора множаться на w компонент вектора і складаються з початковим значеннями. Це було б неможливо при використанні матриць 3х3.
Гомогенні координати
Компонента вектора w також називається гомогенною координатою. Для отримання 3D вектора з гомогенної координати ми ділимо x, y і z координати на w. Зазвичай цього не помічають, оскільки w більшу частину часу дорівнює 1.0. Використання гомогенних координат має кілька переваг: вони дозволяють нам виконувати зрушення на 3D векторах (без w компоненти це було б неможливо) і в наступному розділі ми використовуємо значення w для створення 3D візуалізацій.
Також якщо гомогенна координата дорівнює 0 - й вектор вважається вектором напрямку, оскільки вектор з компонентою w рівною 0 не може бути зрушений.
З матрицею зрушення ми можемо рухати об'єкти по всіх 3 напрямках (x, y, z), що робить цю матрицю вкрай корисною для наших завдань.
Матриця обертання
Останні кілька трансформацій були досить прості для розуміння і уявлення в 2D або 3D просторі, але обертання трохи більш заковиристі. Якщо ви хочете дізнатися як же саме ці матриці формуються - то я рекомендую відео Khan Academy про лінійну алгебру.
Для початку давайте визначимо що взагалі таке - обертання вектора. Обертання 2D та 3D визначається кутом. Кут може виражатися в кутах або в радіанах, в яких повний оборот - це 360 градусів або 2Pi відповідно. Я вважаю за краще працювати з градусами, оскільки вони більш логічні для мене.
Більшість обертальних функцій потребує кута в радіанах, але благо перетворення з однієї системи в іншу виконати досить просто:
Градуси = радіани * (180.0f/PI)
Радіани = градуси * (PI/180.0f)
Де PI приблизно 3.14159265359
Обертання на половину кола - вимагає від нас обертання на 360/2 = 180 градусів. Обертання на 1/5 направо вимагає від нас обертання на 360/5 = 72 градуси направо. Ось приклад звичайного 2D вектора, де v повернуть на 72 градуси направо від k.
Обертання 3D описується кутом і віссю обертання. Кут визначає, наскільки вектор повернеться відносно цієї осі. При обертанні 2D векторів у 3D світі, наприклад, ми встановимо вісь обертання - Z.
За допомогою тригонометрії ми можемо перетворювати вектори на повернуті на певний кут. Зазвичай це робиться хитрою комбінацією sin і cos функцій. Обговорення того, як генерується матриці трансформації - виходить за межі нашого уроку.
Матриця обертання визначена для кожної осі в 3D-просторі, де кут показано як тета.
Матриця обертання навколо осі X:
Матриця обертання навколо осі Y:
Матриця обертання навколо осі Z:
За допомогою матриць обертання ми можемо обертати наші вектори по одній з трьох осей. Також можна поєднувати їх, наприклад на початку повернути по X осі, а потім по Y. Правда такий підхід швидко призведе до проблеми, званий проблемою шарнірного замку (Gimbal Lock). Ми не будемо вдаватися в деталі, але краще використовувати обертання по конкретній осі, наприклад (0.662, 0.2, 0.722) (зауважте, що це одиничний вектор), замість того, щоб поєднувати обертання по конкретних осях. Матриця для таких перетворень існує і виглядає вона наступним чином, де (Rx, Ry, Rz) - це вісь обертання:
Математичні обговорення на рахунок генерації такої матриці виходять за рамки цього уроку. Просто тримайте в голові, що навіть така матриця не вирішує проблему шарнірного замку повність (її просто складніше отримати). Для того, щоб повністю вирішити цю проблему нам доведеться працювати з обертаннями за допомогою кватерніонів, які не просто безпечніші, але ще й набагато доброзичливіші з точки зору обчислень. Як би там не було обговорення кватерніонів відведено в більш пізній урок.
Комбінування матриць
Для того, щоб досягти максимальної корисності використання матриць для трансформацій ми повинні комбінувати матриці трансформації в одну матрицю. Давайте подивимося, чи зможемо ми згенерувати матрицю трансформації, яка буде включати кілька трансформацій. Наприклад, у нас є вектор (x, y, z) і ми хочемо масштабувати його в 2 рази і зрушити на (1, 2, 3). Для цього нам потрібні матриці масштабір








