게임 수학 정리
게임 수학
게임 프로그래밍에 주로 쓰이는 수학 개념을 정리하고자 합니다.
벡터와 행렬을 중심으로, 게임에서 자주 사용되는 수학적 연산과 개념을 다룹니다.
벡터(Vector)
정의
단일 값으로 표현할 수 없는 다차원 데이터를 표현하는 수학적 객체입니다.
간혹 "방향과 크기"를 가진 화살표로 비유되기도 합니다.
게임에서는 위치, 방향, 속도 등을 표현하는 데 사용됩니다.
더 읽어보기: https://en.wikipedia.org/wiki/Vector_(mathematics_and_physics)
연산
- 덧셈(Addition): 두 벡터의 각 성분을 더합니다.
$$ \mathbf{a} + \mathbf{b} = (a_x + b_x, a_y + b_y, a_z + b_z) $$
- 뺄셈(Subtraction): 두 벡터의 각 성분을 뺍니다.
$$ \mathbf{a} - \mathbf{b} = (a_x - b_x, a_y - b_y, a_z - b_z) $$
- 스칼라 곱(Scalar Multiplication): 벡터의 각 성분에 스칼라 값을 곱합니다.
$$ k \cdot \mathbf{a} = (k \cdot a_x, k \cdot a_y, k \cdot a_z) $$
- 내적(Dot Product): 두 벡터의 내적은 스칼라 값을 반환하며, 두 벡터 사이의 각도와 관련이 있습니다.
$$ \mathbf{a} \cdot \mathbf{b} = a_x b_x + a_y b_y + a_z b_z $$
- 외적(Cross Product): 두 벡터의 외적은 새로운 벡터를 생성하며, 이 벡터는 두 벡터에 수직입니다. 특히 3차원 벡터끼리의 외적은 3차원 공간에서 정의됩니다.
$$ \mathbf{a} \times \mathbf{b} = (a_y b_z - a_z b_y, a_z b_x - a_x b_z, a_x b_y - a_y b_x) $$
크기(Magnitude)
벡터의 크기는 피타고라스 정리를 사용하여 계산합니다.
$$ |\mathbf{a}| = \sqrt{a_x^2 + a_y^2 + a_z^2} $$
단위 벡터(Unit Vector)
크기가 1인 벡터로, 주어진 벡터를 그 크기로 나누어 구합니다.
이 과정을 정규화(normalization)라고 합니다.
$$ \hat{\mathbf{a}} = \frac{\mathbf{a}}{|\mathbf{a}|} $$
벡터의 회전(Rotation)
3차원 공간에서 벡터를 회전시키려면 회전 행렬을 사용합니다.
회전 행렬은 회전 축과 회전 각도에 따라 달라집니다.
예를 들어, Z축을 중심으로 θ만큼 회전하는 행렬은 다음과 같습니다.
$$ R_z(\theta) = \begin{pmatrix} \cos(\theta) & -\sin(\theta) & 0 \\ \sin(\theta) & \cos(\theta) & 0 \\ 0 & 0 & 1 \end{pmatrix} $$
게임에서는 객체나 카메라의 회전을 구현할 때 벡터의 회전을 사용합니다.
Unity 처럼 게임 엔진에 따라 사원수(quaternion)를 사용하여 회전을 표현하기도 합니다.
using UnityEngine;
public class RotateVector : MonoBehaviour
{
public Vector3 vector = new Vector3(1, 0, 0); // 회전할 벡터
public float angle = 90f; // 회전 각도
void Start()
{
Quaternion rotation = Quaternion.Euler(0, angle, 0); // Y축을 중심으로 회전
Vector3 rotatedVector = rotation * vector; // 벡터 회전
Debug.Log("Rotated Vector: " + rotatedVector);
}
}
벡터의 투영(Projection)
투영이란 한 벡터를 다른 벡터에 "투사"하는 것을 의미합니다.
벡터 (\mathbf{a})를 벡터 (\mathbf{b})에 투영하는 공식은 다음과 같습니다.
$$ \text{proj}_{\mathbf{b}} \mathbf{a} = \frac{\mathbf{a} \cdot \mathbf{b}}{|\mathbf{b}|^2} \cdot \mathbf{b} = \left( \frac{a_x b_x + a_y b_y + a_z b_z}{b_x^2 + b_y^2 + b_z^2} \right) \cdot (b_x, b_y, b_z) $$
게임에서는 물체가 다른 물체에 대해 어떻게 움직이는지, 충돌을 감지하는 데 벡터의 투영을 사용합니다.
using UnityEngine;
public class VectorProjection : MonoBehaviour
{
public Vector3 vectorA = new Vector3(3, 4, 0);
public Vector3 vectorB = new Vector3(1, 2, 0);
void Start()
{
Vector3 projection = Project(vectorA, vectorB);
Debug.Log("Projection of A onto B: " + projection);
}
Vector3 Project(Vector3 a, Vector3 b)
{
return (Vector3.Dot(a, b) / b.sqrMagnitude) * b;
}
}
벡터의 거리(Distance)
두 벡터 사이의 거리는 두 벡터의 차이를 계산한 후 크기를 구합니다.
$$ d(\mathbf{a}, \mathbf{b}) = |\mathbf{a} - \mathbf{b}| = \sqrt{(a_x - b_x)^2 + (a_y - b_y)^2 + (a_z - b_z)^2} $$
벡터의 각도(Angle)
두 벡터 사이의 각도는 내적을 사용하여 계산할 수 있습니다.
각도는 두 벡터의 내적을 크기의 곱으로 나눈 후 아크코사인 함수를 적용하여 구합니다.
$$ \theta = \cos^{-1}\left(\frac{\mathbf{a} \cdot \mathbf{b}}{|\mathbf{a}| |\mathbf{b}|}\right) = \cos^{-1}\left(\frac{a_x b_x + a_y b_y + a_z b_z}{\sqrt{a_x^2 + a_y^2 + a_z^2} \sqrt{b_x^2 + b_y^2 + b_z^2}}\right) $$
내적을 이용하면 두 벡터 사이의 각도를 쉽게 구할 수 있습니다.
대표적으로 내적을 이용해 부채꼴의 범위의 시야를 구현할 수 있습니다.
using UnityEngine;
public class FieldOfView : MonoBehaviour
{
public float viewAngle = 110f; // 시야각
public float viewDistance = 10f; // 시야 거리
void CheckFieldOfView(Transform target)
{
Vector3 directionToTarget = (target.position - transform.position).normalized;
float angleToTarget = Vector3.Angle(transform.forward, directionToTarget);
if (angleToTarget < viewAngle / 2f && Vector3.Distance(transform.position, target.position) <= viewDistance)
{
Debug.Log("Target is in field of view");
}
else
{
Debug.Log("Target is out of field of view");
}
}
}
벡터의 보간(Interpolation)
보간은 두 벡터 사이의 중간 값을 계산하는 방법입니다.
가장 일반적인 방법은 선형 보간(linear interpolation)입니다.
선형 보간은 두 벡터 (\mathbf{a})와 (\mathbf{b}) 사이의 중간 값을 다음과 같이 계산합니다.
$$ \mathbf{L} = (1 - t) \cdot \mathbf{a} + t \cdot \mathbf{b} $$
여기서 (t)는 0과 1 사이의 값으로, (t = 0)일 때 (\mathbf{L} = \mathbf{a}), (t = 1)일 때 (\mathbf{L} = \mathbf{b})가 됩니다.
Unity에서는 Vector3.Lerp
함수를 사용하여 벡터를 보간할 수 있습니다.
게임에서는 객체의 위치를 부드럽게 이동시키거나 애니메이션을 구현할 때 사용됩니다.
using UnityEngine;
public class VectorLerp : MonoBehaviour
{
public Vector3 start = new Vector3(0, 0, 0);
public Vector3 end = new Vector3(10, 10, 10);
public float t = 0.5f; // 0.0f ~ 1.0f 사이의 값
void Update()
{
Vector3 result = Vector3.Lerp(start, end, t);
Debug.Log("Interpolated Vector: " + result);
}
}
벡터의 반사(Reflection)
벡터의 반사는 주로 충돌 처리에 사용됩니다.
만약 법선 벡터 (\mathbf{n})이 단위 벡터(크기가 1인 벡터)라면, 반사 벡터는 다음과 같습니다:
$$ \mathbf{r} = \mathbf{v} - 2(\mathbf{v} \cdot \mathbf{n})\mathbf{n} \quad (\text{단, } |\mathbf{n}| = 1) $$
법선 벡터 (\mathbf{n})이 단위 벡터가 아닐 경우, 일반적인 공식은 다음과 같습니다:
$$ \mathbf{r} = \mathbf{v} - 2 \frac{\mathbf{v} \cdot \mathbf{n}}{|\mathbf{n}|^2} \mathbf{n} = \mathbf{v} - 2\left(\frac{v_x n_x + v_y n_y + v_z n_z}{n_x^2 + n_y^2 + n_z^2}\right)(n_x, n_y, n_z) $$
아래 C# 코드는 법선 벡터 n
이 단위 벡터라고 가정합니다. Unity의 Vector3.Reflect
메소드는 정규화되지 않은 법선 벡터도 처리합니다.
using UnityEngine;
public class VectorReflection : MonoBehaviour
{
public Vector3 incomingVector = new Vector3(1, -1, 0);
public Vector3 normalVector = new Vector3(0, 1, 0);
void Start()
{
Vector3 reflectedVector = Reflect(incomingVector, normalVector.normalized); // 법선 벡터를 정규화
Debug.Log("Reflected Vector: " + reflectedVector);
}
Vector3 Reflect(Vector3 v, Vector3 n)
{
return v - 2 * Vector3.Dot(v, n) * n;
}
}
예시: 추적 및 회전
게임에서 객체가 다른 객체를 추적하는 경우, 벡터를 사용하여 목표 위치로 이동하는 방향을 계산합니다.
1차원적인 추적은 단순히 목표 위치로 이동하는 것입니다.
using UnityEngine;
public class ObjectTracker : MonoBehaviour
{
public Transform target; // 추적할 대상
void Update()
{
Vector3 direction = (target.position - transform.position).normalized; // 목표 방향
transform.position += direction * Time.deltaTime; // 이동
}
}
각속도의 변화를 통해 회전하는 추적은 벡터의 회전을 사용합니다.
using UnityEngine;
public class ObjectRotator : MonoBehaviour
{
public Transform target; // 추적할 대상
public float rotationSpeed = 5f; // 회전 속도
void Update()
{
Vector3 direction = (target.position - transform.position).normalized; // 목표 방향
Quaternion targetRotation = Quaternion.LookRotation(direction); // 목표 회전
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime); // 부드러운 회전
}
}
행렬(Matrix)
정의
행렬은 숫자나 변수를 직사각형 형태로 배열한 것입니다.
게임에서 변환(translation), 회전(rotation), 크기 조정(scaling) 등을 표현하는 데 사용됩니다.
더 읽어보기: https://en.wikipedia.org/wiki/Matrix_(mathematics)
행렬 연산
- 덧셈(Addition): 두 행렬의 대응하는 성분을 더합니다.
$$ \mathbf{A} + \mathbf{B} = \begin{pmatrix} a_{11} + b_{11} & a_{12} + b_{12} \\ a_{21} + b_{21} & a_{22} + b_{22} \end{pmatrix} $$
- 뺄셈(Subtraction): 두 행렬의 대응하는 성분을 뺍니다.
$$ \mathbf{A} - \mathbf{B} = \begin{pmatrix} a_{11} - b_{11} & a_{12} - b_{12} \\ a_{21} - b_{21} & a_{22} - b_{22} \end{pmatrix} $$
- 곱셈(Multiplication): 두 행렬의 곱셈은 각 행렬의 행과 열을 곱하여 새로운 행렬을 만듭니다.
$$ \mathbf{A} \cdot \mathbf{B} = \begin{pmatrix} a_{11}b_{11} + a_{12}b_{21} & a_{11}b_{12} + a_{12}b_{22} \\ a_{21}b_{11} + a_{22}b_{21} & a_{21}b_{12} + a_{22}b_{22} \end{pmatrix} $$
- 전치(Transpose): 행렬의 행과 열을 바꿉니다.
$$ \mathbf{A}^T = \begin{pmatrix} a_{11} & a_{21} \\ a_{12} & a_{22} \end{pmatrix} $$
- 역행렬(Inverse): 행렬 $\mathbf{A}$ 의 역행렬 $\mathbf{A}^{-1}$ 은 다음과 같은 조건을 만족합니다.
$$ \mathbf{A} \cdot \mathbf{A}^{-1} = \mathbf{I} $$
- 행렬식(Determinant): 2x2 행렬의 행렬식은 다음과 같이 계산됩니다. 일반화된 행렬식은 더 큰 행렬에 대해서도 정의되지만, 여기서는 2x2 행렬에 대해서만 설명합니다.
$$ \text{det}(\mathbf{A}) = a_{11}a_{22} - a_{12}a_{21} $$
변환 행렬(Transformation Matrix)
변환 행렬은 객체의 위치, 회전, 크기 조정을 표현하는 데 사용됩니다.
- 이동(Translation): 객체를 3D 공간에서 이동시키는 행렬입니다.
$$ \mathbf{T} = \begin{pmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \end{pmatrix} $$
-
회전(Rotation): Z축을 중심으로 θ만큼 회전하는 행렬입니다. $$ \mathbf{R} = \begin{pmatrix} \cos(\theta) & -\sin(\theta) & 0 & 0 \\ \sin(\theta) & \cos(\theta) & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} $$
-
크기 조정(Scaling): 객체의 크기를 조정하는 행렬입니다.
$$ \mathbf{S} = \begin{pmatrix} s_x & 0 & 0 & 0 \\ 0 & s_y & 0 & 0 \\ 0 & 0 & s_z & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} $$
위와 같은 변환 행렬들을 행렬의 곱셈을 통해 적용할 수 있습니다.
행렬의 곱셈은 결합법칙이 성립하기에, 여러 변환을 하나의 행렬로 결합하는 것도 가능합니다.
역행렬의 성질과 활용
역행렬로 행렬의 곱 연산을 적용하게 되면, 항등원인 단위 행렬이 됩니다.
따라서, 역행렬을 사용하여 변환을 되돌릴 수 있습니다.
예를 들어, 객체가 이동한 후 원래 위치로 되돌리려면 이동 행렬의 역행렬을 곱하면 됩니다.
using UnityEngine;
public class MatrixInverseExample : MonoBehaviour
{
public Vector3 translation = new Vector3(2, 3, 4);
void Start()
{
// 이동 행렬 생성
Matrix4x4 translationMatrix = Matrix4x4.Translate(translation);
// 이동 행렬의 역행렬 계산
Matrix4x4 inverseTranslationMatrix = translationMatrix.inverse;
Debug.Log("Original Translation Matrix: " + translationMatrix);
Debug.Log("Inverse Translation Matrix: " + inverseTranslationMatrix);
// 원래 위치로 되돌리기
Vector3 originalPosition = inverseTranslationMatrix.MultiplyPoint3x4(translation);
Debug.Log("Original Position: " + originalPosition);
}
}
Unity에서 행렬 사용 예시
Unity에서는 Matrix4x4
클래스를 사용하여 행렬을 다룰 수 있습니다.
(일반적으로 4x4 행렬을 사용하여 3D 공간에서의 변환을 표현합니다.)
using UnityEngine;
public class MatrixExample : MonoBehaviour
{
public Vector3 translation = new Vector3(1, 2, 3);
public float rotationAngle = 45f; // 회전 각도
public Vector3 scale = new Vector3(2, 2, 2);
void Start()
{
// 이동 행렬
Matrix4x4 translationMatrix = Matrix4x4.Translate(translation);
// 회전 행렬 (Z축을 중심으로 회전)
Matrix4x4 rotationMatrix = Matrix4x4.Rotate(Quaternion.Euler(0, 0, rotationAngle));
// 크기 조정 행렬
Matrix4x4 scalingMatrix = Matrix4x4.Scale(scale);
// 전체 변환 행렬
Matrix4x4 transformationMatrix = translationMatrix * rotationMatrix * scalingMatrix;
Debug.Log("Transformation Matrix: " + transformationMatrix);
}
}
이외의 수학 활용
게임 프로그래밍에서는 벡터와 행렬 외에도 다양한 수학적 개념이 사용됩니다.
- 확률과 통계: 게임 AI, 난수 생성, 게임 밸런싱 등에 사용됩니다. 대표적으로는 의사 난수 생성(Pseudo-Random Number Generation)이 있습니다.
- 물리학: 충돌 감지, 중력, 마찰 등 물리적 현상을 모델링하는 데 사용됩니다.
- 기하학: 3D 모델링, 충돌 처리, 경로 찾기 등에 사용됩니다. Convex Hull, AABB(축 정렬 경계 상자) 등의 기하학적 알고리즘이 있습니다.
- 신호 처리: 오디오 처리, 이미지 필터링 등에서 사용됩니다. Fast Fourier Transform(FFT)와 같은 알고리즘이 있습니다.
- 경로 탐색: 이동 경로나 AI 행동을 결정하는 데 사용됩니다. A* 알고리즘, Dijkstra 알고리즘 등이 있습니다.
- 애니메이션: 스켈레톤 애니메이션, 키프레임 애니메이션 등에서 사용됩니다. 보간(interpolation) 기법이 많이 사용됩니다.