본 포스트는 "이득우의 게임 수학"을 읽고 정리한 내용입니다
이득우의 게임 수학 - https://www.onlybook.co.kr/entry/gamemath
스칼라와 벡터
평면의 좌표 $(x, y)$는 두 실수 $x$와 $y$를 결합해 만들어짐
→ 따라서 좌표의 연산은 실수가 지나는 연산의 성질을 바탕으로 설계되어야 함!
즉, 좌표간의 덧셈이나 스칼라 곱셈 등도 실수의 연산 성질을 기반으로 해야 한다는 뜻
저번에 실수의 연산 성질은 체의 구조를 띤다는 사실을 확인했으므로…
- 새로운 공리를 덧붙여서 평면을 대표하는 집합을 규정
- 해당 집합에서 이뤄지는 덧셈/곱셈 연산 체계를 만들어야 함
→ 이를 통해 평면에서의 움직임을 표현할 수 있다
벡터 공간(Vector Space)
: 두 개 이상의 실수를 곱집합으로 묶어 형성된 집합을, 공리적 집합론의 관점에서 규정한 것
- $V$로 표현
벡터(Vector)
: 벡터 공간의 원소
- $\vec{v}$로 표현
- 벡터는 $\vec{v} = (x, y)$ 와 같이 좌표와 동일한 방법으로 표기
x, y를 실수로 규정하기보다는 체의 구조를 지니는 집합, 즉 체 집합의 원소로 규정
그리고 스칼라(Scalar)란, 체의 구조를 가지는 수 집합의 원소
→ 즉, 우리가 좌표로 사용하는 실수 x와 y는 모두 공리적 집합론의 관점에서 스칼라인 것!
📌 공리적 집합론 관점이 아닌, 일반적인 관점으로 보는 벡터와 스칼라
1. 스칼라(Scalar)
: 크기만을 가지는 수
- ex) 온도, 질량, 시간, 에너지, 길이 등...
- 스칼라 간의 덧셈, 뺄셈, 곱셈, 나눗셈은 일반적인 수학 연산 규칙을 따름
2. 벡터(Vector)
: 크기와 방향을 가지는 수
- ex) 힘, 속도, 가속도, 변위, 전기장 등
- 벡터 간의 연산에는 벡터의 덧셈, 뺄셈, 스칼라 곱, 벡터 곱 등이 있으며, 이때 덧셈과 뺄셈은 방향과 크기를 고려해 수행
벡터 공간의 연산
벡터 공간의 두 가지 기본 연산
1. 벡터와 벡터의 덧셈 (줄여서 벡터의 합으로 부름)
$$ \vec{v_1} + \vec{v_2} = (x_1, y_1) + (x_2, y_2) = (x_1 + x_2, y_1 + y_2) $$
2. 스칼라와 벡터의 곱셈 (줄여서 스칼라배 라고 부름)
책에서는 스칼라 곱셈이라 했지만 내적과 헷갈릴까봐..
$$ a \cdot \vec{v} = a \cdot (x, y) = (a \cdot x, a \cdot y) $$
벡터 공간의 8가지 공리 (벡터 공간의 연산이 갖는 성질)
1️⃣ 벡터의 합
- 벡터의 합의 결합법칙
- 벡터의 합의 교환법칙
- 벡터의 합의 항등원 : $\vec{0}$
- 벡터의 합의 역원 : $-\vec{v}$
2️⃣ 스칼라배
- 스칼라배의 호환성 ex) $a(b\vec{v}) = (ab)\vec{v}$
- 스칼라배의 항등원 : 1
- 벡터의 합에 대한 분배법칙 ex) $a(\vec{u} + \vec{v}) = a\vec{u} + a\vec{v}$
- 스칼라 덧셈에 대한 분배법칙 ex) $(a+b)\vec{v} = a\vec{v} + b\vec{v}$
벡터의 연산과 이동
벡터 공간에서 두 연산에 따라 점의 움직임이 어떻게 달라지는지 확인해보자
→ 평면의 점을 각 축에 대해 독립적으로 평행 이동시키는 작업
1. 벡터의 합연산
2. 스칼라배 연산
프로그래밍으로 벡터의 합과 스칼라 곱셈 연산을 구현한 코드
float scalar = 10.f;
Vector2 vec1(2.f, 3.f);
Vector2 vec2(4.f, 5.f);
Vector2 addition = vec1 + vec2; // (6, 8)
Vector2 multiplication = vec1 * scalar; // (20, 30)
✏️실습 예제 3-1) 헬로 벡터
책의 예제 환경을 구성하고, 예제들을 실행해보았다
// 게임 로직과 렌더링 로직이 공유하는 변수
Vector2 currentPosition(100.f, 100.f); // 물체의 위치를 보관하는 변수
// 게임 로직을 담당하는 함수
// 게임 로직은 주로 사용자 입력에 따른 처리를 다룬다
void SoftRenderer::Update2D(float InDeltaSeconds)
{
// 게임 로직에서 사용하는 모듈 내 주요 레퍼런스
auto& g = Get2DGameEngine(); // 게임 엔진 모듈에 접근
const InputManager& input = g.GetInputManager(); // 입력 관리자 접근 (읽기전용)
// 게임 로직의 로컬 변수
static float moveSpeed = 100.f; // 프레임 당 이동 속도
Vector2 inputVector = Vector2(input.GetAxis(InputAxis::XAxis), // X축과
input.GetAxis(InputAxis::YAxis)); // Y축을 결합해
// 입력벡터 생성
// 이동속도 * 프레임 경과시간 -> 해당 프레임에서 이동할 길이를 계산해 입력 벡터와 연산
Vector2 deltaPosition = inputVector * moveSpeed * InDeltaSeconds;
// 물체의 최종 상태 설정
currentPosition += deltaPosition; // DTPosition 값을 반영해 현재 프레임의 최종 위치 확정
}
// 렌더링 로직을 담당하는 함수
void SoftRenderer::Render2D()
{
// 렌더링 로직에서 사용하는 모듈 내 주요 레퍼런스
auto& r = GetRenderer(); // 렌더러 모듈에 접근
const auto& g = Get2DGameEngine(); // 게임 엔진 모듈 접근
// 배경에 격자 그리기
DrawGizmo2D();
// 렌더링 로직의 로컬 변수
// 밝은 회색의 선을 사용해 평행한 벡터를 표현
static float lineLength = 500.f; // 벡터와 평행한 선을 표현할 길이 지정
Vector2 lineStart = currentPosition * lineLength; // 평행선의 시작점 계산
Vector2 lineEnd = currentPosition * -lineLength; // 평행선의 끝점 계산
r.DrawLine(lineStart, lineEnd, LinearColor::LightGray); // 평행선을 밝은 회색으로 표현
// 벡터를 파란 픽셀로 표현
// 점이 잘 보이도록 점의 둘레 8개의 픽셀을 찍어줬다!
r.DrawPoint(currentPosition, LinearColor::Blue); // 이 점이 원래 벡터
r.DrawPoint(currentPosition + Vector2::UnitX, LinearColor::Blue);
r.DrawPoint(currentPosition - Vector2::UnitX, LinearColor::Blue);
r.DrawPoint(currentPosition + Vector2::UnitY, LinearColor::Blue);
r.DrawPoint(currentPosition - Vector2::UnitY, LinearColor::Blue);
r.DrawPoint(currentPosition + Vector2::One, LinearColor::Blue);
r.DrawPoint(currentPosition - Vector2::One, LinearColor::Blue);
r.DrawPoint(currentPosition + Vector2(1.f, -1.f), LinearColor::Blue);
r.DrawPoint(currentPosition - Vector2(1.f, -1.f), LinearColor::Blue);
// 벡터의 현재 좌표를 우상단에 출력
r.PushStatisticText("Coordinate : " + currentPosition.ToString());
}
결과 화면
기본 설정된 좌표 (100, 100)의 벡터와 벡터의 평행선이 그려진 걸 확인할 수 있다
키보드로 방향이동을 하면, 벡터가 이동하게 되면서 벡터와 벡터의 평행선이 이동하게 된다
벡터의 크기와 이동
수의 크기는 원점으로부터 거리를 의미하면서, 절댓값 기호인 || 를 사용해 구할 수 있었음
→ 마찬가지로, 벡터의 크기도 동일하게 원점으로부터의 최단 거리를 의미
따라서 이를 측정하려면 피타고라스 정리를 사용해 거리를 측정
$$ c = \sqrt{a^2 + b^2} $$
벡터의 크기도 같은 기호를 사용해 표현 (||)
그러나 일반적으로 벡터의 크기는 두개의 수직 막대로 표현한다 ex) $||\vec{v}||$
벡터 (x, y)의 크기를 구하는 공식을 표현하면,
$$ ||\vec{v}|| = |\vec{v}| = \sqrt{x^2 + y^2} $$
벡터의 크기는 노름(Norm)이라는 용어로 부르기도 함
단위 벡터
크기가 1인 벡터를 단위 벡터(Unit Vector)라고 하는데,
벡터의 크리를 측정하는 기준이 되며, 벡터와 관련된 다양한 응용식을 전개하는 데 사용
- $\hat{v}$ 기호로 표현
- 스칼라배의 성질을 이용해, 임의의 벡터 $\vec{v}$를 이의 크기인 $|\vec{v}|$로 나누면 단위벡터 $\hat{v}$가 됨
이렇게 $\vec{v}$를 크기가 1인 단위 벡터 $\hat{v}$로 다듬는 과정을 정규화(Normalize)라고 한다
$$ \hat{v} = \frac{\vec{v}}{|\vec{v}|} $$
✏️ 실습 예제 3-2) 벡터로 원 그리기
책의 예제 환경을 구성하고, 예제들을 실행해보았다
다수의 벡터를 사용해 하나의 원을 그리고, 입력에 따라 원을 움직이는 예제다
점으로 표현되는 평면의 벡터를 모아 원의 형태를 만들 것인데,
1️⃣ 지정한 반지름을 가진 원을 만들기 위해선 먼저 원을 둘러싼 사각형 영역을 설정해야 한다
2️⃣ 사각형 영역으로부터 간격이 일정한 벡터를 모두 생성하고,
3️⃣ 이 중에서 원의 영역에 속한 벡터만 골라내면 원을 구성하는 벡터의 묶음을 만들 수 있다
그림으로 순서를 확인하면 다음과 같다!
// 게임 로직과 렌더링 로직이 공유하는 변수
Vector2 currentPosition(100.f, 100.f);
// 게임 로직을 담당하는 함수
void SoftRenderer::Update2D(float InDeltaSeconds)
{
// 게임 로직에서 사용하는 모듈 내 주요 레퍼런스
auto& g = Get2DGameEngine();
const InputManager& input = g.GetInputManager();
// 게임 로직의 로컬 변수
static float moveSpeed = 100.f;
// GetNormalize 함수로 입력 벡터의 크기를 항상 1로 정규화
Vector2 inputVector = Vector2(input.GetAxis(InputAxis::XAxis),
input.GetAxis(InputAxis::YAxis)).GetNormalize();
Vector2 deltaPosition = inputVector * moveSpeed * InDeltaSeconds;
// 물체의 최종 상태 설정
currentPosition += deltaPosition;
}
// 렌더링 로직을 담당하는 함수
void SoftRenderer::Render2D()
{
// 렌더링 로직에서 사용하는 모듈 내 주요 레퍼런스
auto& r = GetRenderer();
const auto& g = Get2DGameEngine();
// 배경에 격자 그리기
DrawGizmo2D();
// 렌더링 로직의 로컬 변수
static float radius = 50.f; // 그릴 원의 반지름을 50으로 지정
static std::vector<Vector2> circles; // 원을 구성하는 점을 저장하는 vector
// 최초에 한번, 반지름보다 긴 벡터를 모아 컨테이너에 담는다
if (circles.empty()) // circles가 비어있을 때에만 원을 채워넣도록
{
for (float x = -radius; x <= raduis; ++x)
{
for (float y = -radius; y <= radius; ++y)
{
Vector2 pointToTest = Vector2(x, y);
float squaredLength = pointToTest.SizeSquared(); // x^2 + y^2 만을 계산해
if (squaredLength <= radius * radius) // 반지름 제곱과 사각형 제곱값을 비교
{ // 결국 x + y <= r 비교와 같음
circles.push_back(Vector2(x, y)); // 원 구성원에 해당 벡터 추가
}
}
}
}
// 원을 구성하는 벡터를 모두 붉은색으로 표시
for(auto const& v : circles)
{
// 각 점의 위치에 현재 중점 값을 더하고 붉은색으로 찍는다
r.DrawPoint(v + currentPosition, LinearColor::Red);
}
// 원의 중심 좌표를 우상단에 출력
r.PushStatisticText("Coordinate " + currentPosition.ToString());
}
결과 화면
키보드로 벡터의 좌표를 이동해 원을 움직일 수 있다
'Game > Game Math' 카테고리의 다른 글
[책으로 배우는 게임 수학] 이득우의 게임 수학 - 벡터 (1), 데카르트 좌표계 (0) | 2024.06.07 |
---|---|
[책으로 배우는 게임 수학] 이득우의 게임 수학 - 수 (2), 함수 (0) | 2024.05.28 |
[책으로 배우는 게임 수학] 이득우의 게임 수학 - 수 (1), 수와 집합 (0) | 2024.05.24 |