원문 : http://www.rastertek.com/dx11tut12.html
Tutorial 12: Font Engine
화면에 텍스트를 쓰는 것은 어느 프로그램이나 상당히 중요한 기능입니다. DirectX 11에서 텍스트 렌더링은 먼저 2D 이미지를 어떻게 렌더링하는지를 아는 것부터 요구됩니다. 이 주제는 이미 이전 강좌에서 다뤘기 때문에 이번 강좌는 그걸 전제로 시작할 것입니다.
가장 필요 할 것은 전용 폰트 이미지입니다. 제가 원하는 문자들을 1024x16 DDS 텍스쳐에 넣은 정말 간단한 것 하나를 만들었습니다.
보시다시피 필요한 기본 문자들로 구성되며 텍스쳐 파일 하나에 다 들어가 있습니다. 이제 필요할때 화면에 각 문자를 그리기 위해 텍스쳐의 인덱스를 사용하는 간단한 폰트 엔진을 만들 수 있습니다. DirectX 11에서 하는 방법은 두 삼감형으로 구성된 사각형을 만들고 문자 하나를 텍스쳐로부터 사각형 위로 렌더링합니다. 그래서 만약 문장이 있으면 필요한 문자들을 알아내고 각각의 사각형을 만들고 문자들을 해당 사각형에 렌더링합니다. 그 후에 모든 사각형을 화면의 문장을 형성하는 위치에 렌더링합니다. 이는 이전 강좌에서 2D 이미지를 화면에 렌더링하기 위해 사용했던 방법과 같습니다.
이제 텍스쳐 인덱스를 위해 텍스쳐에서 각 문자의 크기와 위치를 알려주는 텍스트 파일이 필요합니다. 이 텍스트 파일은 폰트 엔진이 텍스쳐 밖에서 렌더링 할 수 있는 문자를 구성하는데 필요한 픽셀을 빠르게 구하게 해줍니다.
파일의 포맷 : [문자의 아스키 값] [해당 문자] [왼쪽 U 텍스쳐 좌표] [오른쪽 U 텍스쳐 좌표] [문자의 픽셀 너비]
32 0.0 0.0 0
33 ! 0.0 0.000976563 1
34 " 0.00195313 0.00488281 3
35 # 0.00585938 0.0136719 8
36 $ 0.0146484 0.0195313 5
37 % 0.0205078 0.0302734 10
38 & 0.03125 0.0390625 8
39 ' 0.0400391 0.0410156 1
40 ( 0.0419922 0.0449219 3
41 ) 0.0458984 0.0488281 3
42 * 0.0498047 0.0546875 5
43 + 0.0556641 0.0625 7
44 , 0.0634766 0.0644531 1
45 - 0.0654297 0.0683594 3
46 . 0.0693359 0.0703125 1
47 / 0.0712891 0.0751953 4
48 0 0.0761719 0.0820313 6
49 1 0.0830078 0.0859375 3
50 2 0.0869141 0.0927734 6
51 3 0.09375 0.0996094 6
52 4 0.100586 0.106445 6
53 5 0.107422 0.113281 6
54 6 0.114258 0.120117 6
55 7 0.121094 0.126953 6
56 8 0.12793 0.133789 6
57 9 0.134766 0.140625 6
58 : 0.141602 0.142578 1
59 ; 0.143555 0.144531 1
60 < 0.145508 0.151367 6
61 = 0.152344 0.15918 7
62 > 0.160156 0.166016 6
63 ? 0.166992 0.171875 5
64 @ 0.172852 0.18457 12
65 A 0.185547 0.194336 9
66 B 0.195313 0.202148 7
67 C 0.203125 0.209961 7
68 D 0.210938 0.217773 7
69 E 0.21875 0.225586 7
70 F 0.226563 0.232422 6
71 G 0.233398 0.241211 8
72 H 0.242188 0.249023 7
73 I 0.25 0.250977 1
74 J 0.251953 0.256836 5
75 K 0.257813 0.265625 8
76 L 0.266602 0.272461 6
77 M 0.273438 0.282227 9
78 N 0.283203 0.290039 7
79 O 0.291016 0.298828 8
80 P 0.299805 0.306641 7
81 Q 0.307617 0.31543 8
82 R 0.316406 0.323242 7
83 S 0.324219 0.331055 7
84 T 0.332031 0.338867 7
85 U 0.339844 0.34668 7
86 V 0.347656 0.356445 9
87 W 0.357422 0.370117 13
88 X 0.371094 0.37793 7
89 Y 0.378906 0.385742 7
90 Z 0.386719 0.393555 7
91 [ 0.394531 0.396484 2
92 \ 0.397461 0.401367 4
93 ] 0.402344 0.404297 2
94 ^ 0.405273 0.410156 5
95 _ 0.411133 0.417969 7
96 ` 0.418945 0.420898 2
97 a 0.421875 0.426758 5
98 b 0.427734 0.432617 5
99 c 0.433594 0.438477 5
100 d 0.439453 0.444336 5
101 e 0.445313 0.450195 5
102 f 0.451172 0.455078 4
103 g 0.456055 0.460938 5
104 h 0.461914 0.466797 5
105 i 0.467773 0.46875 1
106 j 0.469727 0.472656 3
107 k 0.473633 0.478516 5
108 l 0.479492 0.480469 1
109 m 0.481445 0.490234 9
110 n 0.491211 0.496094 5
111 o 0.49707 0.501953 5
112 p 0.50293 0.507813 5
113 q 0.508789 0.513672 5
114 r 0.514648 0.517578 3
115 s 0.518555 0.523438 5
116 t 0.524414 0.527344 3
117 u 0.52832 0.533203 5
118 v 0.53418 0.539063 5
119 w 0.540039 0.548828 9
120 x 0.549805 0.554688 5
121 y 0.555664 0.560547 5
122 z 0.561523 0.566406 5
123 { 0.567383 0.570313 3
124 | 0.571289 0.572266 1
125 } 0.573242 0.576172 3
126 ~ 0.577148 0.583984 7
인덱스 파일과 텍스쳐 파일로 폰트 엔진을 만드는데 필요한 것을 갖추었습니다. 여러분만의 인덱스 파일을 만들고 싶다면 각 문자는 빈칸으로만 분리되어 있는지 확실히 해야 하며 빈칸이 없는 곳에서 TU와 TV를 생성하는 비트맵 파서를 작성할 수 있어야 합니다.
여러 사용자들이 여러 해상도에서 여러분의 응용프로그램을 실행 시킨다는 것을 기억하세요. 한 크기의 폰트는 모든 해상도에서 깔끔하게 읽지 못할 것입니다. 이러한 문제를 해결하기 위해 3-4개의 다른 폰트 사이즈를 만들고 특정 해상도에 특정 크기를 사용하게 될 것입니다.
Framework
폰트 기능을 클래스 셋에 캡슐화 하기 원하므로 몇몇 새 클래스를 우리의 프레임워크에 추가할 것입니다. 갱신될 프레임워크는 다음과 같습니다.
이번 강좌에서 TextClass, FontClass, FontShaderClass 라는 새로운 클래스 3개가 추가되었습니다. FontShaderClass는 폰트 렌더링을 위한 쉐이더로 이전 강좌에서 비트맵 이미지를 렌더링하는데 TextureShaderClass가 사용된 방법과 유사합니다. FontClass는 폰트 데이터를 가지며 문자열을 렌더링하는데 필요한 정점 버퍼를 만듭니다. TextClass는 화면에 렌더링되어야 하는 각 텍스트 문자열에 대한 정점 버퍼와 인덱스 버퍼를 가지는데 문자열에 대한 정점 버퍼를 생성하기 위해 FontClass를 사용하고 그 버퍼들을 렌더링하기 위해 FontShaderClass를 사용합니다.
Fontclass.h
먼저 FontClass를 보도록 하겠습니다. 이 클래스는 폰트 대한 텍스쳐, 텍스트 파일의 폰트 데이터, 폰트 데이터의 정점 버퍼를 만드는 함수를 다룰 것입니다. 각 문장의 폰트 데이터를 가지는 정점 버퍼들은 이 클래스가 아닌 TextClass에 둘 것입니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: fontclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FONTCLASS_H_
#define _FONTCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <fstream>
using namespace std;
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: FontClass
////////////////////////////////////////////////////////////////////////////////
class FontClass
{
private:
FontType 구조체는 폰트 인덱스 파일에서 읽은 인덱싱하는 데이터를 가지는데 사용됩니다. left와 right는 TU 텍스쳐 좌표입니다. size는 픽셀에서 문자의 너비입니다.
struct FontType
{
float left, right;
int size;
};
VertexType 구조체는 텍스트 문자가 렌더링될 사각형을 생성하기 위해 사용되는 실제 정점 데이터를 위함입니다. 각 문자는 사각형을 만드는데 2개의 삼각형이 필요할 것입니다. 이 삼각형들은 위치와 텍스쳐 데이터만을 가질 것입니다.
struct VertexType
{
D3DXVECTOR3 position;
D3DXVECTOR2 texture;
};
public:
FontClass();
FontClass(const FontClass&);
~FontClass();
bool Initialize(ID3D11Device*, char*, WCHAR*);
void Shutdown();
ID3D11ShaderResourceView* GetTexture();
BuildVertexArray는 이 함수에 파라미터로 넘겨진 char* 문장을 렌더링할 삼각형들의 정점 배열을 생성하고 반환하는 것을 처리할 것입니다. 이 함수는 렌더링할 모든 문장들의 정점 배열을 생성하도록 새 TextClass에 의해 호출됩니다.
void BuildVertexArray(void*, char*, float, float);
private:
bool LoadFontData(char*);
void ReleaseFontData();
bool LoadTexture(ID3D11Device*, WCHAR*);
void ReleaseTexture();
private:
FontType* m_Font;
TextureClass* m_Texture;
};
#endif
Fontclass.cpp
///////////////////////////////////////////////////////////////////////////////
// Filename: fontclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "fontclass.h"
클래스 생성자는 FontClass의 모든 private 멤버 변수를 null로 초기화합니다.
FontClass::FontClass()
{
m_Font = 0;
m_Texture = 0;
}
FontClass::FontClass(const FontClass& other)
{
}
FontClass::~FontClass()
{
}
Initialize는 폰트 텍스쳐와 폰트 데이터를 로드할 것입니다.
bool FontClass::Initialize(ID3D11Device* device, char* fontFilename, WCHAR* textureFilename)
{
bool result;
// Load in the text file containing the font data.
result = LoadFontData(fontFilename);
if(!result)
{
return false;
}
// Load the texture that has the font characters on it.
result = LoadTexture(device, textureFilename);
if(!result)
{
return false;
}
return true;
}
Shutdown은 폰트 텍스쳐와 폰트 데이터를 해제할 것입니다.
void FontClass::Shutdown()
{
// Release the font texture.
ReleaseTexture();
// Release the font data.
ReleaseFontData();
return;
}
LoadFontData 함수는 텍스쳐의 인덱싱 정보를 가지는 fontdata.txt 파일을 로드하는 곳입니다.
bool FontClass::LoadFontData(char* filename)
{
ifstream fin;
int i;
char temp;
먼저 FontType 구조체의 배열을 생성합니다. 배열의 사이즈는 95로 설정했는데 텍스쳐의 문자들의 개수이며 fontdata.txt 파일의 인덱스 개수이기 때문입니다.
// Create the font spacing buffer.
m_Font = new FontType[95];
if(!m_Font)
{
return false;
}
이제 파일을 열고 각 라인을 m_Font 배열로 읽어들입니다. 우리는 TU 왼쪽, 오른쪽 좌표랑 문자의 픽셀 사이즈만 읽습니다.
// Read in the font size and spacing between chars.
fin.open(filename);
if(fin.fail())
{
return false;
}
// Read in the 95 used ascii characters for text.
for(i=0; i<95; i++)
{
fin.get(temp);
while(temp != ' ')
{
fin.get(temp);
}
fin.get(temp);
while(temp != ' ')
{
fin.get(temp);
}
fin >> m_Font[i].left;
fin >> m_Font[i].right;
fin >> m_Font[i].size;
}
// Close the file.
fin.close();
return true;
}
ReleaseFontData 함수는 텍스쳐 인덱싱 데이터를 가지고 있는 배열을 해제합니다.
void FontClass::ReleaseFontData()
{
// Release the font data array.
if(m_Font)
{
delete [] m_Font;
m_Font = 0;
}
return;
}
LoadTexture 함수는 font.dds 파일을 텍스쳐 쉐이더 리소스로 읽어들입니다. 이 파일은 우리가 문자를 얻고 렌더링시 사각형 폴리곤에 쓸 텍스쳐가 될 것입니다.
bool FontClass::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 함수는 font에 사용된 텍스쳐를 해제합니다.
void FontClass::ReleaseTexture()
{
// Release the texture object.
if(m_Texture)
{
m_Texture->Shutdown();
delete m_Texture;
m_Texture = 0;
}
return;
}
GetTexture가 폰트 텍스쳐 인터페이스를 반환하여 폰트 그래픽이 렌더링 될 수 있습니다.
ID3D11ShaderResourceView* FontClass::GetTexture()
{
return m_Texture->GetTexture();
}
BuildVertexArray는 파라미터로 넘겨진 텍스트 문자으로부터 정점 버퍼를 생성하기 위하여 TextClass에 의해 호출될 것입니다. TextClass에서 그려질 각 문장은 생성된 이후 쉽게 렌더링 될 수 있는 전용 정점 버퍼를 가집니다. vertices는 정점 배열의 포인터로 생성되면 TextClass로 반환될 것입니다. sentence는 정점 배열을 만드는데 사용될 텍스트 문장입니다. drawX와 drawY는 문장을 그릴 곳의 화면 좌표입니다.
void FontClass::BuildVertexArray(void* vertices, char* sentence, float drawX, float drawY)
{
VertexType* vertexPtr;
int numLetters, index, i, letter;
// Coerce the input vertices into a VertexType structure.
vertexPtr = (VertexType*)vertices;
// Get the number of letters in the sentence.
numLetters = (int)strlen(sentence);
// Initialize the index to the vertex array.
index = 0;
다음 루프는 정점, 인덱스 배열을 생성할 것입니다. 문장에서 각 문자를 취하고 문자의 두 삼각형을 생성합니다. 폰트 텍스쳐로부터 해당 문자를 픽셀 사이즈와 TU 텍스쳐 좌표가 있는 m_Font 배열을 이용하여 두 삼각형에 매핑합니다. 문자를 위한 폴리곤이 생성되고 나면 다음 문자를 그리기 위해 화면의 X 좌표를 갱신합니다.
// Draw each letter onto a quad.
for(i=0; i<numLetters; i++)
{
letter = ((int)sentence[i]) - 32;
// If the letter is a space then just move over three pixels.
if(letter == 0)
{
drawX = drawX + 3.0f;
}
else
{
// First triangle in quad.
vertexPtr[index].position = D3DXVECTOR3(drawX, drawY, 0.0f); // Top left.
vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].left, 0.0f);
index++;
vertexPtr[index].position = D3DXVECTOR3((drawX + m_Font[letter].size), (drawY - 16), 0.0f); // Bottom right.
vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].right, 1.0f);
index++;
vertexPtr[index].position = D3DXVECTOR3(drawX, (drawY - 16), 0.0f); // Bottom left.
vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].left, 1.0f);
index++;
// Second triangle in quad.
vertexPtr[index].position = D3DXVECTOR3(drawX, drawY, 0.0f); // Top left.
vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].left, 0.0f);
index++;
vertexPtr[index].position = D3DXVECTOR3(drawX + m_Font[letter].size, drawY, 0.0f); // Top right.
vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].right, 0.0f);
index++;
vertexPtr[index].position = D3DXVECTOR3((drawX + m_Font[letter].size), (drawY - 16), 0.0f); // Bottom right.
vertexPtr[index].texture = D3DXVECTOR2(m_Font[letter].right, 1.0f);
index++;
// Update the x location for drawing by the size of the letter and one pixel.
drawX = drawX + m_Font[letter].size + 1.0f;
}
}
return;
}
Font.vs
폰트 정점 쉐이더는 이전 강좌에서 2D 이미지를 렌더링하는데 사용된 텍스쳐 정점 쉐이더의 수정된 버전입니다. 유일한 차이는 정점 쉐이더 이름입니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: font.vs
////////////////////////////////////////////////////////////////////////////////
/////////////
// GLOBALS //
/////////////
cbuffer PerFrameBuffer
{
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 FontVertexShader(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;
}
Font.ps
////////////////////////////////////////////////////////////////////////////////
// Filename: font.ps
////////////////////////////////////////////////////////////////////////////////
/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState SampleType;
pixelColor라는 값을 가지는 새로운 상수 버퍼가 생겼습니다. 폰트 텍스트를 그리는데 쓰일 픽셀 색상을 조절하기 위해 사용합니다.
cbuffer PixelBuffer
{
float4 pixelColor;
};
//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD0;
};
FontPixelShader는 먼저 폰트 텍스쳐를 조사하여 해당 픽셀을 얻습니다. 검정색인 픽셀을 얻었다면 텍스트 픽셀이 아닌 그냥 배경 부분입니다. 이 경우 픽셀의 알파값을 0으로 설정하여 블렌딩을 계산할때 이 픽셀이 투명이라고 결정할 것입니다. 입력 픽셀 색상이 검정색이 아니면 텍스트 픽셀 입니다. 이 경우 원하는 색이 칠해진 픽셀을 얻기 위해 color에 pixelColor를 곱하고 화면에 그립니다.
////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 FontPixelShader(PixelInputType input) : SV_TARGET
{
float4 color;
// Sample the texture pixel at this location.
color = shaderTexture.Sample(SampleType, input.tex);
// If the color is black on the texture then treat this pixel as transparent.
if(color.r == 0.0f)
{
color.a = 0.0f;
}
// If the color is other than black on the texture then this is a pixel in the font so draw it using the font pixel color.
else
{
color.a = 1.0f;
color = color * pixelColor;
}
return color;
}
Fontshaderclass.h
FontShaderClass은 이전 강좌의 TextureShaderClass에 폰트 렌더링을 위한 약간의 코드 수정과 이름만 바꾼 것입니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: fontshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FONTSHADERCLASS_H_
#define _FONTSHADERCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;
////////////////////////////////////////////////////////////////////////////////
// Class name: FontShaderClass
////////////////////////////////////////////////////////////////////////////////
class FontShaderClass
{
private:
struct ConstantBufferType
{
D3DXMATRIX world;
D3DXMATRIX view;
D3DXMATRIX projection;
};
픽셀 쉐이더의 PixelBuffer와 매치되는 새 구조체가 생겼습니다. 렌더링 될 텍스트의 픽셀 색상만을 가집니다.
struct PixelBufferType
{
D3DXVECTOR4 pixelColor;
};
public:
FontShaderClass();
FontShaderClass(const FontShaderClass&);
~FontShaderClass();
bool Initialize(ID3D11Device*, HWND);
void Shutdown();
bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR4);
private:
bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
void ShutdownShader();
void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);
bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR4);
void RenderShader(ID3D11DeviceContext*, int);
private:
ID3D11VertexShader* m_vertexShader;
ID3D11PixelShader* m_pixelShader;
ID3D11InputLayout* m_layout;
ID3D11Buffer* m_constantBuffer;
ID3D11SamplerState* m_sampleState;
FontShaderClass은 텍스트 폰트 렌더링에 쓰이는 픽셀의 색상을 위한 상수 버퍼를 가집니다.
ID3D11Buffer* m_pixelBuffer;
};
#endif
Fontshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: fontshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "fontshaderclass.h"
FontShaderClass::FontShaderClass()
{
m_vertexShader = 0;
m_pixelShader = 0;
m_layout = 0;
m_constantBuffer = 0;
m_sampleState = 0;
생성자에서 픽셀 색상 상수 버퍼를 null로 초기화합니다.
m_pixelBuffer = 0;
}
FontShaderClass::FontShaderClass(const FontShaderClass& other)
{
}
FontShaderClass::~FontShaderClass()
{
}
bool FontShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
bool result;
Initialize은 새 폰트 정점 쉐이더와 픽셀 쉐이더의 HLSL 파일을 로드합니다.
// Initialize the vertex and pixel shaders.
result = InitializeShader(device, hwnd, L"../Engine/font.vs", L"../Engine/font.ps");
if(!result)
{
return false;
}
return true;
}
Shutdown은 폰트 쉐이더와 관련된 포인터, 데이터를 해제하는 ShutdownShader를 호출합니다.
void FontShaderClass::Shutdown()
{
// Shutdown the vertex and pixel shaders as well as the related objects.
ShutdownShader();
return;
}
Render는 쉐이더 파라미터를 설정하며 폰트 쉐이더를 이용하여 버퍼를 그릴 것입니다. 이 과정은 새 pixelColor 파라미터를 제외하고 TextureShaderClass와 같습니다.
bool FontShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR4 pixelColor)
{
bool result;
// Set the shader parameters that it will use for rendering.
result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, pixelColor);
if(!result)
{
return false;
}
// Now render the prepared buffers with the shader.
RenderShader(deviceContext, indexCount);
return true;
}
InitializeShader 함수는 새 HLSL 폰트 정점, 픽셀 쉐이더들과 쉐이더의 인터페이스인 포인터를 설정합니다.
bool FontShaderClass::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 constantBufferDesc;
D3D11_SAMPLER_DESC samplerDesc;
D3D11_BUFFER_DESC pixelBufferDesc;
// Initialize the pointers this function will use to null.
errorMessage = 0;
vertexShaderBuffer = 0;
pixelShaderBuffer = 0;
정점 쉐이더의 이름은 FontVertexShader로 변경되었습니다.
// Compile the vertex shader code.
result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "FontVertexShader", "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;
}
픽셀 쉐이더의 이름은 FontPixelShader로 변경 되었습니다.
// Compile the pixel shader code.
result = D3DX11CompileFromFile(psFilename, NULL, NULL, "FontPixelShader", "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;
}
// 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 constant buffer that is in the vertex shader.
constantBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
constantBufferDesc.ByteWidth = sizeof(ConstantBufferType);
constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
constantBufferDesc.MiscFlags = 0;
constantBufferDesc.StructureByteStride = 0;
// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
result = device->CreateBuffer(&constantBufferDesc, NULL, &m_constantBuffer);
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;
}
여기서 이 클래스가 픽셀 쉐이더에서 픽셀 색상을 설정하게 해줄 새 픽셀 색상 상수 버퍼를 설정합니다.
// Setup the description of the dynamic pixel constant buffer that is in the pixel shader.
pixelBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
pixelBufferDesc.ByteWidth = sizeof(PixelBufferType);
pixelBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
pixelBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
pixelBufferDesc.MiscFlags = 0;
pixelBufferDesc.StructureByteStride = 0;
// Create the pixel constant buffer pointer so we can access the pixel shader constant buffer from within this class.
result = device->CreateBuffer(&pixelBufferDesc, NULL, &m_pixelBuffer);
if(FAILED(result))
{
return false;
}
return true;
}
ShutdownShader 함수는 모든 쉐이더 관련 데이터를 해제합니다.
void FontShaderClass::ShutdownShader()
{
새 픽셀 색상 상수 버퍼는 여기서 해제됩니다.
// Release the pixel constant buffer.
if(m_pixelBuffer)
{
m_pixelBuffer->Release();
m_pixelBuffer = 0;
}
// Release the sampler state.
if(m_sampleState)
{
m_sampleState->Release();
m_sampleState = 0;
}
// Release the constant buffer.
if(m_constantBuffer)
{
m_constantBuffer->Release();
m_constantBuffer = 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는 쉐이더 컴파일 에러를 텍스트 파일에 컴파일 실패 이벤트를 확인할 수 있도록 기록합니다.
void FontShaderClass::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 FontShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR4 pixelColor)
{
HRESULT result;
D3D11_MAPPED_SUBRESOURCE mappedResource;
ConstantBufferType* dataPtr;
unsigned int bufferNumber;
PixelBufferType* dataPtr2;
// Lock the constant buffer so it can be written to.
result = deviceContext->Map(m_constantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if(FAILED(result))
{
return false;
}
// Get a pointer to the data in the constant buffer.
dataPtr = (ConstantBufferType*)mappedResource.pData;
// Transpose the matrices to prepare them for the shader.
D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);
// Copy the matrices into the constant buffer.
dataPtr->world = worldMatrix;
dataPtr->view = viewMatrix;
dataPtr->projection = projectionMatrix;
// Unlock the constant buffer.
deviceContext->Unmap(m_constantBuffer, 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_constantBuffer);
// Set shader texture resource in the pixel shader.
deviceContext->PSSetShaderResources(0, 1, &texture);
여기는 렌더링 전 픽셀 색상을 설정하는 곳입니다. 픽셀 상수 버퍼를 잠그고 픽셀 색상을 버퍼 안데 설정하고 다시 버퍼를 풉니다. 픽셀 쉐이더에서 상수 버퍼의 위치(순번)를 설정하면 사용 준비가 되었습니다.
// Lock the pixel constant buffer so it can be written to.
result = deviceContext->Map(m_pixelBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if(FAILED(result))
{
return false;
}
// Get a pointer to the data in the pixel constant buffer.
dataPtr2 = (PixelBufferType*)mappedResource.pData;
// Copy the pixel color into the pixel constant buffer.
dataPtr2->pixelColor = pixelColor;
// Unlock the pixel constant buffer.
deviceContext->Unmap(m_pixelBuffer, 0);
// Set the position of the pixel constant buffer in the pixel shader.
bufferNumber = 0;
// Now set the pixel constant buffer in the pixel shader with the updated value.
deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_pixelBuffer);
return true;
}
RenderShader는 폰트 쉐이더를 이용하여 준비된 폰트 정점/인덱스 버퍼를 그립니다.
void FontShaderClass::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 the triangles.
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;
}
Textclass.h
TextClass는 프로그램이 필요할 모든 2D 텍스트를 처리합니다. 2D 텍스트를 화면에 렌더링 하며 이것을 돕기 위해 FontClass와 FontShaderClass를 사용합니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: textclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTCLASS_H_
#define _TEXTCLASS_H_
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "fontclass.h"
#include "fontshaderclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: TextClass
////////////////////////////////////////////////////////////////////////////////
class TextClass
{
private:
SentenceType은 각 텍스트 문장의 렌더링 정보를 가지는 구조체 입니다.
struct SentenceType
{
ID3D11Buffer *vertexBuffer, *indexBuffer;
int vertexCount, indexCount, maxLength;
float red, green, blue;
};
VertexType은 FontClass의 것과 일치해야 합니다.
struct VertexType
{
D3DXVECTOR3 position;
D3DXVECTOR2 texture;
};
public:
TextClass();
TextClass(const TextClass&);
~TextClass();
bool Initialize(ID3D11Device*, ID3D11DeviceContext*, HWND, int, int, D3DXMATRIX);
void Shutdown();
bool Render(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX);
private:
bool InitializeSentence(SentenceType**, int, ID3D11Device*);
bool UpdateSentence(SentenceType*, char*, int, int, float, float, float, ID3D11DeviceContext*);
void ReleaseSentence(SentenceType**);
bool RenderSentence(ID3D11DeviceContext*, SentenceType*, D3DXMATRIX, D3DXMATRIX);
private:
FontClass* m_Font;
FontShaderClass* m_FontShader;
int m_screenWidth, m_screenHeight;
D3DXMATRIX m_baseViewMatrix;
이번 강좌에서는 2개의 문장을 사용할 것입니다.
SentenceType* m_sentence1;
SentenceType* m_sentence2;
};
#endif
Textclass.cpp
///////////////////////////////////////////////////////////////////////////////
// Filename: textclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "textclass.h"
클래스 생성자는 private 멤버 변수를 null로 초기화합니다.
TextClass::TextClass()
{
m_Font = 0;
m_FontShader = 0;
m_sentence1 = 0;
m_sentence2 = 0;
}
TextClass::TextClass(const TextClass& other)
{
}
TextClass::~TextClass()
{
}
bool TextClass::Initialize(ID3D11Device* device, ID3D11DeviceContext* deviceContext, HWND hwnd, int screenWidth, int screenHeight,
D3DXMATRIX baseViewMatrix)
{
bool result;
화면 크기와 기본 뷰 행렬을 저장합니다. 이 정보는 2D 텍스트 렌더링에 사용될 것입니다.
// Store the screen width and height.
m_screenWidth = screenWidth;
m_screenHeight = screenHeight;
// Store the base view matrix.
m_baseViewMatrix = baseViewMatrix;
폰트 객체를 생성하고 초기화합니다.
// Create the font object.
m_Font = new FontClass;
if(!m_Font)
{
return false;
}
// Initialize the font object.
result = m_Font->Initialize(device, "../Engine/data/fontdata.txt", L"../Engine/data/font.dds");
if(!result)
{
MessageBox(hwnd, L"Could not initialize the font object.", L"Error", MB_OK);
return false;
}
폰트 쉐이더 객체를 생성하고 초기화합니다.
// Create the font shader object.
m_FontShader = new FontShaderClass;
if(!m_FontShader)
{
return false;
}
// Initialize the font shader object.
result = m_FontShader->Initialize(device, hwnd);
if(!result)
{
MessageBox(hwnd, L"Could not initialize the font shader object.", L"Error", MB_OK);
return false;
}
이번 강좌에 사용될 두 문자열을 생성하고 초기화합니다. 문자열 하나는 100, 100에 흰색으로 "Hello"이며 다른 하나는 100, 200에 노란색으로 "Goodbye"입니다. UpdateSentence 함수는 언제든 문자열의 색상, 내용, 위치를 변경하기 위해 호출될 수 있습니다.
// Initialize the first sentence.
result = InitializeSentence(&m_sentence1, 16, device);
if(!result)
{
return false;
}
// Now update the sentence vertex buffer with the new string information.
result = UpdateSentence(m_sentence1, "Hello", 100, 100, 1.0f, 1.0f, 1.0f, deviceContext);
if(!result)
{
return false;
}
// Initialize the first sentence.
result = InitializeSentence(&m_sentence2, 16, device);
if(!result)
{
return false;
}
// Now update the sentence vertex buffer with the new string information.
result = UpdateSentence(m_sentence2, "Goodbye", 100, 200, 1.0f, 1.0f, 0.0f, deviceContext);
if(!result)
{
return false;
}
return true;
}
Shutdown 함수는 두 문자열과 폰트 객체와 폰트 쉐이더 객체를 해제할 것입니다.
void TextClass::Shutdown()
{
// Release the first sentence.
ReleaseSentence(&m_sentence1);
// Release the second sentence.
ReleaseSentence(&m_sentence2);
// Release the font shader object.
if(m_FontShader)
{
m_FontShader->Shutdown();
delete m_FontShader;
m_FontShader = 0;
}
// Release the font object.
if(m_Font)
{
m_Font->Shutdown();
delete m_Font;
m_Font = 0;
}
return;
}
Render는 화면에 문장 두개를 그릴 것입니다.
bool TextClass::Render(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX orthoMatrix)
{
bool result;
// Draw the first sentence.
result = RenderSentence(deviceContext, m_sentence1, worldMatrix, orthoMatrix);
if(!result)
{
return false;
}
// Draw the second sentence.
result = RenderSentence(deviceContext, m_sentence2, worldMatrix, orthoMatrix);
if(!result)
{
return false;
}
return true;
}
InitializeSentence 함수는 문장을 저장하고 렌더링하는데 사용될 빈 정점 버퍼가 든 SentenceType을 생성합니다. maxLength 파라미터는 정점 버퍼의 최대 크기를 결정합니다.
bool TextClass::InitializeSentence(SentenceType** sentence, int maxLength, ID3D11Device* device)
{
VertexType* vertices;
unsigned long* indices;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;
int i;
// Create a new sentence object.
*sentence = new SentenceType;
if(!*sentence)
{
return false;
}
// Initialize the sentence buffers to null.
(*sentence)->vertexBuffer = 0;
(*sentence)->indexBuffer = 0;
// Set the maximum length of the sentence.
(*sentence)->maxLength = maxLength;
// Set the number of vertices in the vertex array.
(*sentence)->vertexCount = 6 * maxLength;
// Set the number of indexes in the index array.
(*sentence)->indexCount = (*sentence)->vertexCount;
// Create the vertex array.
vertices = new VertexType[(*sentence)->vertexCount];
if(!vertices)
{
return false;
}
// Create the index array.
indices = new unsigned long[(*sentence)->indexCount];
if(!indices)
{
return false;
}
// Initialize vertex array to zeros at first.
memset(vertices, 0, (sizeof(VertexType) * (*sentence)->vertexCount));
// Initialize the index array.
for(i=0; i<(*sentence)->indexCount; i++)
{
indices[i] = i;
}
문장을 위한 정점 버퍼 description의 생성중 Usage 형식을 동적으로 설정합니다. 언제든지 문장의 내용을 변경하고 싶을 수 있기때문입니다.
// Set up the description of the dynamic vertex buffer.
vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
vertexBufferDesc.ByteWidth = sizeof(VertexType) * (*sentence)->vertexCount;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
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;
// Create the vertex buffer.
result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &(*sentence)->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) * (*sentence)->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;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;
// Create the index buffer.
result = device->CreateBuffer(&indexBufferDesc, &indexData, &(*sentence)->indexBuffer);
if(FAILED(result))
{
return false;
}
// Release the vertex array as it is no longer needed.
delete [] vertices;
vertices = 0;
// Release the index array as it is no longer needed.
delete [] indices;
indices = 0;
return true;
}
UpdateSentence 파라미터 sentence에 대한 정점 버퍼의 내용을 수정합니다. 정점 버퍼의 내용을 갱신하기 위해 Map과 Unmap 함수와 덧붙여 memcpy를 사용합니다.
bool TextClass::UpdateSentence(SentenceType* sentence, char* text, int positionX, int positionY, float red, float green, float blue,
ID3D11DeviceContext* deviceContext)
{
int numLetters;
VertexType* vertices;
float drawX, drawY;
HRESULT result;
D3D11_MAPPED_SUBRESOURCE mappedResource;
VertexType* verticesPtr;
문장의 크기와 색상을 설정합니다.
// Store the color of the sentence.
sentence->red = red;
sentence->green = green;
sentence->blue = blue;
// Get the number of letters in the sentence.
numLetters = (int)strlen(text);
// Check for possible buffer overflow.
if(numLetters > sentence->maxLength)
{
return false;
}
// Create the vertex array.
vertices = new VertexType[sentence->vertexCount];
if(!vertices)
{
return false;
}
// Initialize vertex array to zeros at first.
memset(vertices, 0, (sizeof(VertexType) * sentence->vertexCount));
화면에 문장을 그리는 시작 위치를 계산합니다.
// Calculate the X and Y pixel position on the screen to start drawing to.
drawX = (float)(((m_screenWidth / 2) * -1) + positionX);
drawY = (float)((m_screenHeight / 2) - positionY);
FontClass와 문장 정보를 이용하여 정점 배열을 생성합니다.
// Use the font class to build the vertex array from the sentence text and sentence draw location.
m_Font->BuildVertexArray((void*)vertices, text, drawX, drawY);
정점 배열 정보를 sentence 정점 버퍼에 복사합니다.
// Lock the vertex buffer so it can be written to.
result = deviceContext->Map(sentence->vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if(FAILED(result))
{
return false;
}
// Get a pointer to the data in the vertex buffer.
verticesPtr = (VertexType*)mappedResource.pData;
// Copy the data into the vertex buffer.
memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * sentence->vertexCount));
// Unlock the vertex buffer.
deviceContext->Unmap(sentence->vertexBuffer, 0);
// Release the vertex array as it is no longer needed.
delete [] vertices;
vertices = 0;
return true;
}
ReleaseSentence는 sentence 정점, 인덱스 버퍼뿐 아니라 sentence도 해제합니다.
void TextClass::ReleaseSentence(SentenceType** sentence)
{
if(*sentence)
{
// Release the sentence vertex buffer.
if((*sentence)->vertexBuffer)
{
(*sentence)->vertexBuffer->Release();
(*sentence)->vertexBuffer = 0;
}
// Release the sentence index buffer.
if((*sentence)->indexBuffer)
{
(*sentence)->indexBuffer->Release();
(*sentence)->indexBuffer = 0;
}
// Release the sentence.
delete *sentence;
*sentence = 0;
}
return;
}
RenderSentence 함수는 sentence 정점, 인덱스 버퍼를 입력 어셈블러에 넣고 FontShaderClass 객체를 호출하여 넘겨받은 sentence를 그립니다. 현재 뷰 행렬 대신 m_baseViewMatrix를 사용하니 주의하세요. 이로인해 현재 뷰가 어느 곳인지 상관없이 각 프레임마다 화면의 같은 위치에 텍스트를 그릴 수 있게 됩니다. 마찬가지로 일반 투영 행렬 대신에 2D 좌표를 이용하여 그려야하기 때문에 orthoMatrix를 사용합니다.
bool TextClass::RenderSentence(ID3D11DeviceContext* deviceContext, SentenceType* sentence, D3DXMATRIX worldMatrix,
D3DXMATRIX orthoMatrix)
{
unsigned int stride, offset;
D3DXVECTOR4 pixelColor;
bool result;
// 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, &sentence->vertexBuffer, &stride, &offset);
// Set the index buffer to active in the input assembler so it can be rendered.
deviceContext->IASetIndexBuffer(sentence->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);
// Create a pixel color vector with the input sentence color.
pixelColor = D3DXVECTOR4(sentence->red, sentence->green, sentence->blue, 1.0f);
// Render the text using the font shader.
result = m_FontShader->Render(deviceContext, sentence->indexCount, worldMatrix, m_baseViewMatrix, orthoMatrix, m_Font->GetTexture(),
pixelColor);
if(!result)
{
false;
}
return true;
}
D3dclass.h
이번 강좌에서 D3DClass도 블렌딩 상태를 포함하기 위해 수정하였습니다. 블렌딩은 폰트가 배경의 3D 객체와 혼합될게 해줍니다. 블렌딩을 켜지 않으면 텍스트뒤 검정 삼각형들을 볼 것입니다. 그러나 블렌딩을 켜면 텍스트에 대한 픽셀들만 화면에 보여지고 그외 삼각형의 나머지(텍스쳐에서 검정 부분)는 완전히 투명이 됩니다. 여기서 블렌딩에 대해 엄청 자세하게 파지는 않을 거지만 간단한 블렌딩은 이번 강좌에 대해 올바르게 동작하기 위해 필요됩니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _D3DCLASS_H_
#define _D3DCLASS_H_
/////////////
// LINKING //
/////////////
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")
//////////////
// INCLUDES //
//////////////
#include <dxgi.h>
#include <d3dcommon.h>
#include <d3d11.h>
#include <d3dx10math.h>
////////////////////////////////////////////////////////////////////////////////
// Class name: D3DClass
////////////////////////////////////////////////////////////////////////////////
class D3DClass
{
public:
D3DClass();
D3DClass(const D3DClass&);
~D3DClass();
bool Initialize(int, int, bool, HWND, bool, float, float);
void Shutdown();
void BeginScene(float, float, float, float);
void EndScene();
ID3D11Device* GetDevice();
ID3D11DeviceContext* GetDeviceContext();
void GetProjectionMatrix(D3DXMATRIX&);
void GetWorldMatrix(D3DXMATRIX&);
void GetOrthoMatrix(D3DXMATRIX&);
void TurnZBufferOn();
void TurnZBufferOff();
알파 블렌딩을 키고 끄는 두 함수가 추가되었습니다.
void TurnOnAlphaBlending();
void TurnOffAlphaBlending();
private:
bool m_vsync_enabled;
IDXGISwapChain* m_swapChain;
ID3D11Device* m_device;
ID3D11DeviceContext* m_deviceContext;
ID3D11RenderTargetView* m_renderTargetView;
ID3D11Texture2D* m_depthStencilBuffer;
ID3D11DepthStencilState* m_depthStencilState;
ID3D11DepthStencilView* m_depthStencilView;
ID3D11RasterizerState* m_rasterState;
D3DXMATRIX m_projectionMatrix;
D3DXMATRIX m_worldMatrix;
D3DXMATRIX m_orthoMatrix;
ID3D11DepthStencilState* m_depthDisabledStencilState;
두 블렌딩 상태도 생겼습니다. m_alphaEnableBlendingState은 알파 블렌딩을 켠 것, m_alphaDisableBlendingState은 끈 것에 대한 것입니다.
ID3D11BlendState* m_alphaEnableBlendingState;
ID3D11BlendState* m_alphaDisableBlendingState;
};
#endif
D3dclass.cpp
이전 강좌 이후 클래스에서 변화된 함수만 살펴보겠습니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "d3dclass.h"
D3DClass::D3DClass()
{
m_swapChain = 0;
m_device = 0;
m_deviceContext = 0;
m_renderTargetView = 0;
m_depthStencilBuffer = 0;
m_depthStencilState = 0;
m_depthStencilView = 0;
m_rasterState = 0;
m_depthDisabledStencilState = 0;
새로운 두 블렌딩 상태를 null로 설정합니다.
m_alphaEnableBlendingState = 0;
m_alphaDisableBlendingState = 0;
}
bool D3DClass::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullscreen,
float screenDepth, float screenNear)
{
HRESULT result;
IDXGIFactory* factory;
IDXGIAdapter* adapter;
IDXGIOutput* adapterOutput;
unsigned int numModes, i, numerator, denominator;
DXGI_MODE_DESC* displayModeList;
DXGI_SWAP_CHAIN_DESC swapChainDesc;
D3D_FEATURE_LEVEL featureLevel;
ID3D11Texture2D* backBufferPtr;
D3D11_TEXTURE2D_DESC depthBufferDesc;
D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
D3D11_RASTERIZER_DESC rasterDesc;
D3D11_VIEWPORT viewport;
float fieldOfView, screenAspect;
D3D11_DEPTH_STENCIL_DESC depthDisabledStencilDesc;
두 블렌딩 상태를 설정하기 위한 새 description 변수를 만듭니다.
D3D11_BLEND_DESC blendStateDescription;
// Store the vsync setting.
m_vsync_enabled = vsync;
// Create a DirectX graphics interface factory.
result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory);
if(FAILED(result))
{
return false;
}
// Use the factory to create an adapter for the primary graphics interface (video card).
result = factory->EnumAdapters(0, &adapter);
if(FAILED(result))
{
return false;
}
// Enumerate the primary adapter output (monitor).
result = adapter->EnumOutputs(0, &adapterOutput);
if(FAILED(result))
{
return false;
}
// Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor).
result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
if(FAILED(result))
{
return false;
}
// Create a list to hold all the possible display modes for this monitor/video card combination.
displayModeList = new DXGI_MODE_DESC[numModes];
if(!displayModeList)
{
return false;
}
// Now fill the display mode list structures.
result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList);
if(FAILED(result))
{
return false;
}
// Now go through all the display modes and find the one that matches the screen width and height.
// When a match is found store the numerator and denominator of the refresh rate for that monitor.
for(i=0; i<numModes; i++)
{
if(displayModeList[i].Width == (unsigned int)screenWidth)
{
if(displayModeList[i].Height == (unsigned int)screenHeight)
{
numerator = displayModeList[i].RefreshRate.Numerator;
denominator = displayModeList[i].RefreshRate.Denominator;
}
}
}
// Release the display mode list.
delete [] displayModeList;
displayModeList = 0;
// Release the adapter output.
adapterOutput->Release();
adapterOutput = 0;
// Release the adapter.
adapter->Release();
adapter = 0;
// Release the factory.
factory->Release();
factory = 0;
// Initialize the swap chain description.
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
// Set to a single back buffer.
swapChainDesc.BufferCount = 1;
// Set the width and height of the back buffer.
swapChainDesc.BufferDesc.Width = screenWidth;
swapChainDesc.BufferDesc.Height = screenHeight;
// Set regular 32-bit surface for the back buffer.
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
// Set the refresh rate of the back buffer.
if(m_vsync_enabled)
{
swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator;
swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator;
}
else
{
swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
}
// Set the usage of the back buffer.
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
// Set the handle for the window to render to.
swapChainDesc.OutputWindow = hwnd;
// Turn multisampling off.
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
// Set to full screen or windowed mode.
if(fullscreen)
{
swapChainDesc.Windowed = false;
}
else
{
swapChainDesc.Windowed = true;
}
// Set the scan line ordering and scaling to unspecified.
swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
// Discard the back buffer contents after presenting.
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
// Don't set the advanced flags.
swapChainDesc.Flags = 0;
// Set the feature level to DirectX 11.
featureLevel = D3D_FEATURE_LEVEL_11_0;
// Create the swap chain, Direct3D device, and Direct3D device context.
result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1,
D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL, &m_deviceContext);
if(FAILED(result))
{
return false;
}
// Get the pointer to the back buffer.
result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr);
if(FAILED(result))
{
return false;
}
// Create the render target view with the back buffer pointer.
result = m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView);
if(FAILED(result))
{
return false;
}
// Release pointer to the back buffer as we no longer need it.
backBufferPtr->Release();
backBufferPtr = 0;
// Initialize the description of the depth buffer.
ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));
// Set up the description of the depth buffer.
depthBufferDesc.Width = screenWidth;
depthBufferDesc.Height = screenHeight;
depthBufferDesc.MipLevels = 1;
depthBufferDesc.ArraySize = 1;
depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthBufferDesc.SampleDesc.Count = 1;
depthBufferDesc.SampleDesc.Quality = 0;
depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthBufferDesc.CPUAccessFlags = 0;
depthBufferDesc.MiscFlags = 0;
// Create the texture for the depth buffer using the filled out description.
result = m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer);
if(FAILED(result))
{
return false;
}
// Initialize the description of the stencil state.
ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));
// Set up the description of the stencil state.
depthStencilDesc.DepthEnable = true;
depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
depthStencilDesc.StencilEnable = true;
depthStencilDesc.StencilReadMask = 0xFF;
depthStencilDesc.StencilWriteMask = 0xFF;
// Stencil operations if pixel is front-facing.
depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
// Stencil operations if pixel is back-facing.
depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
// Create the depth stencil state.
result = m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState);
if(FAILED(result))
{
return false;
}
// Set the depth stencil state.
m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);
// Initialize the depth stencil view.
ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));
// Set up the depth stencil view description.
depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
depthStencilViewDesc.Texture2D.MipSlice = 0;
// Create the depth stencil view.
result = m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView);
if(FAILED(result))
{
return false;
}
// Bind the render target view and depth stencil buffer to the output render pipeline.
m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView);
// Setup the raster description which will determine how and what polygons will be drawn.
rasterDesc.AntialiasedLineEnable = false;
rasterDesc.CullMode = D3D11_CULL_BACK;
rasterDesc.DepthBias = 0;
rasterDesc.DepthBiasClamp = 0.0f;
rasterDesc.DepthClipEnable = true;
rasterDesc.FillMode = D3D11_FILL_SOLID;
rasterDesc.FrontCounterClockwise = false;
rasterDesc.MultisampleEnable = false;
rasterDesc.ScissorEnable = false;
rasterDesc.SlopeScaledDepthBias = 0.0f;
// Create the rasterizer state from the description we just filled out.
result = m_device->CreateRasterizerState(&rasterDesc, &m_rasterState);
if(FAILED(result))
{
return false;
}
// Now set the rasterizer state.
m_deviceContext->RSSetState(m_rasterState);
// Setup the viewport for rendering.
viewport.Width = (float)screenWidth;
viewport.Height = (float)screenHeight;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;
// Create the viewport.
m_deviceContext->RSSetViewports(1, &viewport);
// Setup the projection matrix.
fieldOfView = (float)D3DX_PI / 4.0f;
screenAspect = (float)screenWidth / (float)screenHeight;
// Create the projection matrix for 3D rendering.
D3DXMatrixPerspectiveFovLH(&m_projectionMatrix, fieldOfView, screenAspect, screenNear, screenDepth);
// Initialize the world matrix to the identity matrix.
D3DXMatrixIdentity(&m_worldMatrix);
// Create an orthographic projection matrix for 2D rendering.
D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth);
// Clear the second depth stencil state before setting the parameters.
ZeroMemory(&depthDisabledStencilDesc, sizeof(depthDisabledStencilDesc));
// Now create a second depth stencil state which turns off the Z buffer for 2D rendering. The only difference is
// that DepthEnable is set to false, all other parameters are the same as the other depth stencil state.
depthDisabledStencilDesc.DepthEnable = false;
depthDisabledStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
depthDisabledStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
depthDisabledStencilDesc.StencilEnable = true;
depthDisabledStencilDesc.StencilReadMask = 0xFF;
depthDisabledStencilDesc.StencilWriteMask = 0xFF;
depthDisabledStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthDisabledStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
depthDisabledStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthDisabledStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
depthDisabledStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthDisabledStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
depthDisabledStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthDisabledStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
// Create the state using the device.
result = m_device->CreateDepthStencilState(&depthDisabledStencilDesc, &m_depthDisabledStencilState);
if(FAILED(result))
{
return false;
}
먼저 블렌드 상태 description을 초기화합니다.
// Clear the blend state description.
ZeroMemory(&blendStateDescription, sizeof(D3D11_BLEND_DESC));
알파 블렌딩 사용 상태 description을 만들기 위해 BlendEnable을 TRUE로 DestBlend은 D3D33_BLEND_INV_SRC_ALPHA로 바꿉니다. 다른 것들은 Windows DirectX 그래픽스 문서에서 보여지는 기본 값으로 설정합니다.
// Create an alpha enabled blend state description.
blendStateDescription.RenderTarget[0].BlendEnable = TRUE;
blendStateDescription.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
blendStateDescription.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
blendStateDescription.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
blendStateDescription.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
blendStateDescription.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
blendStateDescription.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
blendStateDescription.RenderTarget[0].RenderTargetWriteMask = 0x0f;
우리가 설정한 이 description을 사용하여 알파 블렌딩 사용 상태를 만듭니다.
// Create the blend state using the description.
result = m_device->CreateBlendState(&blendStateDescription, &m_alphaEnableBlendingState);
if(FAILED(result))
{
return false;
}
이제 알파 블렌딩 비사용 상태를 만들기 위해 아까 만든 description의 BlendEnable을 FALSE로 바꿉니다. 나머지 설정은 있는 그대로 둡니다.
// Modify the description to create an alpha disabled blend state description.
blendStateDescription.RenderTarget[0].BlendEnable = FALSE;
그리고는 수정된 description을 사용하여 알파 블렌딩 비사용 상태를 만듭니다. 이제 두 블렌딩 상태를 가졌으니 알파 블렌딩 on/off를 전환할 수 있습니다.
// Create the blend state using the description.
result = m_device->CreateBlendState(&blendStateDescription, &m_alphaDisableBlendingState);
if(FAILED(result))
{
return false;
}
return true;
}
void D3DClass::Shutdown()
{
// Before shutting down set to windowed mode or when you release the swap chain it will throw an exception.
if(m_swapChain)
{
m_swapChain->SetFullscreenState(false, NULL);
}
두 새로운 블렌딩 상태를 해제합니다.
if(m_alphaEnableBlendingState)
{
m_alphaEnableBlendingState->Release();
m_alphaEnableBlendingState = 0;
}
if(m_alphaDisableBlendingState)
{
m_alphaDisableBlendingState->Release();
m_alphaDisableBlendingState = 0;
}
if(m_rasterState)
{
m_rasterState->Release();
m_rasterState = 0;
}
if(m_depthStencilView)
{
m_depthStencilView->Release();
m_depthStencilView = 0;
}
if(m_depthDisabledStencilState)
{
m_depthDisabledStencilState->Release();
m_depthDisabledStencilState = 0;
}
if(m_depthStencilState)
{
m_depthStencilState->Release();
m_depthStencilState = 0;
}
if(m_depthStencilBuffer)
{
m_depthStencilBuffer->Release();
m_depthStencilBuffer = 0;
}
if(m_renderTargetView)
{
m_renderTargetView->Release();
m_renderTargetView = 0;
}
if(m_deviceContext)
{
m_deviceContext->Release();
m_deviceContext = 0;
}
if(m_device)
{
m_device->Release();
m_device = 0;
}
if(m_swapChain)
{
m_swapChain->Release();
m_swapChain = 0;
}
return;
}
첫번째 새 함수 TurnOnAlphaBlending은 m_alphaEnableBlendingState의 OMSetBlendState를 사용하여 알파 블렌딩을 켜도록 해줍니다.
void D3DClass::TurnOnAlphaBlending()
{
float blendFactor[4];
// Setup the blend factor.
blendFactor[0] = 0.0f;
blendFactor[1] = 0.0f;
blendFactor[2] = 0.0f;
blendFactor[3] = 0.0f;
// Turn on the alpha blending.
m_deviceContext->OMSetBlendState(m_alphaEnableBlendingState, blendFactor, 0xffffffff);
return;
}
두번째 새 함수인 TurnOffAlphaBlending은 m_alphaDisableBlendingState의 OMSetBlendState 함수를 이용하여 알파 블렌딩을 끄도록 해줍니다.
void D3DClass::TurnOffAlphaBlending()
{
float blendFactor[4];
// Setup the blend factor.
blendFactor[0] = 0.0f;
blendFactor[1] = 0.0f;
blendFactor[2] = 0.0f;
blendFactor[3] = 0.0f;
// Turn off the alpha blending.
m_deviceContext->OMSetBlendState(m_alphaDisableBlendingState, blendFactor, 0xffffffff);
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"
새로운 TextClass 헤더를 추가하였습니다.
#include "textclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
GraphicsClass();
GraphicsClass(const GraphicsClass&);
~GraphicsClass();
bool Initialize(int, int, HWND);
void Shutdown();
void Frame();
bool Render();
private:
D3DClass* m_D3D;
CameraClass* m_Camera;
TextClass 객체인 새로운 private 변수가 생겼습니다.
TextClass* m_Text;
};
#endif
Graphicsclass.cpp
이전 강좌에서 변화된 함수만 살펴볼 것입니다.
////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"
GraphicsClass::GraphicsClass()
{
m_D3D = 0;
m_Camera = 0;
생성자에서 새 TextClass 객체를 null로 초기화합니다.
m_Text = 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;
}
camera 객체로부터 TextClass가 사용할 새 뷰 행렬을 만듭니다. 텍스트는 화면상에 항상 같은 위치에 그려지기 때문에 항상 이 뷰 행렬을 사용할 것입니다.
// 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);
여기서 새 TextClass 객체를 생성하고 초기화합니다.
// Create the text object.
m_Text = new TextClass;
if(!m_Text)
{
return false;
}
// Initialize the text object.
result = m_Text->Initialize(m_D3D->GetDevice(), m_D3D->GetDeviceContext(), hwnd, screenWidth, screenHeight, baseViewMatrix);
if(!result)
{
MessageBox(hwnd, L"Could not initialize the text object.", L"Error", MB_OK);
return false;
}
return true;
}
void GraphicsClass::Shutdown()
{
여기서 TextClass 객체를 해제합니다.
// Release the text object.
if(m_Text)
{
m_Text->Shutdown();
delete m_Text;
m_Text = 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::Render()
{
D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;
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->GetWorldMatrix(worldMatrix);
m_D3D->GetProjectionMatrix(projectionMatrix);
m_D3D->GetOrthoMatrix(orthoMatrix);
// Turn off the Z buffer to begin all 2D rendering.
m_D3D->TurnZBufferOff();
여기서 알파 블렌딩을 켭니다. 그래서 텍스트가 배경과 함께 혼합될 것입니다.
// Turn on the alpha blending before rendering the text.
m_D3D->TurnOnAlphaBlending();
여기서 모든 문장을 그리도록 텍스트 객체의 render를 호출합니다. 2D 이미지와 같이 그리기 전에 Z 버퍼를 끄고 모든 2D가 그려진 후에 다시 켭니다.
// Render the text strings.
result = m_Text->Render(m_D3D->GetDeviceContext(), worldMatrix, orthoMatrix);
if(!result)
{
return false;
}
여기서 알파 블렌딩을 끄며 화면에 그려지는 어떤 것이든 뒤 오브젝트들과 알파 블렌드를 하지 않을 것입니다.
// Turn off alpha blending after rendering the text.
m_D3D->TurnOffAlphaBlending();
// Turn the Z buffer back on now that all 2D rendering has completed.
m_D3D->TurnZBufferOn();
// Present the rendered scene to the screen.
m_D3D->EndScene();
return true;
}
요약
이제 화면의 어느 위치든 색깔있는 텍스트를 렌더링 할 수 있습니다.
연습하기
1. 코드를 컴파일하고 흰색 "Hello"와 그 아래 노란색 "Goodbye"가 화면의 100X100에 나타나는지 확인하세요
2. 문장의 내용, 위치, 색상을 바꿔보세요.
3. 세번째 문장을 만들고 이 문장도 렌더링 해보세요.
4. GraphicsClass::Render 함수에서 블렌딩 호출을 주석처리하고 m_D3D->BeginScene(0.0f, 0.0f, 1.0f, 1.0f);으로 설정해보세요. 그럼 왜 블렌딩이 필요한지 보게될 것입니다.
5. GraphicsClass::Render에서 blending 호출을 돌려 놓고 m_D3D->BeginScene(0.0f, 0.0f, 1.0f, 1.0f);는 그대로 두어 차이를 확인합니다.