여러 벡터를 재료로 사용했을 때 어떤 방향들을 만들어낼 수 있는지 이해한다.
플레이어에게 다음 두 방향 벡터가 있다고 하자.
Forward = (1, 0, 0)
Right = (0, 1, 0)
플레이어가 앞으로 3만큼, 오른쪽으로 2만큼 이동한다면 이동 벡터는 다음과 같다.
3Forward + 2Right
= 3(1, 0, 0) + 2(0, 1, 0)
= (3, 2, 0)
이처럼 여러 벡터에 각각 숫자를 곱한 뒤 더하는 것을 선형 결합(Linear Combination)이라고 한다.
일반적으로 벡터 A, B에 대해
aA + bB
형태로 표현할 수 있다.
여기서 a, b는 스칼라다.
Span은 주어진 벡터들을 선형 결합해서 만들 수 있는 모든 벡터의 집합이다.
예를 들어
A = (1, 0, 0)
하나만 있다고 하자.
2A = (2, 0, 0)
-5A = (-5, 0, 0)
처럼 X축 방향의 벡터는 얼마든지 만들 수 있다.
하지만
(0, 1, 0)
같은 Y축 방향 벡터는 만들 수 없다.
따라서 A 하나가 만드는 Span은 X축 전체, 즉 하나의 직선이다.
A = (1, 0, 0)
B = (0, 1, 0)
두 벡터를 선형 결합하면
aA + bB = (a, b, 0)
가 된다.
a, b에 어떤 실수를 넣어도 되므로 XY 평면 위의 모든 벡터를 만들 수 있다.
따라서
Span(A, B) = XY 평면 전체
가 된다.
다음 세 벡터를 생각해보자.
A = (1, 0, 0)
B = (0, 1, 0)
C = (1, 1, 0)
벡터가 세 개이기는 하지만 모두 XY 평면에 있다.
특히
C = A + B
이므로 C가 새로운 방향을 추가해주지도 않는다.
따라서 이 세 벡터만으로는 Z축 방향을 만들 수 없으며 3D 공간 전체를 표현할 수 없다.
반면
A = (1, 0, 0)
B = (0, 1, 0)
C = (0, 0, 1)
이라면
aA + bB + cC = (a, b, c)
가 되므로 3D 공간의 모든 벡터를 만들 수 있다.
벡터들이 다음 식을 만족한다고 하자.
aA + bB + cC = 0
이 식이
a = b = c = 0
일 때만 성립한다면 A, B, C는 선형 독립(Linear Independent)이다.
반대로 0이 아닌 계수를 사용해도 위 식을 만족시킬 수 있다면 선형 종속(Linear Dependent)이다.
쉽게 말하면 선형 독립은
어떤 벡터도 나머지 벡터들의 선형 결합으로 만들어낼 수 없는 상태
라고 볼 수 있다.
예를 들어
A = (1, 0, 0)
B = (2, 0, 0)
에서는
B = 2A
이므로 두 벡터는 선형 종속이다.
반면
A = (1, 0, 0)
B = (0, 1, 0)
에서는 한 벡터를 다른 벡터의 배수로 만들 수 없으므로 선형 독립이다.
두 개의 0이 아닌 벡터만 비교할 때는 서로 평행하면 종속이고, 평행하지 않으면 독립이라고 볼 수 있다.
하지만 벡터가 세 개 이상이라면 단순히 서로 평행한지만 봐서는 안 된다.
예를 들어
A = (1, 0, 0)
B = (0, 1, 0)
C = (1, 1, 0)
에서는 어느 두 벡터도 서로 평행하지 않지만
C = A + B
이므로 세 벡터 전체는 선형 종속이다.
두 3차원 벡터가 평행하다면
A × B = 0
이다.
따라서 두 벡터의 외적이 0이라면 두 벡터가 같은 직선 방향에 놓여 있다는 뜻이고, 두 벡터는 선형 종속이다.
반대로 외적이 0이 아니라면 두 벡터는 평행하지 않으므로 선형 독립이다.
여러 벡터에 각각 스칼라를 곱한 뒤 더하는 것
aA + bB + cC
주어진 벡터들을 선형 결합해서 만들 수 있는 모든 벡터의 집합
어떤 벡터도 나머지 벡터들의 선형 결합으로 만들어낼 수 없는 상태
3차원 공간에서 서로 선형 독립인 세 벡터가 있다면 그 세 벡터를 이용해 3D 공간 전체를 표현할 수 있다.
반대로 세 벡터 중 하나가
C = A + B
처럼 나머지 벡터로 만들어진다면 C는 새로운 방향을 추가하지 않는다.
다음 세 벡터를 생각해보자.
X = (1, 0, 0)
Y = (0, 1, 0)
Z = (0, 0, 1)
어떤 벡터
P = (3, 2, -1)
는
P = 3X + 2Y - Z
로 표현할 수 있다.
그리고 X, Y, Z를 이용하면 3D 공간의 모든 벡터를 표현할 수 있다.
이처럼 공간의 모든 벡터를 표현할 수 있는 선형 독립 벡터 집합을 Basis(기저)라고 한다.
3D 공간의 Basis라면 다음 조건을 만족해야 한다.
즉,
불필요하게 겹치는 방향 없이 공간 전체를 표현할 수 있어야 한다.
표준적인
X = (1, 0, 0)
Y = (0, 1, 0)
Z = (0, 0, 1)
만 Basis가 되는 것은 아니다.
예를 들어
E1 = (1, 0, 0)
E2 = (1, 1, 0)
E3 = (0, 0, 1)
도 Basis가 될 수 있다.
세 벡터는 서로 90도가 아니지만 선형 독립이며 3D 공간 전체를 만들 수 있기 때문이다.
중요한 점은 벡터와 그 벡터를 표현하는 좌표는 같은 것이 아니라는 것이다.
World 기준으로
P = (3, 2, 1)
이라는 벡터가 있다고 하자.
표준 Basis에서는
P = 3X + 2Y + Z
이므로 좌표가
(3, 2, 1)
이다.
이번에는 Basis가 다음과 같다고 하자.
E1 = (1, 0, 0)
E2 = (1, 1, 0)
E3 = (0, 0, 1)
P를 새로운 Basis로 표현하면
P = aE1 + bE2 + cE3
이고,
a(1,0,0) + b(1,1,0) + c(0,0,1)
= (a+b, b, c)
이므로
(a+b, b, c) = (3, 2, 1)
이다.
따라서
a = 1
b = 2
c = 1
이고 새로운 Basis에서 P의 좌표는
(1, 2, 1)
이다.
실제 벡터 P가 달라진 것은 아니다.
같은 벡터를 표현하는 기준축이 달라졌기 때문에 좌표값이 달라진 것이다.
이 개념이 이후 Local Space와 World Space를 이해하는 핵심이 된다.
어떤 캐릭터가 회전해 있다면 캐릭터가 바라보는
Forward
Right
Up
방향은 World의 X/Y/Z축과 다를 수 있다.
따라서 똑같은 실제 방향도
World 기준 좌표
Local 기준 좌표
가 서로 다를 수 있다.
여기서 기억해야 할 것은
벡터 자체가 바뀐 것이 아니라 벡터를 표현하는 Basis가 바뀐 것이다.
Basis를 이루는 축들이 서로 모두 90도라면 직교 기저(Orthogonal Basis)라고 한다.
예를 들어
E1 = (2, 0, 0)
E2 = (0, 3, 0)
E3 = (0, 0, 5)
에서
E1 · E2 = 0
E1 · E3 = 0
E2 · E3 = 0
이므로 세 벡터는 서로 수직이다.
따라서 직교 기저다.
벡터의 길이는 1일 필요가 없다.
다음 Basis는
X = (1, 0, 0)
Y = (0, 1, 0)
Z = (0, 0, 1)
서로 수직이고
|X| = |Y| = |Z| = 1
이다.
이처럼
서로 직교하면서 각 벡터의 길이까지 1인 Basis
를 정규직교 기저(Orthonormal Basis)라고 한다.
게임과 그래픽스에서 매우 자주 사용하는 형태다.
어떤 벡터가
V = 3Forward + 2Right + 5Up
이라고 하자.
Forward 방향 성분을 알고 싶다면
V · Forward
를 계산하면 된다.
전개하면
3(Forward · Forward)
+ 2(Right · Forward)
+ 5(Up · Forward)
정규직교 Basis에서는
Forward · Forward = 1
Right · Forward = 0
Up · Forward = 0
이므로
V · Forward = 3
이다.
따라서 정규직교 Basis에서는 각 축에 대한 성분을 단순한 내적으로 얻을 수 있다.
Forward 성분 = V · Forward
Right 성분 = V · Right
Up 성분 = V · Up
이것이 정규직교 Basis가 계산하기 편한 이유 중 하나다.
다음 두 벡터를 보자.
A = (1, 0, 0)
B = (1, 1, 0)
두 벡터는 선형 독립이므로 XY 평면의 Basis가 될 수 있다.
하지만
A · B = 1
이므로 서로 수직은 아니다.
이런 Basis에서는 각 축 방향 성분을 단순한 내적만으로 바로 얻을 수 없다.
그래서 그래픽스에서는 필요에 따라 Basis를
서로 수직
+
각각 길이 1
인 형태로 만드는 경우가 많다.
서로 수직이 아닌 두 벡터
A = (1, 0, 0)
B = (1, 1, 0)
가 있다고 하자.
A의 방향은 유지하면서 B를 A와 수직으로 만들고 싶다면 B에서 A 방향 성분을 제거하면 된다.
일반적인 투영 공식은
proj_A(B) = ((B · A) / (A · A)) A
이다.
A가 이미 단위벡터라면 A · A = 1이므로
proj_A(B) = (B · A)A
로 간단해진다.
현재
A = (1, 0, 0)
B = (1, 1, 0)
이고 A는 단위벡터이므로
B · A = 1
이다.
따라서
proj_A(B) = (1, 0, 0)
이고 이를 B에서 제거한다.
B' = B - proj_A(B)
= (1, 1, 0) - (1, 0, 0)
= (0, 1, 0)
결과적으로
A = (1, 0, 0)
B' = (0, 1, 0)
가 되어
A · B' = 0
즉 서로 수직이 된다.
이 과정을 여러 벡터에 반복하는 것이 Gram-Schmidt 직교화의 핵심 아이디어다.
V1 = (1, 0, 0)
V2 = (1, 1, 0)
V3 = (1, 1, 1)
먼저
E1 = V1 = (1, 0, 0)
로 잡는다.
V2에서 E1 방향 성분을 제거하면
E2 = (0, 1, 0)
을 얻을 수 있다.
그 다음 V3에서 E1과 E2 방향 성분을 모두 제거한다.
E3 = (0, 0, 1)
결과적으로
E1 = (1, 0, 0)
E2 = (0, 1, 0)
E3 = (0, 0, 1)
이라는 서로 수직인 Basis를 얻는다.
필요하다면 마지막에 각각 정규화하여 정규직교 Basis로 만들 수 있다.
두 개념은 서로 다르다.
벡터의 방향은 유지하면서 길이를 1로 만든다.
여러 벡터가 서로 90도를 이루도록 만든다.
따라서 Gram-Schmidt를 통해 직교화한 뒤 각 벡터를 정규화하면 정규직교 Basis를 만들 수 있다.
3D 게임에서는 두 방향을 알고 있을 때 외적을 이용해 세 번째 수직 방향을 만드는 경우도 많다.
예를 들어
Forward
Up
을 알고 있다면 두 벡터를 외적하여 Right 방향을 만들 수 있다.
이후 필요하다면 새롭게 만들어진 Right와 Forward를 이용해 Up을 다시 계산하여 축들을 서로 정확히 수직으로 맞출 수도 있다.
Forward
↓
Forward와 Up으로 Right 계산
↓
Right와 Forward로 Up 재계산
단, 외적의 순서를 바꾸면 결과 방향의 부호가 반대가 된다.
또한 엔진의 좌표계와 handedness에 따라 사용하는 순서가 달라질 수 있으므로 특정 공식을 무조건 외우기보다는 외적의 순서에 따라 방향이 뒤집힌다는 원리를 이해하는 것이 중요하다.
공간 전체를 표현할 수 있는 선형 독립 벡터 집합
3차원 공간이라면 일반적으로 독립인 세 벡터로 Basis를 구성한다.
Basis 벡터들이 서로 수직인 기저
Basis 벡터들이 서로 수직이고 각각의 길이도 1인 기저
기존 벡터에서 이전 축 방향으로 투영된 성분을 제거해 서로 수직인 축을 만드는 방법
행렬이 단순히 숫자를 사각형으로 배치한 것이 아니라 무엇을 표현할 수 있는 구조인지 이해한다.
다음과 같은 숫자 배열을 행렬이라고 한다.
[ 1 2 3 ]
[ 4 5 6 ]
이 행렬에는
2개의 행
3개의 열
이 있으므로 2 × 3 행렬이다.
행과 열의 개수가 같은 행렬을 정방행렬(Square Matrix)이라고 한다.
예를 들어
[1 2]
[3 4]
는 2 × 2 정방행렬이다.
정방행렬에서 왼쪽 위부터 오른쪽 아래로 이어지는 원소들을 주대각선(Main Diagonal)이라고 한다.
[2 0 0]
[0 3 0]
[0 0 4]
여기서
2, 3, 4
가 주대각선 원소다.
주대각선을 제외한 모든 값이 0인 행렬을 대각행렬(Diagonal Matrix)이라고 한다.
행과 열을 서로 바꾸는 것을 전치(Transpose)라고 한다.
[1 2 3]
[4 5 6]
을 전치하면
[1 4]
[2 5]
[3 6]
이 된다.
보통
M^T
로 표기한다.
벡터
V = (1, 2, 3)
를 열벡터로 표현하면
[1]
[2]
[3]
이다.
이는 3 × 1 행렬이다.
이를 전치하면
[1 2 3]
이라는 1 × 3 행벡터가 된다.
3차원 단위행렬은
[1 0 0]
[0 1 0]
[0 0 1]
이다.
4차원이라면
[1 0 0 0]
[0 1 0 0]
[0 0 1 0]
[0 0 0 1]
이다.
단위행렬은 보통 I로 표현한다.
적절한 크기의 행렬 M에 대해
IM = M
MI = M
이므로 숫자에서 1과 비슷한 역할을 한다.
3 × 3 단위행렬을 다시 보자.
[1 0 0]
[0 1 0]
[0 0 1]
열을 하나씩 떼어보면
(1, 0, 0)
(0, 1, 0)
(0, 0, 1)
이다.
즉 표준 X, Y, Z Basis가 들어 있다.
따라서 행렬은
여러 Basis Vector를 하나의 구조 안에 묶어 표현하는 방법
으로도 볼 수 있다.
2D의 기본 Basis를 생각해보자.
X = (1, 0)
Y = (0, 1)
이 축들을 90도 회전시켜
X' = (0, 1)
Y' = (-1, 0)
을 얻었다고 하자.
두 벡터를 열에 넣는 convention을 사용한다면 행렬은
[ 0 -1 ]
[ 1 0 ]
이 된다.
첫 번째 열은 새로운 X축,
두 번째 열은 새로운 Y축을 나타낸다.
즉 행렬을 통해
새로운 좌표계의 각 축이 기존 좌표계에서 어느 방향을 향하는지
표현할 수 있다.
벡터
V = (3, 2)
가 있다고 하자.
좌표 (3, 2)는 사실
3X + 2Y
라는 의미다.
이번에는 행렬 안에 새로운 Basis가 들어 있다고 해보자.
M = [ E1 E2 ]
열벡터 convention에서
MV
를 계산하면 행렬은 V의 좌표값을 이용해
3E1 + 2E2
를 계산한다.
즉,
행렬과 벡터의 곱은 행렬 안의 열벡터들을 입력 벡터의 좌표값으로 선형 결합하는 연산
으로 이해할 수 있다.
다음 Basis가 있다고 하자.
E1 = (2, 0)
E2 = (0, 3)
이를 열로 넣으면
M =
[2 0]
[0 3]
이다.
그리고
V = (4, 5)
를 곱하면
MV = 4E1 + 5E2
이므로
4(2,0) + 5(0,3)
= (8,15)
가 된다.
원래 벡터
(4,5)
가
(8,15)
로 변했으므로 이 행렬은 결과적으로
X 방향 2배
Y 방향 3배
의 스케일 변환을 수행한다.
여기서 중요한 점은
Basis를 담은 행렬이라는 관점과 벡터를 변환하는 행렬이라는 관점은 서로 별개의 이야기가 아니다.
행렬 안의 Basis들이 어디를 향하고 얼마나 긴지가 입력 벡터를 어떻게 변환할지를 결정한다.
지금까지 설명에서는 벡터를 열벡터로 두고
M × V
형태로 계산한다고 가정했다.
이 경우 Basis Vector들이 행렬의 열에 들어간다.
하지만 엔진, API, 수학 라이브러리에 따라
행벡터
V × M
방식을 사용하는 경우도 있다.
이때는 Basis의 배치나 곱셈 순서가 달라질 수 있다.
따라서 특정 행이나 열 위치만 외우기보다는
어떤 convention을 사용하는지 먼저 확인하는 것이 중요하다.
행렬은 단순한 숫자 표가 아니다.
특히 게임 수학에서는 행렬을 다음과 같이 볼 수 있다.
여러 Basis Vector를 하나의 구조에 저장한 것
그리고 행렬과 벡터를 곱하면
입력 벡터의 좌표값을 이용해 행렬 안의 Basis를 선형 결합한다.
따라서 행렬은 좌표계를 표현할 수도 있고, 벡터를 회전·스케일 같은 방식으로 변환하는 도구로도 사용할 수 있다.
이후 배우게 될 회전 행렬, 스케일 행렬, 좌표계 변환도 결국 이 개념에서 이어진다.
지금까지 배운 내용을 하나로 연결하면 다음과 같다.
선형 결합
↓
여러 벡터를 몇 배씩 해서 더한다.
Span
↓
그 선형 결합으로 만들 수 있는 모든 방향을 생각한다.
선형 독립
↓
각 벡터가 새로운 방향을 제공하는지 판단한다.
Basis
↓
공간 전체를 표현할 수 있는 최소한의 독립적인 방향들을 정한다.
좌표
↓
Basis를 각각 몇 배해야 원하는 벡터가 되는지를 숫자로 표현한다.
행렬
↓
여러 Basis Vector를 하나의 구조 안에 저장한다.
행렬 × 벡터
↓
벡터의 좌표값을 이용해 Basis를 선형 결합한다.
변환
↓
Basis의 방향이나 길이를 바꾸면 회전이나 스케일 같은 변환이 된다.
결국 선형 결합, Basis, 좌표, 행렬은 따로 떨어진 개념이 아니라 하나의 흐름으로 연결되어 있다.