원문 : http://www.rastertek.com/dx11tut05.html
이번 튜토리얼에서는 DirectX 11에서 텍스쳐를 다루는 방법에 대해 살펴볼 것입니다. 텍스쳐링을 하게 되면 사진이나 다른 그림들을 도형 표면에 씌울 수 있기 때문에 더욱 현실적인 장면을 연출할 수 있게 됩니다. 이번 튜토리얼에서는 아래 이미지를 사용할 것입니다.
그리고 이것을 지난 튜토리얼에서 만들었던 도형에 씌우면 아래와 같이 됩니다.
우리가 사용할 텍스쳐의 포맷은 .dds 파일입니다. 이것은 DirectX가 사용하는 Direct Draw Surface라고 하는 것입니다. DirectX SDK에 dds파일을 만들 수 있는 툴이 포함되어 있습니다. 이 툴은 DirectX Utilities 폴더 아래 DirectX Texture Tool이라는 이름으로 있습니다. 이 툴을 이용하여 다양한 종류와 크기의 텍스쳐를 만들 수 있고 다른 이미지나 텍스쳐를 dds 파일로 저장할 수 있습니다. 참 쉽죠?
그리고 코드를 살펴보기 전에 우선 텍스쳐가 어떻게 도형에 매핑되는지 알아야 합니다. dds 이미지의 픽셀들을 도형에 매핑시키기 위해 텍셀 좌표계(Texel coordinate system, 역자주: Texel은 Texture와 Pixel의 합성어...입니다)를 사용합니다. 이 체계 안에서 정수 값을 가진 픽셀 좌표는 0.0f와 1.0f 사이의 부동 소수점 좌표로 바뀌게 됩니다. 예를 들어, 너비가 256픽셀인 텍스쳐가 있다면 첫번째 픽셀은 x좌표가 0.0f이고, 256번째 픽셀은 1.0f 좌표로 매핑되며, 중간에 위치한 128번째 픽셀은 0.5f로 바뀔 것입니다.
텍셀 좌표계에서 가로 성분(x축)은 U라고 하며, 세로 성분(y축)은 V라고 부릅니다. 가로 성분은 가장 왼쪽이 0.0f에서 오른쪽으로 증가하며, 세로 성분은 맨 위가 0.0f에서 아래로 증가합니다. 다시 한번 예를 들자면, 왼쪽 최상단의 픽셀은 UV좌표가 (U: 0.0, V:0.0)이고, 오른쪽 맨 아래의 픽셀은 UV좌표가 (U: 1.0, V: 1.0)가 됩니다. 이 체계를 그림으로 간략하게 묘사해 보았습니다.
이제 텍스쳐가 어떻게 씌워지는지 알았으므로 이번 튜토리얼에서 업데이트된 프레임워크를 살펴보도록 하겠습니다.
지난 튜토리얼에서 달라진 점은 ModelClass 안에 TextureClass라는 클래스가 들어왔고, ColorShaderClass를 TextureShaderClass라는 클래스가 대체했다는 것입니다. 우선 새로운 HLSL 텍스쳐 셰이더를 보겠습니다.
Texture.vs
텍스쳐 정점 셰이더는 텍스쳐 씌우기를 제공한다는 것만 빼면 앞서 봤었던 컬러 셰이더와 비슷합니다.
//////////////////////////////////////////////////////////////////////////////// // Filename: texture.vs //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; };
이제 더이상 정점에 색상을 사용하지 않고 텍스쳐 좌표를 사용하게 될 것입니다. 텍스쳐에서는 U와 V좌표만을 사용하기 때문에 이를 표현하기 위해 float2 자료형을 이용합니다. 정점 셰이더와 픽셀 셰이더에서 텍스쳐 좌표를 나타내기 위해 TEXCOORD0이라는 것을 사용합니다. 여러 개의 텍스쳐 좌표가 가능하다면 그 값을 0부터 아무 숫자로나 지정할 수도 있습니다.
////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; }; //////////////////////////////////////////////////////////////////////////////// // Vertex Shader //////////////////////////////////////////////////////////////////////////////// PixelInputType TextureVertexShader(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; }
Texture.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: texture.ps ////////////////////////////////////////////////////////////////////////////////
텍스쳐 픽셀 셰이더에는 두 개의 전역변수가 있습니다. 첫번째는 텍스쳐 그 자체인 Texture2D shaderTexture 입니다. 이것은 텍스쳐 자원으로서 모델에 텍스쳐를 그릴 때 사용될 것입니다. 두번째 새 변수는 SamplerState SampleType입니다. 샘플러 상태(Sampler state)는 도형에 셰이딩이 이루어질 때 어떻게 텍스쳐의 픽셀이 씌여지는 지를 수정할 수 있게 해 줍니다. 일례로 너무 멀리 있어 겨우 8픽셀만큼의 영역을 차지하는 도형의 경우 이 샘플러 상태를 사용하여 원래 텍스쳐의 어떤 픽셀 혹은 어떤 픽셀 조합을 사용해야 할지 결정합니다. 그 원본 텍스쳐는 256x256 픽셀의 크기일 수도 있으므로 매우 작아보이는 도형의 품질과 관련하여 이 결정은 매우 중요합니다. 이 샘플러 상태를 TextureShaderClass 클래스에 만들고 연결하여 픽셀 셰이더에서 이를 이용할 수 있게 할 것입니다.
///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType;
////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
픽셀 셰이더가 HLSL의 샘플링 함수를 사용하도록 수정되었습니다. 샘플링 함수(sample function)은 위에서 정의한 샘플러 상태와 텍스쳐 좌표를 사용합니다. 도형의 표면 UV좌표 위치에 들어갈 픽셀 값을 결정하고 반환하기 위해 이 두 변수를 사용합니다.
//////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// 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; }
Textureclass.h
TextureClass는 텍스쳐 자원을 불러오고, 해제하고, 접근하는 작업을 캡슐화합니다. 모든 텍스쳐에 대해 각각 이 클래스가 만들어져 있어야 합니다.
//////////////////////////////////////////////////////////////////////////////// // Filename: textureclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TEXTURECLASS_H_ #define _TEXTURECLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx11tex.h> //////////////////////////////////////////////////////////////////////////////// // Class name: TextureClass //////////////////////////////////////////////////////////////////////////////// class TextureClass { public: TextureClass(); TextureClass(const TextureClass&); ~TextureClass();
처음 두 함수는 주어진 파일 이름으로 텍스쳐를 불러오고 더이상 사용되지 않는 텍스쳐를 해제합니다.
bool Initialize(ID3D11Device*, WCHAR*); void Shutdown();
GetTexture 함수는 셰이더가 그리는 작업에 사용할 수 있도록 텍스쳐 자원의 포인터를 반환합니다.
ID3D11ShaderResourceView* GetTexture(); private:
ID3D11ShaderResourceView* m_texture; }; #endif
Textureclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: textureclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "textureclass.h"
클래스 생성자는 텍스쳐 셰이더 자원의 포인터를 null로 초기화합니다.
TextureClass::TextureClass() { m_texture = 0; } TextureClass::TextureClass(const TextureClass& other) { } TextureClass::~TextureClass() { }
초기화는 Direct3D 디바이스와 텍스쳐의 파일 이름을 가지고 m_texture이라는 셰이더 자원 변수에 텍스쳐 파일을 로드합니다. 그 후에 텍스쳐가 그려질 수 있습니다.
bool TextureClass::Initialize(ID3D11Device* device, WCHAR* filename) { HRESULT result; // Load the texture in. result = D3DX11CreateShaderResourceViewFromFile(device, filename, NULL, NULL, &m_texture, NULL); if(FAILED(result)) { return false; } return true; }
Shutdown 함수는 로드된 텍스쳐 자원을 반환하고 그 포인터를 null로 되돌립니다.
void TextureClass::Shutdown() { // Release the texture resource. if(m_texture) { m_texture->Release(); m_texture = 0; } return; }
GetTexture 함수는 다른 객체가 이 텍스쳐 셰이더 자원에 접근할 필요가 있을 때 사용됩니다. 이 함수를 통해서 텍스쳐를 렌더링할 수 있게 됩니다.
ID3D11ShaderResourceView* TextureClass::GetTexture() { return m_texture; }
Modelclass.h
ModelClass는 지난 튜토리얼의 내용에 더해 텍스쳐링을 지원하는 내용이 추가되었습니다.
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _MODELCLASS_H_ #define _MODELCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h>
이제 TextureClass의 헤더가 ModelClass의 헤더 파일에 포함되어야 합니다.
/////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "textureclass.h" //////////////////////////////////////////////////////////////////////////////// // Class name: ModelClass //////////////////////////////////////////////////////////////////////////////// class ModelClass { private:
VertexType는 색상과 관련된 요소가 텍스쳐 좌표로 대체되었습니다. 텍스쳐 좌표는 이전 튜토리얼의 녹색을 대신하게 됩니다.
struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; }; public: ModelClass(); ModelClass(const ModelClass&); ~ModelClass(); bool Initialize(ID3D11Device*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount();
ModelClass 역시 셰이더에게 자신의 텍스쳐 자원을 전달하고 그리기 위한 GetTexture 함수를 갖고 있습니다.
ID3D11ShaderResourceView* GetTexture(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*);
ModelClass는 private 함수로 이 모델을 그릴 텍스쳐를 불러오고 반환하는 데 사용할 LoadTexture과 ReleaseTexture 함수를 가지고 있습니다.
bool LoadTexture(ID3D11Device*, WCHAR*); void ReleaseTexture(); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount;
변수 m_Texture은 이 모델의 텍스쳐 자원을 불러오고, 반환하고, 접근하는 데 사용됩니다.
TextureClass* m_Texture; }; #endif
Modelclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: modelclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "modelclass.h" ModelClass::ModelClass() { m_vertexBuffer = 0; m_indexBuffer = 0;
클래스 생성자에서는 새로운 변수인 텍스쳐 객체를 null로 초기화합니다.
m_Texture = 0; } ModelClass::ModelClass(const ModelClass& other) { } ModelClass::~ModelClass() { }
이제 Initialize에서는 모델에서 사용할 dds 텍스쳐 파일의 이름을 인자로 받습니다.
bool ModelClass::Initialize(ID3D11Device* device, WCHAR* textureFilename) { bool result; // Initialize the vertex and index buffer that hold the geometry for the triangle. result = InitializeBuffers(device); if(!result) { return false; }
Initialize 함수에서는 텍스쳐를 불러오는 새 함수를 호출합니다.
// Load the texture for this model. result = LoadTexture(device, textureFilename); if(!result) { return false; } return true; } void ModelClass::Shutdown() {
Shutdown 함수에서는 초기화할 때 불러오느 텍스쳐 객체를 반환하는 새 함수를 호출합니다.
// Release the model texture. ReleaseTexture(); // Release 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 m_indexCount; }
GetTexture 함수는 모델 텍스쳐 자원을 반환합니다. 텍스쳐 셰이더가 모델을 그리기 위해서는 이 텍스쳐에 접근할 수 있어야 합니다.
ID3D11ShaderResourceView* ModelClass::GetTexture() { return m_Texture->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. m_vertexCount = 3; // Set the number of indices in the index array. m_indexCount = 3; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; }
정점 배열은 색상 성분 대신 텍스쳐 성분을 가지게 되었습니다. 텍스쳐 벡터(Texture vector)는 언제나 U가 먼저, V가 그 다음입니다. 예를 들어, 첫 번째 텍스쳐 좌표는 U는 0.0, V는 1.0에 해당하는 삼각형의 왼쪽 아래입니다. 이 페이지의 맨 위에 있는 그림을 사용하여 다른 부분이 어떻게 되어야 할 지 생각해 보세요. 참고로 이 좌표를 바꿔서 도형 표면의 어디든 원하는 부분의 텍스쳐를 입힐 수 있습니다. 이 튜토리얼에서는 최대한 간단하게 하기 위해 직접 매핑을 하였습니다.
// Load the vertex array with data. vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // Bottom left. vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f); vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // Top middle. vertices[1].texture = D3DXVECTOR2(0.5f, 0.0f); vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // Bottom right. vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f); // Load the index array with data. indices[0] = 0; // Bottom left. indices[1] = 1; // Top middle. indices[2] = 2; // Bottom right. // Set up the description of the vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; 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, &m_vertexBuffer); if(FAILED(result)) { return false; } // Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; 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; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); 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(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // Release the vertex buffer. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 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, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. deviceContext->IASetIndexBuffer(m_indexBuffer, 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; }
LoadTexture 함수는 제공된 파일 이름으로 텍스쳐 개체를 만들고 초기화하는 새 private 함수입니다.
bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename) { bool result; // Create the texture object. m_Texture = new TextureClass; if(!m_Texture) { return false; } // Initialize the texture object. result = m_Texture->Initialize(device, filename); if(!result) { return false; } return true; }
ReleaseTexture 함수는 LoadTexture 에서 생성한 텍스쳐 객체를 반환합니다.
void ModelClass::ReleaseTexture() { // Release the texture object. if(m_Texture) { m_Texture->Shutdown(); delete m_Texture; m_Texture = 0; } return; }
Textureshaderclass.h
TextureShaderClass 클래스는 이전 튜토리얼의 ColorShaderClass의 개정판입니다. 이 클래스는 정점 셰이더와 픽셀 셰이더를 이용하여 3D 모델을 그리는 데 사용될 것입니다.
//////////////////////////////////////////////////////////////////////////////// // Filename: textureshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TEXTURESHADERCLASS_H_ #define _TEXTURESHADERCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std; //////////////////////////////////////////////////////////////////////////////// // Class name: TextureShaderClass //////////////////////////////////////////////////////////////////////////////// class TextureShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; public: TextureShaderClass(); TextureShaderClass(const TextureShaderClass&); ~TextureShaderClass(); 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;
여기에는 샘플러 상태의 포인터에 해당하는 새로운 private 변수가 있습니다. 이 포인터는 텍스쳐 셰이더와의 인터페이스로서 사용될 것입니다.
ID3D11SamplerState* m_sampleState; };
Textureshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: textureshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "textureshaderclass.h" TextureShaderClass::TextureShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_matrixBuffer = 0;
클래스 생성자에서 새로운 샘플러 변수를 null로 초기화합니다.
m_sampleState = 0; } TextureShaderClass::TextureShaderClass(const TextureShaderClass& other) { } TextureShaderClass::~TextureShaderClass() { } bool TextureShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result;
새로운 HLSL 파일인 texture.vs와 texture.ps가 이 셰이더에 불러와집니다.
// Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/texture.vs", L"../Engine/texture.ps"); if(!result) { return false; } return true; }
Shutdown 함수는 셰이더 변수들을 반환하는 함수를 호출합니다.
void TextureShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
Render 함수는 텍스쳐 자원을 가리키는 포인터인 texture이라는 새로운 인자를 받습니다. 이것은 SetShaderParameters로 전달되어 렌더링에 사용할 셰이더에 연결됩니다.
bool TextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX 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; }
InitializeShader 함수에서는 텍스쳐 셰이더를 초기화합니다.
bool TextureShaderClass::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;
여기에서 초기화를 위해 텍스쳐 샘플러의 description 변수를 사용합니다.
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, "TextureVertexShader", "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, "TexturePixelShader", "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 pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; }
입력 레이아웃이 색상 대신 텍스쳐를 사용하는 것으로 바뀌었습니다. 첫번째인 POSITION은 바뀌지 않았지만 두번째 것의 SemanticName과 Format이 각각 TEXCOORD와 DXGI_FORMAT_R32G32_FLOAT로 바뀌었습니다. 이 두 가지의 변화는 ModelClass의 정의와 셰이더 파일에 있는 VertexType와 형태가 일치하도록 만듭니다.
// 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 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, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; }
샘플러 상태 description도 여기서 설정되며 나중에 픽셀 셰이더로 전달됩니다. 샘플러 description에서 가장 중요한 요소는 바로 Filter입니다. Filter은 최종 도형 표면에서 텍스쳐의 어느 픽셀이 사용되거나 혼합될 것인지 결정하는 방법을 정합니다. 일례로 여기서는 처리량 면에서는 좀 무겁지만 가장 좋은 결과를 보여주는 D3D11_FILTER_MIN_MAG_MIP_LINEAR 옵션을 사용하였습니다. 이것은 축소, 확대, 밉레벨 샘플링 시에 선형 보간(linear interpolation)을 사용한다는 것을 알려줍니다.
AddressU와 AddressV는 좌표가 언제나 0.0f와 1.0f 사이에 있도록 해 주는 Wrap으로 설정되었습니다. 이 범위를 나가는 값은 0.0f와 1.0f 사이의 값으로 재조정됩니다. 모든 다른 샘플러 상태 description의 옵션들은 기본값으로 하였습니다.
// 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 함수는 TextureShaderClass에서 사용한 모든 변수들을 해제합니다.
void TextureShaderClass::ShutdownShader() {
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 TextureShaderClass::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 TextureShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture) { 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 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 constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);
이 함수가 이전 튜토리얼에서 수정된 부분은 픽셀 셰이더에 텍스쳐를 설정하는 부분입니다.
// Set shader texture resource in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &texture); return true; }
RenderShader 함수는 도형을 그리기 위한 셰이더를 호출합니다.
void TextureShaderClass::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);
RenderShader 함수는 그리는 작업 이전에 픽셀 셰이더의 샘플러 상태를 설정하는 부분이 포함되도록 바뀌었습니다.
// Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_ /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "modelclass.h"
GraphicsClass 클래스는 TextureShaderClass의 헤더 참조가 추가되고 ColorShaderClass의 헤더 참조를 없앴습니다.
#include "textureshaderclass.h" ///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f; //////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(); private: D3DClass* m_D3D; CameraClass* m_Camera; ModelClass* m_Model;
TextureShaderClass* m_TextureShader; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h"
m_TextureShader 변수는 생성자에서 null로 초기화됩니다.
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; m_TextureShader = 0; } GraphicsClass::GraphicsClass(const GraphicsClass& other) { } GraphicsClass::~GraphicsClass() { } bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; // 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; } // Create the model object. m_Model = new ModelClass; if(!m_Model) { return false; }
ModelClass::Initialize 함수는 이제 모델을 그리는 데 사용되는 텍스쳐의 이름을 인자로 받습니다.
// Initialize the model object. result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK); return false; }
새로운 TextureShaderClass 객체가 생성되고 초기화됩니다.
// Create the texture shader object. m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; } // Initialize the texture shader object. result = m_TextureShader->Initialize(m_D3D->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() { The TextureShaderClass object is also released in the Shutdown function. // Release the texture shader object. if(m_TextureShader) { m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 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 Direct3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; } bool GraphicsClass::Frame() { bool result; // Render the graphics scene. result = Render(); if(!result) { return false; } return true; } bool GraphicsClass::Render() { D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix; bool result; // 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 view, projection, and world matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetProjectionMatrix(projectionMatrix); m_D3D->GetWorldMatrix(worldMatrix); // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing. m_Model->Render(m_D3D->GetDevice());
모델을 그리기 위해 컬러 셰이더 대신 텍스쳐 셰이더가 호출됩니다. 참고로 이 셰이더는 모델의 텍스쳐에 접근하기 위하여 모델로부터 텍스쳐 자원의 포인터를 받아옵니다.
// Render the model using the texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Model->GetTexture()); if(!result) { return false; } // Present the rendered scene to the screen. m_D3D->EndScene(); return true; }
마치면서
이제 여러분은 텍스쳐를 불러오고, 도형에 입히고, 셰이더를 이용하여 그리는 방법의 기본을 아시게 되었습니다.
연습 문제
1. 코드를 다시 컴파일하고 실제로 화면에 텍스쳐가 입혀진 삼각형이 나오는지 확인해 보십시오. 잘 보인다면 Esc키를 눌러 나오십시오.
2. 여러분만의 dds 텍스쳐를 만들고 seafloor.dds 파일이 있는 폴더에 저장하십시오. GraphicsClass::Initialize 함수에서 모델 초기화를 여러분의 텍스쳐로 하도록 바꾸고 다시 컴파일하여 실행해 보십시오.
3. 코드를 수정하여 두 개의 삼각형이 사각형을 이루도록 바꾸어 보십시오. 사각형에 전체 텍스쳐를 입혀서 화면에 텍스쳐의 모든 부분이 제대로 보이도록 해 보십시오.
4. MIN_MAG_MIP_LINEAR 필터의 효과를 보기 위해 다른 거리가 되도록 카메라를 이동시켜 보십시오.
5. 다른 필터를 시도해 보고 카메라를 옮겨 다른 결과가 나오는지 확인해 보십시오.
소스 코드
Visual Studio 2010 프로젝트 : dx11tut05.zip
소스 : dx11src05.zip
실행 파일 : dx11exe05.zip
'강좌번역 > DirectX 11' 카테고리의 다른 글
DirectX11 Tutorial 7 - 3D 모델 렌더링 (1) | 2013.02.02 |
---|---|
DirectX11 Tutorial 6 - 조명 (1) | 2013.01.22 |
DirectX11 Tutorial 4 - 버퍼, 셰이더, HLSL (20) | 2013.01.06 |
DirectX11 Tutorial 3 - DirectX 11의 초기화 (20) | 2013.01.05 |
DirectX11 Tutorial 2 - 프레임워크와 윈도우 만들기 (10) | 2013.01.05 |