본문 바로가기

게임수학

아핀 공간

아핀 공간 : 가상 세계를 구축하는 기본 공간

 

2 * 2 정방 행렬의 곱셈으로는 2차원 평면에서의 이동을 구현할 수 없다.

표준기저벡터의 원점을 이동시키는 변환이 행렬이 되기 위해서는 선형성을 만족해야 하는데, 선형성이 되기 위해서는

기저벡터는 원점에서부터 출발해야하기 때문이다.

 하지만, 공간의 차원을 하나 더 늘린다면 구현이 가능해진다. 

 

전단 변환으로 변화된 공간의 영역의 특징을 이용하여, 전단 변환의 성질을 잘 활용한다면, 선형 변환의 체계에서 특정 이동 기능의 구현이 가능해진다. 마지막 차원의 값이 1이라는 특정 조건을 활요하면, 게임 콘텐츠 제작에 필수적인 이동 기능을 구현할 수 있다.

 

 이동 변환 행렬에서 주의할 점은 이동에 사용되는 벡터는 언제나 마지막 값이 1이어야 한다.

2차원 벡터 상에서 이동 변환을 이용해 이동하고자 할 때, 벡터 (x,y)는 값을 (x,y,1)로 지정해야 한다.

따라서 마지막 차원 값이 1이 된다는 공통점을 가지고 있으며,이렇게 벡터 공간에서 이동을 위해 마지막 차원의 값을 1로 한정한 부분 공간을 아핀 공간이라고 한다.

 

1. 점

마지막 차원 값이 1인 아핀 공간의 원소를 점이라고 한다. 

점이 지니는 성질은 행렬 곱을 사용해 이동이 가능하다.

 

2차원 공간의 점 (x, y, 1)

3차원 공간의 점(x,y,z,1)

 

 

2. 이동 벡터

 벡터의 곱 연산의 특징을 유지하기 위해서, 아핀 공간은 항상 닫혀 있어야 한다.

이를 위해서 아핀 공간은 벡터라는 개념을 제공한다. 벡터는 아핀 공간 내의 이동을 지정하기 위하여 사용되는데, 아핀 공간의 벡터는 이동 벡터 또는 변위 벡터라고 부른다. 

 

아핀 공간의 점 P1에 이동 벡터 v 를 더한 결과는 P2에 대응된다.

수식으로 표현하면 다음과 같다.

 

P1 + v = P2, v = P2 - P1이다.

주의할 점은, 벡터는 뺄셈 교환 법칙이 적용되지 않으므로 v 값이 음수가 되었을 경우엔, 반대 방향의 벡터 값을 만든다.

 

3. 아핀 공간의 성질

 점과 벡터의 덧셈 연산은 아핀 공간에 대해 닫혀 있다. 따라서, 2차원 평면 공간에서 이동 벡터를 표시하게 된다면, 동일한 크기와 방향으로 이동시킨 결과를 확인할 수 있다. 우리 3차원 현실 세계는 유클리드 공간이라고 하고, 유클리드 공간에서 작용하는 힘을 유클리드 벡터라고 한다. 이에 대응되는 개념이 아핀 공간과 이동 벡터인 것이다.

 

 

4. 아핀 결합

 아핀 공간의 특징으로 인하여, 점과 점의 결합은 이루어질 수 없다. 하지만, 스칼라 값을 곱함으로서 우리는 선형 결합을 이끌어 낼 수 있다. 

  a * P1 + b * P2 = (ax1 + bx2, ay1 + by2, a+ b)이다.

 우리는 아핀 공간에서 a + b = 1의 조건을 만족하기 때문에, 점의 생성이 가능해진다.

이렇게 여러 개의 점을 결합해 새로운 점을 생성하는 수식을 아핀 결합이라고 한다.

 

 

5. 선 그리기 알고리즘

 수학에서 벡터를 표현할 때 y축이 위쪽을 향하는 데카르트 좌표계를 사용한다. 하지만 모니터 화면의 좌표계는 y축이 아래를 향하는 스크린 좌표계를 사용한다. 

 스크린 좌표계는 정수를 사용하며, 데카르트 좌표게와 상이하게 서로 독립된 이산적인 정수를 사용한다.

스크린 좌표계를 사용해 좌표에 무언가를 표시하기 위해서는 좌표와 색상이 지정되어야 한다. 

우리가 사용하는 CK 소프트렌더러는 (0,0)을 기점으로 하였을 때, 오른쪽 하단에 있는 위치한 픽셀을 선택하는 것을 규칙으로 한다.

 

 CK 소프트렌더러는 스크린 좌표계를 다루기 위해  ScreenPoint라는 구조체를 선언한다.

 

struct ScreenPoint
{
public:
	FORCEINLINE constexpr ScreenPoint() = default;
	FORCEINLINE explicit constexpr ScreenPoint(int InX, int InY) : X(InX), Y(InY) { }
	FORCEINLINE explicit constexpr ScreenPoint(float InX, float InY) : X(Math::FloorToInt(InX)), Y(Math::FloorToInt(InY)) { }
	FORCEINLINE explicit constexpr ScreenPoint(const Vector2& InPos) : ScreenPoint(InPos.X, InPos.Y) {}

	constexpr ScreenPoint GetHalf() const { return ScreenPoint(Math::FloorToInt(0.5f * X), Math::FloorToInt(0.5f * Y)); }
	constexpr float AspectRatio() const { return (float)X / (float)Y; } // Y축 기준으로 측정
	FORCEINLINE constexpr bool HasZero() const { return ( X == 0 || Y == 0 ); }

	FORCEINLINE static constexpr ScreenPoint ToScreenCoordinate(const ScreenPoint& InScreenSize, const Vector2& InPos)
	{
		return ScreenPoint(InPos.X + InScreenSize.X * 0.5f, -InPos.Y + InScreenSize.Y * 0.5f);
	}

	FORCEINLINE constexpr Vector2 ToCartesianCoordinate(const ScreenPoint& InScreenSize)
	{
		return Vector2(X - InScreenSize.X * 0.5f + 0.5f, -(Y + 0.5f) + InScreenSize.Y * 0.5f);
	}

	FORCEINLINE constexpr ScreenPoint operator-(const ScreenPoint& InPoint) const;
	FORCEINLINE constexpr ScreenPoint operator+(const ScreenPoint& InPoint) const;

	int X = 0;
	int Y = 0;
};

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

삼각형  (0) 2024.09.18
벡터의 내적  (0) 2024.09.14
행렬  (1) 2024.09.11