cyphen156

게임 수학 3장 : 벡터(가상 공간의 탄생) 본문

수학/게임수학

게임 수학 3장 : 벡터(가상 공간의 탄생)

cyphen156 2023. 8. 17. 13:13

(A1, B1) + (A2, B2) = (A1+A2, B1+B2)

데카르트 좌표계

앞서 장에서 곱집합 == 좌표평면이라 봐도 된다고 설명했습니다.

좌표 평면은 2차원 세계에서 수를 시각화 하여 표현하는 방법입니다.

데카르트 좌표계는 (0,0)이라는 중심 축(원점)이 존재하고 각 축은 대개 x축과 y축으로 불립니다.

점은 좌표평면 내에서 원점을 기준으로 위치하는 지점 마다 크기와 방향을 가지고 있고, 4개의 방향으로 나뉘어 4개의 사분면들을 이룹니다.

스칼라와 벡터

보통 스칼라는 물리학과 수학에서 순수한 힘의 크기(절댓값)을 의미하고, 벡터는 스칼라 + 힘의 방향(+ or -)를 갖는 것을 말합니다.

그런데 게임 수학에서는 물체의 평면에서의 이동을 표현하기 위해 공리라는 것과 체의 구조를 사용하고, 이것을 좌표 평면에 표현할 때 공리적 관점에서 규정한 것을 벡터 공간(V)이라고 하며, 벡터 공간에서 표현되는 원소들을 벡터([각주:1]v→)라고 부릅니다.

벡터는 v→=(x, y)로 표기합니다.

 

벡터공간의 연산

벡터 공간에서의 연산은 두 가지가 있고, 8가지 성질을 갖는데 이를 벡터 공간의 공리라 말합니다. 

1. 벡터와 벡터의 덧셈 = 벡터의 합

2. 스칼라와 벡터의 곱셈 = 스칼라 곱셈 == 스칼라배

※ 스칼라 곱과 스칼라 곱셈은 서로 다른 연산입니다. 스칼라 곱은 내적을 의미합니다.

 

No 분류 공리 수식
1 벡터의 합 결합법칙 u + (v + w) = (u + v) + w
2 교환법칙 u + v = v + u
3 항등원 v + 0 = v
4 역원 v + (-v) = 0
5 스칼라 곱셈 호환성 a(bv) = (ab)v
6 항등원 1 * v = v
7 벡터의 합에 대한 분배법칙 a(u + v) = au + av
8 스칼라 덧셈에 대한 분배 법칙 (a + b)v = av + bv
//벡터의 합과 스칼라 곱셈의 구현
float scarlar = 10.0f;
Vector2 vec1(2.f, 3.f);
Vector2 vec2(4.f, 5.f);

Vector2 addition = vec1 + vec2;
Vector2 multiplication = vec1 * scarlar;

벡터의 크기(Norm)

그럼 벡터의 크기는 어떻게 계산할까요?

벡터의 크기는 피타고라스의 정리(직각삼각형의 길이)를 이용하여 구하게 됩니다.

피타고라스의 정리

삼각형의 빗변의 길이 제곱은 (C**2)직각을 끼인 각으로 갖는 두 변의 길이제곱의 합(A**2 + B**2)과 같다.

그런데 음수는 존재할 수 없으니까 항상 절댓값(Absolute)로 존재하게 되는것을 이용해 벡터의 크기 역시 수직막대 (| C | or || C ||)를 사용하여 표현합니다.

그런데 Norm은 뭘까?

일정한 크기의 단위 벡터(1v->)를 기준으로 벡터의 크기를 측정한 값을 Norm이라 하고, 정규화(Normalize) 한다고 부릅니다.

ex) 5(v->) = 5(Norm)

벡터의 결합과 생성(Span)

벡터 공간에서의 벡터의 합과 스칼라 곱셈의 연산은 선형성이 존재합니다.

이러한 선형 연산을 사용해 n개의 스칼라와 벡터를 결합하여 새로운 벡터를 생성하는 수식을 선형 결합(조합)이라고 합니다.

이 때 벡터의 모든 원소가 0으로 구성되어 있다면 선형 결합의 연산 결과 또한 0이 나오게 됩니다.

벡터에 곱하는 모든 스칼라 값이 0이면 선형 결합의 결과는 항상 영벡터가 되는데 이를 선형 독립의 관계를 갖는다고 합니다.

그런데  a가 0이 아닌 경우에도 영벡터가 나오는 경우가 존재하는데, 이를 선형 종속(의존) 관계를 갖는다고 합니다.

선형 조합 a1v1-> + a2v2-> + ... + anvn-> = V'-> 
선형 독립의 관계(모든 a는 0) a1v1-> + a2v2-> + ... + anvn-> = 0-> 
선형 종속(의존)의 관계 a1v1-> + a2v2-> + ... + anvn-> = 0-> 

선형 독립의 관계를 갖는 벡터를 결합하면 벡터 공간에 생성되는 모든 벡터를 생성할 수 있다고 합니다. 왜그럴까요?

간단히 설명해서 좌표 평면에서의 각 축(x, y)에 대해 축위에 존재하는 단위 벡터(x: (1, 0) / y: (0, 1))를 사용하여 정규화하여 표현할 수 있다는 것을 의미하는 것 입니다.

여기서 독립의 관계는 x : (n, 0), y : (0, n)과 같을 때 0을 의미하는 것 입니다.

5벡터에 대한 각 축의 단위 벡터 표현

그런데 5벡터를 생성하는 방법은 하나만이 아닙니다.

선형 의존 (x: (2, 1) / y : (1, 3))을 통해서도 5, 5의 좌표를 향하는 벡터를 생성할 수 있습니다.

"그렇다면 선형 의존 관계를 이용해서 5, 5가 아닌 다른 벡터를 생성할 수 있을까??"

이에 대한 해답은 연립방정식에 있습니다.

예를 들어 (wx, wy)를 향하는 w벡터를 생성하고자 할때, (2, 1), (3, 1)이라는 두 벡터를 이용하여 생성한다면

연립방정식으로 풀이하면

=> 2a + b = wx, a + 3b = wy

=> a = (3wx - wy)/5, b = (2wy - wx)/5로 정리할 수 있고, 언제나 해가 존재할 수 있음을 알 수 있습니다.

반대로 생성할수 없는 경우도 존재합니다.

두 벡터가 (1, 2), (2, 4)와 같이 하나의 직선 위에 존재하는 벡터인 경우 특정 위치로의 벡터를 생성할 수 없고, 오직 같은 직선위에 있는 벡터만 생성이 가능합니다.

이를 통해서 평행한 두 벡터의 결합의 결과는 두 개 벡터의 결합이 아닌 하나의 벡터에 스칼라 곱을 적용한 결과(벡터 2는 벡터 1의 n배)임을 도출할 수 있기 때문입니다.

결론적으로 두 벡터를 이용한 새로운 벡터의 생성은 선형 독립의 관계에 있는 두 벡터를 사용해야 하고, 단 하나의 해가 존재한다는 것(특별한 관계가 존재한다)을 알 수 있습니다.

초록색 방향으로의 평행이동(1, 3))이 일어나지 않는 이상 5, 5벡터는 생성할 수 없다.

또 선형 독립의 관계가 유지 되려면 2개의 벡터만 사용되어야 합니다. 만약 3개 이상의 벡터를 사용하게 된다면 우선 두 벡터의 결합을 통해 평면의 모든 벡터를 생성할 수 있으므로, 여기에 남은 한 개의 벡터 C의 역원을 곱해 -c를 생성하여 영행렬을 만드는 경우가 발생할 수 있고, 이것(-c를 생성하는것)이 선형 독립의 관계를 만족시키지 못하게 만들기 때문입니다.

=> a(2, 1) + b(1, 3) + c(x, y) = (0, 0)일 때

=> a+b + c = 0

=> a+b = -c라는 결과가 발생하기 때문

선형 독립인 두 벡터에 한 개의 벡터를 추가하면 선형 독립이 유지되지 않는 이유에 대한 ChatGPT4의 추가 설명

더보기
  1. 두 벡터 가 주어져 있고, 이 두 벡터가 선형 독립이라고 가정해보겠습니다. 이 경우, 두 벡터의 선형 결합으로 영벡터(0)를 만들 수 있는 유일한 방법은 각 벡터의 계수가 모두 0일 때입니다.
  2. 이제 세 번째 벡터 를 추가합니다. 만약 이 의 선형 결합으로 표현될 수 있다면, 에 종속된 것입니다. 그리고 이 경우 세 벡터 , , 중에서 적어도 하나는 다른 벡터(들)의 선형 결합으로 표현될 수 있기 때문에, 이 세 벡터는 선형 독립이 아닙니다.
  3. 여기서 "-c라는 벡터를 a+b를 통해 생성하여 최종적으로 세 벡터를 사용한 영벡터의 생성"이라는 설명은, 만약 의 선형 결합으로 표현될 수 있다면, 역시 의 선형 결합으로 표현될 수 있습니다. 따라서, 라는 선형 결합을 통해 영벡터를 만들 수 있게 됩니다. 이는 세 벡터가 선형 독립이 아님을 의미합니다.
  4. 결론적으로, 두 개의 선형 독립인 벡터 에 대해 세 번째 벡터 를 추가할 때, 에 종속되면 세 벡터는 선형 독립이 아닙니다.

+++선형 의존과 선형 독립에 대한 추가 설명

선형 의존

벡터라 함은 물리학에 비유하면 힘의 크기라 볼 수 있다. 

선형 의존이라 함은 벡터가 선형성에 의존하여 진행하고 있다는 것이다.

즉, 힘의 진행 방향이 같거나 반대방향으로 진행하고 있다는 것을 의미한다. 

다른 말로 표현하면 수선 위에 존재하는 같은 기울기를 가지고 있는 상황이라는 것이다.

선형 독립

반대로 선형 독립이라 함은 각 벡터가 모두 다른 기울기를 가지고 있다는 것을 의미한다.

이것을 좌표 평면으로 본다면 다음 표와 같이 원점(0, 0)을 기준으로 힘의 진행 방향이 모두 다르다는 것을 알 수 있고, 이것을 통해 벡터의 기울기가 모두 다르다는 것을 알 수 있다. 이 벡터들이 만나는 점은 원점밖에 존재하지 않는다. 즉, 특별한 스칼라(0)을 곱해야만 한 점에서 만날 수 있다는 것을 의미한다. 

이렇듯 힘의 진행방향이 다른 경우를 선형 독립이라 한다. 

기저 벡터(Basis vector)와 차원(Dimension)

기저(basis)란

벡터 공간 내에서 모든 벡터를 생성할 수 있는 선형 독립 관계를 가지는 벡터의 집합을 기저라고 합니다. 그리고 기저에 속한 벡터기저 벡터라고 합니다.

차원(Dimension)

차원은 기저  집합이 가지는 원소의 수를 이야기합니다.

그러니까 n차원 평면을 생성할 때에는 항상 n 개의 기저 벡터를 가지고 있으니 n차원 실벡터 공간이라고 부릅니다.

그리고 이  차원을 구성하는 다양한 기저 중 한 축(x, y, z...등의 중심 축)만을 사용하는 단위 벡터로 구성된 집합을 특별하게 표준기저(Standard basis)라고 부르며, 각 원소를 표준기저벡터라고 부르고 순서대로 e1, e2, e3....로 표기합니다.

  차원 표기 단위 벡터 표기
1차원 (1) e1
2차원 (1, 0), (0, 1) e1, e2
3차원 (1, 0, 0), (0, 1, 0), (0, 0, 1) e1, e2, e3

실습하기 

다음의 코드는 게임수학 책 내용중 실습 예제 1과 예제 2를 하나의 코드로 합친것 입니다. 

main 브랜치가 아닌 3-1, 3-2브랜치를 통해 전체 프로젝트를 다운받아 사용할 수 있습니다.

gamemath/README.md at 3-1 · onlybooks/gamemath · GitHub

#include "Precompiled.h"
#include "SoftRenderer.h"
#include <random>
using namespace CK::DD;

// 격자를 그리는 함수
void SoftRenderer::DrawGizmo2D()
{
	auto& r = GetRenderer();
	const auto& g = Get2DGameEngine();

	// 그리드 색상
	LinearColor gridColor(LinearColor(0.8f, 0.8f, 0.8f, 0.3f));

	// 뷰의 영역 계산
	Vector2 viewPos = g.GetMainCamera().GetTransform().GetPosition();
	Vector2 extent = Vector2(_ScreenSize.X * 0.5f, _ScreenSize.Y * 0.5f);

	// 좌측 하단에서부터 격자 그리기
	int xGridCount = _ScreenSize.X / _Grid2DUnit;
	int yGridCount = _ScreenSize.Y / _Grid2DUnit;

	// 그리드가 시작되는 좌하단 좌표 값 계산
	Vector2 minPos = viewPos - extent;
	Vector2 minGridPos = Vector2(ceilf(minPos.X / (float)_Grid2DUnit), ceilf(minPos.Y / (float)_Grid2DUnit)) * (float)_Grid2DUnit;
	ScreenPoint gridBottomLeft = ScreenPoint::ToScreenCoordinate(_ScreenSize, minGridPos - viewPos);

	for (int ix = 0; ix < xGridCount; ++ix)
	{
		r.DrawFullVerticalLine(gridBottomLeft.X + ix * _Grid2DUnit, gridColor);
	}

	for (int iy = 0; iy < yGridCount; ++iy)
	{
		r.DrawFullHorizontalLine(gridBottomLeft.Y - iy * _Grid2DUnit, gridColor);
	}

	ScreenPoint worldOrigin = ScreenPoint::ToScreenCoordinate(_ScreenSize, -viewPos);
	r.DrawFullHorizontalLine(worldOrigin.Y, LinearColor::Red);
	r.DrawFullVerticalLine(worldOrigin.X, LinearColor::Green);
}

// 게임 오브젝트 목록


// 최초 씬 로딩을 담당하는 함수
void SoftRenderer::LoadScene2D()
{
	// 최초 씬 로딩에서 사용하는 모듈 내 주요 레퍼런스
	auto& g = Get2DGameEngine();

}

// 게임 로직과 렌더링 로직이 공유하는 변수

Vector2 currentPosition(100.f, 100.f);
Vector2 currentPosition2(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), input.GetAxis(InputAxis::YAxis));
	Vector2 deltaPosition = inputVector * moveSpeed * InDeltaSeconds;
	
	Vector2 inputVector2 = Vector2(input.GetAxis(InputAxis::XAxis), input.GetAxis(InputAxis::YAxis)).GetNormalize();
	Vector2 deltaPosition2 = inputVector2 * moveSpeed * InDeltaSeconds;

	// 물체의 최종 상태 설정
	currentPosition += deltaPosition;

}

// 렌더링 로직을 담당하는 함수
void SoftRenderer::Render2D()
{
	// 렌더링 로직에서 사용하는 모듈 내 주요 레퍼런스
	auto& r = GetRenderer();
	const auto& g = Get2DGameEngine();

	// 배경에 격자 그리기
	DrawGizmo2D();

	// 렌더링 로직의 로컬 변수
	
	// line그리기
	static float lineLength = 500.f;
	Vector2 lineStart = currentPosition * lineLength;
	Vector2 lineEnd = currentPosition * -lineLength;
	r.DrawLine(lineStart, lineEnd, LinearColor::Black);

	r.DrawPoint(currentPosition, LinearColor::Red);
	r.DrawPoint(currentPosition + Vector2::UnitX, LinearColor::Red);
	r.DrawPoint(currentPosition - Vector2::UnitX, LinearColor::Red);
	r.DrawPoint(currentPosition + Vector2::UnitY, LinearColor::Red);
	r.DrawPoint(currentPosition - Vector2::UnitY, LinearColor::Red);
	r.DrawPoint(currentPosition + Vector2::One, LinearColor::Red);
	r.DrawPoint(currentPosition - Vector2::One, LinearColor::Red);
	r.DrawPoint(currentPosition + Vector2(1.f, -1.f), LinearColor::Red);
	r.DrawPoint(currentPosition - Vector2(1.f, -1.f), LinearColor::Red);

	r.PushStatisticText("Coordinate : " + currentPosition.ToString());


	// 렌더링 로직의 로컬 변수
	// Circle그리기
	static float radius = 50.f;
	static std::vector<Vector2> circles;

	if (circles.empty())
	{
		for (float x = -radius; x <= radius; ++x)
		{
			for (float y = -radius; y <= radius; ++y)
			{
				Vector2 pointToTest = Vector2(x, y);
				float squaredLength = pointToTest.SizeSquared();
				if (squaredLength <= radius * radius)
				{
					circles.push_back(Vector2(x, y));
				}
			}
		}
	}

	// 원을 구성하는 벡터를 붉은색으로 표시하기
	for (auto const& v : circles)
	{
		r.DrawPoint(v + currentPosition, LinearColor::Red);
	}
}

// 메시를 그리는 함수
void SoftRenderer::DrawMesh2D(const class DD::Mesh& InMesh, const Matrix3x3& InMatrix, const LinearColor& InColor)
{
}

// 삼각형을 그리는 함수
void SoftRenderer::DrawTriangle2D(std::vector<DD::Vertex2D>& InVertices, const LinearColor& InColor, FillMode InFillMode)
{
}

 

모든 예제 코드의 소스파일은 

GitHub - onlybooks/gamemath: <이득우의 게임 수학> 공식 깃허브 페이지

 

GitHub - onlybooks/gamemath: <이득우의 게임 수학> 공식 깃허브 페이지

<이득우의 게임 수학> 공식 깃허브 페이지. Contribute to onlybooks/gamemath development by creating an account on GitHub.

github.com

또한 제 개인 깃허브 레포지토리 에 있습니다.

Workspace/C++/GameMath at main · cyphen156/Workspace · GitHub

 

 

 

 

 

  1. 벡터의 표현은 v위에 화살표가 올라가있는 것 이지만 입력의 편의를 위해 그냥 v→로 입력하겠습니다. [본문으로]