Unity Chapter 5-3. C# 프로그래밍 [중급] (1/2) : 회전과 쿼터니언
카테고리: Unity Lesson 1
태그: C Sharp Math Unity Game Engine
인프런에 있는 이제민님의 레트로의 유니티 C# 게임 프로그래밍 에센스 강의를 듣고 정리한 필기입니다. 😀
🌜 [레트로의 유니티 C# 게임 프로그래밍 에센스] 강의 들으러 가기!
Chapter 5. C# 프로그래밍 : 중급 (1/2)
🔔 회전과 쿼터니언 (Quaternion)
물체의 회전
을 쿼터니언
을 통해서 지정하기 Transform 컴포넌트의 Rotation 값을 Vector3로 지정하는 것 처럼 보이지만 사실 쿼터니언으로 지정하고 Vector3인 것처럼 눈속임 하는 것이다. 쿼터니언이 너무 복잡해서 유니티에서 Vector3로 Rotation을 지정하는 것처럼 보이게 해둔 것.
transform.rotation = new Vector3(30, 30, 30); // 에러 💥
빨간 줄이 그어지면서 에러 가 발생한다. 그 이유는 사실 rotation은 Vector3를 사용하지 않기 때문이다. 쿼터니언을 사용하나 유니티에서 그냥 간단하게 Vector3를 사용하는 것처럼 보이게 해둔 것.
- 큐브 오브젝트를 만든 후 Rotation 값은 60, 60, 60으로 해주자.
- 📜
SetRotation.cs
스크립트를 만들어 큐브에 붙여주자
Vector3를 쿼터니언으로 변환하는 방법
transform.rotation = Quaternion.Euler(new Vector3(30, 30, 30)); // 저장하고 플레이 하면 rotation 값이 (30, 30, 30)으로 변경된다.
Quaternion.Euler(Vector3)
- Vector3를 매개변수로 넘겨 받아 그 Vector3를 쿼터니언 데이터 타입으로 변환해 리턴시켜주는 함수다.
- 참고로 오일러(Euler)는 3차원 벡터로 공간의 회전을 표시할 수 있도록 체계를 만든 수학자 이름.
- Vector3를 매개변수로 넘겨 받아 그 Vector3를 쿼터니언 데이터 타입으로 변환해 리턴시켜주는 함수다.
쿼터니언은 오일러 각의 문제점을 보완한다.
오일러 각
- 3차원 공간에서 물체의 방향과 회전을 정의하기 위해 오일러가 고안한 3개의 각도.
- 3차원 벡터(Vector3)를 사용한다.
오일러 각
에 의하면 3차원 공간에 놓인 물체의 방향은 3번의 "순차적인" 회전을 통해 얻을 수 있다.- 물체를 회전하면 그 물체의 좌표계(Local)도 같이 회전한다.
- 예를 들어
- 어떤 물체 P를 각 x, y, z 이렇게 3개의 방향으로 회전시킬때 먼저 x 방향으로 회전시키면 P_X 상태가 된다.
- 순차적으로 그 다음 y 방향으로 회전시키면 P_Y 상태가 된다.
- P_Y는 P에 대해서 y 방향으로 회전한 것이 아니라 x방향으로 한번 회전한 상태인 P_X에 대해서 y 방향으로 회전한 것이다.
- y축으로 돌리는 순간 x축도 함께 돌게 됨.
- 순차적으로 그 다음 z 방향으로 회전시키면 P_Z 상태가 된다.
- P_Z는 P에 대해서 z 방향으로 회전한 것이 아니라 x방향으로 한번, 이어서 y방향으로 회전한 상태인 P_Y에 대해서 z 방향으로 회전한 것이다.
- z축으로 돌리는 순간 x, y축도 함께 돌리게 됨.
- 즉, 순수하게 P에 대해서 x, y, z 방향으로 회전한 것이 아니라는 것이다.
- 세 축이 회전에 있어서 서로 종속적임. 각 축에 대해 독립적으로 계산할 수가 없다.
- 예를 들어
오일러 각의 문제점 : 짐벌락
오일러 각은 회전할 때 3개의 축을 나눠서 순차적으로 회전시킬 수 밖에 없기 때문에 세 축끼리 종속적이라는 문제가 생긴다. 이는 곧 짐벌락
현상을 일으킨다.
짐벌락
이란 특정 축이 회전했을 때 나머지 두 축이 겹쳐져서 한 축의 역할이 사라지는 현상이다. 사진에서 초록색 축을 먼저 360도로 한바퀴 회전시킨 후 빨간색 축을 순차적으로 뒤이어 90도 회전하면 이전에 이미 회전했던 초록색 축이 이 과정에 종속되어 같이 90도로 회전된다. 그러면서 초록색 축이 파란색 축과 완전히 겹쳐버리고 축이 2개 밖에 없어지는 현상이 되버린다. 하나의 축의 역할이 사라져 버리는 것이다.- 물체를 90도 단위로 회전해서 이전에 x축이 있던 자리에 회전한 후 z축이 온다면 한 축의 정보가 사라져 버려 2차원처럼 되버림.
- 어떤 방향으로 회전하든
90도
로 회전하면 축이 겹칠 수 밖에 없다.- 겹쳐버리니까 두 축중 어떤걸 기준으로 해야할지 구분이 안가서 에러가 발생함.
- 그래서 옛날 게임들은 90도로 회전하는 것을 피하기 위해 89도, 91도 이런식으로 설정했었다.
쿼터니언
- 오일러 각의 문제점을 보완하여 짐벌락을 피한다.
- 오일러각과 다르게 3개의 축을 동시에 회전시킨다.
- 즉 다른 축에 대한 종속성이 서로 없다.
- 4차원 벡터를 사용한다. 정확히 말하자면 3개의 허수와 4개의 실수를 사용한다. (i, j, k는 허수.)
- 벡터를 하나 더 사용하니 축이 겹쳐도 3차원 공간에선 문제가 없다.
- 그리고 유니티에선 Vector3로 쿼터니언을 사용할 수 있게끔 편의를 제공한다.
- 벡터를 하나 더 사용하니 축이 겹쳐도 3차원 공간에선 문제가 없다.
\[q = w + xi + yj + zk\]
스칼라값 하나와 각 요소에 허수인 계수가 붙는 3차원 벡터 이렇게 총 4차원 벡터로 표현한다.
\[q = (w, (xi, yj, zk))\]
진짜 어렵다.. 사실 이렇게 써놓고도 뭔소린지 이해가 잘 안간다 휴 ㅠ ㅠ…
Quaternion 함수들 소개
1. 위에 소개된 Quaternion.Euler
함수
- Vector3를 매개변수로 넘겨 받아 그 Vector3를 쿼터니언 데이터 타입으로 변환해 리턴시켜주는 함수다.
2. Quaternion.LookRotation
함수
- 벡터를 매개변수로 넣어주면 오브젝트가 그 벡터의 방향을 쳐다보게끔 자기 자신의 방향을 회전한다.
transform.rotation = Quaternion.LookRotation(new Vector3(0, 1, 0));
- 절대 좌표 (0, 1, 0)을 쳐다보라는게 아니라 현재 오브젝트 위치(출발)로부터 (0, 1, 0)벡터만큼 더한 곳(목적지)을 쳐다 보라는 의미에 가까운 듯!
- 위에
벡터의 덧셈
참고! - 애초에
벡터
는 절대적 위치는 중요치 않음. - Vector2(0, 1, 0)이니까 딱 정수리, 위를 쳐다보게 된다.
- 위에
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SetRotation : MonoBehaviour
{
public Transform targetTransform;
void Start()
{
Vector3 direction = targetTransform.position - transform.position;
Quaternion newRotation = Quaternion.LookRotation(direction);
transform.rotation = newRotation;
}
// Update is called once per frame
void Update()
{
}
}
- 어떤 오브젝트가 있는 위치를 바라보게끔 회전하기
public Transform targetTransform
- public이므로 유니티에서 슬롯이 열린다.
- 바라볼 대상이 되는 *오브젝트*를 이 targetTransform 슬롯에 드래그 앤 드롭해서 넣어준다.
- “바라볼 대상의 Transform 컴포넌트”를 넣어야 하지 않나 싶었는데 Transform 컴포넌트를 가진 오브젝트라면 Transform 타입 슬롯이라도 오브젝트를 넣을 수 있다.
- Rigidbody 타입 변수 슬롯이여도 Rigidbody 해당 컴포넌트를 가진 오브젝트라면 오브젝트를 드래그 앤 드롭하여 넣을 수 있다.
- “바라볼 대상의 Transform 컴포넌트”를 넣어야 하지 않나 싶었는데 Transform 컴포넌트를 가진 오브젝트라면 Transform 타입 슬롯이라도 오브젝트를 넣을 수 있다.
벡터의 뺄셈
- 목적지의 위치 - 출발지의 위치 = 움직여야할 방향과 거리를 담은 벡터
- Vector3 direction = targetTransform.position - transform.position;
- 바라볼 오브젝트의 위치에서 내 위치를 빼주면 바라 볼 오브젝트까지의 방향과 거리를 담은 벡터를 구할 수 있다. 이게 바로
direction
벡터.Quaternion newRotation = Quaternion.LookRotation(direction); transform.rotation = newRotation;
- 바라볼 오브젝트의 위치에서 내 위치를 빼주면 바라 볼 오브젝트까지의 방향과 거리를 담은 벡터를 구할 수 있다. 이게 바로
- 이제 나는 슬롯에 넣어준 오브젝트를 바라보게끔 회전한다.
- 내 rotation값을
direction
으로 대입함.- 더 정확히 말하자면 쿼터니언으로 변환한 direction값
- 내 rotation값을
- 내 위치(transform.position)에서
direction
벡터만큼 더한 위치 좌표(*targetTransform.position*)를 바라보게 된다.
3. Quaternion.Lerp
함수
두 개의 회전 값을 정하면 그 중간의 적당한 회전 값을 리턴
Quaternion aRotation = Quaternion.Euler(new Vector3(30, 0, 0));
Quaternion bRotation = Quaternion.Euler(new Vector3(60, 0, 0));
Quaternion targetRotation = Quaternion.Lerp(aRotation, bRotation, 0.5f);
transform.rotation = targetRotation;
targetRotation
엔aRotation
과bRotation
의 중간 값(0.5f)이 들어가게 된다. 즉 30도와 60도의 중간인 (45, 0, 0) 로 회전하게 된다.0.5f
면 딱 중간1.0f
면 bRotation을 그대로 따름0.0f
면 aRotation을 그대로 따름0.2f
면 aRotation에 좀 더 가깝게 회전
회전값끼리의 덧셈
- 이미 회전이 되있는 상태에서 더 회전하기
- (45, 0, 0) 회전값에서 (30, 0, 0)만큼 더 회전하고 싶을 때
1. transform.Rotate(Vector3) 함수 사용
Quaternion targetRotation = Quaternion.Euler(new Vector3(45, 0, 0));
transform.rotation = targetRotation; // 현재 회전값 (45, 0, 0)
transform.Rotate(new Vector3(30, 0, 0)); // 여기서 (30, 0, 0)만큼 더 회전
2. 벡터끼리 합하기, eulerAngle
eulerAngle
- 쿼터니언 타입을 Vector3로 바꿔준다.
- 현재 회전 벡터값에서 회전할 만큼의 벡터를 더해주고 쿼터니언으로 바꿔 transform.rotation에 저장하기.
Quaternion originalRotation = Quaternion.Euler(new Vector3(45, 0, 0));
Vector3 originalRotationInVector3 = originalRotation.eulerAngles; // 쿼터니언 👉 Vector 3 변환
Vector3 targetRotationInVector3 = originalRotationInVector3 + new Vector3(30, 0, 0);
Quaternion targetRotation = Quaternion.Euler(targetRotationInVector3); // Vector3 👉 쿼터니언
transform.rotation = targetRotation;
4. 쿼터니언끼리 합하기 (곱셈)
쿼터니언끼리 합할 때 +
를 사용하지 않고 *
곱하기를 사용한다. 쿼터니언은 행렬이기 때문이다. 행렬에선 대부분 어떤 성분을 증가시킬 때는 곱셈을 사용한다.
두 쿼터니언의 회전량을 더하려면, 두 쿼터니언 값을 곱해야 한다.
Quaternion originalRotation = Quaternion.Euler(new Vector3(45, 0, 0));
Quaternion plusRotation = Quaternion.Euler(new Vector3(30, 0, 0));
Quaternion targetRotation = originalRotation * plusRotation; // +을 쓰지않고 *을 써 곱한다. 행렬이기 때문에.
transform.rotation = targetRotation;
Local 회전, Global 회전
transform.rotation = Quaternion.Euler(new Vector3(45, 0, 0)); // 현재 회전값
transform.Rotate(new Vector3(30, 0, 0));
Rotate
함수는Local
축을 기준으로 회전한다.- 따라서 실행시켜보면 rotation 값 (Global기준) 이 (75, 0, 0)이 되있는게 아니라 이상한 값이 되있을 것이다.
rotation 값
자체는Global
기준이기 때문에..
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글남기기