[게임수학] 6-2. 투영 벡터, Vector3.ProjectOnPlane()

2025. 9. 12. 20:03·게임수학
728x90

→ 이 글은 「이득우의 게임 수학」을 바탕으로 작성했습니다.

■ 투영 벡터

[벡터의 투영(Projection)]

벡터의 투영이란, 투영할 벡터$(\vec v)$ 가 기준 벡터$(\vec u)$ 방향으로 얼마나 기여하는지를 나타내는 벡터를 구하는 것이다.

  • 투영 벡터$(\vec t)$는 기준 벡터 $(\vec u)$와 “평행하지만”, 방향(부호)은 같을 수 있고 반대일 수 있다.

조금 더 자세히 말하면 $\vec v$를 기준 벡터 $\vec u$에 대해 분해했을 때, 그 중 $\vec u$와 평행한(수평 성분)만 남긴 벡터이다. 벡터에 분해에 대해서는 하단에 추가로 설명할 테니, 투영 벡터 $\vec t$를 구하는 공식을 작성해 보자.

 

1. 기준 벡터 u의 단위 벡터 구하기

[투영 벡터 구하기 - 1]

$$ \vec t = |\vec t| \cdot \frac{\vec u}{|\vec u|} $$

투영 벡터의 크기$(|\vec t|)$를 알고 있다면, 기준 벡터 $\vec u$를 정규화시킨 단위 벡터$(\hat u)$에 그 크기를 곱해 투영 벡터$(\vec t)$를 구할 수 있다.

 

즉, 투영 벡터는 기준 벡터와 평행하면서 그 방향으로의 “기여 정도(= 크기)”만을 반영한 벡터라고 할 수 있다.

  • 위 수식에서 내적과 삼각함수의 관계식을 이용해 전개해 보자.

 

2. 투영 벡터의 크기(|t|) 구하기

[투영 벡터 구하기 - 2]

벡터 $\vec v$와 기준 벡터 $\vec u$가 이루는 각을 $\theta$라고 하면, 투영 벡터 $\vec t$의 크기는 삼각함수의 정의를 통해 아래와 같이 표현할 수 있다.

$$ \cos \theta = \frac{|\vec t|}{|\vec v|} $$

이는 $\cos \theta = \frac{인접변}{빗변}$이라는 정의에 따라, 인접변이 투영 벡터의 크기$|\vec t|$, 빗변이 $|\vec v|$가 되기 때문이다.

$$ |\vec t| = |\vec v|\cos\theta $$

$|\vec t|$를 구하기 위해, 양변에 $|\vec v|$를 곱하면 위와 같이 구할 수 있고, 이 식을 [투영 벡터 구하기 - 1] 에 대입하면,

$$ \vec t = |\vec v|\cos\theta\cdot \frac{\vec u}{|\vec u|} $$

와 같이 투영 벡터$\vec t$를 구할 수 있다.

 

3. cos 값을 내적으로 구하기

우리가 앞서 배운 내적 공식$(\vec v \cdot \vec u) = |\vec v||\vec u|\cos\theta$를 사용하여 $\cos\theta$를 구해보자.

$$ \cos \theta = \frac{\vec v \cdot \vec u}{|\vec v||\vec u|} $$

$\cos \theta$를 구하기 위해 양 변을 $|\vec v||\vec u|$로 나누면 위와 같이 $\cos\theta$를 표현할 수 있고, 이 식을 [투영 벡터 구하기 - 2]의 식에 대입하면,

$$ \vec t = |\vec v|\cdot \frac{\vec v \cdot \vec u}{|\vec v||\vec u|} \cdot \frac{\vec u}{|\vec u|} $$

와 같이 표현된다.

 

4. 최종 정리

$$ \vec t = |\vec v|\cdot \frac{\vec v \cdot \vec u}{|\vec v||\vec u|} \cdot \frac{\vec u}{|\vec u|} $$

위 식에서 분자와 분모를 소거하면,

$$ \vec t = \frac{\vec v\cdot \vec u}{|\vec u||\vec u|}\cdot \vec u $$

로 정리가 가능하다. 또한, 벡터 크기의 제곱$(|\vec u||\vec u|)$은, 동일한 벡터를 내적한 결과와 같으므로 이를 내적으로 표현한다면,

$$ \vec t = \frac{\vec v\cdot \vec u}{\vec u\cdot \vec u}\cdot \vec u $$

와 같이 정리가 가능하다. 여기서 기준이 되는 벡터 $\vec u$의 크기가 1이라면, 투영 벡터를 구하는 수식은 더 간단하게 정리할 수 있다.

$$ \vec t = (\vec v\cdot \vec u)\cdot \vec u $$

[투영 공식을 사용하여 투영 벡터 t 구하기]

 

■ 투영 벡터 응용

[벡터의 성분 분해]

임의의 벡터 $\vec v$는, 다른 벡터 $\vec u$에 대해 두 가지 성분으로 분해할 수 있다.

  • $\vec{v}_{\parallel} :$ 벡터 $\vec u$와 평행한 성분. (투영 벡터)
  • $\vec{v}_{\perp}$ : 벡터 $\vec u$와 수직인 성분. (잔여 벡터)

따라서 벡터 $\vec v$는 아래와 같이 표현할 수 있다.

$$ \vec v = \vec{v}{\parallel} + \vec{v}{\perp} $$

이는 벡터의 기본적인 성질로, 하나의 벡터를 특정 방향 성분(평행 성분)과 그와 직교하는 성분(수직 성분)으로 정확하게 분해할 수 있다는 것을 의미한다.

  • 즉, 벡터 $\vec u$를 좌표축으로 삼아 새로운 기준을 만들고, 벡터를 투영하여 평행, 수직 성분으로 분리.

벡터 $\vec v$가 존재하는 공간은 선형 공간이며, 한 벡터에 “평행한 방향”과 그에 수직인 방향은 서로 선형 독립이기 때문에, 이 두 성분만으로 언제나 $\vec v$를 완전히 표현할 수 있다.

$$ \vec{v}{\perp} = \vec v-\vec{v}{\parallel} $$

[수직 성분만 남기기]

벡터 $\vec u$와 평행한 성분, 즉 투영 벡터를 빼면 $\vec u$에 수직인 성분 $\vec{v}_{\perp}$만 남게 된다. 이러한 성질을 이용하여 플레이어가 경사로를 따라 자연스럽게 이동하는 기능을 구현할 수 있다.

 

1. 경사로 이동방향 구하기

[경사로 이동 방향 구하기]

플레이어가 경사로 위에서 이동할 때, 기존 이동 벡터$(\vec f)$를 경사면 위의 방향으로 정렬해야 한다. 이를 위해 “경사면의 노말 벡터$(\vec n)$”에 대해 수직인 성분만 남겨야 한다.

🛠️벡터 설정

  • 투영할 벡터 : 기존 플레이어의 이동 방향$(\vec f)$
  • 기준 벡터 : 경사로의 노말$(\vec n)$
  • 경사로 이동 벡터 : 기존 이동 방향$(\vec f)$ - 평행 성분(투영된 벡터$\vec p$) = 수직 성분$(\vec d)$

앞서 배웠던 공식$(\vec f \cdot \vec n)\cdot \vec n$(이때 $\vec n$은 단위벡터)을 적용하여, 투영 벡터 $\vec p$를 구한 뒤, 기존 플레이어 이동 방향에서 투영 벡터를 빼면, $\vec n$의 수직인 성분$(\vec d)$만 남게 되고 이것이 바로 경사로에서 이동 방향이다.

public class ProjectionPlayer : MonoBehaviour
{
    [SerializeField] private float speed = 20f;
    [SerializeField] private Transform rayTransform;
    [SerializeField] private LayerMask groundLayer;
    private void Update()
    {
        var ray = new Ray(rayTransform.position, Vector3.down);

        if (Physics.Raycast(ray, out var hit, 100f, groundLayer) == false)
        {
            return;
        }

        // 투영할 벡터(플레이어의 정면)      
        var forward = transform.forward.normalized;
        // 기준 벡터(평면의 normal)
        var normal = hit.normal;

        // 투영된 벡터
        var projection = Vector3.Dot(forward, normal) * normal;
        // 평면의 normal과 수직인 벡터
        var dir = forward - projection;

        var input = Input.GetAxis("Horizontal");
        transform.Translate(dir * speed * input * Time.deltaTime, Space.World);
        
        // debug
        Debug.DrawRay(rayTransform.position, projection, Color.red);
        Debug.DrawRay(rayTransform.position, dir, Color.green);
    }
}

[경사로 이동]

위에서 정리한 수식을 그대로 코드로 옮겨 적으면 간단하게 구현이 가능하다.

// 투영할 벡터(플레이어의 정면)      
var forward = transform.forward.normalized;
// 기준 벡터(평면의 normal)
var normal = hit.normal;
// 함수 사용
var dir = Vector3.ProjectOnPlane(forward, normal);

Unity에서는 지정한 벡터에서 특정 평면의 노말 방향 성분을 제거하고, 평면 위에 남는 성분만을 반환하는 Vector3.ProjectOnPlane(Vector3 vector, Vector3 planeNormal) 함수를 제공한다.

728x90

'게임수학' 카테고리의 다른 글

[게임수학] 7. 게임 엔진  (0) 2025.09.24
[게임수학] 6.삼각형(Mesh)  (0) 2025.09.20
[게임수학] 6-1. 내적(Dot product)  (0) 2025.09.12
[게임수학] 5.아핀공간  (1) 2025.09.08
[게임수학] 4. 행렬  (1) 2025.09.05
'게임수학' 카테고리의 다른 글
  • [게임수학] 7. 게임 엔진
  • [게임수학] 6.삼각형(Mesh)
  • [게임수학] 6-1. 내적(Dot product)
  • [게임수학] 5.아핀공간
브라더스톤
브라더스톤
유티니, C#과 관련한 여러 정보를 끄적여둔 블로그입니다. Email : dkavmdk98@gmail.com
  • 브라더스톤
    젊은 프로그래머의 슬픔
    브라더스톤
  • 전체
    오늘
    어제
    • 개발 노트 (51)
      • Unity,C# (30)
        • Unity 정보 (7)
        • 알고리즘 (11)
        • 자료구조 (3)
        • 절차적생성(PCG) (9)
      • 게임수학 (14)
      • C++ (7)
        • 자료구조 (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    외적
    c++
    PerlinNoise
    transform.RotateAround
    UI Toolkit
    스택
    절차적던전생성
    게임수학
    정렬알고리즘
    절차적지형생성
    BSP
    알고리즘
    최단경로찾기
    unity
    C#
    CustomWindow
    이진공간분할
    커스텀 윈도우
    pcg
    자료구조
  • 최근 댓글

  • 최근 글

  • 250x250
  • hELLO· Designed By정상우.v4.10.3
브라더스톤
[게임수학] 6-2. 투영 벡터, Vector3.ProjectOnPlane()
상단으로

티스토리툴바