서의 공간

7. 3D Model Rendering 본문

Graphics API/DirectX 11 - Rastertek

7. 3D Model Rendering

홍서의 2021. 1. 7. 06:58

이번 주제는 DirectX 11에서 HLSL을 사용해 3D 모델을 렌더링 하는 방법을 알아본다. 이전 튜토리얼 디퓨즈 라이팅 코드를 베이스로 하여 진행한다.

 

사실 이미 이전 튜토리얼에서 3D 모델을 렌더링 했었다. 다만 그 모델들이 그저 하나의 삼각형이거나 사각형이었기 때문에 우리가 원했던 입체적인 느낌의 모델은 아니었다. 앞서 기본 도형을 다루었기 때문에 이제는 좀 더 복잡한 모델을 렌더링 해볼 것이다. 가장 먼저 큐브를 렌더링 해보고 점차 복잡한 모델로 넘어가겠다.

 

3D 모델을 만들 수 있는 여러 가지 도구가 있다. 그중에 Maya와 3D Studio Max는 가장 많이 사용되는 3D 모델링 프로그램들이다. Blender는 오픈소스 3D 모델링 프로그램으로서 무료로 사용할 수 있다.

 

이 각각의 프로그램들은 각자의 고유한 파일 확장자가 있다(Maya는 .obj, 3d studio는 .3ds 등). 고유한 확장자가 있다 하더라도 대부분은 사용자가 원하는 확장자로 내보내기(export)하여 사용할 수 있다. 이 튜토리얼에서는 직접 만든 3D 모델링 확장자를 이용해 큐브를 렌더링 한다. 이 커스텀 확장자의 형식은 매우 간단하게 이루어져 있다. .txt 확장자로 저장되고 각 줄은 정점의 위치 벡터 (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에서 사용할 수 있도록 변환해야 한다. DirectX 11에서는 왼손 좌표계를 쓰므로 위 txt 파일은 그러한 순서에 맞게 작성했다.


ModelClass.h

텍스트 모델 파일을 읽기 위한 코드가 추가되었다.

더보기
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_

// INCLUDES
#include <d3d11.h>
#include <DirectXMath.h>
#include <fstream>
using namespace std;
using namespace DirectX;

// MY CLASS INCLUDES
#include "TextureClass.h"

class ModelClass
{
private:
	struct VertexType
	{
		XMFLOAT3 position;
		XMFLOAT2 texture;
		XMFLOAT3 normal;
	};
	struct ModelType
	{
		float x, y, z;
		float tu, tv;
		float nx, ny, nz;
	};

public:
	ModelClass();
	ModelClass(const ModelClass&);
	~ModelClass();
	bool Initialize(ID3D11Device*, const char*, const WCHAR*);
	void Shutdown();
	void Render(ID3D11DeviceContext*);
	int GetIndexCount();
	ID3D11ShaderResourceView* GetTexture();

private:
	bool InitializeBuffers(ID3D11Device*);
	void ShutdownBuffers();
	void RenderBuffers(ID3D11DeviceContext*);
	bool LoadTexture(ID3D11Device*, const WCHAR*);
	void ReleaseTexture();
	bool LoadModel(const char*);
	void ReleaseModel();

private:
	ID3D11Buffer* mVertexBuffer;
	ID3D11Buffer* mIndexBuffer;
	int mVertexCount, mIndexCount;
	TextureClass* mTexture;
	ModelType* mModel;
};
#endif

 

ModelClass.cpp

Initialize() 함수에서 새로운 함수 LoadModel() 함수를 호출한다. txt 파일 이름으로 파일을 읽어 모델 데이터를 로드한다. 모델 데이터 배열이 채워지면 이 배열을 이용해 정점 및 인덱스 버퍼를 만들 수 있게 된다. InitializeBuffers() 함수는 이 모델 데이터에 의존하므로 LoadModel() 함수 호출 이후에 호출해야 한다.

 

InitializeBuffers() 함수에서는 이제 더 이상 수동으로 정점과 인덱스를 정의하지 않는다. LoadModel() 함수에서 텍스트 파일로부터 모델 데이터를 불러오고 그 데이터를 사용할 것이기 때문이다.

모델 데이터를 함수 내부 포인터 변수 vertices, indices에 각각 복사하여 정점, 인덱스 버퍼를 만들기 위해 준비한다.

 

LoadModel() 함수에서는 Modelclass.h에 선언한 mModel 배열 변수로 모델 데이터를 로드한다. 텍스트 파일을 열고 각 행을 배열로 읽는다. 정점 개수와 인덱스 개수가 모두 이 함수에서 설정된다.

더보기
#include "ModelClass.h"

ModelClass::ModelClass()
{
	mVertexBuffer = 0;
	mIndexBuffer = 0;
	mTexture = 0;
	mModel = 0;
}

ModelClass::ModelClass(const ModelClass &)
{
}

ModelClass::~ModelClass()
{
}

bool ModelClass::Initialize(ID3D11Device * device, const char* modelFileName, const WCHAR* textureFilename)
{
	bool result;

	// Load in the model data.
	result = LoadModel(modelFileName);
	if (!result) return false;

	// Initialize the vertex and index buffers.
	result = InitializeBuffers(device);
	if (!result) return false;

	// Load the texture for this model.
	result = LoadTexture(device, textureFilename);
	if (!result) return false;

	return true;
}

void ModelClass::Shutdown()
{
	// Release the model texture.
	ReleaseTexture();
	// Shutdown the vertex and index buffers.
	ShutdownBuffers();
	// Release the model data.
	ReleaseModel();

	return;
}

void ModelClass::Render(ID3D11DeviceContext * deviceContext)
{
	// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
	RenderBuffers(deviceContext);

	return;
}

int ModelClass::GetIndexCount()
{
	return mIndexCount;
}

ID3D11ShaderResourceView * ModelClass::GetTexture()
{
	return mTexture->GetTexture();
}

bool ModelClass::InitializeBuffers(ID3D11Device * device)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;
	int i;

	// Create the vertex array.
	vertices = new VertexType[mVertexCount];
	if (!vertices) return false;
	// Create the index array.
	indices = new unsigned long[mIndexCount];
	if (!indices) return false;

	// Load the vertex array and index array with data.
	for (i = 0; i < mVertexCount; i++)
	{
		vertices[i].position = XMFLOAT3(mModel[i].x, mModel[i].y, mModel[i].z);
		vertices[i].texture = XMFLOAT2(mModel[i].tu, mModel[i].tv);
		vertices[i].normal = XMFLOAT3(mModel[i].nx, mModel[i].ny, mModel[i].nz);
		
		indices[i] = i;
	}
	
	// Set up the description of the static vertex buffer.
	vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	vertexBufferDesc.ByteWidth = sizeof(VertexType) * mVertexCount;
	vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vertexBufferDesc.CPUAccessFlags = 0;
	vertexBufferDesc.MiscFlags = 0;
	vertexBufferDesc.StructureByteStride = 0;
	// Give the subresource structure a pointer to the vertex data.
	vertexData.pSysMem = vertices;
	vertexData.SysMemPitch = 0;
	vertexData.SysMemSlicePitch = 0;
	// Now create the vertex buffer.
	result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &mVertexBuffer);
	if (FAILED(result)) return false;

	// Set up the description of the static index buffer.
	indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	indexBufferDesc.ByteWidth = sizeof(unsigned long) * mIndexCount;
	indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	indexBufferDesc.CPUAccessFlags = 0;
	indexBufferDesc.MiscFlags = 0;
	indexBufferDesc.StructureByteStride = 0;
	// Give the subresource structure a pointer to the index data.
	indexData.pSysMem = indices;
	indexData.SysMemPitch = 0;
	indexData.SysMemSlicePitch = 0;
	// Create the index buffer.
	result = device->CreateBuffer(&indexBufferDesc, &indexData, &mIndexBuffer);
	if (FAILED(result)) return false;

	// Release the arrays now that the vertex and index buffers have been created and loaded.
	delete[] vertices;
	vertices = 0;
	delete[] indices;
	indices = 0;

	return true;
}

void ModelClass::ShutdownBuffers()
{
	// Release the index buffer.
	if (mIndexBuffer)
	{
		mIndexBuffer->Release();
		mIndexBuffer = 0;
	}
	// Release the vertex buffer.
	if (mVertexBuffer)
	{
		mVertexBuffer->Release();
		mVertexBuffer = 0;
	}

	return;
}

void ModelClass::RenderBuffers(ID3D11DeviceContext * deviceContext)
{
	unsigned int stride;
	unsigned int offset;

	// Set vertex buffer stride and offset.
	stride = sizeof(VertexType);
	offset = 0;
	// Set the vertex buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetVertexBuffers(0, 1, &mVertexBuffer, &stride, &offset);
	// Set the index buffer to active in the input assembler so it can be rendered.
	deviceContext->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
	// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
	deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	return;
}

bool ModelClass::LoadTexture(ID3D11Device * device, const WCHAR * fileName)
{
	bool result;

	// Create the texture object.
	mTexture = new TextureClass;
	if (!mTexture) return false;

	// Initialize the texture object.
	result = mTexture->Initialize(device, fileName);
	if (!result) return false;

	return true;
}

void ModelClass::ReleaseTexture()
{
	// Release the texture object.
	if (mTexture)
	{
		mTexture->Shutdown();
		delete mTexture;
		mTexture = 0;
	}

	return;
}

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

	// Open the model file.
	fin.open(fileName);

	// If it could not open the file then exit.
	if (fin.fail()) return false;

	// Read up to the value of vertex count.
	fin.get(input);
	while (input != ':')
	{
		fin.get(input);
	}
	// Read int the vertex count.
	fin >> mVertexCount;

	// Set the number of indices to be the same as the vertex count.
	mIndexCount = mVertexCount;

	// Create the model using the vertex count that was read in.
	mModel = new ModelType[mVertexCount];
	if (!mModel) return false;

	// Read up to the beginning of the data.
	fin.get(input);
	while (input != ':')
	{
		fin.get(input);
	}
	fin.get(input);
	fin.get(input);

	// Read in the vertex data.
	for (i = 0; i < mVertexCount; i++)
	{
		fin >> mModel[i].x >> mModel[i].y >> mModel[i].z;
		fin >> mModel[i].tu >> mModel[i].tv;
		fin >> mModel[i].nx >> mModel[i].ny >> mModel[i].nz;
	}

	// Close the model file.
	fin.close();

	return true;
}

void ModelClass::ReleaseModel()
{
	if (mModel)
	{
		delete[] mModel;
		mModel = 0;
	}

	return;
}

GraphicsClass.cpp

Initialize() 함수에서 ModelClass 객체를 만든다. 이 객체를 초기화할 때 cube.txt 파일 이름을 파라미터로 전달하여 3D 큐브를 로드한다.

더보기
#include "GraphicsClass.h"

GraphicsClass::GraphicsClass()
{
	mDirect3D = nullptr;
	mCamera = 0;
	mModel = 0;
	mLightShader = 0;
	mLight = 0;
}

GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}

GraphicsClass::~GraphicsClass()
{
}

bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;

	// Create the Direct3D object.
	mDirect3D = new D3DClass;
	if (!mDirect3D) return false;

	// Initialize the Direct3D object.
	result = mDirect3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if (!result)
	{
		MessageBox(hwnd, L"Could nor initialize Direct3D", L"Error", MB_OK);
		return false;
	}
	
	// Create the camera object.
	mCamera = new CameraClass;
	if (!mCamera) return false;
	// Set the initial position of the camera.
	mCamera->SetPosition(0.0f, 0.0f, -5.0f);

	// Create the model object.
	mModel = new ModelClass;
	if (!mModel) return false;

	// Initialize the model object.
	result = mModel->Initialize(mDirect3D->GetDevice(), "Models/cube.txt", L"Images/seafloor.dds");
	if (!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}

	// Create the light shader object.
	mLightShader = new LightShaderClass;
	if (!mLightShader) return false;
	// Initialize the texture shader object.
	result = mLightShader->Initialize(mDirect3D->GetDevice(), hwnd);
	if (!result)
	{
		MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
		return false;
	}
	
	// Create the light object.
	mLight = new LightClass;
	if (!mLight) return false;

	// Initialize the light object.
	// white
	mLight->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
	mLight->SetDirection(0.0f, 0.0f, 1.0f);


	return true;
}

void GraphicsClass::ShutDown()
{
	// Release the light object.
	if (mLight)
	{
		delete mLight;
		mLight = 0;
	}
	// Release the light shader object.
	if (mLightShader)
	{
		mLightShader->Shutdown();
		delete mLightShader;
		mLightShader = 0;
	}
	// Release the model object.
	if (mModel)
	{
		mModel->Shutdown();
		delete mModel;
		mModel = 0;
	}

	// Release the camera object.
	if (mCamera)
	{
		delete mCamera;
		mCamera = 0;
	}

	// Release the Direct3D object.
	if (mDirect3D)
	{
		mDirect3D->Shutdown();
		delete mDirect3D;
		mDirect3D = 0;
	}

	return;
}

bool GraphicsClass::Frame()
{
	bool result;

	static float rotation = 0.0f;

	// Update the rotation variable each frame.
	rotation += XM_PI * 0.001f;
	if (rotation > 360.0f)
	{
		rotation -= 360.0f;
	}

	// Render the graphics scene.
	result = Render(rotation);
	if (!result) return false;

	return true;
}

bool GraphicsClass::Render(float rotation)
{
	XMMATRIX worldMatrix, viewMatrix, projectionMatrix;
	bool result;

	// Clear the buffers to begin the scene.
	mDirect3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
	// Generate the view matrix based on the camera's position.
	mCamera->Render();
	// Get the world, view, and projection matrices from the camera and d3d objects.
	mDirect3D->GetWorldMatrix(worldMatrix);
	mCamera->GetViewMatrix(viewMatrix);
	mDirect3D->GetProjectionMatrix(projectionMatrix);

	// Rotate the world matrix by the rotation value so that the triangle will spin.
	worldMatrix = XMMatrixRotationY(rotation);
	// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	mModel->Render(mDirect3D->GetDeviceContext());
	// Render the model using the light shader.
	result = mLightShader->Render(mDirect3D->GetDeviceContext(), mModel->GetIndexCount(),
		worldMatrix, viewMatrix, projectionMatrix, mModel->GetTexture(),
		mLight->GetDirection(), mLight->GetDiffuseColor());
	if (!result) return false;
	// Present the rendered scene to the screen.
	mDirect3D->EndScene();

	return true;
}

실행결과

'Graphics API > DirectX 11 - Rastertek' 카테고리의 다른 글

32. Glass and Ice  (0) 2021.01.30
29. Water  (0) 2021.01.21
6. Diffuse Lighting  (0) 2021.01.07
5. Texturing  (0) 2021.01.07
4. Buffers, Shaders, and HLSL  (0) 2021.01.07
Comments