Більшість програмістів, вперше намагаються запрограмувати переміщення камери, пишуть приблизно такий код:
Цей код не буде працювати, або, по крайней мере, буде здаватися. що код працює, але тільки для перших змін і, може бути для маленьких змін до тангажу (pitch) і крен (roll). Ви тільки що виявили це - і ось чому ви читаєте цю статтю - вірно?
Отже, коли ви конвертуєте ці три кути в матрицю (або коли ви використовуєте три функції glRotate), програма, яка формує матрицю, робить якісь припущення про порядок цих трьох обертань.
Уявіть літак. Три можливі обертання по осях, називаються 'нишпорення' ( 'Heading'), 'тангажу' ( 'Pitch') і 'Крен' ( 'Roll'). 'Heading' - 'нишпорення' іноді називають 'Yaw' - але я ненавиджу цей вибір, оскільки в скороченому вигляді це слово записують як 'Y', а це може призвести до плутанини з позначенням осі ординат Y - тому я і замінив слово 'Yaw 'на' Heading '.
- Зміни в 'Heading' (нишпорення) виробляють повороти наліво або направо.
- Зміни в тангажу ( 'Pitch') змушують ніс літака опускатися, а хвіст підніматися (або навпаки).
- Зміни в крен ( 'Roll') піднімають кінець одного крила і опускають кінець іншого.
прекрасно, це все, що мала б робити ваша програма. Також домовимося, що позитивними будуть обертання, що відбуваються проти руху годинникової стрілки, і приймемо, що нульового обертанню відповідає горизонтальний політ на Північ.
(Коли ви використовуєте три кути для подання обертань, їх часто називають як "Ейлерови кути обертання" або просто "Eulers '. Я ж зазвичай записую Ейлерови кути як (H, P, R).)
Фактично, вся проблема полягає в тому, що коли ви комбінуєте ці три обертання, ви ПОВИННІ робити це в особливому порядку - ви можете вибрати будь-який порядок - але математика завжди змушує вас зробити конкретний вибір. Тобто ви можете зробити повороти в такому порядку: спочатку крен, потім тангаж і в кінці рисканье. Тепер подивимося, що може трапитися з такою послідовністю операцій - і, для простоти, розглянемо тільки крен і тангажу.
(Якщо ви - правша, то вам набагато легше буде стежити за ходом обговорення, якщо ви складете літачок з паперу. Складіть один зараз же! Лівші зазвичай мають краще просторову уяву і можуть у всьому розібратися в думках :-)
- Літак (при відсутності обертань) горизонтально вирівняний і спрямований носом прямо на Північ.
- Задамо кути нишпорення / тангажу / Крену - (0,90,90) і подивимося, що станеться:
- Літак тепер має крен в 90 градусів (так. Що правий кінець крила вказує на небо, а лівий на землю).
- . і тангажу в 90 градусів (мається на увазі, що ніс піднятий ВГОРУ). так, що ніс літака вказує на Захід, а хвіст спрямований на Схід, праве крило і раніше направлено вгору, і ліве - вниз.
- Тепер пілот намагається вирівняти літак по горизонталі, повертаючи рукоятку джойстика направо, - що відповідно до вищенаведеним кодом має поступово зменшити крен літака до нуля. Ви можете очікувати в результаті, що ніс літака буде як і раніше спрямований до Заходу, - але на самій-то справі тепер ніс літака і крен.
- Так, в кінці цього маневру, HPR - кути зміняться від (0,90,90) до (0,90,0) - яке тепер насправді становище літака у просторі?
- Давайте повернемося назад, до першого пункту, (літак горизонтально вирівняний і спрямований носом на північ) і визначимо положення літака, якщо HPR - кути задані, як (0,90,0).
- Відмінно, нульовий крен задає правильне положення крил - ліве крило направлено на захід і праве на схід. і 90 - градусний тангаж піднімає ніс літака до неба!
Таким чином, після зменшення кута нахилу до нуля, наш літак, замість того, щоб бути горизонтально вирівняним і летіти на захід, виявляється в положенні з носом, задертим вертикально вгору! З точки зору пілота, його літак мав 90-градусний лівий крен і летів на захід, а після повороту рукоятки джойстика направо, з метою вирівняти літак горизонтально, фактично змінився кут нишпорення. тому, що з положення польоту на захід, літак виявився з стирчав вгору носом, оскільки ніс літака повернувся направо, а хвіст - наліво, т. е. за визначенням, зробив "нишпорення". Спроба вирівняти літак, все ще летить на захід, змінюючи кут нишпорення (працюючи кермом) або тангажу, також завершуються невдачею!
Такого роду явища часто називають "шарнірний замок" ( 'gimbal lock') - по імені ефекту, який трапляється з реальними механізмами, що мають три осьових шарніра (т. Е. З трьома ступенями свободи). Коли осі двох шарнірів виявляються паралельними один одному, ви втрачаєте одну ступінь свободи в цій системі. У мене є автомобільний ключ запалювання, такий гнучкий, що його можна зігнути в прямий кут, але, незважаючи на це їм легко можна запустити двигун.
Додайте третій кут обертання, і справа може зайти в глухий кут.
Ви тільки дізналися про все це. Але що ж потрібно зробити, щоб це виправити?
Нам необхідно зрозуміти, на якому етапі ми зробили помилку. Проблема в тому, що перша зміна кута нахилу сталося ДО зміни кута тангажу, а зменшення крену - "вирівнювання" - після положення, що виник в результаті первинних изменеий кутів Крену + тангажу (т. Е. Не просто відновлення початкового кута крену, що фактично робить наївний код на початку цього документа).
Прекрасно, після сказаного спокусливо змінити наш код так:
безсумнівно вам здасться. що цей код працює - і математично він виведений коректно. Оскільки ми зберігаємо "поточний" обертання як матрицю, будь-які поняття про "порядку" операцій до освіти матриці були забуті, - і якщо зміни кутів відносно невеликі (зазвичай такі і бувають), то теоретично цей код буде прекрасно працювати.
Все ж є дратує проблема, що виникає через особливості машинної математики - помилки округлення.
Коли ви зберігаєте обертання як ейлерову кутів, може статися накопичення крихітних помилок, що з'являються при округленні, - якщо ви змінюєте кут "нишпорення" по 0.1 градуса в кожному з 3600 кадрів, то в результаті кут "нишпорення" не дорівнює точно нулю. Однак, гравець керуючий сценою за допомогою джойстика, не помітить цієї помилки - це цілком природно для людей.
На жаль матриці 3х3 і 4х4 можуть зробити набагато більше дій, ніж просте обертання. При правильному виборі значень елементів матриці, вона може призвести зрушення, розтягування або ще що-небудь. Ось що трапляється, коли ви багаторазово перемножуєте матриці обертання: матриця поступово починає виробляти зрушення і розтягування - після десяти хвилин роботи джойстиком, ви почнете неозброєним оком бачити спотворення в сцені. Оскільки користувач не має контролю над кодом, то він і не має способу відкоригувати спотворення.
Існує механізм "виправлення" матриці. Якщо ви читали мою статтю "Подружитеся з матрицями" ( "Matrices can be your Friends"), то ви розумієте, що ви можете нормалізувати три ряди матриці і вирівняти осі так, що вони стануть взаємно перпендикулярними - і ваша матриця знову стане "чисто обертальної ". Проробіть цю операцію в кожному кадрі або хоча б раз за кілька кадрів і ніхто не помітить ніяких спотворень.
Це працює цілком прийнятно, але я помітив, що багато людей знаходять це погано працюючим в системах, в яких поточний обертання відомо тільки у вигляді матриці. Якщо, наприклад, ви хочете створити компас, то вам необхідно знати, як орієнтована сцена. Але отримати цю інформацію безпосередньо з матриці можна. Тому багато людей конвертують матрицю назад, в кути Ейлера.
Якщо ви збираєтеся зробити що - небудь на зразок цього, то тоді ви можете робити і так:
Ви економите час, відмовившись від заплутаною нормалізації матриці, так як в кожному кадрі матриця відновлюється з ейлерову кутів. Однак, треба вжити заходів обережності, оскільки при конвертації матриці в Ейлерови кути ми отримуємо кілька "невідомих" значень при деяких орієнтаціях. Якщо позиція точки спостереження повернута на 90 градусів за кутом рискання, тоді зміни в кутах крену і тангажа зроблять правильний ефект. Отже, хоча і крен + тангаж == constant, різниця між ними нечітка і може повністю змінюватися від кадру до кадру. Якщо ви використовуєте тільки кут рискання для створення, скажімо, компаса, тоді чекайте неймовірних свідчень при деяких кутах Крену і тангажу. Таке трапляється і в реальному житті - так що особливо не переживайте про це!
На додаток до ейлерову кутах і матричних перетворень, існує ще один, дуже зручний спосіб представлення обертань. Кватерніон (як це і видно за назвою) являє собою набір з чотирьох чисел, які можуть бути представлені як вектор (X, Y, Z) одиничної довжини і кут обертання навколо цього вектора. Комбінації переміщень вектора і зміни кута кодують будь-які можливі обертання.
Це та ж сама ідея, що і в чотирьох параметрах функції glRotate (хоча і чотири числа кватерниона, для зручності в розрахунках, мають інше значення).
Я не збираюся тут детально описувати кватерніони, але вони дійсно зручні у використанні, оскільки (подібно матрицями) кодують кути незалежно від порядку введення, і. до того ж, (подібно ейлерову кутах) можуть ЄДИНИМ ЧИНОМ закодувати обертання, і, отже набагато менш сприйнятливі до помилок округлення (хоча вам необхідно перевіряти векторну частину кватерниона - його довжина завжди повинна залишатися рівною 1.0 - це набагато простіше зробити, ніж нормалізувати матрицю).
Інші статті для програмістів.