Unity Chapter 5-3. C# 프로그래밍 [중급] (1/2) : 회전과 쿼터니언

Date:     Updated:

카테고리:

태그:

인프런에 있는 이제민님의 레트로의 유니티 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차원 벡터로 공간의 회전을 표시할 수 있도록 체계를 만든 수학자 이름.

쿼터니언은 오일러 각의 문제점을 보완한다.

오일러 각

  • 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개의 축을 나눠서 순차적으로 회전시킬 수 밖에 없기 때문에 세 축끼리 종속적이라는 문제가 생긴다. 이는 곧 짐벌락 현상을 일으킨다.

image

  • 짐벌락이란 특정 축이 회전했을 때 나머지 두 축이 겹쳐져서 한 축의 역할이 사라지는 현상이다. 사진에서 초록색 축을 먼저 360도로 한바퀴 회전시킨 후 빨간색 축을 순차적으로 뒤이어 90도 회전하면 이전에 이미 회전했던 초록색 축이 이 과정에 종속되어 같이 90도로 회전된다. 그러면서 초록색 축이 파란색 축과 완전히 겹쳐버리고 축이 2개 밖에 없어지는 현상이 되버린다. 하나의 축의 역할이 사라져 버리는 것이다.
    • 물체를 90도 단위로 회전해서 이전에 x축이 있던 자리에 회전한 후 z축이 온다면 한 축의 정보가 사라져 버려 2차원처럼 되버림.
  • 어떤 방향으로 회전하든 90도로 회전하면 축이 겹칠 수 밖에 없다.
    • 겹쳐버리니까 두 축중 어떤걸 기준으로 해야할지 구분이 안가서 에러가 발생함.
    • 그래서 옛날 게임들은 90도로 회전하는 것을 피하기 위해 89도, 91도 이런식으로 설정했었다.

쿼터니언

  • 오일러 각의 문제점을 보완하여 짐벌락을 피한다.
    • 오일러각과 다르게 3개의 축을 동시에 회전시킨다.
    • 즉 다른 축에 대한 종속성이 서로 없다.
  • 4차원 벡터를 사용한다. 정확히 말하자면 3개의 허수와 4개의 실수를 사용한다. (i, j, k는 허수.)
    • 벡터를 하나 더 사용하니 축이 겹쳐도 3차원 공간에선 문제가 없다.
      • 그리고 유니티에선 Vector3로 쿼터니언을 사용할 수 있게끔 편의를 제공한다.

\[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 해당 컴포넌트를 가진 오브젝트라면 오브젝트를 드래그 앤 드롭하여 넣을 수 있다.
    • 벡터의 뺄셈
      • 목적지의 위치 - 출발지의 위치 = 움직여야할 방향과 거리를 담은 벡터
      • Vector3 direction = targetTransform.position - transform.position;
        • 바라볼 오브젝트의 위치에서 내 위치를 빼주면 바라 볼 오브젝트까지의 방향과 거리를 담은 벡터를 구할 수 있다. 이게 바로 direction벡터.
          Quaternion newRotation = Quaternion.LookRotation(direction);
          transform.rotation = newRotation;
          
    • 이제 나는 슬롯에 넣어준 오브젝트를 바라보게끔 회전한다.
      • 내 rotation값을 direction으로 대입함.
        • 더 정확히 말하자면 쿼터니언으로 변환한 direction값
    • 내 위치(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;
  • targetRotationaRotationbRotation의 중간 값(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기준이기 때문에..


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

댓글남기기