서의 공간

5. Texturing 본문

Graphics API/DirectX 11 - Rastertek

5. Texturing

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

이 튜토리얼에서는 DirectX 11에서 텍스처링을 사용하는 방법을 설명한다. 텍스처링을 사용하면 사진과 기타 이미지를 다각형면에 적용하여 씬에 사실감을 더할 수 있다. 해당 튜토리얼에서는 다음 이미지를 사용한다.

필요한 경우 Convertio 웹사이트에서 이미지 파일의 형식을 변환하여 사용한다.

다음은 위 이미지를 이전 튜토리얼에서 그렸던 삼각형에 적용한 결과이다.

우리가 사용할 텍스처의 형식은 .tga 파일이다. 이 형식의 파일은 빨강, 초록, 파랑 및 알파 채널을 지원하는 일반적인 그래픽 형식이다. 일반적으로 모든 이미지 편집 소프트웨어를 사용하여 targa 파일을 만들고 편집할 수 있다.

코드를 시작하기 전에 텍스처 매핑이 어떻게 작동하는지 이해해야 한다. .tga 이미지의 픽셀을 다각형에 매핑하기 위해 Texel Coordinate System(텍셀 좌표계)를 사용한다. 이 좌표계는 픽셀의 정수 값(일반적으로 0~255)을 0.0f와 1.0f 사이의 부동소수점 값으로 변환한다. 예를 들어 텍스처 너비가 256픽셀인 경우 첫 번째 픽셀은 0.0f에 매핑되고 마지막 256번째 픽셀은 1.0f에 매핑되며 128번째 픽셀은 중간에 있는 픽셀이므로 0.5f에 매핑된다.

텍셀 좌표계에서 너비 값은 "U"이고 높이 값은 "V"이다. 너비의 범위는 왼쪽 끝 0.0f에서 오른쪽 끝 1.0f까지이다. 높이는 위 끝 0.0f에서 아래 끝 1.0f까지이다. 예를 들어 왼쪽 위 끝은 (u, v) = (0.0f, 0.0f)로 표시하고, 오른쪽 아래 끝은 (u, v) = (1.0f, 1.0f)로 표시한다. 다음 그림을 참조한다.

이제 텍스처를 다각형에 매핑하는 방법에 대한 기본적인 이해를 마쳤다. 다음은 업데이트된 프레임워크의 다이어그램이다.

 

프레임워크

새로운 작업의 변경 사항은 ModelClass 내부에 있는 TextureClass, 그리고 앞서 튜토리얼에서 만들었던 ColorShaderClass를 대체하는 TextureShaderClass이다. 먼저 HLSL 텍스처 셰이더를 살펴본다.


Texture.vs

텍스처 정점 셰이더는 텍스처링을 하기위해 몇 가지 변경 사항을 제외하면 이전 컬러 셰이더와 유사하다.

텍스처 정점 셰이더의 정점 타입(VertexInputType)에서 더 이상 색상을 사용하지 않고 대신 텍스처 좌표를 사용한다. 텍스처 좌표의 시맨틱은 TEXCOORD0이다. 0을 임의의 숫자로 변경하여 여러 텍스처 좌표가 허용되는 좌표 집합을 나타낼 수 있다.

이전 튜토리얼의 ColorVertexShader와 또다른 차이점은 입력 정점에서 색상 정보를 가져오는 대신 텍스터 좌표 정보를 가져와서 픽셀 셰이더로 전달한다.

더보기
// GLOBALS
cbuffer MatrixBuffer
{
	matrix worldMatrix;
	matrix viewMatrix;
	matrix projectionMatrix;
};

// TYPEDEFS
struct VertexInputType
{
	float4 position : POSITION;
	float2 tex : TEXCOORD0;
};

struct PixelInputType
{
	float4 position : SV_POSITION;
	float2 tex : TEXCOORD0;
};

PixelInputType TextureVertexShader(VertexInputType input)
{
	PixelInputType output;

	// Change the position vector to be 4 units for proper matrix calculatrions.
	input.position.w = 1.0f;
	// Calculate the position of the vertex against world, view, projection matrices.
	output.position = mul(input.position, worldMatrix);
	output.position = mul(output.position, viewMatrix);
	output.position = mul(output.position, projectionMatrix);
	// Store the texture coordinates for the pixel shader.
	output.tex = input.tex;

	return output;
}

 

Texture.ps

텍스처 픽셀 셰이더에는 두 개의 전역 변수가 있다. 첫 번째는 텍스처 자원인 Texture2D shaderTexture이다. 이것은 모델에서 텍스처를 렌더링 하는 데 사용될 텍스처 리소스가 된다(텍스처 리소스를 하나의 이미지 파일이라고 생각하라). 두 번째 변수는 SamplerState SampleType이다. 이 샘플러 상태를 사용하면 음영 처리할 때 픽셀이 다각형면에 적용되는 방식을 수정할 수 있다. 예를 들어 다각형이 실제로 멀리 떨어져 있고 화면에서 8픽셀만 구성하는 경우 샘플러 상태를 사용하여 원본 텍스처와 실제로 그려질 픽셀 또는 픽셀 조합을 파악한다. 원래 텍스처가 256픽셀 x 256픽셀이라면 멀리 떨어져 있을 때(화면에서 8픽셀만 차지할 때) 어떤 픽셀을 그릴 것인지 결정하는 것은 여전히 텍스처가 정확하게 보여야 하므로 매우 중요하다. TextureShaderClass에서 샘플러 상태를 생성하여 픽셀 셰이더에 바인딩한다. 그리하여 픽셀 셰이더가 그려야 할 픽셀 샘플을 결정하는 데 사용할 수 있도록 한다.

텍스처 픽셀 셰이더의 PixelInputType도 색상 값 대신 텍스처 좌표를 사용하도록 수정한다.

TexturePixelShader() 함수에서 HLSL Sample() 함수를 사용한다. 이 함수는 위에서 정의한 샘플러상태와 픽셀의 텍스처 좌표를 사용한다. 말 그대로 텍스처의 한 픽셀을 가져온다. 샘플러 상태와 텍스처 좌표를 사용해 다각형면에 해당 UV위치의 픽셀 값을 적용하고 반환한다.

더보기
// GLOBALS
Texture2D shaderTexture;
SamplerState SampleType;

// TYPEDEFS
struct PixelInputType
{
	float4 position : SV_POSITION;
	float2 tex : TEXCOORD0;
};

float4 TexturePixelShader(PixelInputType input) : SV_TARGET
{
	float4 textureColor;

	// Sample the pixel color from the texture using the sampler at this texture coordinate location.
	textureColor = shaderTexture.Sample(SampleType, input.tex);

	return textureColor;
}

TextureShaderClass.h

TextureShaderClass는 이전 튜토리얼의 ColorShaderClass를 업데이트 한 버전이다. 이 클래스는 정점 및 픽셀 셰이더를 사용해 3D 모델을 그린다.

더보기
#ifndef _TEXTURESHADERCLASS_H_
#define _TEXTURESHADERCLASS_H_

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

class TextureShaderClass
{
private:
	struct MatrixBufferType
	{
		XMMATRIX world;
		XMMATRIX view;
		XMMATRIX projection;
	};

public:
	TextureShaderClass();
	TextureShaderClass(const TextureShaderClass&);
	~TextureShaderClass();
	bool Initialize(ID3D11Device*, HWND);
	void Shutdown();
	bool Render(ID3D11DeviceContext*, int, 
		XMMATRIX, XMMATRIX, XMMATRIX, ID3D11ShaderResourceView*);

private:
	bool InitializeShader(ID3D11Device*, HWND, const WCHAR*, const WCHAR*);
	void ShutdownShader();
	void OutputShaderErrorMessage(ID3DBlob*, HWND, const WCHAR*);
	bool SetShaderParameters(ID3D11DeviceContext*, XMMATRIX, XMMATRIX, XMMATRIX,
		ID3D11ShaderResourceView*);
	void RenderShader(ID3D11DeviceContext*, int);

private:
	ID3D11VertexShader* mVertexShader;
	ID3D11PixelShader* mPixelShader;
	ID3D11InputLayout* mLayout;
	ID3D11Buffer* mMatrixBuffer;
	ID3D11SamplerState* mSampelState;
};
#endif

 

TextureShaderClass.cpp

Render() 함수에서는 이제 텍스처 리소스에 대한 포인터(ID3D11ShaderResourceView*)인 texture라는 파라미터를 사용한다. 그런 다음 texture를 SetShaderParameters() 함수로 전달하여 셰이더에 설정하도록 한다.

 

InitializeShader() 함수는 텍스처 셰이더를 설정한다. 새롭게 만들 샘플러상태(SamplerState)의 description 변수 samplerDesc을 선언하고, 정점 셰이더와 픽셀 셰이더를 생성한다. 레이아웃 생성하는 부분에서 이제 색상 대신 텍스처 좌표를 사용하므로 시맨틱 이름을 TEXCOORD로 Format은 DXGI_FORMAT_R32G32_FLOAT으로 변경한다. 

그다음 새로운 변경 사항은 샘플러 상태(SamplerState) 객체를 만들기 위한 description을 작성한다. 이 description에서 가장 중요한 부분은 Filter(필터)이다. Filter는 다각형면에서 텍스처의 최종 모양을 만들기 위해 사용하거나 결합할 픽셀을 어떻게 결정한 것인지에 대한 방법을 의미한다. 예제에서는 D3D11_FILTER_MIN_MAG_MIP_LINEAR을 사용하는데(선형 필터링), 조금 비싼 필터링이지만 점 필터링보다 더 나은 시각적 결과를 보여준다. 선형 필터링은 축소, 확대 및 밉맵 레벨 샘플링을 위해 선형 보간을 사용한다.

AddressU 및 AddressV는 UV좌표가 0.0f와 1.0f 사이에 제한되도록 D3D11_TEXTURE_ADDRESS_WRAP으로 설정한다. 샘플러 상태 description의 다른 속성들은 전부 기본값으로 설정했다.

 

SetShaderParamters() 함수는 텍스처 리소스(ID3D11TextureResourceView*)에 대한 포인터 변수인 texture를 가져와서 픽셀 셰이더에 셰이더 자원으로 바인딩한다. 정점 버퍼 또는 인덱스 버퍼가 렌더링 되기 전에 텍스처를 바인딩해야 한다.

 

RenderShader() 함수에서는 새롭게 샘플러상태를샘플러 상태를 바인딩하는 부분이 추가되었다. PSSetSamplers() 함수를 호출하여 바인딩한다. 렌더링 전에 픽셀 셰이더에 샘플러 상태를 바인딩해야 한다.

더보기
#include "TextureShaderClass.h"

TextureShaderClass::TextureShaderClass()
{
	mVertexShader = 0;
	mPixelShader = 0;
	mLayout = 0;
	mMatrixBuffer = 0;
	mSampelState = 0;
}

TextureShaderClass::TextureShaderClass(const TextureShaderClass &)
{
}

TextureShaderClass::~TextureShaderClass()
{
}

bool TextureShaderClass::Initialize(ID3D11Device * device, HWND hwnd)
{
	bool result;
	
	// Initialize the vertex and pixel shaders.
	result = InitializeShader(device, hwnd, L"../rastertek/texture.vs",
		L"../rastertek/texture.ps");
	if (!result) return false;

	return true;
}

void TextureShaderClass::Shutdown()
{
	// Shutdown the vertex and pixel shaders as well as the related objects.
	ShutdownShader();

	return;
}

bool TextureShaderClass::Render(ID3D11DeviceContext *deviceContext, int indexCount,
	XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix,
	ID3D11ShaderResourceView * texture)
{
	bool result;

	// Set the shader parameters that it will use for rendering.
	result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix,
		texture);
	if (!result) return false;
	// Now render the prepared buffers with the shader.
	RenderShader(deviceContext, indexCount);

	return true;
}

bool TextureShaderClass::InitializeShader(ID3D11Device * device, HWND hwnd, 
	const WCHAR * vsFileName, const WCHAR * psFileName)
{
	HRESULT result;
	ID3DBlob* errorMessage;
	ID3DBlob* vertexShaderBuffer;
	ID3DBlob* pixelShaderBuffer;
	D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
	unsigned int numElements;
	D3D11_BUFFER_DESC matrixBufferDesc;
	D3D11_SAMPLER_DESC samplerDesc;

	// Initialize the pointers this function will use to null.
	errorMessage = 0;
	vertexShaderBuffer = 0;
	pixelShaderBuffer = 0;
	// Compile the vertex shader code.
	result = D3DCompileFromFile(vsFileName, nullptr, nullptr, "TextureVertexShader",
		"vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, &vertexShaderBuffer, &errorMessage);
	if (FAILED(result))
	{
		// If the shader failed to compile is should have writen something to the error message.
		if (errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, vsFileName);
		}
		// If there was nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, vsFileName, L"Missing Shader File", MB_OK);
		}

		return false;
	}
	// Compile the pixel shader code.
	result = D3DCompileFromFile(psFileName, nullptr, nullptr, "TexturePixelShader",
		"ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, &pixelShaderBuffer, &errorMessage);
	if (FAILED(result))
	{
		// If the shader failed to compile it should have written something to the error message.
		if (errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, psFileName);
		}
		// If there was nothing in the error message then it simply could not find the file itself.
		else
		{
			MessageBox(hwnd, psFileName, L"Missing Shader File", MB_OK);
		}

		return false;
	}
	// Create the vertex shader from the buffer.
	// ID3D11VertexShader 객체 생성
	result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(),
		vertexShaderBuffer->GetBufferSize(), nullptr, &mVertexShader);
	if (FAILED(result)) return false;

	// Create the pixel shader from the buffer.
	// ID3D11PixelShade 객체 생성
	result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(),
		pixelShaderBuffer->GetBufferSize(), nullptr, &mPixelShader);
	if (FAILED(result)) return false;

	// Create the vertex input layout description.
	// This setup needs to match the VertexType structure in the ModelClass and in the shader.
	polygonLayout[0].SemanticName = "POSITION";
	polygonLayout[0].SemanticIndex = 0;
	polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[0].InputSlot = 0;
	polygonLayout[0].AlignedByteOffset = 0;
	polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[0].InstanceDataStepRate = 0;

	polygonLayout[1].SemanticName = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

	// Get a count of the elements in the layout.
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	// Create the vertex input layout.
	result = device->CreateInputLayout(polygonLayout, numElements,
		vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &mLayout);
	if (FAILED(result)) return false;

	// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
	// ID3D11VertextShader, ID3D11PixelShader객체에 bytecode가 저장되어 있음. 더이상 버퍼는 필요없다.
	vertexShaderBuffer->Release();
	vertexShaderBuffer = 0;

	pixelShaderBuffer->Release();
	pixelShaderBuffer = 0;

	// Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
	matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
	matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	matrixBufferDesc.MiscFlags = 0;
	matrixBufferDesc.StructureByteStride = 0;

	// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&matrixBufferDesc, nullptr, &mMatrixBuffer);
	if (FAILED(result)) return false;

	// Create a texture sampler state description.
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.MipLODBias = 0.0f;
	samplerDesc.MaxAnisotropy = 1;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	samplerDesc.BorderColor[0] = 0;
	samplerDesc.BorderColor[1] = 0;
	samplerDesc.BorderColor[2] = 0;
	samplerDesc.BorderColor[3] = 0;
	samplerDesc.MinLOD = 0;
	samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
	
	// Create the texture sampler state.
	result = device->CreateSamplerState(&samplerDesc, &mSampelState);
	if (FAILED(result)) return false;

	return true;
}

void TextureShaderClass::ShutdownShader()
{
	// Release the sampler state.
	if (mSampelState)
	{
		mSampelState->Release();
		mSampelState = 0;
	}
	// Release the matrix constant buffer.
	if (mMatrixBuffer)
	{
		mMatrixBuffer->Release();
		mMatrixBuffer = 0;
	}
	// Release the layout.
	if (mLayout)
	{
		mLayout->Release();
		mLayout = 0;
	}
	// Release the pixel shader.
	if (mPixelShader)
	{
		mPixelShader->Release();
		mPixelShader = 0;
	}
	// Release the vertex shader.
	if (mVertexShader)
	{
		mVertexShader->Release();
		mVertexShader = 0;
	}

	return;
}

void TextureShaderClass::OutputShaderErrorMessage(ID3DBlob * errorMessage, HWND hwnd,
	const WCHAR * shaderFileName)
{
	char* compileErrors;
	unsigned long long bufferSize, i;
	ofstream fout;

	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());
	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();
	// Open a file to write the error message to.
	fout.open("shader-error.txt");
	// Write out the error message.
	for (i = 0; i < bufferSize; i++)
	{
		fout << compileErrors[i];
	}
	// Close the file.
	fout.close();
	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;
	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.",
		shaderFileName, MB_OK);

	return;
}

bool TextureShaderClass::SetShaderParameters(ID3D11DeviceContext * deviceContext,
	XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix,
	ID3D11ShaderResourceView * texture)
{
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	MatrixBufferType* dataPtr;
	unsigned int bufferNumber;

	// Transpose the matrices to prepare them for the shader.
	worldMatrix = XMMatrixTranspose(worldMatrix);
	viewMatrix = XMMatrixTranspose(viewMatrix);
	projectionMatrix = XMMatrixTranspose(projectionMatrix);
	// Lock the constant buffer so it can be written to.
	result = deviceContext->Map(mMatrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if (FAILED(result)) return false;
	// Get a pointer to the data in the constant buffer.
	// MatrixBufferType은 클래스에서 정의한 변환 매트릭스 구조체이다.
	// 여기서 알 수 있는 것은 D3D11_MAPPED_SUBRESOURCE에 수정한 데이터를 입력하는 곳이고,
	// 여기를 통해서 mMatrixBuffer를 수정한다. mMatrixBuffer는 변환을 위한 constant버퍼임
	dataPtr = (MatrixBufferType*)mappedResource.pData;
	// Copy the matrices into the constant buffer.
	dataPtr->world = worldMatrix;
	dataPtr->view = viewMatrix;
	dataPtr->projection = projectionMatrix;
	// Unlock the constant buffer.
	deviceContext->Unmap(mMatrixBuffer, 0);
	// Set the position of the constant buffer in the vertex shader.
	bufferNumber = 0;
	// Finaly set the constant buffer in the vertex shader with the updated values.
	deviceContext->VSSetConstantBuffers(bufferNumber, 1, &mMatrixBuffer);
	// Set shader texture resource in the pixel shader.
	deviceContext->PSSetShaderResources(0, 1, &texture);

	return true;
}

void TextureShaderClass::RenderShader(ID3D11DeviceContext * deviceContext, int indexCount)
{
	// Set the vertex input layout.
	deviceContext->IASetInputLayout(mLayout);
	// Set the vertex and pixel shaders that will be used to render this triangle.
	deviceContext->VSSetShader(mVertexShader, nullptr, 0);
	deviceContext->PSSetShader(mPixelShader, nullptr, 0);
	// Set the sampler state in the pixel shader.
	deviceContext->PSSetSamplers(0, 1, &mSampelState);

	// Render the triangle.
	deviceContext->DrawIndexed(indexCount, 0, 0);

	return;
}

TextureClass.h

TextureClass는 단일 텍스처 리소스의 불러오기, 텍스처 리소스 삭제, 텍스처 리소스에 액세스를 캡슐화한다. 필요한 각 텍스처에 대해 이 객체를 인스턴스화 해야한다.

 

TextureClass는 텍스처를 보다 쉽게 제어하기 위해 DirectXTex 라이브러리를 사용했다. 클래스를 작성하기 전에 먼저 라이브러리를 빌드하고 링크하는 방법을 알아본다. DirectXTex 깃허브에서 Code -> DownloadZIP을 클릭하여 라이브러리를 다운로드한다. 다운을 받고 압축을 풀면 DirectXTex-master 폴더에 여러 버전의 프로젝트 파일을 볼 수 있다. 예로 현재 이 튜토리얼을 작성하고 실행한 환경은 Windows10에서 Visual Studio 2017을 사용한다. 따라서 DirectXTex_Desktop_2017_Win10.sln을 열 것이다. 마찬가지로 학습자의 각자 환경에 맞는 프로젝트를 선택하여 프로젝트를 연다. 세부 프로젝트 설정은 따로 하지 않아도 되며 원한다면 각자 원하는 대로 설정한다. 그다음 솔루션 탐색기에서 DirectXTex 프로젝트를 마우스 오른쪽 클릭하여 빌드한다. 빌드가 완료되었다면 라이브러리 파일을 생성한 것이다. 이제 DirectXTex-master 폴더에서 DirectXTex.h, DirectXTex.inl 이 2개의 파일들 복사하여 현재 튜토리얼의 프로젝트 폴더에 붙여 넣고, DirectXTex/Bin/Desktop_2017_Win10/x64/Debug 폴더(사용자의 환경에 따라 폴더 경로가 다를 것이다)로 들어가서 DirectXTex.lib, DirectXTex.pch, DirectXTex.pdb 이 3개의 파일들을 복사하여 현재 튜토리얼을 진행하고 있는 프로젝트에 붙여 넣는다. 이렇게 총 5개의 파일들을 붙여 넣으면 준비는 끝났다.

 

다시 튜토리얼 프로젝트로 돌아와서 라이브러리를 연결하기 위해 D3dClass.h 파일을 열어 다음 코드를 추가한다.

// filename: D3DClass.h 
#ifndef _D3DCLASS_H_
#define _D3DCLASS_H_

// LINKING
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3dcompiler.lib")
#pragma comment(lib, "directxtex.lib")

... 생략

그리고 다음은 TextureClass의 헤더 파일이다.

더보기
#ifndef _TEXTURECLASS_H_
#define _TEXTURECLASS_H_

// INCLUDES
#include <d3d11.h>
#include "DirectXTex.h"

class TextureClass
{
public:
	TextureClass();
	TextureClass(const TextureClass&);
	~TextureClass();

	bool Initialize(ID3D11Device*, const WCHAR*);
	void Shutdown();
	ID3D11ShaderResourceView* GetTexture();

private:
	ID3D11ShaderResourceView* mTexture;
};
#endif

 

TextureClass.cpp

Initialize() 함수는 Direct3D device와 이미지 파일의 이름을 입력으로 받는다. 그리고 DirectXTex 라이브러리를 사용하여

이미지를 불러와 셰이더 리소스 뷰를 생성한다. 현재 코드 상에서 지원하는 이미지 파일 형식은 '.tga', '.dds', '.jpg', '.png' 이다. 여기서 우리는 보통 tga나 dds 파일을 사용할 것이다. 셰이더 리소스 뷰를 생성하면 그것의 포인터를 mTextureView 변수에 저장한다. 

더보기
#include "TextureClass.h"
using namespace DirectX;

TextureClass::TextureClass()
{
	mTextureView = 0;
}

TextureClass::TextureClass(const TextureClass &)
{
}

TextureClass::~TextureClass()
{
}

bool TextureClass::Initialize(ID3D11Device * device, const WCHAR * fileName)
{
	HRESULT result;
	ScratchImage image;
	WCHAR ext[4];
	size_t l = wcslen(fileName);
	wcsncpy_s(ext, &fileName[l - 3], 3);
	ext[3] = '\0';
	if (wcscmp(ext, L"tga") == 0)
	{
		result = LoadFromTGAFile(fileName, nullptr, image);
	}
	else if (wcscmp(ext, L"dds") == 0)
	{
		result = LoadFromDDSFile(fileName, DDS_FLAGS_NONE, nullptr, image);
	}
	else if (wcscmp(ext, L"jpg") == 0 || wcscmp(ext, L"png") == 0)
	{
		result = LoadFromWICFile(fileName, WIC_FLAGS_NONE, nullptr, image);
	}
	if (FAILED(result)) return false;


	result = CreateShaderResourceView(device, image.GetImages(),
		image.GetImageCount(), image.GetMetadata(), &mTextureView);
	if (FAILED(result)) return false;

	return true;
}

void TextureClass::Shutdown()
{
	// Release the texture resource.
	if (mTextureView)
	{
		mTextureView->Release();
		mTextureView = 0;
	}

	return;
}

ID3D11ShaderResourceView * TextureClass::GetTexture()
{
	return mTextureView;
}

ModelClass.h

ModelClass는 텍스처 관련 부분이 새롭게 추가된 것 말고는 바뀐 것이 없다.

더보기
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_

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

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

class ModelClass
{
private:
	struct VertexType
	{
		XMFLOAT3 position;
		XMFLOAT2 texture;
	};

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

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

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

 

ModelClass.cpp

Initialize() 함수는 모델이 사용할 텍스처의 파일 이름과 device를 입력으로 받는다.

InitializeBuffers() 함수에서 정점 배열에는 정점이 색상 속성 대신 텍스처 좌표 속성을 가진다. 각 정점마다 텍스처 좌표를 설정하는데, 항상 (u, v) 순이다.

LoadTexture()는 텍스처 객체를 생성한 다음 파라미터로 주어진 파일 이름으로 초기화하는 함수이다. 이 함수는 Initialize() 함수에서 호출한다.

더보기
#include "ModelClass.h"

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

ModelClass::ModelClass(const ModelClass &)
{
}

ModelClass::~ModelClass()
{
}

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

	// 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();

	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;

	// Set the number of vertices in the vertex array.
	mVertexCount = 3;
	// Set the number of indices in the index array.
	mIndexCount = 3;
	// 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 with data.
	vertices[0].position = XMFLOAT3(-1.0f, -1.0f, 0.0f);
	vertices[0].texture = XMFLOAT2(0.0f, 1.0f);
	vertices[1].position = XMFLOAT3(0.0f, 1.0f, 0.0f);
	vertices[1].texture = XMFLOAT2(0.5f, 0.0f);
	vertices[2].position = XMFLOAT3(1.0f, -1.0f, 0.0f);
	vertices[2].texture = XMFLOAT2(1.0f, 1.0f);
	// Load the index array with data.
	indices[0] = 0;
	indices[1] = 1;
	indices[2] = 2;
	
	// 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, 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;
}

GraphicsClass.h

GraphicsClass도 텍스처 관련 부분이 새롭게 추가된 것 말고는 바뀐 것이 없다.

더보기
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

// INCLUDES
#include <Windows.h>

// MY CLASS INCLUDES
#include "D3DClass.h"
#include "CameraClass.h"
#include "ModelClass.h"
#include "TextureShaderClass.h"


// GLOBALS
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void ShutDown();
	bool Frame();

private:
	bool Render();

private:
	D3DClass* mDirect3D;
	CameraClass* mCamera;
	ModelClass* mModel;
	TextureShaderClass* mTextureShader;
};
#endif

 

GraphicsClass.cpp

Initialize() 함수에서 모델 객체를 초기화할 때 이미지 파일의 이름을 파라미터로 넘기는 것을 확인할 수 있다. 그리고 TextureShaderClass 객체를 생성하고 초기화한다.

Render() 함수에서 새롭게 생성한 TextureShader 객체를 통해 텍스처를 렌더링 한다.

더보기
#include "GraphicsClass.h"

GraphicsClass::GraphicsClass()
{
	mDirect3D = nullptr;
	mCamera = 0;
	mModel = 0;
	mTextureShader = 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(),
		const_cast<WCHAR*>(L"Images/stone01.tga"));
	if (!result)
	{
		MessageBox(hwnd, L"Could not initialize the texture object.", L"Error", MB_OK);
		return false;
	}

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

	return true;
}

void GraphicsClass::ShutDown()
{
	// Release the texture shader object.
	if (mTextureShader)
	{
		mTextureShader->Shutdown();
		delete mTextureShader;
		mTextureShader = 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;

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

	return true;
}

bool GraphicsClass::Render()
{
	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);
	// 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 texture shader.
	result = mTextureShader->Render(mDirect3D->GetDeviceContext(), mModel->GetIndexCount(),
		worldMatrix, viewMatrix, projectionMatrix, mModel->GetTexture());
	if (!result) return false;
	// Present the rendered scene to the screen.
	mDirect3D->EndScene();

	return true;
}

실행결과

 

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

7. 3D Model Rendering  (0) 2021.01.07
6. Diffuse Lighting  (0) 2021.01.07
4. Buffers, Shaders, and HLSL  (0) 2021.01.07
3. Initializing DirectX 11  (0) 2021.01.07
2. Creating a Framework and Window  (0) 2021.01.07
Comments