벡터는 방향과 크기를 표현하는 기초적인 수학 개념입니다. 게임과 앱에서 벡터는 캐릭터의 위치, 오브젝트의 이동 속도, 두 오브젝트 간 거리와 같은 기본적인 프로퍼티를 나타낼 때 주로 사용됩니다.
벡터 연산은 그래픽스, 물리, 애니메이션 등과 같은 컴퓨터 프로그래밍의 많은 요소에 있어 핵심적이며, Unity를 최대한 활용하기 위해서는 벡터 연산을 깊이 이해하는 것이 좋습니다.
벡터는 여러 차원으로 표현할 수 있으며, Unity는 2D, 3D, 4D 벡터 작업을 위한 Vector2, Vector3 및 Vector4 클래스를 제공합니다. 이 세 가지 유형의 Vector 클래스는 다수의 동일한 함수(크기 등)를 공유하므로, 이 페이지에 있는 대부분의 정보는 별도의 언급이 없는 한 세 가지 유형의 벡터 모두에 적용됩니다.
이 페이지에서는 Vector 클래스의 개를 살펴보고, 해당 클래스들이 스크립트 작성 시 일반적으로 어떻게 사용되는지 설명합니다. 벡터 클래스의 모든 멤버에 대한 전체 레퍼런스는 Vector2, Vector3, Vector4에 대한 스크립트 레퍼런스 페이지를 참조하십시오.
두 벡터를 더하면 그 결과는 원래의 벡터를 하나씩 단계적으로 적용한 결과와 동일합니다. 두 파라미터의 순서는 중요하지 않으며 결과는 항상 동일합니다.
첫 번째 벡터가 공간 내 하나의 점일 경우, 두 번째 벡터는 그 위치에서의 오프셋 또는 그 위치에서 ’점프’한 것으로 해석할 수 있습니다. 예를 들어 지면의 어떤 위치로부터 5유닛 위에 있는 지점을 찾으려면 다음 연산을 사용하면 됩니다.
var pointInAir = pointOnGround + new Vector2(0, 5);
벡터가 힘을 나타내는 경우, 벡터를 힘의 강도와 방향으로 간주하면 그 의미를 더욱 직관적으로 이해할 수 있습니다. 두 개의 힘 벡터를 더하면 두 힘을 결합한 것과 동일한 새로운 벡터가 생성됩니다. 이 개념은 여러 개의 서로 다른 컴포넌트에 동시에 힘을 가할 때 유용합니다. 예를 들면 전방으로 발사된 로켓이 측풍의 영향을 받는 경우가 이에 해당합니다.
이 예시는 2D 벡터를 사용하고 있지만 3D 및 4D 벡터에도 동일한 개념이 적용됩니다.
벡터의 뺄셈은 한 오브젝트에서 다른 오브젝트까지의 거리와 방향을 구할 때 주로 사용됩니다. 주의할 점은 벡터의 뺄셈에서는 두 파라미터의 순서가 중요 하다는 점입니다.
// The vector d has the same magnitude as c but points in the opposite direction.
var c = b - a;
var d = a - b;
숫자의 경우 음의 벡터를 더하는 것은 양의 벡터를 빼는 것과 동일합니다.
// These both give the same result.
var c = a - b;
var c = a + -b;
음의 벡터는 오리지널 벡터와 크기가 같고 같은 선상에서 정반대 방향을 가리키는 벡터를 말합니다.
공백 내 한 포인트의 값을 다른 포인트의 값에서 빼면 그 결과는 한 오브젝트에서 다른 오브젝트를 “가리키는” 벡터 입니다.
// Gets a vector that points from the player's position to the target's.
var heading = target.position - player.position;
이 벡터는 타겟 오브젝트의 방향을 가리키며, 이 벡터의 크기는 두 위치 사이의 거리와 같습니다. 경우에 따라 고정된 거리를 가지며 목표물로의 방향을 나타내는 ‘정규화된’ 벡터가 필요할 수 있습니다(발사체의 이동 방향을 지정할 때 등). 이 경우 해당 벡터를 자체 크기로 나누어 정규화할 수 있습니다.
var distance = heading.magnitude;
var direction = heading / distance; // This is now the normalized direction.
방법은 크기와 정규화 된 프로퍼티를 모두 별도로 사용하는 것보다 바람직합니다. 둘 다 CPU를 많이 소모하기 때문입니다(모두 제곱근 연산을 수반함).
거리를 비교 용도로만 사용해야 하는 경우(근접성 검사 등) 크기 계산을 하지 않아도 됩니다. sqrMagnitude 프로퍼티는 크기 값을 제곱을 제공하고 크기와 같이 계산되지만 많은 시간을 소모하는 제곱근 연산을 요구하지 않습니다. 크기를 알려진 거리에 비교하는 대신, 크기의 제곱을 거리의 제곱에 비교하면 됩니다.
if (heading.sqrMagnitude < maxRange * maxRange) {
// Target is within range.
}
그러면 실제 크기를 비교에 사용하는 것보다 훨씬 더 효율적입니다.
때로는 3D 작업 시 타겟에 이르는 ’지상 방향’이 필요할 수 있습니다. 예를 들어 지상에 있는 플레이어가 공중의 타겟에 접근해야 하는 경우를 생각해 봅시다. 이때 플레이어의 위치를 타겟의 위치에서 빼면 결과 벡터는 타겟이 존재하는 위쪽을 향합니다. 이 벡터는 플레이어의 트랜스폼 방향을 지정하는 데 적합하지 않습니다. 이 역시 위쪽을 향하기 때문입니다. 실제로 필요한 것은 플레이어의 위치에서 타겟의 수직 아래에 있는 지상 지점에 이르는 벡터입니다. 이 벡터는 뺄셈의 결과물에서 Y 좌표를 0으로 설정하여 쉽게 얻을 수 있습니다.
var heading = target.position - player.position;
heading.y = 0; // This is the overground heading.
벡터를 다룰 때 일반적인 숫자(플로트 값 등)를 흔히 스칼라라고 부릅니다. 스칼라란 크기(‘스케일’)만을 가진다는 의미입니다. 반면 벡터는 크기와 방향을 모두 가집니다.
벡터에 스칼라를 곱하면 원래의 벡터와 동일한 방향을 가리키는 벡터를 얻습니다. 그러나 새 벡터의 크기는 원래 벡터의 크기에 스칼라 값을 곱한 것과 동일합니다.
마찬가지로 스칼라 나눗셈은 오리지널 벡터의 크기를 스칼라로 나누는 것입니다.
이러한 연산은 벡터가 이동 오프셋 또는 힘을 나타낼 때 유용합니다. 이 방식을 통해 벡터의 방향에는 영향을 주지 않으면서 그 크기를 변경할 수 있습니다.
어떤 벡터를 자신의 크기로 나누면 그 결과는 크기가 1인 벡터가 되며, 이 벡터를 정규화된 벡터라 합니다. 정규화된 벡터를 스칼라로 곱하면 그 결과물의 크기는 곱한 스칼라 값과 같습니다. 이는 힘의 방향은 일정하지만 그 강도를 제어할 수 있는 경우에 유용합니다(예: 자동차 바퀴의 힘은 항상 앞으로 작용하지만 그 힘의 크기는 운전자가 제어합니다).
두 벡터의 내적은 스칼라입니다. 이 스칼라는 두 벡터의 크기를 곱한 뒤 두 벡터 사잇각의 코사인 값을 곱한 것과 같습니다. 두 벡터 모두 정규화된 벡터일 경우, 코사인 값은 첫 번째 벡터가 두 번째 벡터의 방향으로 얼마나 뻗어 있는지를 나타냅니다. 또한 그 반대도 성립하며, 파라미터의 순서는 상관이 없습니다.
아래에서 레퍼런스 벡터와 비교한 다양한 각도의 벡터가 1과 –1 사이의 내적 값을 반환하는 방식을 확인할 수 있습니다.
내적은 코사인 계산보다 더 간단한 수학 연산으로, 일부 상황에서는 Mathf.Cos 함수 또는 벡터 크기 연산 대신에 사용할 수 있습니다(정확히 동일한 일을 하지는 않지만 때로는 얻을 수 있는 효과가 동일합니다). 그러나 내적 함수 연산에 소요되는 CPU 시간이 훨씬 짧으므로 이는 유용한 최적화 수단이 될 수 있습니다.
내적은 다른 벡터의 방향에 있는 한 벡터의 크기를 계산하려는 경우에 유용합니다.
예를 들어 자동차의 속도계는 일반적으로 바퀴의 회전 속도를 측정해 속도를 표시합니다. 이 경우 자동차 움직임의 일부분은 자동차 머리가 향하는 방향과는 다르며 이는 속도계로 측정되지 않습니다. 오브젝트의 rigidbody.velocity 벡터의 크기는 전체적인 이동 방향으로 향하는 속도를 나타내지만, 여기서 전방으로 향하는 속도를 분리해 내려면 내적을 사용해야 합니다.
var fwdSpeed = Vector3.Dot(rigidbody.velocity, transform.forward);
방향은 원하는 대로 설정할 수 있지만 방향 벡터는 이 계산에서 항상 정규화되어야 합니다. 이 결과는 속도의 크기보다 더 정확할 뿐만 아니라 크기를 찾는 과정에서 제곱근을 구하느라 많은 시간이 소모되는 것도 방지합니다.
외적은 3D 벡터에서만 의미가 있습니다. 외적은 두 개의 3D 벡터를 입력으로 취하여 또 다른 3D 벡터를 반환합니다.
결과 벡터는 두 벡터와 수직을 이룹니다. 입력 벡터의 순서에 따라 오른손 법칙’을 적용하면 결과 벡터의 방향을 판단할 수 있습니다. 입력 벡터의 순서에 맞게 손가락을 펼치면 엄지손가락이 출력 벡터 방향을 가리키게 됩니다. 파라미터 순서가 바뀌면 결과 벡터가 정확히 반대 방향을 가리키게 되나 그 크기는 동일합니다.
결과 벡터의 크기는 입력 벡터들의 크기를 서로 곱한 결과에 두 벡터 사잇각의 사인 값을 곱한 것과 같습니다. 사인 함수에서 자주 사용되는 값은 아래와 같습니다.
외적은 여러 유용한 정보가 결합된 결과물이므로 일견 복잡해 보일 수 있습니다. 그러나 외적은 내적과 마찬가지로 수학적으로 매우 효율적이며, 연산이 느린 초월 함수(사인, 코사인 등)를 써야 했을 상황에서 코드 최적화를 할 때 유용하게 사용할 수 있습니다.
노멀 벡터(평면에 수직인 벡터)는 메시 생성 시 자주 필요하며, 경로 추적 및 기타 상황에서도 유용합니다. 평면의 세 점(예: 메시 삼각형의 각 꼭짓점)이 주어질 경우 다음과 같이 노멀 벡터를 구할 수 있습니다. - 세 점 중 아무 점이나 하나를 선택합니다. - 이 점을 다른 두 점에서 각각 뺍니다. 그러면 두 개의 새로운 벡터(“Side 1”과 “Side 2”)가 생성됩니다. - 벡터 “Side 1”과 “Side 2”의 외적을 계산합니다. - 외적의 결과는 세 개의 원래 점이 있는 평면에 수직인 새로운 벡터, 즉 “노멀”입니다.
Vector3 a;
Vector3 b;
Vector3 c;
Vector3 side1 = b - a;
Vector3 side2 = c - a;
Vector3 normal = Vector3.Cross(side1, side2);
두 벡터를 외적 함수에 전달해야 하는 순서를 결정할 때는 ’왼손 규칙’을 사용하면 됩니다. 표면의 윗면을 내려다볼 때(즉 노멀 벡터가 뻗어나가는 쪽에서 평면 방향을 보면) 첫 번째 벡터는 시계 방향으로 두 번째 벡터를 향해 돌아야 합니다.
입력 벡터의 순서가 바뀌면 결과는 정확히 반대 방향을 가리킵니다.
메시의 경우 노멀 벡터도 정규화되어야 합니다. 정규화된 프로퍼티를 사용하여 이 작업을 수행할 수 있지만 때때로 유용한 또 다른 트릭이 있습니다. 수직 벡터를 자체 크기로 나눠 정규화할 수도 있습니다.
float perpLength = perp.magnitude;
perp /= perpLength;
삼각형의 영역은 perpLength / 2와 같다는 점도 알아두면 유용합니다. 이는 전체 메시의 표면적을 알고 싶을 때, 또는 상대적 면적에 비례하는 확률에 따라 삼각형을 무작위로 선택하려는 경우에 유용합니다.