SeokguKim's Garage

A little space for SeokguKim


게임 수학 정리

Tags: development, math

게임 수학

게임 프로그래밍에 주로 쓰이는 수학 개념을 정리하고자 합니다.
벡터와 행렬을 중심으로, 게임에서 자주 사용되는 수학적 연산과 개념을 다룹니다.

벡터(Vector)

정의

단일 값으로 표현할 수 없는 다차원 데이터를 표현하는 수학적 객체입니다.
간혹 "방향과 크기"를 가진 화살표로 비유되기도 합니다.
게임에서는 위치, 방향, 속도 등을 표현하는 데 사용됩니다.

더 읽어보기: https://en.wikipedia.org/wiki/Vector_(mathematics_and_physics)

연산

a+b=(a x+b x,a y+b y,a z+b z) \mathbf{a} + \mathbf{b} = (a_x + b_x, a_y + b_y, a_z + b_z)

a−b=(a x−b x,a y−b y,a z−b z) \mathbf{a} - \mathbf{b} = (a_x - b_x, a_y - b_y, a_z - b_z)

k⋅a=(k⋅a x,k⋅a y,k⋅a z) k \cdot \mathbf{a} = (k \cdot a_x, k \cdot a_y, k \cdot a_z)

a⋅b=a xb x+a yb y+a zb z \mathbf{a} \cdot \mathbf{b} = a_x b_x + a_y b_y + a_z b_z

a×b=(a yb z−a zb y,a zb x−a xb z,a xb y−a yb x) \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)

벡터의 크기는 피타고라스 정리를 사용하여 계산합니다.

|a|=a x 2+a y 2+a z 2 |\mathbf{a}| = \sqrt{a_x^2 + a_y^2 + a_z^2}

단위 벡터(Unit Vector)

크기가 1인 벡터로, 주어진 벡터를 그 크기로 나누어 구합니다.
이 과정을 정규화(normalization)라고 합니다.

a^=a|a| \hat{\mathbf{a}} = \frac{\mathbf{a}}{|\mathbf{a}|}

벡터의 회전(Rotation)

3차원 공간에서 벡터를 회전시키려면 회전 행렬을 사용합니다.
회전 행렬은 회전 축과 회전 각도에 따라 달라집니다.
예를 들어, Z축을 중심으로 θ만큼 회전하는 행렬은 다음과 같습니다.

R z(θ)=(cos(θ) −sin(θ) 0 sin(θ) cos(θ) 0 0 0 1) 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})에 투영하는 공식은 다음과 같습니다.

proj ba=a⋅b|b| 2⋅b=(a xb x+a yb y+a zb zb x 2+b y 2+b z 2)⋅(b x,b y,b z) \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(a,b)=|a−b|=(a x−b x) 2+(a y−b y) 2+(a z−b z) 2 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)

두 벡터 사이의 각도는 내적을 사용하여 계산할 수 있습니다.
각도는 두 벡터의 내적을 크기의 곱으로 나눈 후 아크코사인 함수를 적용하여 구합니다.

θ=cos −1(a⋅b|a||b|)=cos −1(a xb x+a yb y+a zb za x 2+a y 2+a z 2b x 2+b y 2+b z 2) \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}) 사이의 중간 값을 다음과 같이 계산합니다.

L=(1−t)⋅a+t⋅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인 벡터)라면, 반사 벡터는 다음과 같습니다:

r=v−2(v⋅n)n(단, |n|=1) \mathbf{r} = \mathbf{v} - 2(\mathbf{v} \cdot \mathbf{n})\mathbf{n} \quad (\text{단, } |\mathbf{n}| = 1)

법선 벡터 (\mathbf{n})이 단위 벡터가 아닐 경우, 일반적인 공식은 다음과 같습니다:

r=v−2v⋅n|n| 2n=v−2(v xn x+v yn y+v zn zn x 2+n y 2+n z 2)(n x,n y,n z) \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)

행렬 연산

A+B=(a 11+b 11 a 12+b 12 a 21+b 21 a 22+b 22) \mathbf{A} + \mathbf{B} = \begin{pmatrix} a_{11} + b_{11} & a_{12} + b_{12} \\ a_{21} + b_{21} & a_{22} + b_{22} \end{pmatrix}

A−B=(a 11−b 11 a 12−b 12 a 21−b 21 a 22−b 22) \mathbf{A} - \mathbf{B} = \begin{pmatrix} a_{11} - b_{11} & a_{12} - b_{12} \\ a_{21} - b_{21} & a_{22} - b_{22} \end{pmatrix}

A⋅B=(a 11b 11+a 12b 21 a 11b 12+a 12b 22 a 21b 11+a 22b 21 a 21b 12+a 22b 22) \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}

A T=(a 11 a 21 a 12 a 22) \mathbf{A}^T = \begin{pmatrix} a_{11} & a_{21} \\ a_{12} & a_{22} \end{pmatrix}

A⋅A −1=I \mathbf{A} \cdot \mathbf{A}^{-1} = \mathbf{I}

det(A)=a 11a 22−a 12a 21 \text{det}(\mathbf{A}) = a_{11}a_{22} - a_{12}a_{21}

변환 행렬(Transformation Matrix)

변환 행렬은 객체의 위치, 회전, 크기 조정을 표현하는 데 사용됩니다.

T=(1 0 0 t x 0 1 0 t y 0 0 1 t z 0 0 0 1) \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}

S=(s x 0 0 0 0 s y 0 0 0 0 s z 0 0 0 0 1) \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);
    }
}

이외의 수학 활용

게임 프로그래밍에서는 벡터와 행렬 외에도 다양한 수학적 개념이 사용됩니다.