Tutorial 7: 3D Model Rendering

Tutorial 7: 3D Model Rendering This tutorial will cover how to render 3D models in DirectX 11 using HLSL The code in this tutorial is based on the code from the diffuse lighting tutorial. We have already been rendering 3D models in the previous tutorials;

www.rastertek.com

DirectX11의 공부 목적으로 위 링크의 튜토리얼을 보고 제 생각을 포함하여 정리하였습니다.


들어가며

 

이 튜토리얼에서는 HLSL을 사용하여 DirectX 11에서 3D 모델을 렌더링하는 방법을 다룹니다.이 튜토리얼의 코드는 디퓨즈 조명 튜토리얼의 코드를 기반으로 합니다.

이전 튜토리얼에서 우리는 이미 3D 모델을 렌더링했습니다.하지만 그것들은 단일 삼각형으로 구성되어 있었고 상당히 단순했습니다.이제 기본 사항을 다루었으니, 더 복잡한 객체를 렌더링하는 방법으로 나아가겠습니다.이번에는 큐브 객체를 렌더링할 것입니다.더 복잡한 모델을 렌더링하는 방법에 대해 논의하기 전에 먼저 모델 형식에 대해 이야기하겠습니다.

사용자가 3D 모델을 만들 수 있게 해주는 많은 도구가 있습니다.Maya와 Blender가 가장 인기 있는 3D 모델링 프로그램 중 두 가지입니다.기능이 적지만 우리가 필요로 하는 기본 작업을 수행할 수 있는 다른 많은 도구들도 있습니다.

어떤 도구를 사용하든, 그들은 모두 다양한 형식으로 모델을 내보낼 것입니다. 제 제안은 여러분이 직접 모델 형식을 만들고, 내보낸 형식을 여러분의 형식으로 변환하는 파서를 작성하는 것입니다. 그 이유는 여러분이 사용하는 3D 모델링 패키지가 시간이 지남에 따라 변경될 수 있고, 그들의 모델 형식도 변경될 것이기 때문입니다.또한, 여러분은 여러 3D 모델링 패키지를 사용할 수 있어서 여러 다른 형식을 다루어야 할 수도 있습니다.그래서 여러분이 자신의 형식을 가지고, 다른 형식을 자신의 형식으로 변환한다면, 코드를 변경할 필요가 없습니다.단지 여러분의 파서 프로그램을 변경하여 그 형식을 여러분의 형식으로 변환하면 됩니다.대부분의 3D 모델링 패키지는 해당 모델링 프로그램에만 유용한 많은 불필요한 데이터를 내보내는데, 여러분의 모델 형식에는 그 모든 것이 필요하지 않습니다.

자신의 형식을 만드는 데 가장 중요한 부분은 그것이 여러분이 필요로 하는 모든 것을 포함하고, 사용하기에 간단하다는 것입니다.또한, 애니메이션 데이터가 포함된 객체와 정적인 객체 등 다양한 객체를 위해 몇 가지 다른 형식을 만드는 것도 고려할 수 있습니다.

제가 제시할 모델 형식은 매우 기본적입니다.모델 한개의 정점에 대해 한 줄이 매핑됩니다. 각 줄은 코드에서 사용되는 정점 형식에 맞게 위치 벡터(x, y, z), 텍스처 좌표(tu, tv), 그리고 법선 벡터(nx, ny, nz)를 포함할 것입니다. 형식에는 정점 수가 상단에 있어 첫 번째 줄을 읽고 데이터를 읽기 전에 필요한 메모리 구조를 빌드할 수 있습니다. 또한 형식은 세 줄마다 하나의 삼각형을 만들고, 모델 형식의 정점이 시계 방향으로 표시되어야 합니다.다음은 우리가 렌더링할 큐브의 모델 파일입니다.

 

▼Cube.txt

더보기
Vertex Count: 36

Data:

-1.0  1.0 -1.0 0.0 0.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 1.0 0.0  0.0  0.0 -1.0
-1.0 -1.0 -1.0 0.0 1.0  0.0  0.0 -1.0
-1.0 -1.0 -1.0 0.0 1.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 1.0 0.0  0.0  0.0 -1.0
 1.0 -1.0 -1.0 1.0 1.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 0.0 0.0  1.0  0.0  0.0
 1.0  1.0  1.0 1.0 0.0  1.0  0.0  0.0
 1.0 -1.0 -1.0 0.0 1.0  1.0  0.0  0.0
 1.0 -1.0 -1.0 0.0 1.0  1.0  0.0  0.0
 1.0  1.0  1.0 1.0 0.0  1.0  0.0  0.0
 1.0 -1.0  1.0 1.0 1.0  1.0  0.0  0.0
 1.0  1.0  1.0 0.0 0.0  0.0  0.0  1.0
-1.0  1.0  1.0 1.0 0.0  0.0  0.0  1.0
 1.0 -1.0  1.0 0.0 1.0  0.0  0.0  1.0
 1.0 -1.0  1.0 0.0 1.0  0.0  0.0  1.0
-1.0  1.0  1.0 1.0 0.0  0.0  0.0  1.0
-1.0 -1.0  1.0 1.0 1.0  0.0  0.0  1.0
-1.0  1.0  1.0 0.0 0.0 -1.0  0.0  0.0
-1.0  1.0 -1.0 1.0 0.0 -1.0  0.0  0.0
-1.0 -1.0  1.0 0.0 1.0 -1.0  0.0  0.0
-1.0 -1.0  1.0 0.0 1.0 -1.0  0.0  0.0
-1.0  1.0 -1.0 1.0 0.0 -1.0  0.0  0.0
-1.0 -1.0 -1.0 1.0 1.0 -1.0  0.0  0.0
-1.0  1.0  1.0 0.0 0.0  0.0  1.0  0.0
 1.0  1.0  1.0 1.0 0.0  0.0  1.0  0.0
-1.0  1.0 -1.0 0.0 1.0  0.0  1.0  0.0
-1.0  1.0 -1.0 0.0 1.0  0.0  1.0  0.0
 1.0  1.0  1.0 1.0 0.0  0.0  1.0  0.0
 1.0  1.0 -1.0 1.0 1.0  0.0  1.0  0.0
-1.0 -1.0 -1.0 0.0 0.0  0.0 -1.0  0.0
 1.0 -1.0 -1.0 1.0 0.0  0.0 -1.0  0.0
-1.0 -1.0  1.0 0.0 1.0  0.0 -1.0  0.0
-1.0 -1.0  1.0 0.0 1.0  0.0 -1.0  0.0
 1.0 -1.0 -1.0 1.0 0.0  0.0 -1.0  0.0
 1.0 -1.0  1.0 1.0 1.0  0.0 -1.0  0.0

보시다시피 x, y, z, tu, tv, nx, ny, nz 데이터가 총 36줄 있습니다.매 세 줄이 하나의 삼각형을 구성하여 총 12개의 삼각형이 큐브를 형성하게 됩니다.이 형식은 매우 직관적이며, 정점 버퍼로 직접 읽어 들여 수정 없이 렌더링할 수 있습니다.

이제 주의할 점은 일부 3D 모델링 프로그램이 데이터를 왼손 좌표계나 오른손 좌표계와 같은 다른 순서로 내보낼 수 있다는 것입니다.기본적으로 DirectX 11은 왼손 좌표계를 사용하므로 모델 데이터도 이에 맞춰야 합니다.이러한 차이점을 유의하고, 파싱 프로그램이 데이터를 올바른 형식이나 순서로 변환할 수 있도록 해야 합니다.

 


ModelClass.h

 

파일을 읽기 위한 fstream을 사용하고, 모델 형식을 나타내는 구조체 ModelTpye을 선언, 포인터를 생성합니다.

#include <fstream>
using namespace std;

class ModelClass
{
private:
    struct ModelType
    {
        float x, y, z;
        float tu, tv;
        float nx, ny, nz;
    };
	
private:
    ModelType* m_model;

 

또한 이제 초기화에서 모델의 파일 이름을 받아 초기화 단계에서 모델 데이터를 로드할것입니다.

모델 데이터 로드, 언로드 함수를 추가합니다.

	// ....
public:
	bool Initialize(ID3D11Device* , ID3D11DeviceContext* , char*, char* modelFile);

private:
	bool LoadTexture(ID3D11Device*, ID3D11DeviceContext*, char*);
	void ReleaseTexture();
}

 

ModleClass.cpp

 

1. Initialzie

초기화 첫 부분에 모델을 로드합니다.

bool ModelClass::Initialize(ID3D11Device* device, ID3D11DeviceContext* deviceContext, char* modelFilename, char* textureFilename)
{
	// Load in the model data
	LoadModel(modelFilename);
   	
	// ....
}

 

2. InitializeBuffer.cpp

이제 더 이상 정점 및 인덱스 수를 수동으로 설정하지 않습니다.

인덱스 버퍼는 정점 버퍼에 들어간 정점의 순서와 똑같습니다.

bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;
	
	// Create the vertex array.
	vertices = new VertexType[m_vertexCount];
    
	// Create the index array.
	indices = new unsigned long[m_indexCount];

	// Create the vertex array.
	vertices = new VertexType[m_vertexCount];

	// Load the vertex array and index array with data.
	for (int i = 0; i < m_vertexCount; i++)
	{
		vertices[i].position = XMFLOAT3(m_model[i].x, m_model[i].y, m_model[i].z);
		vertices[i].texture = XMFLOAT2(m_model[i].tu, m_model[i].tv);
		vertices[i].normal = XMFLOAT3(m_model[i].nx, m_model[i].ny, m_model[i].nz);

		indices[i] = i;
	}

 

 

3. LoadModel

간단한 파일 읽어내는 코드다.

bool ModelClass::LoadModel(char* filename)
{
	ifstream fin;
	char input;
	int i;

	// 파일 오픈.
	fin.open(filename);

	// 파일을 열수 없으면 종료.
	if (fin.fail())
	{
		return false;
	}

	// 정점 개수가 나오기 전까지 읽어냅니다.
	fin.get(input);
	while (input != ':')
	{
		fin.get(input);
	}

	// 나머지 줄을 읽어 정점 개수를 m_vertexCount에 가져옵니다.
	fin >> m_vertexCount;

	// 인덱스의 개수는 정점 개수와 같습니다.
	m_indexCount = m_vertexCount;

	// 읽어온 정점 개수를 사용하여 배열을 만들어 포인터에 넣습니다.
	m_model = new ModelType[m_vertexCount];

	// 정점 데이터가 나오기 전까지 읽어냅니다.
	fin.get(input);
	while (input != ':')
	{
		fin.get(input);
	}
	// 공백문자 읽어내기
	fin.get(input);
	fin.get(input);

	// 정점 데이터를 읽습니다 
	for (i = 0; i < m_vertexCount; i++)
	{
		fin >> m_model[i].x >> m_model[i].y >> m_model[i].z;
		fin >> m_model[i].tu >> m_model[i].tv;
		fin >> m_model[i].nx >> m_model[i].ny >> m_model[i].nz;
	}

	// 파일 닫기
	fin.close();

	return true;
}

 

ApplicationClass.cpp

 

1. Initialize

모델의 초기화에서 모델 파일의 이름을 사용하기에 모델이름과 함께 초기화 시켜줍니다.

bool ApplicationClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;
	char textureFileName[128];
	char modelFileName[128]; // 추가
    
	// 모델의 파일명을 설정합니다.
	strcpy_s(modelFilename, "../Engine/data/cube.txt");
    
    m_Model = new ModelClass;
    
    // 파일명과 함께 모델을 초기화합니다.
	m_Model->Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), modelFilename, TextureFilename);

 


요약

 

3D모델을 로드하고 렌더링했습니다.

 

초기화

  • ModelClass
    1. 3D모델 파일의 정점 데이터를 가져옵니다.
    2. 가져온 데이터로 정점,인덱스 버퍼를 생성합니다.

 


용어 정리

 

없다!

 

'DirectX 11 > DX11 Tutorial' 카테고리의 다른 글

[06] 난반사 조명(Diffuse light)  (0) 2024.05.24
[05] 텍스쳐링  (0) 2024.05.23
[04] 버퍼, 셰이더, HLSL  (0) 2024.05.21
[03] DirectX 11 초기화 (3)  (0) 2024.05.20
[02] DirectX 11 초기화 (2)  (0) 2024.05.20

+ Recent posts