9장 방향, 크기, 회전

29535 단어 TILmathUnityTIL

9.1 벡터 수학

벡터의 정의

벡터의 실질적 정의는 분야에 따라 조금씩 다르게 적용된다.

  • 물리학자, 공학자, 게임 개발자에게 벡터는 공간상의 화살표로 사용된다.
    예) (10,5,0)은 오른쪽으로 10, 위쪽으로 5만큼 이동하는 화살표
  • 데이터를 다루는 프로그래머에게 벡터는 나열된 숫자 데이터를 묶는 단위이다.
    예) (172, 64)은 키 172, 몸무게 64를 나타내는 데이터
  • 수학자에게는 벡터 연산을 만족하고 정해진 개수의 원소를 가지면 무엇이든 벡터이다.

어떤 벡터 집합에 속한 벡터는 해당 벡터 집합에서 요구하는 개수의 원소를 반드시 가져야 한다. 즉, 3D 벡터인 Vector3 타입은 무조건 x,y,z에 대응하는 세 원소를 가져야 한다.
벡터는 공간에서 물체의 좌표를 나타내는 용도로 사용할 수 있다. 하지만 이것이 벡터의 전부는 아니다. 사실 물체의 절대적인 위치를 표현하는 벡터는 '특수한 조건을 사용한 벡터'이다. 벡터의 값은 어떤 방향으로 얼마만큼 가고 있는지 표현하는 것이며, 출발점이 어디인지는 정하지 않기 때문이다.

절대 위치와 상대 위치

벡터는 방향크기 magnitude를 가진다. 이것은 화살표의 방향과 길이로 비유할 수 있다. 예를 들어 (1, 1)이라는 Vector2는 게임 월드에서 두 가지 의미를 가진다.

  • 상대 좌표 : (내가 어디 있는지는 모르지만) 현재 좌표에서 (1, 1)만큼 더 가려고 한다. (상대적인 방향과 크기)
  • 절대 좌표 : 게임 세상 속에서 나의 좌표가 (1, 1)이다. (절대적인 좌표)

벡터의 크기

벡터는 방향과 크기를 가진다. (-3, 4)의 방향은 점 (-3, 4)로 향하는 화살표의 방향이다. (-3, 4)의 크기는 화살표의 길이에 해당한다. 화살표의 길이는 화살표를 빗변으로 하는 직각삼각형을 그린 뒤 피타고라스의 정리를 사용하여 구할 수 있다.

피타고라스의 정리 https://mathbang.net/130

(-3, 4)의 크기 = (3)2+42=5\sqrt{(-3)^2 + 4^2} = 5

벡터의 크기 = x2+y2+z2\sqrt{x^2 + y^2 + z^2}

벡터의 크기는 화살표의 길이에 대응하므로 음수가 될 수 없다.
방향은 같지만 크기가 다른 벡터를 셀 수 없이 많이 만들 수 있다.

벡터의 스칼라 곱

(-6, 8)의 크기 10은 (-3, 4)의 크기 5의 2배이다. 마찬가지로 (-6, 8)의 x 값 -6, y 값 8은 각각 (-3, 4)의 x 값 -3, y 값 4의 2배이다. 크기뿐만 아니라 개별 원소 모두 (-6, 8)이 (-3, 4)의 2배이다. 이 경우 (-6, 8)은 (-3, 4)의 2배곱이라고 할 수 있다.

이렇게 벡터에는 배수를 취하는 숫자를 곱할 수 있으며 이것을 스칼라 곱이라고 한다. 여기서 곱하는 수를 스칼라 Scala라고 부른다. 벡터에 스칼라 곱을 하면 다음과 같이 벡터의 각 원소에 개별적으로 곱셈이 적용된다.

(-3, 4) x 2 = (-3 x 2, 4 x 2) = (-6, 8)

벡터에 스칼라 곱을 하면 개별 x, y 원소의 값에 배수가 취해질 뿐만 아니라 벡터의 크기 또한 곱한 만큼 늘어난다.
스칼라 곱은 벡터의 크기는 늘리거나 줄일 수는 있어도 벡터의 방향은 변경할 수 없다.

방향 벡터

'A의 방향으로 B만큼의 속력'을 '벡터 A x 스칼라 B'로 표현했을 때 벡터 A의 크기가 1이 아니면 '벡터 A x 스칼라 B'로 표현된 속도의 실제 속력은 B보다 크거나 작은 문제가 있다.
이때 방향 벡터를 사용한다. 방향벡터는 크기가 1인 벡터로, 정규화된 벡터 Normalized Vector라고 부른다. 크기가 1이므로 방향은 같지만 크기가 서로 다른 벡터를 비교하는 기준으로 삼을 수도 있다.

벡터 정규화

만약 (1, -1)의 방향벡터를 구하고 싶다면 벡터의 시작점에서 반지름이 1인 원을 그려서 (1, -1)의 원 바깥 부분을 잘라낸다. 이런 식으로 (방향은 유지하면서) 벡터의 크기를 1로 만드는 것이 벡터 정규화이다.
방향벡터는 순수하게 방향만 나타낸다. 방향벡터의 크기는 1이고, 1은 곱셈의 항등원이기 때문이다. 숫자 1에는 무엇을 곱해도 곱한 숫자가 결과로 나온다. 마찬가지로 크기가 1인 벡터는 어떠한 배수를 곱해도 곱한 배수가 그대로 계산된 벡터의 크기가 된다. 따라서 다음과 같이 (3, -3)을 순수하게 방향과 크기(속력)으로 나누어 표현할 수 있다.

(3, -3) = (0,71, -0.71) x 4.24

즉 (3, -3)이 표현하는 속도는 방향이 (0.71, -0.71)이고 속력이 4.24이다. 마찬가지로 (3, -3)과 방향은 같지만 10의 속력을 가지는 속도를 다음과 같이 표현할 수 있다.

(0.71, -0.71) x 10

즉, 어떤 벡터의 방향과 크기(속력)를 직관적으로 파악하기 쉽게 나누어 표현할 때는 '방향벡터 x 스칼라 곱'으로 표현할 수 있다.

벡터의 덧셈

벡터 간에는 덧셈이 가능하다. 두 벡터를 더하면 같은 자리의 성분끼리 합쳐진다.

벡터 A(3, 2) + 벡터 B(1, 6) = (3 + 1, 2 + 6) = (4, 8)

이렇게 두 벡터 A와 B를 더하는 행위를 공간상에서 보면 A만큼 이동한 상태에서 B만큼 더 이동한다는 의미이다.
이외에도 A를 위치, B를 이동을 표현하는 벡터로 사용하면 A + B = (4, 8)은 현재 위치 A(3, 2)에서 B(1, 6)만큼 더 이동한 도착 위치가 (4, 8)이라고 해석할 수 있다.

벡터의 뺄셈

B - A = A에서 B까지의 간격 = A에서 B까지의 방향과 거리

벡터또한 뺄셈이 가능하며, 몇 차원 벡터든 상관없이 B - A는 A에서 B로 가려면 어떤 방향으로 얼마만큼 가야 하는지 나타낸다. B에서 A를 빼면 같은 자리의 성분끼리 뺄셈이 된다.

벡터 B(-2, 8) - 벡터 A(1, 3) = (-2 -1, 8 -3) = (-3, 5)
B - A = (-3, 5)를 공간상에서 보면 A에서 B로 가는 방향과 거리를 표현한다는 것을 알 수 있다.

즉 다음과 같이 도착 지점 벡터에서 현재 위치 벡터를 빼면 현재 위치에서 도착 지점까지의 방향과 거리가 나온다.

목적지 - 현재 위치 = 현재 위치에서 목적지까지의 방향과 거리

벡터의 내적

벡터의 내적은 어떤 벡터를 다른 벡터로 투영하는 연산으로, 점 연산 dot product이라고 부르기도 한다. 두 벡터 A와 B사이의 내적은 A·B로 표현한다.
A·B를 구하는 공식

A · B - |A||B|cosθ

내적의 의미
벡터 A와 벡터 B의 내적 A · B는 벡터 B를 벡터 A의 지평선으로 끌어내리는 연산이다. 즉, 상대방 벡터가 자신의 지평선으로 끌어내려졌을 때 가지는 길이가 내적의 결과가 된다. 벡터의 내적을 사용하는 이유는 자신과 상대방 사시의 각도가 벌어질수록 투영된 길이가 짧아지는 현상을 이용해 둘 사이의 각도를 알 수 있기 때문이다.

두 방향벡터 사이의 각도에 따른 내적 결과

둘 사이의 각도내적 결과
+1
0° ~ 90°+1 ~ 0
90°0
90° ~ -180°0 ~ -1
180°-1

벡터의 외적

벡터의 외적 outer product은 두 벡터를 모두 수직으로 통과하는 벡터를 구하는 연산이며, 벡터 곱 vector product이나 교차 곱 cross product으로 부르기도 한다.
벡터 A를 벡터 B로 외적하는 표현은 A x B이다. 연산 결과가 숫자인 내적과 달리 외적은 연산 결과가 벡터이다.

그림에서 A x B = C, 즉 벡터 C는 벡터 A를 벡터 B로 외적한 결과이다. 그리고 벡터 C는 벡터 A와 벡터 B에 모두에 수직이다. 즉, 두 벡터 사이의 외적 결과는 두 벡터에 모두 수직인 벡터이다.

또한 A x B = C라고 했을 때 외적의 연산 순서를 뒤집은 B x A = -C이다. C와 -C 모두 A와 B에 수직인 벡터지만 방향이 반대이다. 즉, 외적의 연산 순서를 뒤집으면 결과 벡터의 방향이 반대가 된다.

벡터 C가 벡터 A와 벡터 B에 수직이면 벡터 C는 평면 L에도 수직이다. 어떤 평면 위의 두 직선과 수직인 직선은 해당 평면 위의 다른 모든 직선과도 수직이기 때문이다.

결론적으로 그림에서 벡터 C는 평면 L이 바라보고 있는 방향을 나타낸다. 그림의 벡터 C를 크기 1로 정규화하면 실제로 평면 L의 방향으로 상용할 수 있다. 이렇게 어떤 평면과 수직이라서 해당 평면의 얼굴이 향하는 방향을 나타내는 벡터를 노말 벡터 Normal Vector 또는 법선 벡터라고 한다.

9.2 유니티 C# 벡터

Vector 타입

유니티는 Vector2, Vector3, Vector4 타입을 지원한다. 새로운 벡터값을 만들기 위해서는 다음과 같은 형태로 생성자를 호출한다.

  • new Vector(x, y);
  • new Vector(x, y, z);
  • new Vector(x, y, z, w);
    생성된 벡터 값의 각 원소는 다음과 같이 개별적으로 접근 및 수정이 가능하다.
Vector3 a = new Vector(1, 2, 3);
a.x = 10;
a.y = 20;
a.z = 30;

Vector 타입은 유니티 라이브러리 내부에 클래스가 아니라 구조체로 선언되어 있다. 구조체는 클래스와 달리 참조 타입으로 동작하지 않고 값 타입으로 동작한다.

Vector3 연산

스칼라 곱
벡터에 배수를 취한다. (Vector3 * 스칼라)

Vector3 a = new Vector3(3, 6, 9);
a = a * 10; // a(30, 60, 90)

벡터의 덧셈과 뺄셈
두 벡터를 서로 더하거나 뺀다. (Vector3 + Vector3 / Vector3 - Vector3)

Vector3 a = new Vector3(2, 4, 8);
Vector3 b = new Vector3(3, 6, 9);

Vector3 c = a + b; // c(5, 10,17)
Vector3 d = a - b; // c(-1, -2, -1)

벡터 정규화 (방향벡터로 만들기)
해당 벡터와 방향은 같지만 크기가 1인 벡터를 생성한다. (Vector3.normalized)

Vector3 a = new Vector3(3, 3, 3);
Vector3 b = a.normalized; // b(0.57..., 0.57..., 0.57...)

벡터의 크기
벡터의 크기(길이)를 구한다. (Vertor3.magnitude)

Vector3 a = new Vector3(3, 3, 3);
float b = a.magnitude; // 5.19...

벡터의 내적
벡터 b를 벡터 a로 투영한 길이를 구한다. (Vector3.dot(a,b))

Vector3 a = new Vector3(0,1,0);
Vector3 b = new Vector3(1,0,0);
float c = Vector3.dot(a,b); // 0(수직인 벡터 내적)

벡터의 외적
두 벡터 모두에 수직인 벡터를 구한다. (Vector3.Cross(a,b))

Vector3 a = new Vector3(0,0,1); // 앞쪽(Z) 방향벡터
Vector3 b = new Vector3(1,0,0); // 오른쪽(X) 방향벡터
// 외적 결과 c는 앞쪽과 오른쪽 모두에 수직인 위쪽(Y) 방향벡터
Vector3 c = Vector3.Cross(a,b); // c(0,1,0)

Vector3 응용

두 지점 사이의 거리
destPos - currentPos의 크기를 구하여 두 지점 사이의 거리를 구할 수 있다. 두 벡터 사이의 거리를 계산하는 Distance() 메서드가 Vector3에 내정되어 있다.

Vector3 currentPos = new Vector3(1, 0, 1); // 현재 위치
Vector3 destPos = new Vector3(5, 3, 5); // 목적지

// currentPos에서 destPos로 향하는 벡터
Vector3 delta = currentPos - destPos;

// currentPos에서 destPos까지의 거리(크기)
float distance = delta.magnitude;

float EasyDistance = Vector3.Distance(currentPos, destPos);

현재 위치에서 목적지로 향하는 방향
currentPos에서 destPos로 향하는 방향 벡터는 destPos - currentPos를 정규화한 벡터이다.

Vector3 currentPos = new Vector3(1,0,1);
Vector3 destPos = new Vector3(5,3,5);

// currentPos에서 destPos로 향하는 방향 벡터
Vector3 direction = (destPos - currentPos).normalized;

// 목적지를 향해 10만큼 현재 위치에서 이동한 새로운 위치
Vector3 newPos = currentPos + direction * 10;

9.3 쿼터니언

쿼터니언 Quaternion은 회전을 나타내는 타입이다.

트랜스폼 컴포넌트의 멤버 변수 position(위치), localScale(로컬 스케일)의 타입은 Vector3이다. 하지만 트랜스폼 컴포넌트의 rotation(회전)의 타입은 Vector3가 아닌 Quaternion이다.

짐벌락(Gimbal Lock)

3D 벡터를 사용해 3D 회전을 나타내는 표현을 오일러각 Euler angle이라고 한다. 수학자 오일러가 고안한 이 표현법은 '물체가 회전하기 전의 좌표계'에서 '회전한 다음의 좌표계'로 바뀌려면 기존 좌표계를 세 번에 걸쳐 각각 얼마만큼 회전 시키면 되는지 세 각도로 물체의 회전을 표현한 것이다.
그런데 오일러각 체계에서는 회전을 한 번에 계산하지 않고, 세 번 나누어서 순서대로 계산하기 때문에 축이 겹치는 문제가 발생할 수 있다. 어떤 축의 회전은 다른 축의 회전에 영향을 준다. 따라서 오일러 각 체계에서 X, Y, Z 회전을 나누어 순서대로 처리할 때 앞선 회전이 그다음 적용될 회전에 영향을 끼친다.
이런 이유로 오일러각 체계에서는 특정한 경우 앞선 두 번의 회전에 의해 세 번째 회전의 자유도가 상시되어 세 축 중 한 축의 회전을 사용할 수 없게 되는 현상이 발생한다. 이것을 짐벌락이라고 한다.
짐벌락 현상이 일어나는 이유는 간단히 회전을 세 번 나누어 실행하는 도중에 축 두 개가 겹쳐 하나의 축으로 '잠금'되기 때문이다. 짐벌락 현상은 어떤 축을 90도 회전할 때 특히 자주 발생한다.

쿼터니언

쿼터니언은 원소로 x,y,z 외에도 w를 가지는 값으로, 사원수라고 부르기도 한다. 쿼터니언은 '한 번에 회전하는 방식'이기 때문에 오일러각과 달리 짐벌락 현상이 없으며 90도 회전을 제대로 표현할 수 있다.
하지만 쿼터니언은 복잡한 계산법을 기반으로 하므로 직관적으로 이해하고 사용하기 힘들다. 그래서 유니티 내부에서는 쿼터니언으로 처리하지만 인스펙터 창에서는 트랜스폼 컴포넌트의 회전을 Vector3(오일러각)으로 다루고 있다.
같은 이유로 유니티 코드 상에서 쿼터니언을 직접 생성하고, 내부를 수정하는 것을 허용하지 않는다. 대신 Vector3 타입의 오일러각이나 다른 참고 값을 사용해 쿼터니언을 쉽게 생성하는 메서드를 제공한다.

쿼터니언 예제

새로운 회전 데이터 생성
오일러각을 표현하는 Vector3 값에서 새로운 Quaternion 값을 생성할 수 있다.

Quaternion.Euler(Vector3);

회전을 Vector3(오일러각)으로 가져오기
Quaternion 타입은 저장된 회전값을 Vector3 타입의 오일러각으로 변환한 변수 eulerAngles를 제공한다.

Quaternion rotation = Quaternion.Euler(new Vector3(0, 60, 0));
Vector3 eulerRotation = rotation.eulerAngles; // (0,60,0)

현재 회전에서 '더' 회전하기
(30,0,0)만큼 이미 회전한 상태에서 (0,60,0)만큼 더 회전한 회전값을 생성

Quaternion a = Quaternion.Euler(30, 0, 0);
Quaternion b = Quaternion.Euler(0, 60, 0);

// a만큼 회전한 상태에서 b만큼 더 회전한 회전값을 표현
Quaternion rotation = a * b;

좋은 웹페이지 즐겨찾기