그래픽

Direct3D (13) / 큐브 여러개 생성하기

murlocdev 2026. 8. 17. 05:05

이번엔 큐브를 여러개 생성하려고 한다

struct FTransform
{
	XMFLOAT3 Location = { 0.0f,0.0f,0.0f };			
	XMFLOAT3 Rotation = { 0.0f,0.0f,0.0f };			
	XMFLOAT3 Scale = { 1.0f,1.0f,1.0f };			

	XMMATRIX GetMatrix() const {
		return
			XMMatrixScaling(
				Scale.x,
				Scale.y,
				Scale.z
			) *

			XMMatrixRotationX(Rotation.x) *
			XMMatrixRotationY(Rotation.y) *
			XMMatrixRotationZ(Rotation.z) *

			XMMatrixTranslation(
				Location.x,
				Location.y,
				Location.z);
	}
};

 

먼저 FTransform 구조체를 만들어서 코드의 재사용을 줄여주려고 한다

앞에서 하나의 큐브를 렌더링할 때는 World Matrix를 직접 생성하여 큐브의 위치, 회전, 크기를 지정했다

하지만 여러 개의 큐브를 렌더링하려면 각각의 큐브마다 Transform 정보를 관리해야 하기 때문에 같은 코드가 반복될 수 있다

이를 정리하기 위해 먼저 FTransform 구조체를 만들어 Transform 정보를 하나로 묶어준다

 

	XMMATRIX world =								
		XMMatrixScaling(							
			1.0f,									
			1.0f,									
			1.0f) *									
		XMMatrixRotationX(g_Angle) *				
		XMMatrixRotationY(g_Angle) *				
		XMMatrixRotationZ(g_Angle) *				
		XMMatrixTranslation(						
			0.0f,									
			0.0f,									
			0.0f);									

	XMMATRIX projection =							
		XMMatrixPerspectiveFovLH(					
			XMConvertToRadians(60.0f),				
			1280.0f / 720.0f,						
			0.1f,									
			20.0f);									

	DrawCube(world, view,projection);

기존의 Render()함수에서 오브젝트를 배치하는것은 위의 코드와 같이 world를 일일이 설정해야 했다

 

	FTransform CubeTransform;						
	
	CubeTransform.Location = { 0.0f, 0.0f, 0.0f };	
	CubeTransform.Rotation = { g_Angle, g_Angle, g_Angle };	
	CubeTransform.Scale = { 1.0f,1.0f,1.0f };		

	XMMATRIX projection =							
		XMMatrixPerspectiveFovLH(					
			XMConvertToRadians(60.0f),				
			1280.0f / 720.0f,						
			0.1f,									
			20.0f);									

	DrawCube(CubeTransform.GetMatrix(), view, projection);

새로 만든 함수는 구조체를 이용하여 여러개의 world 행렬(위치,회전,크기)을 배열로 관리할 수 있게 되었다

 

	std::vector<FTransform> CubeTransforms;
	
	CubeTransforms.push_back(
		{
			{ -1.5f, 0.0f, 0.0f },
			{ 0.0f, g_Angle, 0.0f },
			{ 1.0f, 1.0f, 1.0f }
		});

	CubeTransforms.push_back(
		{
			{ 1.5f, 0.0f, 0.0f },
			{ g_Angle, 0.0f, 0.0f },
			{ 1.0f, 1.0f, 1.0f }
		});

	CubeTransforms.push_back(
		{
			{ 1.5f, 0.0f, 0.0f },
			{ 0.0f, 0.0f, g_Angle },
			{ 1.0f, 1.0f, 1.0f }
		});

	XMMATRIX projection =							
		XMMatrixPerspectiveFovLH(					
			XMConvertToRadians(60.0f),					
			1280.0f / 720.0f,							
			0.1f,										
			20.0f);										

	for (const FTransform& Transform : CubeTransforms)
	{
		DrawCube(
			Transform.GetMatrix(),
			view,
			projection);
	}

이제 큐브를 여러개 만들기 위해서는 vector로 transform을 관리하면 된다

 

더보기
#include <Windows.h>									// win32 api 헤더
#include <d3d11.h>										// dx11 헤더
#include <dxgi.h>										// dxgi 헤더
#include <d3dcompiler.h>								// 쉐이더 컴파일러 헤더
#include <DirectXMath.h>								// DirectX 수학 라이브러리 헤더
#include <algorithm>									// std::clamp를 사용하기 위한 헤더
#include <vector>

#pragma comment(lib, "d3d11.lib")						// 링크
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3dcompiler.lib")

using namespace DirectX;								// DirectX 수학 라이브러리 네임스페이스

ID3D11Device* g_Device = nullptr;						// GPU 장치
ID3D11DeviceContext* g_Context = nullptr;				// GPU 컨텍스트
IDXGISwapChain* g_SwapChain = nullptr;					// 화면에 그려질 버퍼를 관리하는 객체 (더블 버퍼링 구현에 사용)
ID3D11RenderTargetView* g_RTV = nullptr;				// 렌더 타겟 뷰 (화면에 그려질 버퍼를 GPU가 인식할 수 있도록 하는 객체)
ID3D11Buffer* g_VertexBuffer = nullptr;					// 정점 버퍼 (3D 모델의 정점 데이터를 GPU에 저장하는 객체)
ID3D11VertexShader* g_VertexShader = nullptr;			// 정점 쉐이더 (3D 모델의 정점 데이터를 처리하는 GPU 프로그램)
ID3D11PixelShader* g_PixelShader = nullptr;				// 픽셀 쉐이더 (화면에 그려질 픽셀의 색상을 계산하는 GPU 프로그램)
ID3D11InputLayout* g_InputLayout = nullptr;				// 입력 레이아웃 (GPU가 정점 데이터를 어떻게 해석할지 정의하는 객체)

ID3D11Buffer* g_ConstantBuffer = nullptr;				// 상수 버퍼 (쉐이더에서 사용할 상수 데이터를 GPU에 저장하는 객체)
ID3D11Texture2D* g_DepthStencilBuffer = nullptr;		// 깊이 스텐실 버퍼 (깊이 테스트와 스텐실 테스트를 위한 버퍼)
ID3D11DepthStencilView* g_DSV = nullptr;				// 깊이 스텐실 뷰 (깊이 스텐실 버퍼를 GPU가 인식할 수 있도록 하는 객체)
ID3D11Buffer* g_IndexBuffer = nullptr;					// 인덱스 버퍼 (3D 모델의 정점 데이터를 인덱스로 참조하여 그리는 객체)

float g_Angle = 0.0f;									// 회전 각도 (삼각형 회전을 구현하기 위해 사용할 변수)

bool g_IsRightMouseDown = false;						// 마우스 오른쪽 버튼이 눌렸는지 여부를 저장하는 변수 (카메라 회전을 구현하기 위해 사용할 변수)
POINT g_LastMousePos = {};								// 마우스의 마지막 위치를 저장하는 변수 (카메라 회전을 구현하기 위해 사용할 변수)
float g_Yaw = 0.0f;										// Yaw (카메라의 좌우 회전을 구현하기 위해 사용할 변수)
float g_Pitch = 0.0f;									// Pitch (카메라의 상하 회전을 구현하기 위해 사용할 변수)

XMVECTOR worldUp = XMVectorSet(0, 1, 0, 0);				// 월드 좌표계에서의 상방 방향 벡터를 저장할 XMVECTOR 타입 변수 (Y축 방향)
														// 언리얼 엔진에서는 카메라가 회전해도 상방 방향이 항상 Y축 방향이므로 up 벡터는 항상 (0, 1, 0)으로 설정

XMFLOAT3 g_CameraLocation =								// 카메라 위치 (카메라의 위치를 저장하는 변수)
{							
	0.0f,												// 카메라의 x 좌표
	0.0f,												// 카메라의 y 좌표
	-3.0f												// 카메라의 z 좌표
};

struct ConstantBuffer									// 상수 버퍼 구조체 (쉐이더에서 사용할 상수 데이터를 저장하는 구조체)
{
	//float angle;										// 회전 각도 (쉐이더에서 사용할 회전 각도를 저장하는 변수)
	//float padding[3];									// 상수 버퍼 구조체의 크기를 16바이트 단위로 맞추기 위한 패딩 (Direct3D 11에서는 상수 버퍼의 크기가 16바이트 단위로 정렬되어야 함)

	XMFLOAT4X4 World;									// 회전 행렬 (쉐이더에서 사용할 회전 행렬을 저장하는 변수)
	XMFLOAT4X4 View;									// 뷰 행렬 (쉐이더에서 사용할 뷰 행렬을 저장하는 변수)
	XMFLOAT4X4 Projection;								// 투영 행렬 (쉐이더에서 사용할 투영 행렬을 저장하는 변수)
};

struct Vertex											// 정점 구조체 (3D 모델의 정점 정보를 저장하는 구조체)
{
	float x;
	float y;
	float z;

	float r;
	float g;
	float b;
};

struct FTransform
{
	XMFLOAT3 Location = { 0.0f, 0.0f, 0.0f };
	XMFLOAT3 Rotation = { 0.0f, 0.0f, 0.0f };
	XMFLOAT3 Scale = { 1.0f, 1.0f, 1.0f };

	XMMATRIX GetMatrix() const
	{
		return
			XMMatrixScaling(Scale.x, Scale.y, Scale.z)*
			XMMatrixRotationX(Rotation.x)*
			XMMatrixRotationY(Rotation.y)*
			XMMatrixRotationZ(Rotation.z)*
			XMMatrixTranslation(Location.x, Location.y, Location.z);
	}
};


//Vertex vertices[3] =									// 삼각형을 구성하는 3개의 정점 데이터 (각 정점의 x, y, z 좌표를 저장하는 배열)
//{
//	{ 0.0f,  0.5f, 0.0f, 1.0f, 0.0f, 0.0f },			// 빨강
//	{ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f },			// 초록
//	{-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f }				// 파랑
//};

//정육면체를 구성하는 8개의 정점 데이터 (각 정점의 x, y, z 좌표와 r, g, b 색상값을 저장하는 배열)
Vertex vertices[8] =
{
	// 앞면
	{ -0.5f,  0.5f, -0.5f, 1.0f, 0.0f, 0.0f },
	{  0.5f,  0.5f, -0.5f, 0.0f, 1.0f, 0.0f },
	{  0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f },
	{ -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f },

	// 뒷면
	{ -0.5f,  0.5f,  0.5f, 1.0f, 0.0f, 1.0f },
	{  0.5f,  0.5f,  0.5f, 0.0f, 1.0f, 1.0f },
	{  0.5f, -0.5f,  0.5f, 0.0f, 1.0f, 1.0f },
	{ -0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f }
};

// 인덱스 버퍼를 구성하는 36개의 인덱스 데이터 (정육면체를 구성하는 삼각형의 정점 인덱스를 저장하는 배열)
unsigned int indices[] =
{
	// 앞면
	0, 1, 2,
	0, 2, 3,

	// 뒷면
	4, 6, 5,
	4, 7, 6,

	// 왼쪽
	4, 0, 3,
	4, 3, 7,

	// 오른쪽
	1, 5, 6,
	1, 6, 2,

	// 위
	4, 5, 1,
	4, 1, 0,

	// 아래
	3, 2, 6,
	3, 6, 7
};

void DrawTriangle(XMMATRIX world, XMMATRIX view, XMMATRIX projection)		// 삼각형을 그리는 함수 (월드 행렬과 뷰 행렬을 인자로 받아 삼각형을 그리는 함수)
{						
	ConstantBuffer cb;									// 상수 버퍼 구조체 생성 (쉐이더에서 사용할 상수 데이터를 저장하는 구조체)

	XMStoreFloat4x4(									// 회전 행렬을 상수 버퍼 구조체에 저장하는 함수
		&cb.World,										// 상수 버퍼 구조체의 World 멤버 변수에 회전 행렬 저장
		XMMatrixTranspose(world));						// 회전 행렬을 전치(transpose)하여 상수 버퍼 구조체에 저장 (Direct3D 11에서는 행렬을 전치하여 저장해야 함)

	XMStoreFloat4x4(									// 뷰 행렬을 상수 버퍼 구조체에 저장하는 함수
		&cb.View,										// 상수 버퍼 구조체의 View 멤버 변수에 뷰 행렬 저장
		XMMatrixTranspose(view));						// 뷰 행렬을 전치하여 상수 버퍼 구조체에 저장 (Direct3D 11에서는 행렬을 전치하여 저장해야 함)

	XMStoreFloat4x4(									// 투영 행렬을 상수 버퍼 구조체에 저장하는 함수
		&cb.Projection,									// 상수 버퍼 구조체의 Projection 멤버 변수에 투영 행렬 저장
		XMMatrixTranspose(projection));					// 투영 행렬을 전치하여 상수 버퍼 구조체에 저장 (Direct3D 11에서는 행렬을 전치하여 저장해야 함)

	g_Context->UpdateSubresource(						// 상수 버퍼의 데이터를 업데이트하는 함수
		g_ConstantBuffer,								// 업데이트할 상수 버퍼 객체
		0,												// 업데이트할 서브리소스 인덱스 (0이면 첫 번째 서브리소스)
		nullptr,										// 업데이트할 영역 (nullptr이면 전체 영역 업데이트)
		&cb,											// 업데이트할 데이터의 포인터 (상수 버퍼 구조체의 주소)
		0,												// 행렬의 행 크기 (0이면 기본값 사용)
		0);												// 행렬의 열 크기 (0이면 기본값 사용)

	g_Context->VSSetConstantBuffers(					// 상수 버퍼를 정점 쉐이더에 바인딩하는 함수
		0,												// 상수 버퍼 슬롯 번호 (0이면 첫 번째 슬롯)
		1,												// 상수 버퍼의 개수 (1이면 하나의 상수 버퍼 사용)
		&g_ConstantBuffer);								// 상수 버퍼 배열 (하나의 상수 버퍼를 사용할 때는 버퍼 포인터의 주소를 전달)

	g_Context->Draw(									// 정점 데이터를 사용하여 도형을 그리는 함수
		3,												// 그릴 정점의 개수 (삼각형을 그리기 위해 3개의 정점 사용)
		0);												// 정점 데이터의 시작 위치 (버퍼의 처음부터 사용)
}

void DrawCube(XMMATRIX world, XMMATRIX view, XMMATRIX projection)		// 큐브를 그리는 함수 (월드 행렬과 뷰 행렬을 인자로 받아 큐브를 그리는 함수)
{
	ConstantBuffer cb;									// 상수 버퍼 구조체 생성 (쉐이더에서 사용할 상수 데이터를 저장하는 구조체)

	XMStoreFloat4x4(									// 회전 행렬을 상수 버퍼 구조체에 저장하는 함수
		&cb.World,										// 상수 버퍼 구조체의 World 멤버 변수에 회전 행렬 저장
		XMMatrixTranspose(world));						// 회전 행렬을 전치(transpose)하여 상수 버퍼 구조체에 저장 (Direct3D 11에서는 행렬을 전치하여 저장해야 함)

	XMStoreFloat4x4(									// 뷰 행렬을 상수 버퍼 구조체에 저장하는 함수
		&cb.View,										// 상수 버퍼 구조체의 View 멤버 변수에 뷰 행렬 저장
		XMMatrixTranspose(view));						// 뷰 행렬을 전치하여 상수 버퍼 구조체에 저장 (Direct3D 11에서는 행렬을 전치하여 저장해야 함)

	XMStoreFloat4x4(									// 투영 행렬을 상수 버퍼 구조체에 저장하는 함수
		&cb.Projection,									// 상수 버퍼 구조체의 Projection 멤버 변수에 투영 행렬 저장
		XMMatrixTranspose(projection));					// 투영 행렬을 전치하여 상수 버퍼 구조체에 저장 (Direct3D 11에서는 행렬을 전치하여 저장해야 함)

	g_Context->UpdateSubresource(						// 상수 버퍼의 데이터를 업데이트하는 함수
		g_ConstantBuffer,								// 업데이트할 상수 버퍼 객체
		0,												// 업데이트할 서브리소스 인덱스 (0이면 첫 번째 서브리소스)
		nullptr,										// 업데이트할 영역 (nullptr이면 전체 영역 업데이트)
		&cb,											// 업데이트할 데이터의 포인터 (상수 버퍼 구조체의 주소)
		0,												// 행렬의 행 크기 (0이면 기본값 사용)
		0);												// 행렬의 열 크기 (0이면 기본값 사용)

	g_Context->VSSetConstantBuffers(					// 상수 버퍼를 정점 쉐이더에 바인딩하는 함수
		0,												// 상수 버퍼 슬롯 번호 (0이면 첫 번째 슬롯)
		1,												// 상수 버퍼의 개수 (1이면 하나의 상수 버퍼 사용)
		&g_ConstantBuffer);								// 상수 버퍼 배열 (하나의 상수 버퍼를 사용할 때는 버퍼 포인터의 주소를 전달)

	g_Context->DrawIndexed(								// 인덱스 버퍼를 사용하여 도형을 그리는 함수
		36,												// 그릴 인덱스의 개수 (정육면체를 그리기 위해 36개의 인덱스 사용)
		0,												// 인덱스 버퍼의 시작 위치 (0이면 버퍼의 처음부터 사용)
		0);												// 정점 데이터의 시작 위치 (버퍼의 처음부터 사용)
}

bool InitShader()
{
	ID3DBlob* vsBlob = nullptr;							// 정점 쉐이더 컴파일 결과를 저장할 Blob 객체 포인터

	// =========================
	// Vertex Shader
	// =========================

	HRESULT hr = D3DCompileFromFile(
		L"VertexShader.hlsl",							// 컴파일할 쉐이더 파일 경로
		nullptr,										// 쉐이더 컴파일에 사용할 매크로 정의 (nullptr이면 매크로 정의 없음)
		nullptr,										// 쉐이더 컴파일에 사용할 포함 파일 시스템 (nullptr이면 기본 포함 파일 시스템 사용)
		"VSMain",										// 쉐이더의 진입점 함수 이름 (쉐이더 코드에서 VSMain이라는 함수가 정점 쉐이더의 진입점으로 정의되어 있어야 함)
		"vs_5_0",										// 쉐이더 모델 버전 (vs_5_0은 Direct3D 11에서 지원하는 정점 쉐이더 모델 버전)
		0,												// 컴파일 옵션 (0이면 기본 옵션 사용)
		0,												// 컴파일 옵션 (0이면 기본 옵션 사용)
		&vsBlob,										// 컴파일된 쉐이더 코드를 저장할 Blob 객체 포인터
		nullptr);										// 컴파일 에러 메시지를 저장할 Blob 객체 포인터 (nullptr이면 에러 메시지 저장 안함)

	if (FAILED(hr))										// 쉐이더 컴파일이 실패하면 false 반환
	{
		return false;									// 초기화 실패
	}

	hr = g_Device->CreateVertexShader(					// 컴파일된 쉐이더 코드를 사용하여 정점 쉐이더 객체를 생성하는 함수
		vsBlob->GetBufferPointer(),						// 컴파일된 쉐이더 코드의 포인터
		vsBlob->GetBufferSize(),						// 컴파일된 쉐이더 코드의 크기
		nullptr,										// 클래스 인스턴스화 인터페이스 (nullptr이면 사용 안함)
		&g_VertexShader);								// 생성된 정점 쉐이더 객체를 g_VertexShader 포인터에 저장

	if (FAILED(hr))										// 정점 쉐이더 객체 생성이 실패하면 false 반환
	{
		return false;									// 초기화 실패
	}

	D3D11_INPUT_ELEMENT_DESC layout[] =					// 정점 데이터의 구조를 GPU에 알려주는 입력 레이아웃 설명 배열 (각 요소는 정점 데이터의 한 속성을 설명)
	{
		{
			"POSITION",									// 정점 데이터의 의미 (쉐이더 코드에서 POSITION이라는 의미로 사용됨)
			0,											// 정점 데이터의 인덱스 (같은 의미의 데이터가 여러 개 있을 때 구분하기 위한 인덱스, 0이면 첫 번째 POSITION 데이터)
			DXGI_FORMAT_R32G32B32_FLOAT,				// 정점 데이터의 형식 (3개의 float으로 x, y, z 좌표를 나타냄)
			0,											// 정점 버퍼의 슬롯 번호 (0번 정점 버퍼)
			0,											// 정점 데이터 시작 위치
			D3D11_INPUT_PER_VERTEX_DATA,				// 정점마다 하나씩 데이터를 제공하는 방식
			0											// Instance Data Step Rate (인스턴스 데이터 사용 안함)
		},
		
		{
			"COLOR",									// 정점 데이터의 의미 (쉐이더 코드에서 COLOR이라는 의미로 사용됨)
			0,											// 정점 데이터의 인덱스 (같은 의미의 데이터가 여러 개 있을 때 구분하기 위한 인덱스, 0이면 첫 번째 COLOR 데이터)
			DXGI_FORMAT_R32G32B32_FLOAT,				// 정점 데이터의 형식 (3개의 float으로 r, g, b 색상값을 나타냄)
			0,											// 정점 버퍼의 슬롯 번호 (0번 정점 버퍼)
			12,											// 정점 데이터 시작 위치 (POSITION 데이터가 float x,y,z 12바이트이므로 COLOR 데이터는 12바이트 뒤에 시작)
			D3D11_INPUT_PER_VERTEX_DATA,				// 정점마다 하나씩 데이터를 제공하는 방식
			0											// Instance Data Step Rate (인스턴스 데이터 사용 안함)
		}
	};

	// =========================
	// Input Layout
	// =========================

	hr = g_Device->CreateInputLayout(					// 입력 레이아웃 객체 생성
		layout,											// 입력 레이아웃 설명 배열
		2,												// 입력 레이아웃 설명 배열의 요소 개수 (POSITION, COLOR)
		vsBlob->GetBufferPointer(),						// 컴파일된 쉐이더 코드의 포인터 (입력 레이아웃이 어떤 쉐이더와 호환되는지 확인하기 위해 필요)
		vsBlob->GetBufferSize(),						// 컴파일된 쉐이더 코드의 크기 (입력 레이아웃이 어떤 쉐이더와 호환되는지 확인하기 위해 필요)
		&g_InputLayout);								// 생성된 입력 레이아웃 객체를 g_InputLayout 포인터에 저장

	if(FAILED(hr))
	{
		return false;
	}

	vsBlob->Release();									// 컴파일된 쉐이더 코드를 저장한 Blob 객체 해제 (메모리 누수 방지)

	// =========================
	// Pixel Shader
	// =========================

	ID3DBlob* psBlob = nullptr;							// 픽셀 쉐이더 컴파일 결과를 저장할 Blob 객체 포인터

	hr = D3DCompileFromFile(							// 픽셀 쉐이더 컴파일 함수
		L"PixelShader.hlsl",							// 컴파일할 쉐이더 파일 경로
		nullptr,										// 쉐이더 컴파일에 사용할 매크로 정의 (nullptr이면 매크로 정의 없음)
		nullptr,										// 쉐이더 컴파일에 사용할 포함 파일 시스템 (nullptr이면 기본 포함 파일 시스템 사용)
		"PSMain",										// 쉐이더의 진입점 함수 이름 (쉐이더 코드에서 PSMain이라는 함수가 픽셀 쉐이더의 진입점으로 정의되어 있어야 함)
		"ps_5_0",										// 쉐이더 모델 버전 (ps_5_0은 Direct3D 11에서 지원하는 픽셀 쉐이더 모델 버전)
		0,												// 컴파일 옵션 (0이면 기본 옵션 사용)
		0,												// 컴파일 옵션 (0이면 기본 옵션 사용)
		&psBlob,										// 컴파일된 쉐이더 코드를 저장할 Blob 객체 포인터
		nullptr);										// 컴파일 에러 메시지를 저장할 Blob 객체 포인터 (nullptr이면 에러 메시지 저장 안함)

	if (FAILED(hr))
	{
		psBlob->Release();								// 컴파일된 쉐이더 코드를 저장한 Blob 객체 해제 (메모리 누수 방지)
		return false;
	}

	hr = g_Device->CreatePixelShader(					// 컴파일된 쉐이더 코드를 사용하여 픽셀 쉐이더 객체를 생성하는 함수
		psBlob->GetBufferPointer(),						// 컴파일된 쉐이더 코드의 포인터
		psBlob->GetBufferSize(),						// 컴파일된 쉐이더 코드의 크기
		nullptr,										// 클래스 인스턴스화 인터페이스 (nullptr이면 사용 안함)
		&g_PixelShader);								// 생성된 픽쉘 쉐이더 객체를 g_PixelShader 포인터에 저장

	if(FAILED(hr))
	{
		return false;
	}

	psBlob->Release();									// 컴파일된 쉐이더 코드를 저장한 Blob 객체 해제 (메모리 누수 방지)

	return true;										// 초기화 성공
}

bool InitGeometry()										// 버퍼를 초기화하는 함수 (삼각형을 그리기 위한 정점 데이터를 GPU에 업로드하는 함수)
{
	// 정점 버퍼
	D3D11_BUFFER_DESC bd = {};							// 버퍼 설명 구조체 (GPU 버퍼를 생성하기 위한 설정을 저장하는 구조체)
	bd.Usage = D3D11_USAGE_DEFAULT;						// 버퍼 기본 사용 방식 (CPU가 자주 접근하지 않고 GPU가 주로 사용하는 버퍼)
	bd.ByteWidth = sizeof(vertices);					// 버퍼의 크기 (정점 데이터의 전체 크기)
	bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;			// 버퍼의 용도 (정점 버퍼로 사용)

	D3D11_SUBRESOURCE_DATA initData = {};				// 초기 데이터 구조체 (버퍼에 업로드할 초기 데이터를 저장하는 구조체)
	initData.pSysMem = vertices;						// 초기 데이터로 사용할 정점 데이터의 포인터

	HRESULT hr = g_Device->CreateBuffer(				// GPU 버퍼를 생성하는 함수
		&bd,											// 버퍼 설명 구조체
		&initData,										// 초기 데이터 구조체
		&g_VertexBuffer);								// 생성된 버퍼 객체를 g_VertexBuffer 포인터에 저장

	if (FAILED(hr))										// 버퍼 생성이 실패하면 false 반환
	{
		return false;									// 초기화 실패
	}

	// 상수 버퍼
	D3D11_BUFFER_DESC cbd = {};							// 상수 버퍼 설명 구조체 (GPU 상수 버퍼를 생성하기 위한 설정을 저장하는 구조체)

	cbd.Usage = D3D11_USAGE_DEFAULT;					// 상수 버퍼 기본 사용 방식 (CPU가 자주 접근하지 않고 GPU가 주로 사용하는 버퍼)
	cbd.ByteWidth = sizeof(ConstantBuffer);				// 상수 버퍼의 크기 (상수 버퍼 구조체의 크기)
	cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;			// 상수 버퍼의 용도 (상수 버퍼로 사용)
	
	HRESULT chr = g_Device->CreateBuffer(				// GPU 상수 버퍼를 생성하는 함수
		&cbd,											// 상수 버퍼 설명 구조체
		nullptr,										// 초기 데이터 구조체 (nullptr이면 초기 데이터 없음)
		&g_ConstantBuffer);								// 생성된 상수 버퍼 객체를 g_ConstantBuffer 포인터에 저장

	if (FAILED(chr))									// 상수 버퍼 생성이 실패하면 false 반환
	{
		return false;
	}

	// 인덱스 버퍼
	D3D11_BUFFER_DESC ibd = {};							// 인덱스 버퍼 설명 구조체 (GPU 인덱스 버퍼를 생성하기 위한 설정을 저장하는 구조체)
	ibd.Usage = D3D11_USAGE_DEFAULT;					// 인덱스 버퍼 기본 사용 방식 (CPU가 자주 접근하지 않고 GPU가 주로 사용하는 버퍼)
	ibd.ByteWidth = sizeof(indices);					// 인덱스 버퍼의 크기 (인덱스 데이터의 전체 크기)
	ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;			// 인덱스 버퍼의 용도 (인덱스 버퍼로 사용)

	D3D11_SUBRESOURCE_DATA indexData = {};				// 초기 데이터 구조체 (버퍼에 업로드할 초기 데이터를 저장하는 구조체)
	indexData.pSysMem = indices;						// 초기 데이터로 사용할 인덱스 데이터의 포인터

	HRESULT ihr = g_Device->CreateBuffer(				// GPU 인덱스 버퍼를 생성하는 함수
		&ibd,											// 인덱스 버퍼 설명 구조체
		&indexData,										// 초기 데이터 구조체
		&g_IndexBuffer);								// 생성된 인덱스 버퍼 객체를 g_IndexBuffer 포인터에 저장

	if (FAILED(ihr))									// 인덱스 버퍼 생성이 실패하면 false 반환
	{
		return false;
	}

	D3D11_VIEWPORT vp = {};								// 뷰포트 구조체 (화면에 그려질 영역을 정의하는 구조체)
	vp.Width = 1280.0f;									// 뷰포트의 너비 (화면에 그려질 영역의 너비)
	vp.Height = 720.0f;									// 뷰포트의 높이
	vp.MinDepth = 0.0f;									// 뷰포트의 최소 깊이 (화면에 그려질 영역의 깊이 범위의 최소값)
	vp.MaxDepth = 1.0f;									// 뷰포트의 최대 깊이 (화면에 그려질 영역의 깊이 범위의 최대값)
	vp.TopLeftX = 0.0f;									// 뷰포트의 왼쪽 상단 X 좌표 (화면에 그려질 영역의 왼쪽 상단 모서리의 X 좌표)
	vp.TopLeftY = 0.0f;									// 뷰포트의 왼쪽 상단 Y 좌표 (화면에 그려질 영역의 왼쪽 상단 모서리의 Y 좌표)

	g_Context->RSSetViewports(							// GPU에 뷰포트를 설정하는 함수
		1,												// 설정할 뷰포트의 개수 (1이면 하나의 뷰포트 설정)
		&vp);											// 설정할 뷰포트 배열 (하나의 뷰포트를 사용할 때는 뷰포트 구조체의 주소를 전달)

	return true;										// 초기화 성공
}

// Direct3D 11 초기화 함수
bool InitD3D(HWND hwnd)
{
	DXGI_SWAP_CHAIN_DESC sd = {};                       // swap chain을 생성하기 위한 구조체
	sd.BufferCount = 1;                                 // 버퍼 개수 (더블 버퍼링이므로 1)
	sd.BufferDesc.Width = 1280;                         // 버퍼의 너비
	sd.BufferDesc.Height = 720;                         // 버퍼의 높이
	sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;  // 버퍼의 픽셀 형식 (RGBA 각각 8비트)
	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;   // 버퍼의 용도 (렌더 타겟으로 사용)
	sd.OutputWindow = hwnd;                             // 버퍼가 그려질 윈도우 핸들
	sd.SampleDesc.Count = 1;                            // 멀티샘플링 카운트 (1이면 멀티샘플링 사용 안함. MSAA는 안티앨리어싱 게임에서 계단현상 제거하는 옵션)
	sd.Windowed = TRUE;                                 // 창 모드로 실행

	HRESULT hr = D3D11CreateDeviceAndSwapChain(         // Direct3D 11 장치와 swap chain을 생성하는 함수
		nullptr,                                        // 기본 어댑터 사용 (첫 번째 GPU)
		D3D_DRIVER_TYPE_HARDWARE,                       // 하드웨어 가속을 사용하는 드라이버 타입 (GPU 사용)
		nullptr,                                        // 소프트웨어 렌더러 사용 안함
		0,                                              // 생성 옵션 (디버그 모드 등)
		nullptr,                                        // 피처 레벨 배열 (Direct3D 11.0, 11.1 등 지원하는 피처 레벨을 지정할 수 있음)
		0,                                              // 피처 레벨 (Direct3D 11.0, 11.1 등)
		D3D11_SDK_VERSION,                              // SDK 버전 (항상 D3D11_SDK_VERSION 사용)
		&sd,                                            // swap chain 설명 구조체
		&g_SwapChain,                                   // 생성된 swap chain 객체를 받을 포인터
		&g_Device,                                      // 생성된 Direct3D 장치 객체를 받을 포인터
		nullptr,                                        // 피처 레벨을 받을 포인터 (필요 없으면 nullptr)
		&g_Context);                                    // 생성된 Direct3D 컨텍스트 객체를 받을 포인터
    
	if (FAILED(hr))                                     // 생성이 실패하면 false 반환
        return false;

	ID3D11Texture2D* backBuffer = nullptr;              // back buffer를 저장할 포인터

	g_SwapChain->GetBuffer(                             // swap chain이 가지고 있는 back buffer를 가져오는 함수
		0,                                              // 버퍼 인덱스 (0이면 첫 번째 버퍼)
		__uuidof(ID3D11Texture2D),                      // 가져올 버퍼의 인터페이스 ID (ID3D11Texture2D)
		(void**)&backBuffer);                           // 가져온 버퍼를 저장할 포인터

	g_Device->CreateRenderTargetView(                   // back buffer를 렌더 타겟 뷰로 만드는 함수
		backBuffer,                                     // 렌더 타겟 뷰로 만들 텍스처 (back buffer)
		nullptr,                                        // 렌더 타겟 뷰 설명 (nullptr이면 기본값 사용)
		&g_RTV);                                        // 생성된 렌더 타겟 뷰 객체를 g_RTV 포인터에 저장

	backBuffer->Release();                              // Back buffer는 더 이상 필요 없으므로 해제

	D3D11_TEXTURE2D_DESC depthDesc = {};                // 깊이 스텐실 버퍼를 생성하기 위한 구조체

	depthDesc.Width = 1280;							    // 깊이 스텐실 버퍼의 너비
	depthDesc.Height = 720;							    // 깊이 스텐실 버퍼의 높이
	depthDesc.MipLevels = 1;						    // 깊이 스텐실 버퍼의 MIP 맵 레벨 수 (1이면 MIP 맵 사용 안함)
	depthDesc.ArraySize = 1;						    // 깊이 스텐실 버퍼의 배열 크기 (1이면 단일 텍스처)
	depthDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;   // 깊이 스텐실 버퍼의 픽셀 형식 (24비트 깊이 + 8비트 스텐실)
	depthDesc.SampleDesc.Count = 1;					    // 멀티샘플링 카운트 (1이면 멀티샘플링 사용 안함)
	depthDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;	    // 깊이 스텐실 버퍼의 용도 (깊이 스텐실 버퍼로 사용)

	g_Device->CreateTexture2D(
		&depthDesc,                                     // 깊이 스텐실 버퍼 설명 구조체
		nullptr,                                        // 초기 데이터 (nullptr이면 초기 데이터 없음)
		&g_DepthStencilBuffer);                         // 생성된 깊이 스텐실 버퍼 객체를 g_DepthStencilBuffer 포인터에 저장
										
	g_Device->CreateDepthStencilView(					// 깊이 스텐실 뷰를 생성하는 함수
		g_DepthStencilBuffer,							// 깊이 스텐실 버퍼를 뷰로 만들 텍스처
		nullptr,										// 깊이 스텐실 뷰 설명 (nullptr이면 기본값 사용)
		&g_DSV);										// 생성된 깊이 스텐실 뷰 객체를 g_DSV 포인터에 저장

	return true;                                        // 초기화 성공
}													    
													    
void Render()                                           // RGB 색상값과 알파값을 포함하는 4개의 요소로 구성된 배열 (RGBA)
{			
	XMVECTOR camPos =									// 카메라 위치를 저장할 XMVECTOR 타입 변수
		XMLoadFloat3(&g_CameraLocation);				// XMFLOAT3 타입의 g_CameraLocation 변수를 XMVECTOR 타입으로 변환하여 camPos에 저장

	XMVECTOR forward =									// 카메라의 전방 방향을 저장할 XMVECTOR 타입 변수
		XMVectorSet(									// 카메라의 전방 방향 벡터를 계산하는 함수
			cosf(g_Pitch) * sinf(g_Yaw),				// X 좌표 (카메라의 피치와 요를 이용하여 계산)
			sinf(g_Pitch),								// Y 좌표 (카메라의 피치를 이용하여 계산)
			cosf(g_Pitch) * cosf(g_Yaw),				// Z 좌표 (카메라의 피치와 요를 이용하여 계산)
			0.0f);										// W 좌표 (카메라의 전방 방향 벡터는 방향 벡터이므로 W 좌표는 0으로 설정)

	forward = XMVector3Normalize(forward);				// 카메라의 전방 방향 벡터를 정규화하여 forward에 저장 (정규화는 벡터의 길이를 1로 만드는 과정)

	XMVECTOR right =									// 카메라의 우방 방향을 저장할 XMVECTOR 타입 변수
		XMVector3Normalize(								// 카메라의 우방 방향 벡터를 정규화하여 right에 저장
			XMVector3Cross(worldUp, forward));			// 카메라의 우방 방향 벡터를 계산하는 함수 (월드 좌표계에서의 상방 방향 벡터와 전방 방향 벡터의 외적을 계산하여 우방 방향 벡터를 구함)

	XMVECTOR up = XMVector3Cross(forward, right);		// 카메라의 상방 방향을 저장할 XMVECTOR 타입 변수 (우방 방향 벡터와 전방 방향 벡터의 외적을 계산하여 상방 방향 벡터를 구함)

	XMVECTOR lookAt = camPos + forward;					// 카메라가 바라보는 지점을 저장할 XMVECTOR 타입 변수 (카메라 위치 + 전방 방향 벡터)

	float speed = 0.01f;								// 카메라 이동 속도 (키보드 입력에 따라 카메라가 이동하는 속도를 조절하는 변수)

	if (GetAsyncKeyState('W') & 0x8000)					// W 키가 눌렸는지 확인하는 함수 (0x8000은 키가 눌렸음을 나타내는 비트 마스크)
		camPos += forward * speed;						// 카메라 위치를 전방 방향으로 이동 (W 키를 누르면 카메라가 전방으로 이동)

	if (GetAsyncKeyState('S') & 0x8000)
		camPos -= forward * speed;

	if (GetAsyncKeyState('A') & 0x8000)
		camPos -= right * speed;

	if (GetAsyncKeyState('D') & 0x8000)
		camPos += right * speed;

	if (GetAsyncKeyState('Q') & 0x8000)
		camPos -= worldUp * speed;

	if (GetAsyncKeyState('E') & 0x8000)
		camPos += worldUp * speed;

	if (GetAsyncKeyState(VK_RBUTTON) & 0x8000)			// 마우스 오른쪽 버튼이 눌렸는지 확인
	{
		if (!g_IsRightMouseDown)						// 마우스 오른쪽 버튼이 처음 눌렸을 때만 처리
		{
			g_IsRightMouseDown = true;					// 마우스 오른쪽 버튼이 눌렸음을 표시

			GetCursorPos(&g_LastMousePos);				// 마우스 커서의 현재 위치를 g_LastMousePos에 저장
		}

		POINT curMousePos;								// 마우스 커서의 현재 위치를 저장할 POINT 구조체
		GetCursorPos(&curMousePos);						// 마우스 커서의 현재 위치를 currentPos에 저장

		int deltaX = curMousePos.x - g_LastMousePos.x;	// 마우스 커서의 X축 이동 거리 계rm
		int deltaY = curMousePos.y - g_LastMousePos.y;	// 마우스 커서의 Y축 이동 거리 계산

		float sensitivity = 0.003f;						// 마우스 이동 감도 설정

		g_Yaw += deltaX * sensitivity;					// 마우스 이동 거리에 따라 Yaw 값 변경
		g_Pitch -= deltaY * sensitivity;				// 마우스 이동 거리에 따라 Pitch 값 변경

		g_Pitch = std::clamp(							// Pitch 값을 -90도에서 90도로 제한
			g_Pitch,									// 현재 Pitch 값
			-XM_PIDIV2 + 0.01f,							// 최소값 (-90도 + 0.01)
			XM_PIDIV2 - 0.01f);							// 최대값 (90도 - 0.01)

		g_LastMousePos = curMousePos;					// g_LastMousePos를 현재 마우스 위치로 업데이트
	}
	else
	{
		g_IsRightMouseDown = false;						// 마우스 오른쪽 버튼이 눌리지 않았음을 표시
	}

	XMMATRIX view =										// 뷰 공간의 좌표를 정의하는 행렬
		XMMatrixLookAtLH(								// 왼손 좌표계 기준의 뷰 행렬 생성 함수
			camPos,										// 카메라의 위치 벡터
			lookAt,										// 카메라가 바라보는 지점 벡터 (카메라 위치 + 전방 방향 벡터)
			up);										// 카메라의 상방 방향 벡터 (월드 좌표계에서의 상방 방향 벡터)

    float color[4] =
    {
        0.0f,
        0.3f,
        0.8f,
        1.0f
    };

	g_Context->OMSetRenderTargets(                      // 렌더 타겟을 설정하는 함수
		1,                                              // 렌더 타겟의 개수 (1이면 하나의 렌더 타겟 사용)
		&g_RTV,                                         // 출력 대상
		g_DSV);											// DepthStencilBuffer (깊이 버퍼 사용 안함)

	g_Context->ClearRenderTargetView(                   // 렌더 타겟 뷰를 지정된 색상으로 지우는 함수
		g_RTV,                                          // 지울 렌더 타겟 뷰
		color);                                         // 지울 색상 (RGBA)

	g_Context->ClearDepthStencilView(					// 깊이 스텐실 뷰를 지우는 함수
		g_DSV,											// 지울 깊이 스텐실 뷰
		D3D11_CLEAR_DEPTH,								// 지울 깊이 스텐실 뷰의 종류 (깊이 버퍼만 지움)
		1.0f,											// 깊이 버퍼를 지울 때 사용할 값 (1.0f이면 가장 먼 거리)
		0);												// 스텐실 버퍼를 지울 때 사용할 값 (0이면 스텐실 버퍼를 지우지 않음)

	g_Context->IASetInputLayout(g_InputLayout);         // 입력 레이아웃 설정 (GPU가 정점 데이터를 어떻게 해석할지 설정)

	UINT stride = sizeof(Vertex);                       // 정점 데이터의 크기 (한 정점이 차지하는 바이트 수)	
	UINT offset = 0;                                    // 정점 데이터의 시작 위치 (버퍼의 처음부터 사용)

	g_Context->IASetVertexBuffers(						// 정점 버퍼 설정 (GPU에 정점 데이터를 제공하는 함수)
		0,												// 정점 버퍼 슬롯 번호 (0이면 첫 번째 슬롯)
		1,												// 정점 버퍼의 개수 (1이면 하나의 버퍼 사용)
		&g_VertexBuffer,								// 정점 버퍼 배열 (하나의 버퍼를 사용할 때는 버퍼 포인터의 주소를 전달)
		&stride,										// 정점 데이터의 크기 (한 정점이 차지하는 바이트 수)
		&offset);										// 정점 데이터의 시작 위치 (버퍼의 처음부터 사용)

	g_Context->IASetIndexBuffer(						// 인덱스 버퍼 설정 (GPU에 인덱스 데이터를 제공하는 함수)
		g_IndexBuffer,									// 사용할 인덱스 버퍼 객체
		DXGI_FORMAT_R32_UINT,							// 인덱스 데이터의 형식 (32비트 부호 없는 정수)
		0);												// 인덱스 데이터의 시작 위치 (버퍼의 처음부터 사용)

	g_Context->IASetPrimitiveTopology(					// 정점 데이터를 어떻게 연결하여 도형을 만들지 설정하는 함수
		D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);			// 정점 데이터를 3개씩 묶어서 삼각형을 만드는 방식 (각 3개의 정점이 하나의 삼각형을 형성)

	g_Context->VSSetShader(								// 정점 쉐이더 설정 (GPU에 정점 쉐이더를 사용하도록 하는 함수)
		g_VertexShader,									// 사용할 정점 쉐이더 객체
		nullptr,										// 클래스 인스턴스화 인터페이스 (nullptr이면 사용 안함)
		0);												// 추가 옵션 (0이면 기본값 사용)

	g_Context->PSSetShader(								// 픽셀 쉐이더 설정 (GPU에 픽셀 쉐이더를 사용하도록 하는 함수)
		g_PixelShader,									// 사용할 픽셀 쉐이더 객체
		nullptr,										// 클래스 인스턴스화 인터페이스 (nullptr이면 사용 안함)
		0);												// 추가 옵션 (0이면 기본값 사용)
	
	g_Context->IASetIndexBuffer(						// 인덱스 버퍼 설정 (GPU에 인덱스 데이터를 제공하는 함수)
		g_IndexBuffer,									// 사용할 인덱스 버퍼 객체
		DXGI_FORMAT_R32_UINT,							// 인덱스 데이터의 형식 (32비트 부호 없는 정수)
		0);												// 인덱스 데이터의 시작 위치 (버퍼의 처음부터 사용)

	g_Angle = XMConvertToRadians(45.0f);				// 회전 각도를 라디안 단위로 변환하여 g_Angle 변수에 저장 (45도)
	
	std::vector<FTransform> CubeTransforms;
	
	CubeTransforms.push_back(
		{
			{ -1.5f, 0.0f, 0.0f },
			{ 0.0f, g_Angle, 0.0f },
			{ 1.0f, 1.0f, 1.0f }
		});

	CubeTransforms.push_back(
		{
			{ 1.5f, 0.0f, 0.0f },
			{ g_Angle, 0.0f, 0.0f },
			{ 1.0f, 1.0f, 1.0f }
		});

	CubeTransforms.push_back(
		{
			{ 0.0f, 0.0f, 0.0f },
			{ 0.0f, 0.0f, g_Angle },
			{ 1.0f, 1.0f, 1.0f }
		});

	XMMATRIX projection =								// 투영 공간의 좌표를 정의하는 행렬
		XMMatrixPerspectiveFovLH(						// 원근 투영 행렬 생성 함수 (왼손 좌표계 기준)
			XMConvertToRadians(60.0f),					// 수직 시야각 (60도)
			1280.0f / 720.0f,							// 화면의 가로 세로 비율 (1280 / 720)
			0.1f,										// 근평면 (카메라에서 가장 가까운 거리), 이상의 값만 렌더링
			20.0f);										// 원평면 (카메라에서 가장 먼 거리), 미만의 값만 렌더링

	for (const FTransform& Transform : CubeTransforms)
	{
		DrawCube(
			Transform.GetMatrix(),
			view,
			projection);
	}

	//g_Angle = XMConvertToRadians(90.0f);

	//world =												// 월드공간의 좌표를 정의하는 행렬
	//	XMMatrixScaling(								// 크기 조절
	//		1.0f,										// x축 크기
	//		1.0f,										// y축 크기
	//		1.0f) *										// z축 크기
	//	XMMatrixRotationZ(g_Angle) *					// 회전
	//	XMMatrixTranslation(							// 이동
	//		5.0f,										// 이동할 x 좌표
	//		0.0f,										// 이동할 y 좌표
	//		0.0f);										// 이동할 z 좌표

	//DrawCube(world, view, projection);				

	//world =												// 월드공간의 좌표를 정의하는 행렬
	//	XMMatrixScaling(								// 크기 조절
	//		1.0f,										// x축 크기
	//		1.0f,										// y축 크기
	//		1.0f) *										// z축 크기
	//	XMMatrixRotationZ(g_Angle) *					// 회전
	//	XMMatrixTranslation(							// 이동
	//		-5.0f,										// 이동할 x 좌표
	//		0.0f,										// 이동할 y 좌표
	//		0.0f);										// 이동할 z 좌표

	//DrawCube(world, view, projection);				

	//world =												// 월드공간의 좌표를 정의하는 행렬
	//	XMMatrixScaling(								// 크기 조절
	//		1.0f,										// x축 크기
	//		1.0f,										// y축 크기
	//		1.0f) *										// z축 크기
	//	XMMatrixRotationZ(g_Angle) *					// 회전
	//	XMMatrixTranslation(							// 이동
	//		0.0f,										// 이동할 x 좌표
	//		0.0f,										// 이동할 y 좌표
	//		5.0f);										// 이동할 z 좌표

	//DrawCube(world, view, projection);					

	g_SwapChain->Present(                               // 화면에 그려진 버퍼를 표시하는 함수
		1,                                              // VSync 사용 (1이면 모니터의 수직 동기화에 맞춰 버퍼를 교체, 0이면 즉시 교체)
		0);                                             // 추가 옵션 (0이면 기본값 사용)

	XMStoreFloat3(&g_CameraLocation, camPos);			// XMVECTOR 타입의 camPos를 XMFLOAT3 타입으로 변환하여 g_CameraLocation에 저장
}

void Cleanup()                                          // 객체 해제 함수
{
	if (g_VertexBuffer) g_VertexBuffer->Release();      // 정점 버퍼 해제

	if (g_InputLayout) g_InputLayout->Release();        // 입력 레이아웃 해제
	if (g_VertexShader) g_VertexShader->Release();      // 정점 쉐이더 해제
	if (g_PixelShader) g_PixelShader->Release();        // 픽셀 쉐이더 해제

	if (g_RTV) g_RTV->Release();                        // 렌더 타겟 뷰 해제
	if (g_SwapChain) g_SwapChain->Release();            // swap chain 해제
	if (g_Context) g_Context->Release();                // GPU 컨텍스트 해제
	if (g_Device) g_Device->Release();                  // GPU 장치 해제

	if (g_ConstantBuffer) g_ConstantBuffer->Release();  // 상수 버퍼 해제

	if (g_DSV) g_DSV->Release();								// 깊이 스텐실 뷰 해제
	if (g_DepthStencilBuffer) g_DepthStencilBuffer->Release();  // 깊이 스텐실 버퍼 해제	

	if (g_IndexBuffer) g_IndexBuffer->Release();		// 인덱스 버퍼 해제
}

LRESULT CALLBACK WndProc(                               // 윈도우 메시지를 처리하는 함수
	HWND hwnd,                                          // 윈도우 핸들
	UINT msg,                                           // 메시지 코드
	WPARAM wParam,                                      // 메시지에 대한 추가 정보 (예: 키보드 입력, 마우스 클릭 등)
	LPARAM lParam)                                      // 메시지에 대한 추가 정보 (예: 키보드 입력, 마우스 클릭 등)
{
	switch (msg)                                        // 메시지 코드에 따라 처리할 작업을 결정
    {
	case WM_DESTROY:                                    // 윈도우가 파괴될 때 발생하는 메시지
		PostQuitMessage(0);                             // 메시지 루프에 WM_QUIT 메시지를 보내서 프로그램 종료를 알리는 함수
		return 0;                                       // 메시지 처리 완료를 알리는 반환값
    }

	return DefWindowProc(                               // 기본 윈도우 프로시저를 호출하여 처리되지 않은 메시지를 처리 (개발자가 직접 처리하지 않은 메시지는 운영체제가 기본적으로 처리하도록 하는 함수)
		hwnd,                                           // 윈도우 핸들
		msg,											// 메시지 코드
		wParam,											// 메시지에 대한 추가 정보
		lParam);										// 메시지에 대한 추가 정보
}

int WINAPI wWinMain(									// 윈도우 애플리케이션 진입점 함수
	HINSTANCE hInstance,								// 프로그램 인스턴스 핸들
	HINSTANCE,											// 이전 인스턴스 핸들 (윈도우 95/98에서는 사용되었지만 현대 윈도우에서는 항상 nullptr)
	PWSTR,												// 명령줄 인수 (유니코드 문자열로 전달, 일반적으로 사용되지 않음)
	int nCmdShow)										// 창의 초기 표시 방법을 나타내는 정수 (예: SW_SHOW, SW_HIDE 등)
{
	WNDCLASS wc = {};									// 윈도우 클래스 구조체 (윈도우의 속성과 동작을 정의하는 구조체)

	wc.lpfnWndProc = WndProc;							// 윈도우 프로시저 함수 포인터 (윈도우 메시지를 처리하는 함수)
	wc.hInstance = hInstance;							// 프로그램 인스턴스 핸들
	wc.lpszClassName = L"MyWindow";						// 윈도우 클래스 이름 (윈도우를 생성할 때 이 이름을 사용하여 어떤 클래스의 윈도우를 생성할지 지정)

	RegisterClass(&wc);									// 윈도우 클래스를 등록하는 함수 (윈도우를 생성하기 전에 클래스 정보를 운영체제에 등록하여 나중에 이 클래스를 기반으로 윈도우를 생성할 수 있도록 하는 함수)

	HWND hwnd = CreateWindow(							// 윈도우를 생성하는 함수
		L"MyWindow",									// 생성할 윈도우의 클래스 이름 (RegisterClass에서 등록한 클래스 이름)
		L"DX11 Study",									// 생성할 윈도우의 제목 (윈도우 상단에 표시되는 텍스트)
		WS_OVERLAPPEDWINDOW,							// 윈도우 스타일 (WS_OVERLAPPEDWINDOW는 일반적인 창 모양을 나타내는 스타일로, 제목 표시줄, 테두리, 최소화/최대화 버튼 등이 포함됨)
		100,											// 윈도우의 초기 X 위치
		100,											// 윈도우의 초기 Y 위치
		1280,											// 윈도우의 너비
		720,											// 윈도우의 높이
		nullptr,										// 부모 윈도우 핸들 (nullptr이면 부모 윈도우가 없음)
		nullptr,										// 메뉴 핸들 (nullptr이면 메뉴가 없음)
		hInstance,										// 프로그램 인스턴스 핸들
		nullptr);										// 추가 매개변수 (일반적으로 사용되지 않음)

    ShowWindow(hwnd, nCmdShow);							// 윈도우를 화면에 표시하는 함수 (생성된 윈도우를 실제로 화면에 보이도록 하는 함수)
														// nCmdShow 매개변수는 창의 초기 표시 방법을 나타내며, 일반적으로 wWinMain 함수의 매개변수로 전달된 값을 사용

	if (!InitD3D(hwnd))									// Direct3D 11 초기화에 실패하면 프로그램을 종료하는 조건문
		return -1;										// 초기화 실패 시 -1을 반환하여 프로그램 종료

	if(!InitShader())									// 쉐이더 초기화에 실패하면 프로그램을 종료하는 조건문
		return -1;										// 초기화 실패 시 -1을 반환하여 프로그램 종료

	if(!InitGeometry())									// 정점 버퍼 초기화에 실패하면 프로그램을 종료하는 조건문
		return -1;										// 초기화 실패 시 -1을 반환하여 프로그램 종료

	MSG msg = {};										// 윈도우 메시지를 저장하는 구조체 (윈도우 메시지 루프에서 메시지를 받아 처리하는 데 사용)

	while (msg.message != WM_QUIT)						// 프로그램이 종료될 때까지 메시지 루프를 계속 실행하는 조건문 (WM_QUIT 메시지가 발생하면 루프 종료)
    {
		if (PeekMessage(								// 메시지 큐에서 메시지를 확인하는 함수
			&msg,										// 메시지를 저장할 구조체 포인터
			nullptr,									// 윈도우 핸들 (nullptr이면 모든 윈도우의 메시지를 확인)
			0,											// 메시지 범위의 최소값 (0이면 모든 메시지를 확인)
			0,											// 메시지 범위의 최대값 (0이면 모든 메시지를 확인)
			PM_REMOVE))									// 메시지를 큐에서 제거하는 옵션 (PM_REMOVE는 메시지를 확인한 후 큐에서 제거, PM_NOREMOVE는 메시지를 확인하지만 큐에서 제거하지 않음)
        {
			TranslateMessage(&msg);						// 키보드 입력 메시지를 문자 메시지로 변환하는 함수 (예: WM_KEYDOWN을 WM_CHAR로 변환)
            DispatchMessage(&msg);						// 메시지를 해당 윈도우 프로시저로 전달하는 함수 (예: WndProc 함수로 메시지를 전달하여 처리)
        }
        else
        {
			Render();									// 화면에 그리는 함수 (Direct3D 11을 사용하여 화면에 그래픽을 렌더링하는 함수)
														// 메시지 큐에 처리할 메시지가 없을 때 Render 함수를 호출하여 화면을 계속 업데이트 (게임 루프의 핵심 부분)
        }
    }

	Cleanup();											// Direct3D 11에서 사용한 리소스를 해제하는 함수 (프로그램 종료 전에 GPU 리소스를 정리하여 메모리 누수 방지)

	return 0;											// 프로그램이 정상적으로 종료되었음을 나타내는 반환값 (0은 성공적으로 종료되었음을 의미)
}