Unity에서는 오일러 각과 쿼터니언을 모두 사용하여 회전과 방향을 나타낼 수 있습니다. 표현은 둘 다 동일하지만 용도와 제한 사항은 서로 다릅니다.
보통 씬에서는 방향을 오일러 각으로 표시하는 트랜스폼 컴포넌트를 사용하여 오브젝트를 회전합니다. 그러나 Unity는 회전과 방향을 내부적으로 쿼터니언으로 저장하므로 짐벌 락으로 이어질 수도 있는 더 복잡한 모션에 유용할 수 있습니다.
오일러 각
트랜스폼 좌표에서 Unity는 벡터 프로퍼티 Transform.eulerAngles X, Y, Z를 사용하여 회전을 표시합니다. 노멀 벡터와는 달리 이 값은 X, Y, Z 축에 대한 실제 회전 각도(단위: 도)를 나타냅니다.
오일러 각 회전은 3개의 축을 중심으로 3개의 개별 회전을 수행합니다. Unity는 Z축, X축, Y축을 중심으로 오일러 회전을 순차적으로 수행합니다. 이 회전 방법은 외부 회전입니다. 회전하는 동안 원래 좌표계가 변경되지 않습니다.
게임 오브젝트를 회전하려면 각 축이 트랜스폼 컴포넌트로 회전할 각의 크기를 입력할 수 있습니다. 스크립트로 게임 오브젝트를 회전하려면 Transform.eulerAngles를 사용합니다. 오일러 각으로 변환하여 계산하고 회전하려면 짐벌 락 문제가 발생할 위험이 있습니다.
짐벌 락
3D 공간에 있는 오브젝트가 자유도를 잃고 2차원 내에서만 회전할 수 있는 경우를 짐벌 락이라고 합니다. 두 축이 평행하면 오일러 각으로 짐벌 락이 발생할 수 있습니다. 스크립트에서 회전 값을 오일러 각으로 변환하지 않으면 쿼터니언을 사용하여 짐벌 락을 방지해야 합니다.
짐벌 락 문제가 있는 경우 Transform.RotateAround를 사용하면 오일러 각을 피할 수 있습니다. 각 축에 Quaternion.AngleAxis를 사용하여 함께 곱할 수도 있습니다(쿼터니언 곱셈은 각 회전에 차례로 적용됩니다).
쿼터니언
쿼터니언은 3D 공간에서 공간 방향과 회전의 고유한 표현을 위한 수학적 표기법을 제공합니다. 쿼터니언은 4개의 숫자를 사용하여 3D에서 단위 축을 중심으로 회전 방향과 각도를 인코딩합니다. 이 4개의 값은 각이나 각의 크기가 아닌 복소수입니다. 자세한 내용은 쿼터니언의 수학을 참조하십시오.
쿼터니언 회전은 계산에 있어 효율적이고 안정적이기 때문에 Unity는 회전 값을 쿼터니언으로 변환하여 저장합니다. 단일 쿼터니언은 축에 대해 360도보다 큰 회전을 나타낼 수 없기 때문에 에디터에서는 회전을 쿼터니언으로 표시하지 않습니다.
Quaternion 클래스를 사용하면 쿼터니언을 직접 사용할 수 있습니다. 회전에 스크립트를 사용하는 경우 Quaternion 클래스와 함수를 사용하여 회전 값을 만들고 변경할 수 있습니다. 회전에 오일러 각으로 값을 적용할 수 있지만 문제를 방지하기 위해 쿼터니언으로 저장해야 합니다.
오일러 각과 쿼터니언 간의 전환
다음 스크립트를 사용하여 쿼터니언과 오일러 각 간을 변환하고 원하는 방식으로 회전을 보고 편집할 수 있습니다.
- Quaternion.Euler 함수를 사용하여 오일러 각도에서 쿼터니언으로 변환할 수 있습니다.
- Quaternion.eulerAngles 함수를 사용하여 쿼터니언을 오일러 각으로 변환할 수 있습니다.
중요 클래스 - Quaternion
Unity는 Quaternion 클래스를 사용하여 게임 오브젝트의 3차원 방향을 저장하고, 이를 통해 한 방향에서 다른 방향으로의 상대 회전을 설명합니다.
이 페이지는 Quaternion 클래스의 개요, 그리고 이 클래스를 사용하는 스크립팅의 일반적인 용도에 대해 설명합니다. Quaternion 클래스의 모든 멤버에 대한 전체 레퍼런스는 Quaternion 스크립트 레퍼런스를 참조하십시오.
오일러 각(게임 오브젝트의 회전을 위해 인스펙터에 표시되는 X, Y, Z 값)과 Unity가 게임 오브젝트의 실제 회전을 저장하는 데 사용하는 기본 쿼터니언 값 간의 차이를 이해하고 있어야 합니다. 이 항목에 대한 기본 정보는 Unity의 회전 및 방향을 참조하십시오.
스크립트에서 회전 처리를 다루는 경우 Quaternion 클래스와 이 클래스의 함수를 사용하여 회전 값을 만들고 수정해야 합니다. 오일러 각을 사용할 수 있는 상황도 있지만, 다음 사항을 염두에 둬야 합니다. - 오일러 각을 처리하는 Quaternion 클래스 함수를 사용해야 합니다. - 회전의 오일러 값을 검색 및 수정하고 다시 적용하면 의도하지 않은 부작용이 발생할 수 있습니다(아래 참조).
쿼터니언 직접 생성 및 조정
Unity Quaternion 클래스의 여러 함수를 통해 오일러 각을 전혀 사용하지 않고도 회전을 만들고 조정할 수 있으며, 대부분의 일반적인 경우 이러한 함수들을 사용해야 합니다. 각각의 함수는 코드 샘플이 있는 스크립트 레퍼런스에 연결됩니다.
회전 생성:
회전 조작:
Transform 클래스도 쿼터니언 회전에 사용할 수 있는 다음의 메서드를 제공합니다.
오일러 각 사용
일부의 경우 스크립트에서 오일러 각을 사용하는 것이 더 좋습니다. 이 경우 각을 변수로 유지하고 회전에 오일러 각으로 적용하는 데만 사용해야 하고, 궁극적으로 쿼터니언으로 저장되어야 합니다. 오일러 각을 쿼터니언에서 검색해서 가져올 수 있지만, 검색해서 가져온 후 수정하고 다시 적용하면 문제가 발생할 수 있습니다.
이러한 문제가 정확히 어떻게 발생하는지에 대한 자세한 내용은 eulerAngles 스크립트 레퍼런스 페이지를 참조하십시오.
아래에는 흔히 발생하는 잘못된 방법에 대한 몇 개의 예제가 있습니다. 설명을 위해 X축을 중심으로 게임 오브젝트를 초당 10도씩 회전시키려는 가상의 예제를 사용합니다. 다음 2코드는 잘못된 방법입니다.
// rotation scripting mistake #1
// the mistake here is that we are modifying the x value of a quaternion
// this value does not represent an angle, and does not produce desired results
void Update ()
{
var rot = transform.rotation;
rot.x += Time.deltaTime * 10;
transform.rotation = rot;
}
// rotation scripting mistake #2
// Read, modify, then write the Euler values from a Quaternion.
// Because these values are calculated from a Quaternion,
// each new rotation might return very different Euler angles, which might suffer from gimbal lock.
void Update ()
{
var angles = transform.rotation.eulerAngles;
angles.x += Time.deltaTime * 10;
transform.rotation = Quaternion.Euler(angles);
}
그리고 다음은 스크립트에서 오일러 각을 올바르게 사용하는 예입니다.
// Rotation scripting with Euler angles correctly.
// Store the Euler angle in a class variable, and only use it to
// apply it as an Euler angle, but never rely on reading the Euler back.
float x;
void Update ()
{
x += Time.deltaTime * 10;
transform.rotation = Quaternion.Euler(x,0,0);
}
오일러각과 쿼터니언각의 차이
새로운 씬을 하나 만들고 Models>Player폴더의 Player를 끌어다 놓습니다. (Prefab은 이미 다른 스크립트가 들어 있어 안됩니다.) 이 강좌가 처음이 신분들은 아무 게임오브젝트를 만들어 놓습니다.
아래 스크립트를 끌어다 적용합니다.
using UnityEngine;
public class ExampleScript : MonoBehaviour {
float rotationSpeed = 45;
Vector3 currentEulerAngles;
Quaternion currentRotation;
float x;
float y;
float z;
void Update() {
if (Input.GetKeyDown(KeyCode.Alpha1)) x = 1 - x;
if (Input.GetKeyDown(KeyCode.Alpha2)) y = 1 - y;
if (Input.GetKeyDown(KeyCode.Alpha3)) z = 1 - z;
if(Input.GetKeyDown(KeyCode.Alpha0)) {
x = 0; y = 0; z = 0;
currentEulerAngles = Vector3.zero;
}
//modifying the Vector3, based on input multiplied by speed and time
currentEulerAngles += new Vector3(x, y, z) * Time.deltaTime * rotationSpeed;
//moving the value of the Vector3 into Quanternion.eulerAngle format
currentRotation.eulerAngles = currentEulerAngles;
//apply the Quaternion.eulerAngles change to the gameObject
transform.rotation = currentRotation;
}
void OnGUI() {
GUIStyle style = new GUIStyle();
style.fontSize = 24;
// Use eulerAngles to show the euler angles of the quaternion stored in Transform.Rotation
GUI.Label(new Rect(10, 0, 0, 0), "Rotating on X:" + x + " Y:" + y + " Z:" + z, style);
//outputs the Quanternion.eulerAngles value
GUI.Label(new Rect(10, 25, 0, 0), "CurrentEulerAngles: " + currentEulerAngles, style);
//outputs the transform.eulerAngles of the GameObject
GUI.Label(new Rect(10, 50, 0, 0), "GameObject World Euler Angles: " + transform.eulerAngles, style);
}
}
1,2,3을 누르면 해당되는 x,y,z축으로 무한히 회전합니다.
0을 누르면 초기화 됩니다.
Play시켜보면 오일러나 GameObject World Euler Angle 둘 다 (0,0,0)입니다.
처음 1만 눌러 90도 전에 다시 1을 눌러 세워 보겠습니다. 아직 2개의 값은 비슷합니다.
다시1을 눌러 90도가 넘은면 1을 눌러 세워 봅니다. World Euler Angles.x가 줄어들고 y,z가 180됩니다.
일단 1,2,3을 토글하면서 오일러값과 쿼터니언의 값 변화를 보세요 쿼터니언값의 변화는 상식적이지 않습니다. 연속적인 회전이 안됩니다.
0을 눌러 초기화 합니다
우선 1만 눌러 적당히 90을 만들어 아래로 눕게 합니다. 그다음 2,나 3을 눌러보면 둘다 Y축으로 돌아가는데 하나는 반대로 돌아갑니다.
이게 짐벌록입니다. x축을 90돌면서 차일드인 Z축이 같이 돌아 Y축과 겹쳐졌기 때문입니다.
다른 축으로도 실험해보왔지만 일어나지 않습니다. 이건 유니티의 x,y,z계산 우선순위때문인듯 합니다.
재미있는건 1을 눌러 90도쯤 변화시켜 눕힌후 2를 눌러 y축을 회전시킨후 3을 누르면 각도는 열심히 변하는데 실제 게임오브젝트는 멈춰있습니다 y축과 z축의 방향이 서로 반대라 합쳐서 0이 되기 때문입니다. 꼭해보시기 바랍니다. 희안합니다.
사진으로 간단하게 설명하겠다.
우선 다음 사진과 같이 x, y ,z 축을 가진 오브젝트가 있다고 해보자.
저 화살표가 가리키는 방향을 계속 유의하자. 이 오브젝트의 x(빨간축) 축을 90도 회전시켜보겠다.
x축(빨간축)으로 90도 회전시킨 모습이다. 화살표를 보면 오른쪽 방향을 가리키고 있다.
여기까지는 문제가 없다.
자, 그다음 Y축(초록색)으로 90도 회전시켜보겠다.
Y축으로 90도 회전시키니 다음과 같은 모습이 된다.
Z 축과 X 축이 한축으로 합쳐지면서 한축에 대한 계산이 불가능해집니다.
이러한 현상을 바로 '짐벌락(Gimbal-lock)' 이라고 한다.
이러한 짐벌락 현상이 생기는 이유는 오일러 앵글이 자체적으로 설정되어있는 순서로 해당 축들을 개별적으로 계산하기 때문이다.
Unity에서는 오일러 앵글이 X, Y, Z 순서로 계산된다.
세 개의 축을 동시에 계산하지 않고 각 축을 독립적으로 판단하기에 다음과 같이 어쩌다가 겹쳐버리는 현상이 발생하는 것이다. 이렇게 축이 겹쳐버리면 한 축에 대해서는 계산이 불가능하기에, 정확한 각도 계산이 불가능하다.
Unity로 2D 게임을 제작할 때는 오일러 각으로도 각도 구현에 문제가 없지만, 모든 각도를 통제해줘야 하는 3D 게임 같은 경우에는 오일러 앵글만으로는 구현에 한계가 있다.
오일러 각의 이러한 문제를 해결하기 위해 나온 것이 바로 쿼터니언(Quaternion)이다.
쿼터니언(Quaternion)
쿼터니언은 각 축을 한꺼번에 계산하기 때문에 짐벌락 문제가 발생하지 않는다.
Euler angle 과는 다르게 쿼터니언은 4개의 성분(x, y, z, w)으로 이루어져 있다.
해당 성분은 벡터(x, y, z)와 스칼라(w)를 의미한다.
쿼터니언은 내부가 수학적으로 복잡하게 구현되어있어 이를 제대로 이해하지 못한다면 자유자재로 다루기는 상당히 까다롭다.
쿼터니언은 방향(orientation)과 회전(rotation) 둘을 다 표현할 수 있다.
하지만 쿼터니언의 회전은 한 orientation 에서 다른 orientation 으로 측정하기에 180 보다 큰 값을 표현할 수 없다는 단점이 있다.
이 점이 쿼터니언을 직관적으로 이해할 수 없는 큰 이유 중 하나이다.
다행히 유니티에는 이러한 쿼터니언을 간단하게 사용 가능하도록 만들어진 함수들이 다양하게 존재한다.
유니티 공식 문서에도 쿼터니언은 쓰기 어려우니 자기들이 만든 함수를 사용하도록 권장한다.
쿼터니언을 다루기 위해 사용되는 대표적인 함수들의 사용법을 정리해봤다.
public static Quaternion Euler(float x, float y, float z);
유니티에서는 Quaternion.Euler 함수를 통해서 오일러각을 쿼터니언으로 변경시켜 사용한다.
해당 함수 인자에 오일러각을 넣으면 쿼터니언으로 변환된 값을 반환시켜준다.
Euler 함수 사용법
transform.roation = Quaternion.Euler(new Vector3(120,60,100));
float x;
void Update ()
{
x += Time.deltaTime * 10;
transform.rotation = Quaternion.Euler(x,0,0);
}
public static Quaternion LookRotation(Vector3 forward, Vector3 upwards = Vector3.up);
두 번째 인자인 upwards upwards는 Vector3.up 으로 디폴트 인자가 세팅되어 있다.
첫 번째 인자에 방향벡터를 입력하면 해당 자기 위치기준에서의 해당 방향벡터를 바라보게 된다.
어떠한 타겟을 향해 회전시키고 싶다면 다음과 같이 사용하면 된다.
Vector3 direction = (traget.position - this.transform.position).normalized;
Quaternion lookRotation = Quaternion.LookRotation(direction);
this.transform.rotation = lookRotation;
public static Quaternion Slerp(Quaternion a, Quaternion b, float t);
/from/과 /to/사이를 /t/로 구형보간 합니다.
Quaternion.Slerp 함수는 두 쿼터니언의 중간값을 리턴 시켜준다.
Slerp 함수를 사용할 때 종종 세 번째 인자인 t 값에 대하여 헷갈리는데,
Slerp 함수는 lerp 함수에서 쓰이는 선형보간법이 아닌 구면선형보간법 기반으로 되있다는 점을 명심해야한다.
Lerp 함수와 Slerp 함수는 세 번째 인자에 따른 반환값이 다르다.
Slerp를사용하고 싶다면 선형보간법과 구면선형보간법의 차이를 숙지해야 원하는 각도 변환을 연출할 수 있을 것이다.
Lerp, Slerp 차이.
Lerp와 Slerp의 차이를 간단하게 그림으로 나타내면 다음과 같다.
초록점에서 빨간 점으로 이동할 때 Lerp는 직선으로 Slerp는 곡선으로 보간 된다.
transform.rotation = Quaternion.Slerp(A.transform.rotation, B.transform.rotation, Time.deltaTime);
public static Quaternion FromToRotation(Vector3 fromDirection, Vector3 toDirection);
/fromDirection/에서 /toDirection/으로 회전한 rotation을 생성합니다.
FromToRotaion 함수는 fromDirection 의 방향벡터를 toDirection 으로 회전한 쿼터니언을 반환한다.
rotation(0,0,0)
rotation 값이 (0,0,0)인인 큐브이다.
transform.rotation = Quaternion.FromToRotation(Vector3.up, Vector3.right);
다음 함수를 적용 시키면
큐브의 up vector(초록색 화살표)가 옆으로 꺽여있다.
카메라 위치 때문에 반대로 보이지만 큐브가 오른쪽으로 90도 회전한 모습이다.
FromToRotation 함수는 중심축을 첫 번째 인자로 넣고 회전하고 싶은 방향벡터를 두 번째 인자로 넣어서 사용하면 될듯싶다.
참조 : 유니티매뉴얼 , https://hub1234.tistory.com/21
'유니티스크립팅 > 유니티매뉴얼' 카테고리의 다른 글
카메라 - 유니티 (0) | 2023.03.03 |
---|---|
마우스좌표를 월드좌표로 - 2D (0) | 2023.03.02 |
Vector3 (0) | 2023.02.25 |
변수와 함수 (0) | 2023.02.25 |
Scripts as Behaviour Components (0) | 2023.02.25 |