Я обертаю предмет на двох осях, то чому він продовжує крутитись навколо третьої осі?


38

Я бачу, що досить часто виникають питання, які мають цю основну проблему, але всі вони потрапляють у деталі певної функції чи інструменту. Ось спроба створити канонічну відповідь, на яку ми можемо звернутися до користувачів, коли це з’явиться - з великою кількістю анімованих прикладів! :)


Скажімо, ми робимо камеру від першої особи. Основна ідея полягає в тому, що ви повинні позіхати, щоб дивитись вліво-вправо, а нахил - дивитися вгору-вниз. Тому ми пишемо трохи такого коду (використовуючи Unity як приклад):

void Update() {
    float speed = lookSpeed * Time.deltaTime;

    // Yaw around the y axis using the player's horizontal input.        
    transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f);

    // Pitch around the x axis using the player's vertical input.
    transform.Rotate(-Input.GetAxis("Vertical") * speed,  0f, 0f);
}

або можливо

// Construct a quaternion or a matrix representing incremental camera rotation.
Quaternion rotation = Quaternion.Euler(
                        -Input.GetAxis("Vertical") * speed,
                         Input.GetAxis("Horizontal") * speed,
                         0);

// Fold this change into the camera's current rotation.
transform.rotation *= rotation;

І це здебільшого працює, але з часом погляд починає кривитись. Камера, здається, повертається на осі рулону (z), хоча ми лише сказали їй обертатись на x і y!

Анімований приклад того, як камера від першої особи нахиляється набік

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

Анімований приклад того, що земна куля нахиляється набік

Ця ж проблема - через деякий час Північний полюс починає бродити вліво або вправо. Ми робимо вхід на дві осі, але ми отримуємо це заплутане обертання на третій. І буває, чи застосовуємо всі наші обертання навколо локальних осей об'єкта чи глобальних осей світу.

У багатьох двигунах ви також побачите це в інспекторі - поверніть об'єкт у світі, і раптом цифри змінюються на осі, яку ми навіть не торкалися!

Анімований приклад, що показує, що маніпулює об'єктом поруч із зчитуванням його кутів повороту.  Кут z змінюється, навіть якщо користувач не маніпулював цією віссю.

Отже, це помилка в двигуні? Як ми можемо сказати програмі, що ми не хочемо, щоб вона додала додаткову ротацію?

Чи має це щось спільне з кутами Ейлера? Чи варто замість цього використовувати кватерніони або матриці обертання чи базові вектори?



1
Я знайшов відповідь на наступне питання також дуже корисною. gamedev.stackexchange.com/questions/123535/…
Тревіс Петтрі

Відповіді:


51

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

Ви відкрили реальний факт про те, як працює обертання в тривимірному просторі, і це відходить від нашої інтуїції щодо інших перетворень, таких як переклад:

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

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

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

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

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

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

Отже, що ми з цим робимо?

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

Ось та сама послідовність кроку нахилу, яка стала рулоном у наведеному вище прикладі, але тепер ми застосуємо наше позіхання навколо глобальної осі Y замість об'єкта

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

Таким чином, ми можемо виправити камеру від першої особи за допомогою мантри "Pitch Locally, Yaw Globalno":

void Update() {
    float speed = lookSpeed * Time.deltaTime;

    transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f, Space.World);
    transform.Rotate(-Input.GetAxis("Vertical") * speed,  0f, 0f, Space.Self);
}

Якщо ви поєднуєте обертання за допомогою множення, ви перевернете лівий / правий порядок одного з множень, щоб отримати той самий ефект:

// Yaw happens "over" the current rotation, in global coordinates.
Quaternion yaw = Quaternion.Euler(0f, Input.GetAxis("Horizontal") * speed, 0f);
transform.rotation =  yaw * transform.rotation; // yaw on the left.

// Pitch happens "under" the current rotation, in local coordinates.
Quaternion pitch = Quaternion.Euler(-Input.GetAxis("Vertical") * speed, 0f, 0f);
transform.rotation = transform.rotation * pitch; // pitch on the right.

(Конкретний порядок залежатиме від умов множення у вашому середовищі, але лівий = більш глобальний / правий = більше локальний - це загальний вибір)

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

// Construct a new orientation quaternion or matrix from Euler/Tait-Bryan angles.
var newRotation = Quaternion.Euler(totalPitch, totalYaw, 0f);
// Apply it to our object.
transform.rotation = newRotation;

або рівнозначно ...

// Form a view vector using total pitch & yaw as spherical coordinates.
Vector3 forward = new Vector3(
                    Mathf.cos(totalPitch) * Mathf.sin(totalYaw),
                    Mathf.sin(totalPitch),
                    Mathf.cos(totalPitch) * Mathf.cos(totalYaw));

// Construct an orientation or view matrix pointing in that direction.
var newRotation = Quaternion.LookRotation(forward, new Vector3(0, 1, 0));
// Apply it to our object.
transform.rotation = newRotation;

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

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

Анімований приклад земної кулі, що демонструє краще поворот

Обмеження

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

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

Наприклад, ми можемо використовувати локальні обертання для оновлення вектора "вперед", а потім використовувати цей вектор вперед разом з опорним вектором "вгору" для побудови нашої кінцевої орієнтації. (Використовуючи, наприклад, Quaternion Unity's.LookRotation метод , або вручну побудуючи ортонормальну матрицю з цих векторів) Керуючи вектором вгору, ми керуємо рухом або скручуванням.

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


1
Чи можу я запитати, як ви зробили gifs? Вони добре виглядають.
Балінт

1
@ Bálint Я використовую безкоштовну програму під назвою LICEcap - вона дозволяє записати частину екрану в gif. Потім я обрізую анімацію / додаю додатковий текст підпису / стискаю gif за допомогою Photoshop.
DMGregory

Це відповідь лише для Єдності? Чи є більш загальна відповідь у Мережі?
posfan12

2
@ posfan12 У прикладі коду використовується синтаксис Unity для конкретності, але ситуації та математика, що займаються, застосовуються однаково в усьому плані. Чи є у вас якісь труднощі із застосуванням прикладів до вашого оточення?
DMGregory

2
Математика вже присутня вище. Судячи з вашої редакції, ви просто масуєте неправильні її частини. Схоже, ви використовуєте визначений тут тип вектора . Це включає метод побудови матриці обертання з набору кутів, як описано вище. Почніть з матриці ідентифікації та виклику TCVector::calcRotationMatrix(totalPitch, totalYaw, identity)- це еквівалентно прикладу "відразу відразу Ейлера".
DMGregory

1

ПІСЛЯ 16 годин функціонування батьків та обертання lol.

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

Або іншими словами, мета полягає в тому, щоб обертати позіхання і нахил, не впливаючи на рулон.

Ось декілька, які насправді працювали в моїй заявці

У єдності:

  1. Створіть порожнє. Назвіть це ForYaw.
  2. Дайте, що дитина порожня, називається ForPitch.
  3. Нарешті, складіть куб і перемістіть його на z = 5 (вперед). Тепер ми можемо побачити, чого ми намагаємось уникнути, що буде скручувати кубик (випадковий «котик», поки ми позіхаємо і крокуємо) .

Тепер створюйте сценарії в Visual Studio, виконайте налаштування та закінчіть це в оновлення:

objectForYaw.Rotate(objectForYaw.up, 1f, Space.World);
    objectForPitch.Rotate(objectForPitch.right, 3f, Space.World);

    //this will work on the pitch object as well
    //objectForPitch.RotateAround
    //    (objectForPitch.position, objectForPitch.right, 3f);

Зауважте, що для мене це все зламалося, коли я намагався змінити порядок батьків. Або якщо я поміняю Space.World для дочірнього об'єкта кроку (батько Яу не проти повертати через Space.Self). Заплутаний. Я напевно міг би скористатися деякими поясненнями, чому мені довелося взяти локальну вісь, але застосувати її через світовий простір - чому Space.Self все руйнує?


Мені не ясно, чи призначено це відповісти на питання, чи ви звертаєтесь за допомогою до розуміння того, чому написаний вами код працює таким чином. Якщо це остання, вам слід опублікувати нове запитання, посилаючись на цю сторінку, якщо вам це подобається. Як підказка, me.Rotate(me.right, angle, Space.World)це те саме me.Rotate(Vector3.right, angle, Space.Self, що і ваші вкладені перетворення еквівалентні прикладам "Налаштування локально, потяг глобально" у прийнятій відповіді.
DMGregory

Трохи обох. Частково поділитися у відповіді (наприклад, ось як саме це працювало на мене). А також ввести питання. Цей натяк змусив мене задуматися. Дуже дякую!
Єх

Дивовижно це не було схвалено. Після години читання вичерпних відповідей, які, хоча навчальний процес все ще не працював, проста відповідь на використання осі обертання об'єктів замість загальної осі Vector3.right - все, що потрібно, щоб обертання було закріплено на об'єкті. Неймовірно, що я шукав, був похований тут з 0 голосами і на 2-й сторінці Google багато-багато подібних питань.
user4779
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.