수학/이득우의 게임 수학 - 실습 예제

와이어프레임 렌더링의 구현

monstro 2025. 11. 26. 15:34
728x90
반응형

- 개요

정점 버퍼인덱스 버퍼를 사용하여 삼각형의 외곽선을 그려 메시를 표현하는 와이어프레임을 구현한다

 

- 예제

1) 정점 구조체

struct Vertex2D
{
public:
        // 4개의 생성자가 존재 
        // 기본 생성자 / 정점의 위치를 인자로 받는 생성자 / 정점의 위치 + 정점의 색상을 인자로 받는 생성자 / 정점의 위치 + 정점의 색상 + 정점의 텍스처를 인자로 받는 생성자
	constexpr Vertex2D() = default;
	constexpr Vertex2D(const Vector2& InPosition) : Position(InPosition) { }
	constexpr Vertex2D(const Vector2& InPosition, const LinearColor& InColor) : Position(InPosition), Color(InColor) { }
	constexpr Vertex2D(const Vector2& InPosition, const LinearColor& InColor, const Vector2& InUV) : Position(InPosition), Color(InColor), UV(InUV) { }

        // 정점과 스칼라 곱연산을 위한 연산자 오버로딩
	constexpr Vertex2D operator*(float InScalar) const
	{
		return Vertex2D(
			Position * InScalar,
			Color * InScalar,
			UV * InScalar
		);
	}

        // 정점과 다른 정점의 합연산을 위한 연산자 오버로딩
	constexpr Vertex2D operator+(const Vertex2D& InVector) const
	{
		return Vertex2D(
			Position + InVector.Position,
			Color + InVector.Color,
			UV + InVector.UV
		);
	}

        // 3개의 멤버변수가 존재
        // 정점의 위치 / 정점의 색상 / 정점의 텍스처
	Vector2 Position;
	LinearColor Color;
	Vector2 UV;
};

 

위와 같이 하나의 정점을 의미하는 Vertex2D 구조체를 생성하였다

구성은 위와 같으며, 멤버변수로 위치 + 색상 + 텍스처를 갖도록 설정하였다

 

2) 렌더링 로직

...

// 화면에 렌더링하는 함수
void SoftRenderer::Render2D()
{
	// 화면에 렌더하기 위해 가져올 레퍼런스
	auto& r = GetRenderer();
	const auto& g = Get2DGameEngine();

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

	// 렌더링할 메시 데이터의 변수 선언
        // 렌더링할 정사각형의 절반 크기 / 렌더링할 정사각형의 정점 개수 / 렌더링할 정사각형을 구성한 삼각형의 개수
	static constexpr float squareHalfSize = 0.5f;
	static constexpr size_t vertexCount = 4;
	static constexpr size_t triangleCount = 2;

	// 정점 버퍼를 생성 -> 첫 번째 정점(-0.5, -0.5) / 두 번째 정점(-0.5, 0.5) / 세 번째 정점(0.5, 0.5) / 네 번째 정점(0.5, -0.5)
	static constexpr std::array<Vertex2D, vertexCount> rawVertices = {
		Vertex2D(Vector2(-squareHalfSize, -squareHalfSize)),
		Vertex2D(Vector2(-squareHalfSize, squareHalfSize)),
		Vertex2D(Vector2(squareHalfSize, squareHalfSize)),
		Vertex2D(Vector2(squareHalfSize, -squareHalfSize))
	};

        // 인덱스 버퍼를 생성 
        // 첫 번째 삼각형의 정점 순회 : 첫 번째 정점 -> 두 번째 정점 -> 세 번째 정점
        // 두 번째 삼각형의 정점 순회 : 첫 번째 정점 -> 세 번째 정점 -> 네 번째 정점
	static constexpr std::array<size_t, triangleCount * 3> indices = {
		0, 1, 2,
		0, 2, 3
	};

	// 크기 변환 행렬
	Vector3 sBasis1(currentScale, 0.f, 0.f);
	Vector3 sBasis2(0.f, currentScale, 0.f);
	Vector3 sBasis3 = Vector3::UnitZ;
	Matrix3x3 sMatrix(sBasis1, sBasis2, sBasis3);

	// 회전 변환 행렬
	float sin = 0.f, cos = 0.f;
	Math::GetSinCos(sin, cos, currentDegree);
	Vector3 rBasis1(cos, sin, 0.f);
	Vector3 rBasis2(-sin, cos, 0.f);
	Vector3 rBasis3 = Vector3::UnitZ;
	Matrix3x3 rMatrix(rBasis1, rBasis2, rBasis3);

	// 이동 변환 행렬
	Vector3 tBasis1 = Vector3::UnitX;
	Vector3 tBasis2 = Vector3::UnitY;
	Vector3 tBasis3(currentPosition.X, currentPosition.Y, 1.f);
	Matrix3x3 tMatrix(tBasis1, tBasis2, tBasis3);

	// 모든 아핀 변환을 곱한 합성 행렬 생성, 크기-회전-이동 순으로 적용
	Matrix3x3 finalMatrix = tMatrix * rMatrix * sMatrix;

	// 행렬 곱이 적용된 각 정점을 렌더링에 사용할 정점 버퍼에 저장
	static std::vector<Vertex2D> vertices(vertexCount);
	for (size_t vi = 0; vi < vertexCount; ++vi)
	{
		vertices[vi].Position = finalMatrix * rawVertices[vi].Position;
	}

	// 변환된 정점들 간의 선 그리기
	for (size_t ti = 0; ti < triangleCount; ++ti)
	{
		size_t bi = ti * 3;
		r.DrawLine(vertices[indices[bi]].Position, vertices[indices[bi + 1]].Position, _WireframeColor);
		r.DrawLine(vertices[indices[bi]].Position, vertices[indices[bi + 2]].Position, _WireframeColor);
		r.DrawLine(vertices[indices[bi + 1]].Position, vertices[indices[bi + 2]].Position, _WireframeColor);
	}

	// 현재 위치, 크기, 각도를 화면에 출력
	r.PushStatisticText(std::string("Position : ") + currentPosition.ToString());
	r.PushStatisticText(std::string("Scale : ") + std::to_string(currentScale));
	r.PushStatisticText(std::string("Degree : ") + std::to_string(currentDegree));
}

...

 

위와 같이 렌더링하는 로직을 구성하였다

https://monstrosite.tistory.com/1012에서 작성한 정점 버퍼인덱스 버퍼를 사용하여 메쉬를 표현한다

또, 합성 행렬을 사용하여 정점 버퍼에 저장된 정점들을 렌더링할 수 있도록 변환하였다

 

- 최종 실행 결과

 

728x90
반응형