DirectX11 Tutorial 17: 멀티텍스쳐링과 텍스쳐 배열

강좌번역/DirectX 11 2013. 6. 8. 21:47 by 빠재

원문: http://rastertek.com/dx11tut17.html




이 튜토리얼에서는 DirectX 11에서 텍스쳐 배열을 사용하는 방법과 더불어 멀티텍스쳐(multitexturing, 다중 텍스쳐)를 사용하는 방법을 다룰 것입니다. 멀티텍스쳐링은 두 개의 서로 다른 텍스쳐를 혼합하여 하나의 텍스쳐로 만드는 과정입니다. 두 텍스쳐를 혼합하는 공식은 여러분이 원하고자 하는 바에 따라 다르게 설계할 수 있습니다. 이 튜토리얼에서는 두 텍스쳐의 픽셀값의 평균으로 값을 내어 골고루 혼합된 최종 텍스쳐를 얻어낼 것입니다.


텍스쳐 배열은 여러 텍스쳐들을 동시에 gpu에서 활용하는 기능으로 DirectX 10에서 처음 소개되었습니다. 그 이전에는 gpu에서 한번에 하나의 텍스쳐만 사용할 수 있었기 때문에 텍스쳐를 로드하고 해제하는 과정에서 많은 오버헤드가 발생했었습니다. 많은 사람들은 이 문제를 텍스쳐 아틀라스(Texture atlas, 여러 작은 텍스쳐들을 하나의 큰 텍스쳐에 모아놓은 것)를 만들어 놓고 단지 uv값만 바꾸어 이용하는 방식으로 해결하였습니다. 하지만 멀티텍스쳐링을 사용하면 텍스쳐 아틀라스는 더 이상 필요하지 않습니다.


이 튜토리얼에서 사용할 첫번째 텍스쳐는 아래 그림과 같으며 기본 텍스쳐라고 부를 것입니다.





그리고 기본 텍스쳐와 혼합할 두번째 텍스쳐는 색상 텍스쳐라고 부르며 아래 그림과 같습니다.





이 두 텍스쳐들은 픽셀 셰이더에 의해 픽셀 단위로 혼합될 것입니다. 혼합 공식은 다음과 같습니다.




두 텍스쳐로 위의 공식을 적용하면 다음과 같은 결과가 나올 것입니다.






그런데 아마 굳이 윗 공식처럼 하지 않아도 아래처럼 공식처럼 하면 되지 않을까... 하는 분들이 계실 겁니다.





위처럼 픽셀값을 반반 나누어 더하지 않는 이유는 우리가 가지고 있는 이미지의 픽셀들이 이미 모니터의 감마값에 맞추어져 있기 때문입니다. 이 감마값은 픽셀값들을 0.0에서 1.0 사이의 곡선 형태로(직선이 아니라) 나타나게 합니다. 그렇기 때문에 이런 비선형적인 색상값들을 다루기 위해 감마 보정이라는 단계가 필요합니다. 만약 감마 보정을 하지 않고 단순히 반반 더해버리면 아래 그림처럼 뭔가 씻겨 나간 듯한 이미지를 얻게 됩니다.






또한 참고해야 할 것은 대부분의 장치들이 감마값이 다르고 이 값들은 테이블이나 유저가 직접 조절할 수 있도록 감마 슬라이더의 형태로 제공된다는 것입니다. 이 예제에서는 필자의 모니터의 감마값이 2.0이기 때문에 개발의 편의를 위해 그 값을 기준으로 진행하도록 하겠습니다.

(역자주: .....)


우선 텍스쳐 셰이더 파일을 조금 고쳐 만든 멀티텍스쳐 셰이더 파일을 보도록 하겠습니다.




Multitexture.vs


정점 셰이더는 이름만 바뀌었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: multitexture.vs
////////////////////////////////////////////////////////////////////////////////


/////////////
// 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;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType MultiTextureVertexShader(VertexInputType input)
{
    PixelInputType output;
    

    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and 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;
}





Multitexture.ps


////////////////////////////////////////////////////////////////////////////////
// Filename: multitexture.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////




혼합할 두 텍스쳐가 저장될 2개짜리 텍스쳐 배열을 추가합니다. 텍스쳐 배열을 사용하는 것은 그래픽 카드 성능의 관점에서 봤을 때 두 개의 변수에 따로 텍스쳐를 담는 것보다 더 효율적입니다. DirectX의 이전 버전들은 텍스쳐 교체가 매우 비용이 큰 작업이었기 때문에 많은 엔진들이 텍스쳐와 재질 교체를 위해 작성되어야만 했었습니다. 텍스쳐 배열은 그런 성능상의 비용을 줄이는데 도움이 됩니다.


Texture2D shaderTextures[2];
SamplerState SampleType;


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




튜토리얼의 모든 작업은 이 픽셀 셰이더에서 이루어집니다. 우선 두 텍스쳐에서 현재 텍스쳐 좌표에 해당하는 픽셀들을 샘플링합니다. 그 다음에는 픽셀값들이 감마 보정 때문에 비선형적인 값을 가지므로 일단 곱셈으로 이 둘을 혼합흡니다. 또한 감마값도 곱해주는데, 여기서는 모니터의 감마값이 2.0에 가깝기 때문에 2.0을 곱하도록 합니다. 일단 혼합된 픽셀값이 구해지면 0~1사이의 값을 가지도록 잘라내고 리턴합니다. 또한 텍스쳐 배열의 각 텍스쳐는 [] 연산자를 사용하여 접근한다는 사실을 참고하기 바랍니다.


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 MultiTexturePixelShader(PixelInputType input) : SV_TARGET
{
    float4 color1;
    float4 color2;
    float4 blendColor;


    // Get the pixel color from the first texture.
    color1 = shaderTextures[0].Sample(SampleType, input.tex);

    // Get the pixel color from the second texture.
    color2 = shaderTextures[1].Sample(SampleType, input.tex);

    // Blend the two pixels together and multiply by the gamma value.
    blendColor = color1 * color2 * 2.0;
    
    // Saturate the final color.
    blendColor = saturate(blendColor);

    return blendColor;
}





Multitextureshaderclass.h


MultiTextureShaderClass 클래스의 코드는 TextureShaderClass 클래스를 약간 수정한 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: multitextureshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MULTITEXTURESHADERCLASS_H_
#define _MULTITEXTURESHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: MultiTextureShaderClass
////////////////////////////////////////////////////////////////////////////////
class MultiTextureShaderClass
{
private:
	struct MatrixBufferType
	{
		D3DXMATRIX world;
		D3DXMATRIX view;
		D3DXMATRIX projection;
	};

public:
	MultiTextureShaderClass();
	MultiTextureShaderClass(const MultiTextureShaderClass&);
	~MultiTextureShaderClass();

	bool Initialize(ID3D11Device*, HWND);
	void Shutdown();
	bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**);

private:
	bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
	void ShutdownShader();
	void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

	bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**);
	void RenderShader(ID3D11DeviceContext*, int);

private:
	ID3D11VertexShader* m_vertexShader;
	ID3D11PixelShader* m_pixelShader;
	ID3D11InputLayout* m_layout;
	ID3D11Buffer* m_matrixBuffer;
	ID3D11SamplerState* m_sampleState;
};

#endif





Multitextureshaderclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: multitextureshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "multitextureshaderclass.h"


MultiTextureShaderClass::MultiTextureShaderClass()
{
	m_vertexShader = 0;
	m_pixelShader = 0;
	m_layout = 0;
	m_matrixBuffer = 0;
	m_sampleState = 0;
}


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


MultiTextureShaderClass::~MultiTextureShaderClass()
{
}


bool MultiTextureShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
	bool result;




초기화 함수에서 멀티텍스쳐 HLSL 파일을 로드합니다.


	// Initialize the vertex and pixel shaders.
	result = InitializeShader(device, hwnd, L"../Engine/multitexture.vs", L"../Engine/multitexture.ps");
	if(!result)
	{
		return false;
	}

	return true;
}




Shutdown 함수에서는 ShutdownShader 함수로 관련된 셰이더 인터페이스를 해제합니다.


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

	return;
}




Render 함수는 텍스쳐 배열의 포인터를 인자로 받습니다. 이를 통해 셰이더가 혼합을 할 때 두 텍스쳐에 접근할 수 있게 됩니다.


bool MultiTextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, 
				     D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray)
{
	bool result;


	// Set the shader parameters that it will use for rendering.
	result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, textureArray);
	if(!result)
	{
		return false;
	}

	// Now render the prepared buffers with the shader.
	RenderShader(deviceContext, indexCount);

	return true;
}




InitializeShader 함수는 정점/픽셀 셰이더를 로드하고 셰이더의 레이아웃, 행렬 버퍼, 샘플 상태를 세팅합니다.


bool MultiTextureShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	ID3D10Blob* vertexShaderBuffer;
	ID3D10Blob* 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 = D3DX11CompileFromFile(vsFilename, NULL, NULL, "MultiTextureVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 
				       0, NULL, &vertexShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it 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 = D3DX11CompileFromFile(psFilename, NULL, NULL, "MultiTexturePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 
				       0, NULL, &pixelShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen 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.
	result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, 
					    &m_vertexShader);
	if(FAILED(result))
	{
		return false;
	}

	// Create the vertex shader from the buffer.
	result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, 
					   &m_pixelShader);
	if(FAILED(result))
	{
		return false;
	}

	// Create the vertex input layout description.
	// This setup needs to match the VertexType stucture 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(), &m_layout);
	if(FAILED(result))
	{
		return false;
	}

	// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
	vertexShaderBuffer->Release();
	vertexShaderBuffer = 0;

	pixelShaderBuffer->Release();
	pixelShaderBuffer = 0;

	// Setup the description of the matrix dynamic 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 matrix constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
	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, &m_sampleState);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}




ShutdownShader 함수에서는 InitializeShader 함수에서 설정한 모든 인터페이스들을 해제합니다.


void MultiTextureShaderClass::ShutdownShader()
{
	// Release the sampler state.
	if(m_sampleState)
	{
		m_sampleState->Release();
		m_sampleState = 0;
	}

	// Release the matrix constant buffer.
	if(m_matrixBuffer)
	{
		m_matrixBuffer->Release();
		m_matrixBuffer = 0;
	}

	// Release the layout.
	if(m_layout)
	{
		m_layout->Release();
		m_layout = 0;
	}

	// Release the pixel shader.
	if(m_pixelShader)
	{
		m_pixelShader->Release();
		m_pixelShader = 0;
	}

	// Release the vertex shader.
	if(m_vertexShader)
	{
		m_vertexShader->Release();
		m_vertexShader = 0;
	}

	return;
}




OutputShaderErrorMessage 함수는 정점/픽셀 셰이더 HLSL 파일을 컴파일할 때 오류가 있는 경우 에러 메세지를 출력합니다.


void MultiTextureShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
	char* compileErrors;
	unsigned 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;
}




SetShaderParameters 함수는 렌더링하기 전에 행렬과 텍스쳐 배열을 셰이더에 전달합니다.


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


	// Transpose the matrices to prepare them for the shader.
	D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
	D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
	D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

	// Lock the matrix constant buffer so it can be written to.
	result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the constant buffer.
	dataPtr = (MatrixBufferType*)mappedResource.pData;

	// Copy the matrices into the constant buffer.
	dataPtr->world = worldMatrix;
	dataPtr->view = viewMatrix;
	dataPtr->projection = projectionMatrix;

	// Unlock the matrix constant buffer.
	deviceContext->Unmap(m_matrixBuffer, 0);

	// Set the position of the matrix constant buffer in the vertex shader.
	bufferNumber = 0;

	// Now set the matrix constant buffer in the vertex shader with the updated values.
	deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);




여기서 텍스쳐 배열을 전달합니다. PSSetShaderResources 함수가 그 값을 설정하는 데 사용됩니다. 첫번째 인자는 셰이더에서의 배열의 시작 지점입니다. 두번째 인자는 얼마나 많은 텍스쳐들이 전달될 것인지를 알려줍니다. 세번째 인자는 텍스쳐 배열의 주소(pointer)입니다.


	// Set shader texture array resource in the pixel shader.
	deviceContext->PSSetShaderResources(0, 2, textureArray);

	return true;
}




RenderShader 함수는 레이아웃과 셰이더, 샘플러를 설정합니다. 그리고 나서 셰이더를 이용하여 모델을 그려냅니다.


void MultiTextureShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
	// Set the vertex input layout.
	deviceContext->IASetInputLayout(m_layout);

	// Set the vertex and pixel shaders that will be used to render this triangle.
	deviceContext->VSSetShader(m_vertexShader, NULL, 0);
	deviceContext->PSSetShader(m_pixelShader, NULL, 0);

	// Set the sampler state in the pixel shader.
	deviceContext->PSSetSamplers(0, 1, &m_sampleState);

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

	return;
}








Texturearrayclass.h


TextureArrayClass 클래스는 이전에 사용했던 TextureClass를 대체하게 됩니다. 하나의 텍스쳐만 사용했던 이전과는 달리 여러 텍스쳐를 가지고 접근할 수 있도록 해 줍니다. 이 튜토리얼에서는 2개의 텍스쳐를 다루는 방법을 설명하지만 이는 쉽게 확장할 수 있습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: texturearrayclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTUREARRAYCLASS_H_
#define _TEXTUREARRAYCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx11tex.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: TextureArrayClass
////////////////////////////////////////////////////////////////////////////////
class TextureArrayClass
{
public:
	TextureArrayClass();
	TextureArrayClass(const TextureArrayClass&);
	~TextureArrayClass();

	bool Initialize(ID3D11Device*, WCHAR*, WCHAR*);
	void Shutdown();

	ID3D11ShaderResourceView** GetTextureArray();

private:
This is the two element texture array private variable.

	ID3D11ShaderResourceView* m_textures[2];
};

#endif





Texturearrayclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: texturearrayclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "texturearrayclass.h"
클래스 생성자에서는 텍스쳐 배열을 null로 설정합니다.
TextureArrayClass::TextureArrayClass()
{
	m_textures[0] = 0;
	m_textures[1] = 0;
}


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


TextureArrayClass::~TextureArrayClass()
{
}




Initialize 함수는 두 텍스쳐 파일의 이름을 받아 텍스쳐 배열에 두 파일에 해당하는 텍스쳐들을 생성합니다.


bool TextureArrayClass::Initialize(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2)
{
	HRESULT result;


	// Load the first texture in.
	result = D3DX11CreateShaderResourceViewFromFile(device, filename1, NULL, NULL, &m_textures[0], NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Load the second texture in.
	result = D3DX11CreateShaderResourceViewFromFile(device, filename2, NULL, NULL, &m_textures[1], NULL);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}




Shutdown 함수는 텍스쳐 배열의 각 요소들을 해제합니다.


void TextureArrayClass::Shutdown()
{
	// Release the texture resources.
	if(m_textures[0])
	{
		m_textures[0]->Release();
		m_textures[0] = 0;
	}

	if(m_textures[1])
	{
		m_textures[1]->Release();
		m_textures[1] = 0;
	}

	return;
}




GetTextureArray 함수는 호출하는 객체가 텍스쳐에 접근할 수 있도록 텍스쳐 배열의 포인터를 리턴합니다.


ID3D11ShaderResourceView** TextureArrayClass::GetTextureArray()
{
	return m_textures;
}







Modelclass.h


ModelClass 클래스는 텍스쳐 배열을 사용하도록 바뀌었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_


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




TextureArrayClass 클래스의 헤더 파일이 이전 TextureClass를 대체합니다.


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


////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:
	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

	struct ModelType
	{
		float x, y, z;
		float tu, tv;
		float nx, ny, nz;
	};

public:
	ModelClass();
	ModelClass(const ModelClass&);
	~ModelClass();

	bool Initialize(ID3D11Device*, char*, WCHAR*, WCHAR*);
	void Shutdown();
	void Render(ID3D11DeviceContext*);

	int GetIndexCount();
	ID3D11ShaderResourceView** GetTextureArray();

private:
	bool InitializeBuffers(ID3D11Device*);
	void ShutdownBuffers();
	void RenderBuffers(ID3D11DeviceContext*);

	bool LoadTextures(ID3D11Device*, WCHAR*, WCHAR*);
	void ReleaseTextures();

	bool LoadModel(char*);
	void ReleaseModel();

private:
	ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
	int m_vertexCount, m_indexCount;
	ModelType* m_model;




TextureClass 객체 대신 TextureArrayClass 객체를 사용합니다.


	TextureArrayClass* m_TextureArray;
};

#endif





Modelclass.cpp


이전 튜토리얼과 달라진 부분을 중심으로 다루겠습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"


ModelClass::ModelClass()
{
	m_vertexBuffer = 0;
	m_indexBuffer = 0;
	m_model = 0;




TextureArray 변수를 초기화합니다.


	m_TextureArray = 0;
}


bool ModelClass::Initialize(ID3D11Device* device, char* modelFilename, WCHAR* textureFilename1, WCHAR* textureFilename2)
{
	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;
	}




여러개의 파일 이름을 인자로 받는 LoadTexture 함수를 호출하여 텍스쳐 배열에 로드하고 결과물로서 그려질 텍스쳐들을 로드합니다.


	// Load the textures for this model.
	result = LoadTextures(device, textureFilename1, textureFilename2);
	if(!result)
	{
		return false;
	}

	return true;
}


void ModelClass::Shutdown()
{




ReleaseTextures는 Shutdown 함수에서 텍스쳐들을 해제할 때 호출됩니다.


	// Release the model textures.
	ReleaseTextures();

	// Shutdown the vertex and index buffers.
	ShutdownBuffers();

	// Release the model data.
	ReleaseModel();

	return;
}




GetTextureArray 라는 함수를 두어 모델을 그릴 때 사용하는 텍스쳐 배열을 리턴하도록 합니다.


ID3D11ShaderResourceView** ModelClass::GetTextureArray()
{
	return m_TextureArray->GetTextureArray();
}




LoadTextures 함수는 TextureArrayClass 객체를 생성하고 입력으로 주어진 두 텍스쳐를 로드하도록 바뀌었습니다.


bool ModelClass::LoadTextures(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2)
{
	bool result;


	// Create the texture array object.
	m_TextureArray = new TextureArrayClass;
	if(!m_TextureArray)
	{
		return false;
	}

	// Initialize the texture array object.
	result = m_TextureArray->Initialize(device, filename1, filename2);
	if(!result)
	{
		return false;
	}

	return true;
}




ReleaseTextures 함수는 TextureArray 객체를 해제합니다.


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

	return;
}







Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


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


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"




MultiTextureShaderClass 함수의 헤더를 포함시킵니다.


#include "multitextureshaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	ModelClass* m_Model;




MultiTextureShaderClass 객체를 선언합니다.


	MultiTextureShaderClass* m_MultiTextureShader;
};

#endif







Graphicsclass.cpp


이전 튜토리얼에서 달라진 부분을 중심으로 다루겠습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Model = 0;




생성자에서 멀티텍스쳐 셰이더 객체를 null로 초기화합니다.


	m_MultiTextureShader = 0;
}


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

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

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK);
		return false;
	}

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

	// Initialize a base view matrix with the camera for 2D user interface rendering.
	m_Camera->SetPosition(0.0f, 0.0f, -1.0f);
	m_Camera->Render();
	m_Camera->GetViewMatrix(baseViewMatrix);

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




ModelClass 객체는 이전과 다르게 초기화됩니다. 이 튜토리얼에서는 일반 사각형에만 보이도록 하는 것이 가장 결과 확인에 좋기 때문에 square.txt 파일으로 로드하도록 합니다. 또한 하나의 텍스쳐가 아니라 텍스쳐 배열에 담을 두 개의 텍스쳐를 로드합니다.


	// Initialize the model object.
	result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/stone01.dds", 
				     L"../Engine/data/dirt01.dds");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}




멀티텍스쳐 셰이더 객체를 생성하고 초기화합니다.


	// Create the multitexture shader object.
	m_MultiTextureShader = new MultiTextureShaderClass;
	if(!m_MultiTextureShader)
	{
		return false;
	}

	// Initialize the multitexture shader object.
	result = m_MultiTextureShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the multitexture shader object.", L"Error", MB_OK);
		return false;
	}

	return true;
}


void GraphicsClass::Shutdown()
{




Shutdown 함수에서 멀티텍스쳐 셰이더 객체를 해제합니다.


	// Release the multitexture shader object.
	if(m_MultiTextureShader)
	{
		m_MultiTextureShader->Shutdown();
		delete m_MultiTextureShader;
		m_MultiTextureShader = 0;
	}

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

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

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

	return;
}


bool GraphicsClass::Frame()
{




혼합 효과를 좀 더 자세히 볼 수 있도록 카메라를 좀 더 가까이 위치시킵니다.


	// Set the position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -5.0f);

	return true;
}


bool GraphicsClass::Render()
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;


	// Clear the buffers to begin the scene.
	m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the world, view, projection, and ortho matrices from the camera and D3D objects.
	m_D3D->GetWorldMatrix(worldMatrix);
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);
	m_D3D->GetOrthoMatrix(orthoMatrix);

	// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_Model->Render(m_D3D->GetDeviceContext());




모델을 렌더링하기 위해 멀티텍스쳐 셰이더를 사용합니다. 셰이더의 입력으로 ModelClass의 텍스쳐 배열을 사용했다는 것을 참고하시기 바랍니다.


	// Render the model using the multitexture shader.
	m_MultiTextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
				     m_Model->GetTextureArray());

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}








마치면서


두 텍스쳐를 고르게 혼합하고 감마 보정까지 수행한 결과를 얻을 수 있습니다. 또한 텍스쳐 배열을 이용하여 렌더링 성능을 높이는 방법에 대해서도 살펴보았습니다.









연습문제


1. 소스를 다시 컴파일하고 결과 이미지를 보십시오. esc키를 눌러 종료합니다.


2. 두 텍스쳐를 다른 이미지로 바꾸어 결과를 확인해 보십시오.







소스 코드


Visual Studio 2008 프로젝트: dx11tut17.zip


소스 코드: dx11src17.zip


실행 파일: dx11exe17.zip

Nav