이전에 사용했던 2D 이미지를 그리는 방법으로 이제 텍스트를 출력하려고 합니다.
우선 필요한 것은 자신만의 글꼴 이미지 입니다. 여기에 몇 개의 글씨가 있는 간단함 1024 x 16 크기의 dds 텍스쳐를 만들었습니다.

보이는 것처럼 하나의 텍스쳐 파일 안에 필요한 기본 글자가 모두 들어있습니ㅏㄷ. 이것을 이용해 필요한 글자의 텍스쳐를 인덱스를 이용해 그려내는 단순한 글꼴 엔진을 만들 수 있습니다. DirectX11 에서는 2개의 삼각형으로 이루어진 사각형을 만들고 그 사각형에 원하는 글자의 텍스쳐를 그리면 됩니다. 따라서 문장이 이씅면 문장에 있는 가 문자별로 사각형을 만들어 주고 그 위에 글자를 그립니다. 이렇게 모든 사각형이 화면에 그려지면 문장이 완성됩니다. 이 과정은 지난 튜토리얼의 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
프레임 워크

TextClass, FontClass, FontShader 클래스가 추가되었습니다. FontShaderClass는 글씨를 그리는 쉐이더에 해당하는 클래스인데, 그 과정은 이전에 비트맵 이미지를 그리는 데 사용했던 TextureShaderClass와 비슷합니다. FontClass는 글꼴 데이터를 가지고 문자열을 그리기 위한 정점 버퍼들을 만들어냅니다. TextClass는 화면에 그릴 문자열들에 해당하는 정점/인덱스 버퍼들을 가지고 있습니다. 이 버퍼들은 FontClass에 의해 생성되고 FontShaderClass를 통해 화면에 그려집니다.
FontClass
FontClass.h
#pragma once
class TextureClass;
class FontClass
{
private:
struct FontType
{
float left, right;
int size;
};
struct VertexType
{
XMFLOAT3 position;
XMFLOAT2 texture;
};
public:
FontClass();
FontClass(const FontClass& other);
~FontClass();
bool FontClass::Initialize(ID3D11Device* device, char* fontFileName, WCHAR* textureFileName);
void ShutDown();
ID3D11ShaderResourceView* GetTexture();
void BuildVertexArray(void* vertices, char* sentence, float drawX, float drawY);
private:
bool LoadFontData(char* fileName);
void ReleaseFontData();
bool LoadTexture(ID3D11Device* device, WCHAR* fileName);
void ReleaseTexture();
private:
FontType* _font = nullptr;
TextureClass* _texture = nullptr;
};
FontClass는 글꼴 텍스쳐, 텍스트 파일로부터 읽어들인 글꼴 데이터, 그리고 이 데이터로부터 정점 버퍼를 생성하는 함수를 포함합니다. 각각의 글꼴 데이터를 가진 정점 버퍼는 이 클래스가 아니라 TextClass에서 관리합니다.
struct FontType
{
float left, right;
int size;
};
FontType 구조체는 골꼴 인덱스 파일의 인덱스 데이터를 저장하기 위해 사용됩니다. left와 right는 텍스쳐 U좌표에 해당합니다. 그리고 size는 문자의 픽셀 너비를 저장합니다.
struct VertexType
{
XMFLOAT3 position;
XMFLOAT2 texture;
};
VertexType 구조체는 실제로 문자가 그려질 사각형을 만드는 데 필요한 정점 데이터를 저장합니다. 각각의 문자느 사각형을 만들기 위해 2개의 삼각형이 필요합니다. 이 삼각형들은 공간상의 위치(position)와 텍스쳐 위치(texture)만을 가집니다.
void BuildVertexArray(void* vertices, char* sentence, float drawX, float drawY);
BuildVertexArray 함수는 문자열을 입력으로 받아 글자를 그릴 삼각형들의 정점 배열을 만들어 반환하는 ㅣㅇㄹ을 합니다. 이 함수는 그려낼 문장이 있을 때 TextClas의 생성자에서 호출될 것입니다.
FontClass.cpp
#include "stdafx.h"
#include "TextureClass.h"
#include "FontClass.h"
#include <fstream>
using namespace std;
FontClass::FontClass()
{
}
FontClass::FontClass(const FontClass& other)
{
}
FontClass::~FontClass()
{
}
bool FontClass::Initialize(ID3D11Device* device, char* fontFileName, WCHAR* textureFileName)
{
// 폰트 데이터가 포함된 텍스트 파일을 로드함
if (!LoadFontData(fontFileName))
{
return false;
}
// 폰트를 갖고있는 텍스쳐를 로드
return LoadTexture(device, textureFileName);
}
void FontClass::ShutDown()
{
// 폰트 텍스쳐 해제
ReleaseTexture();
// 폰트 데이터 해제
ReleaseFontData();
}
bool FontClass::LoadFontData(char* fileName)
{
ifstream fin;
int i;
char temp;
// 폰트 공간을 담을 버퍼를 생성
_font = new FontType[95];
if (_font == nullptr)
{
return false;
}
// 문자 사이의 크기와 공간을 읽음
fin.open(fileName);
if (fin.fail() == true)
{
return false;
}
// 텍스트를 위한 95개의 아스키 코드를 읽음
for (int i = 0; i < 95; i++)
{
fin.get(temp);
while (temp != ' ')
{
fin.get(temp);
}
fin.get(temp);
while (temp != ' ')
{
fin.get(temp);
}
fin >> _font[i].left;
fin >> _font[i].right;
fin >> _font[i].size;
}
fin.close();
return true;
}
void FontClass::ReleaseFontData()
{
SAFE_DELETE_ARRAY(_font);
}
bool FontClass::LoadTexture(ID3D11Device* device, WCHAR* fileName)
{
_texture = new TextureClass();
if (_texture == nullptr)
{
return false;
}
return _texture->Initialize(device, fileName);
}
void FontClass::ReleaseTexture()
{
if (_texture)
{
_texture->Shutdown();
SAFE_DELETE(_texture);
}
}
ID3D11ShaderResourceView* FontClass::GetTexture()
{
return _texture->GetTexture();
}
void FontClass::BuildVertexArray(void* vertices, char* sentence, float drawX, float drawY)
{
VertexType* vertexPtr;
int numLetters, index, i, letter;
// 정점 포인터 할당
vertexPtr = (VertexType*)vertices;
// 문장안에 있는 단어의 갯수를 갖고옴
numLetters = (int)strlen(sentence);
// 정점 배열의 접근할 인덱스를 초기화
index = 0;
// Quad에 단어를 그림
for (int i = 0; i < numLetters; i++)
{
letter = ((int)sentence[i]) - 32;
// 만약 빈칸이면 3픽셀만큼 움직여줌
if (letter == 0)
{
drawX += 3.0f;
}
else
{
// 첫번째 삼각형
vertexPtr[index].position = XMFLOAT3(drawX, drawY, 0.0f); // Top Left
vertexPtr[index].texture = XMFLOAT2(_font[letter].left, 0.0f);
index++;
vertexPtr[index].position = XMFLOAT3(drawX + _font[letter].size, (drawY - 16), 0.0f); // Top right
vertexPtr[index].texture = XMFLOAT2(_font[letter].right, 1.0f);
index++;
vertexPtr[index].position = XMFLOAT3(drawX, (drawY - 16), 0.0f); // Bottom Left
vertexPtr[index].texture = XMFLOAT2(_font[letter].left, 1.0f);
index++;
// 두번째 삼각형
vertexPtr[index].position = XMFLOAT3(drawX, drawY, 0.0f); // Top Left
vertexPtr[index].texture = XMFLOAT2(_font[letter].left, 0.0f);
index++;
vertexPtr[index].position = XMFLOAT3(drawX + _font[letter].size, drawY, 0.0f); // Top right
vertexPtr[index].texture = XMFLOAT2(_font[letter].right, 0.0f);
index++;
vertexPtr[index].position = XMFLOAT3(drawX + _font[letter].size, (drawY - 16), 0.0f); // Bottom Left
vertexPtr[index].texture = XMFLOAT2(_font[letter].right, 1.0f);
index++;
// 단어와 픽셀의 크기로 인해 그릴수 있는 x 위치를 업데이트
drawX = drawX + _font[letter].size + 1.0f;
}
}
}
bool FontClass::Initialize(ID3D11Device* device, char* fontFileName, WCHAR* textureFileName)
{
// 폰트 데이터가 포함된 텍스트 파일을 로드함
if (!LoadFontData(fontFileName))
{
return false;
}
// 폰트를 갖고있는 텍스쳐를 로드
return LoadTexture(device, textureFileName);
}
Initialize함수에서는 글꼴 데이터와 글꼴 텍스쳐를 불러옵니다.
bool FontClass::LoadFontData(char* fileName)
{
ifstream fin;
int i;
char temp;
// 폰트 공간을 담을 버퍼를 생성
_font = new FontType[95];
if (_font == nullptr)
{
return false;
}
LoadFontData함수에서 우선 FontType형의 배열을 마듭니다. 배열의 크기는 텍스쳐에 있는 글자 수, 그리고 fontdata.txt파일의 인덱스 개수인 95로 합니다.
// 문자 사이의 크기와 공간을 읽음
fin.open(fileName);
if (fin.fail() == true)
{
return false;
}
// 텍스트를 위한 95개의 아스키 코드를 읽음
for (int i = 0; i < 95; i++)
{
fin.get(temp);
while (temp != ' ')
{
fin.get(temp);
}
fin.get(temp);
while (temp != ' ')
{
fin.get(temp);
}
fin >> _font[i].left;
fin >> _font[i].right;
fin >> _font[i].size;
}
fin.close();
return true;
}
파일을 열어 각 라인을 읽어 _font배열에 저장합니다. 여기서는 단순히 텍스쳐의 U좌표인 left와 right, 그리고 문자의 픽셀 너비를 읽어들입니다.
bool FontClass::LoadTexture(ID3D11Device* device, WCHAR* fileName)
{
_texture = new TextureClass();
if (_texture == nullptr)
{
return false;
}
return _texture->Initialize(device, fileName);
}
LoadTexture함수는 font.dds 파일을 읽어 텍스쳐 쉐이더에 전달합니다. 이 텍스쳐에서 글자를 뽑아내 각각의 사각형에 입혀 화면에 그리게 됩니다.
void FontClass::BuildVertexArray(void* vertices, char* sentence, float drawX, float drawY)
{
VertexType* vertexPtr;
int numLetters, index, i, letter;
// 정점 포인터 할당
vertexPtr = (VertexType*)vertices;
// 문장안에 있는 단어의 갯수를 갖고옴
numLetters = (int)strlen(sentence);
// 정점 배열의 접근할 인덱스를 초기화
index = 0;
BuildVertexArray함수는 TextClass에 의해 호출되어 인자로 받은 문장으로 정점 버퍼를 만들어냅니다. 이렇게 TextureClass는 이렇게 만들어진 자신만의 정점 버퍼를 가지게 되기 때문에 손쉽게 글자를 그릴 수 있습니다. 첫번째 인자인 vertices는 정점 배열을 가리키는 포인터로서 TextClass에게 이 함수에서 내용을 채운 배열을 전달하는 데 사용됩니다. 그 다음 인자인 sentence는 정점 배열을 만드는 데 필요한 문장(문자열)입니다. drawX와 drawY는 문장이 그려질 화면상의 좌표입니다.
// Quad에 단어를 그림
for (int i = 0; i < numLetters; i++)
{
letter = ((int)sentence[i]) - 32;
// 만약 빈칸이면 3픽셀만큼 움직여줌
if (letter == 0)
{
drawX += 3.0f;
}
else
{
// 첫번째 삼각형
vertexPtr[index].position = XMFLOAT3(drawX, drawY, 0.0f); // Top Left
vertexPtr[index].texture = XMFLOAT2(_font[letter].left, 0.0f);
index++;
vertexPtr[index].position = XMFLOAT3(drawX + _font[letter].size, (drawY - 16), 0.0f); // Top right
vertexPtr[index].texture = XMFLOAT2(_font[letter].right, 1.0f);
index++;
vertexPtr[index].position = XMFLOAT3(drawX, (drawY - 16), 0.0f); // Bottom Left
vertexPtr[index].texture = XMFLOAT2(_font[letter].left, 1.0f);
index++;
// 두번째 삼각형
vertexPtr[index].position = XMFLOAT3(drawX, drawY, 0.0f); // Top Left
vertexPtr[index].texture = XMFLOAT2(_font[letter].left, 0.0f);
index++;
vertexPtr[index].position = XMFLOAT3(drawX + _font[letter].size, drawY, 0.0f); // Top right
vertexPtr[index].texture = XMFLOAT2(_font[letter].right, 0.0f);
index++;
vertexPtr[index].position = XMFLOAT3(drawX + _font[letter].size, (drawY - 16), 0.0f); // Bottom Left
vertexPtr[index].texture = XMFLOAT2(_font[letter].right, 1.0f);
index++;
// 단어와 픽셀의 크기로 인해 그릴수 있는 x 위치를 업데이트
drawX = drawX + _font[letter].size + 1.0f;
}
}
반복문을 돌면서 정점/인덱스 배열을 만듭니다. 우선 문장을 구성하는 문자들을 취하여 문자당 두 개의 삼각형을 만듭니다. 그리고 나서 이 두 삼각형에 실제로 그려질 글자에 해당하는 텍스쳐를 _font배열의 U좌표와 픽셀 너비를 사용하여 매핑시킵니다. 일단 글자에 해당하는 도형이 생성되면 X좌표를 갱신하여 글자가 그려질 위치를 잡습니다.
FontVertex.hlsl
/////////////
// 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;
};
////////////////////
// Vertext Shader //
////////////////////
PixelInputType FontVertexShader(VertexInputType input)
{
PixelInputType output;
// 적절한 행렬 계산을 위해 위치 벡터를 4 단위로 변경합니다.
input.position.w = 1.0f;
// 월드, 뷰 및 투영 행렬에 대한 정점의 위치를 계산합니다.
output.position = mul(input.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);
// 픽셀 쉐이더의 텍스쳐 좌표를 저장합니다.
output.tex = input.tex;
return output;
}
기존의 2D이미지를 출력하는데 사용했던 TextureVertex.hlsl과 다른게 없습니다.
Font Shader
FontPixel.hlsl
/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState SampleType;
cbuffer PixelBuffer
{
float4 pixelColor;
};
//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD0;
};
//////////////////
// Pixel Shader //
//////////////////
float4 FontPixelShader(PixelInputType input) : SV_TARGET
{
float4 color;
// 이 텍스쳐 좌표를 위치에서 샘플러를 사용하여 텍스처에서 픽셀 색상을 샘플링합니다.
color = shaderTexture.Sample(SampleType, input.tex);
// 텍스처의 색상이 검은색이면 이 픽셀을 투명으로 처리합니다.
if(color.r == 0.0f)
{
color.a = 0.0f;
}
// 텍스처의 색상이 검은색이 아닐 경우 글꼴 픽셀 색상을 사용하여 그립니다.
else
{
color.rgb = pixelColor.rgb;
color.a = 1.0f;
}
return color;
}
cbuffer PixelBuffer
{
float4 pixelColor;
};
//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD0;
};
색상(PixelColor)을 가진 새로운 상수 버퍼를 만듭니다. 이것을 이용해서 문자열을 그릴 때 사용할 색상을 정할 수 있습니다.
//////////////////
// Pixel Shader //
//////////////////
float4 FontPixelShader(PixelInputType input) : SV_TARGET
{
float4 color;
// 이 텍스쳐 좌표를 위치에서 샘플러를 사용하여 텍스처에서 픽셀 색상을 샘플링합니다.
color = shaderTexture.Sample(SampleType, input.tex);
// 텍스처의 색상이 검은색이면 이 픽셀을 투명으로 처리합니다.
if(color.r == 0.0f)
{
color.a = 0.0f;
}
// 텍스처의 색상이 검은색이 아닐 경우 글꼴 픽셀 색상을 사용하여 그립니다.
else
{
color.rgb = pixelColor.rgb;
color.a = 1.0f;
}
return color;
}
FontPixelShader함수에서는 우선 출력할 픽셀에 해당하는 글꼴상의 텍스쳐를 가져옵니다. 만약 그 새깔이 검은색이라면 이것은 실제 글자를 구성하는 픽셀이 아니라 단지 배경색으로 취급해야 합니다. 이 경우 해당 픽셀의 알파값을 0으로 하여 실제 글자 외에는 배경이 비춰질 수 있게 해야 합니다. 만약 이 색상이 검은색이 아니라면, 해당 픽셀은 글자의 일부분 입니다. 따라서 해당 픽셀은 PixelColor을 사용하여 화면에 그려줍니다.
FontShaderClass
FontShaderClass.h
#pragma once
class FontShaderClass
{
private:
struct ConstantBufferType
{
XMMATRIX world;
XMMATRIX view;
XMMATRIX projection;
};
struct PixelBufferType
{
XMFLOAT4 pixelColor;
};
public:
FontShaderClass();
FontShaderClass(const FontShaderClass& other);
~FontShaderClass();
bool Initialize(ID3D11Device* device, HWND hwnd);
void ShutDown();
bool Render(ID3D11DeviceContext* deviceContext, int indexCount, XMMATRIX wold, XMMATRIX view, XMMATRIX projection, ID3D11ShaderResourceView* texture, XMFLOAT4 pixelColor);
private:
bool InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vertexShaderPath, WCHAR* pixelShaderPath);
void ShutdownShader();
void OutputShaderErrorMessage(ID3D10Blob* blob, HWND hwnd, WCHAR* shaderPath);
bool SetShaderParameters(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, XMFLOAT4 pixelColor);
void RenderShader(ID3D11DeviceContext* deviceContext, int indexCount);
private:
ID3D11VertexShader* _vertexShader = nullptr;
ID3D11PixelShader* _pixelShader = nullptr;
ID3D11InputLayout* _layout = nullptr;
ID3D11Buffer* _constantBuffer = nullptr;
ID3D11SamplerState* _samplerState = nullptr;
ID3D11Buffer* _pixelBuffer = nullptr;
};
이전에 사용하던 클레스인 TextureShaderClass에 글자를 표시하기 위해 몇가지만 수정을 했습니다.
FontShaderClass.cpp
#include "stdafx.h"
#include "FontShaderClass.h"
FontShaderClass::FontShaderClass()
{
}
FontShaderClass::FontShaderClass(const FontShaderClass& other)
{
}
FontShaderClass::~FontShaderClass()
{
}
bool FontShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
// 정점 및 픽셀 쉐이더를 초기화합니다.
return InitializeShader(device, hwnd, L"Shader/FontVertex.hlsl", L"Shader/FontPixel.hlsl");
}
void FontShaderClass::ShutDown()
{
// 정점 및 픽셀 쉐이더와 관련된 객체를 종료합니다.
ShutdownShader();
}
bool FontShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, XMMATRIX wold, XMMATRIX view, XMMATRIX projection, ID3D11ShaderResourceView* texture, XMFLOAT4 pixelColor)
{
// 랜더링에 사용할 쉐이더 매개 변수를 설정합니다.
if (SetShaderParameters(deviceContext, wold, view, projection, texture, pixelColor) == false)
{
return false;
}
// 설정된 버퍼를 쉐이더로 렌더링 합니다.
RenderShader(deviceContext, indexCount);
return true;
}
bool FontShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vertexShaderPath, WCHAR* pixelShaderPath)
{
HRESULT result;
ID3D10Blob* errorMessage = nullptr;
// 정점 버퍼 쉐이더 코드를 컴파일합니다.
ID3D10Blob* vertexShaderBuffer = nullptr;
result = D3DCompileFromFile(vertexShaderPath, NULL, NULL, "FontVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, &vertexShaderBuffer, &errorMessage);
if (FAILED(result))
{
// 쉐이더 컴파일 실패시 에러코드를 출력 합니다.
if (errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, vertexShaderPath);
}
// 컴파일 오류가 아니라면 쉐이더 파일을 찾을 수 없는 경우입니다.
else
{
MessageBox(hwnd, vertexShaderPath, L"Missing Shader File", MB_OK);
}
return false;
}
// 픽셀 쉐이더 코드를 컴파일합니다.
ID3D10Blob* pixelShaderBuffer = nullptr;
result = D3DCompileFromFile(pixelShaderPath, NULL, NULL, "FontPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, &pixelShaderBuffer, &errorMessage);
if (FAILED(result))
{
// 쉐이더 컴파일 실패시 에러코드를 출력 합니다.
if (errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, pixelShaderPath);
}
// 컴파일 오류가 아니라면 쉐이더 파일을 찾을 수 없는 경우입니다.
else
{
MessageBox(hwnd, pixelShaderPath, L"Missing Shader File", MB_OK);
}
return false;
}
// 버퍼로부터 정점 셰이더를 생성한다.
result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &_vertexShader);
if (FAILED(result))
{
return false;
}
// 버퍼에서 픽셀 쉐이더를 생성합니다.
result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &_pixelShader);
if (FAILED(result))
{
return false;
}
// 정점 입력 레이아웃 구조체를 설정합니다.
// 이 설정은 MeldClass와 쉐이더 VertexType 구조와 일치해야 합니다.
D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
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;
// 레이아웃의 요소 수를 가져옵니다.
UINT numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);
// 정점 입력 레이아웃을 만듭니다.
result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &_layout);
if (FAILED(result))
{
return false;
}
// 더 이상 사용되지 않는 정점 셰이더 퍼버와 픽셀 셰이더 버퍼를 해제합니다.
vertexShaderBuffer->Release();
vertexShaderBuffer = 0;
pixelShaderBuffer->Release();
pixelShaderBuffer = 0;
// 정점 셰이더에 있는 행렬 상수 버퍼의 구조체를 작성합니다.
D3D11_BUFFER_DESC constantBufferDesc;
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;
// 상수 버퍼 포인터를 만들어 이 클래스에서 정점 쉐이더 상수 버퍼에 접근할 수 있게 합니다.
result = device->CreateBuffer(&constantBufferDesc, NULL, &_constantBuffer);
if (FAILED(result))
{
return false;
}
// 텍스처 샘플러 상태 구조체를 생성 및 설정합니다.
D3D11_SAMPLER_DESC samplerDesc;
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;
// 텍스처 샘플러 상태를 만듭니다.
result = device->CreateSamplerState(&samplerDesc, &_samplerState);
if (FAILED(result))
{
return false;
}
// 픽셀 쉐이더에 있는 픽셀 상수 구조체 버퍼를 만듭니다.
D3D11_BUFFER_DESC pixelBufferDesc;
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;
// 살수 버퍼 포인터를 만들어 이 클래스에서 픽셀 쉐이더 상수 버퍼에 접근할 수 있게 합니다.
result = device->CreateBuffer(&pixelBufferDesc, NULL, &_pixelBuffer);
if (FAILED(result))
{
return false;
}
return true;
}
void FontShaderClass::ShutdownShader()
{
SAFE_RELEASE(_pixelBuffer);
SAFE_RELEASE(_samplerState);
SAFE_RELEASE(_constantBuffer);
SAFE_RELEASE(_layout);
SAFE_RELEASE(_pixelShader);
SAFE_RELEASE(_vertexShader);
}
void FontShaderClass::OutputShaderErrorMessage(ID3D10Blob* blob, HWND hwnd, WCHAR* shaderPath)
{
// 에러 메시지를 출력창에 표시합니다.
OutputDebugStringA(reinterpret_cast<const char*>(blob->GetBufferPointer()));
// 에러 메시지 해제
SAFE_RELEASE(blob);
// 컴파일 에러가 있음을 팝업 메시지로 띄어줍니다.
MessageBox(hwnd, L"Error compile shader.", shaderPath, MB_OK);
}
bool FontShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, XMFLOAT4 pixelColor)
{
HRESULT result;
// 상수 버퍼의 내용을 쓸 수 있도록 잠급니다.
D3D11_MAPPED_SUBRESOURCE mappedResource;
result = deviceContext->Map(_constantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if (FAILED(result))
{
return false;
}
// 상수 버퍼의 데이터에 대한 포인터를 가져옵니다.
ConstantBufferType* dataPtr = (ConstantBufferType*)mappedResource.pData;
// 행렬을 transpose하여 쉐이더에서 사용할 수 있게 합니다.
worldMatrix = XMMatrixTranspose(worldMatrix);
viewMatrix = XMMatrixTranspose(viewMatrix);
projectionMatrix = XMMatrixTranspose(projectionMatrix);
// 상수 버퍼에 행렬을 복사합니다.
dataPtr->world = worldMatrix;
dataPtr->view = viewMatrix;
dataPtr->projection = projectionMatrix;
// 상수 버퍼의 잠금을 풉니다.
deviceContext->Unmap(_constantBuffer, 0);
// 정점 쉐이더에서의 상수 버퍼의 위치를 설정합니다.
unsigned int bufferNumber = 0;
// 마지막으로 정점 쉐이더의 상수 버퍼를 바뀐 값으로 바꿉니다.
deviceContext->VSSetConstantBuffers(bufferNumber, 1, &_constantBuffer);
// 픽셀 쉐이더에서 풰시어 텍스처 리소스를 설정합니다.
deviceContext->PSSetShaderResources(0, 1, &texture);
// 픽셀 쉐이더에 상수 버퍼를 쓰기 위해 잠급니다.
result = deviceContext->Map(_pixelBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if (FAILED(result))
{
return false;
}
// 픽셀 상수 버퍼에서 포인터를 얻어옵니다.
PixelBufferType* dataPtr2 = (PixelBufferType*)mappedResource.pData;
// 픽셀 버퍼에 색상을 복사합니다.
dataPtr2->pixelColor = pixelColor;
// 잠금을 풉니다.
deviceContext->Unmap(_pixelBuffer, 0);
// 픽셀 쉐이더의 픽셀 상수 버퍼의 위치를 조정합니다.
bufferNumber = 0;
// 픽셀 쉐이더에 값을 업데이트 해줍니다.
deviceContext->PSSetConstantBuffers(bufferNumber, 1, &_pixelBuffer);
return true;
}
void FontShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
// 정점 입력 레이아웃을 설정합니다.
deviceContext->IASetInputLayout(_layout);
// 삼각형을 그릴 정점 쉐이더와 픽셀 쉐이더를 설정합니다.
deviceContext->VSSetShader(_vertexShader, NULL, 0);
deviceContext->PSSetShader(_pixelShader, NULL, 0);
// 픽셀 쉐이더에서 샘플러 상태를 설정합니다.
deviceContext->PSSetSamplers(0, 1, &_samplerState);
// 삼각형을 그립니다.
deviceContext->DrawIndexed(indexCount, 0, 0);
}
// 텍스처 샘플러 상태 구조체를 생성 및 설정합니다.
D3D11_SAMPLER_DESC samplerDesc;
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;
픽셀 쉐이더에 글자 색을 지정할 상수 버퍼를 생성합니다.
bool FontShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, XMFLOAT4 pixelColor)
{
HRESULT result;
// 상수 버퍼의 내용을 쓸 수 있도록 잠급니다.
D3D11_MAPPED_SUBRESOURCE mappedResource;
result = deviceContext->Map(_constantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if (FAILED(result))
{
return false;
}
// 상수 버퍼의 데이터에 대한 포인터를 가져옵니다.
ConstantBufferType* dataPtr = (ConstantBufferType*)mappedResource.pData;
// 행렬을 transpose하여 쉐이더에서 사용할 수 있게 합니다.
worldMatrix = XMMatrixTranspose(worldMatrix);
viewMatrix = XMMatrixTranspose(viewMatrix);
projectionMatrix = XMMatrixTranspose(projectionMatrix);
// 상수 버퍼에 행렬을 복사합니다.
dataPtr->world = worldMatrix;
dataPtr->view = viewMatrix;
dataPtr->projection = projectionMatrix;
// 상수 버퍼의 잠금을 풉니다.
deviceContext->Unmap(_constantBuffer, 0);
// 정점 쉐이더에서의 상수 버퍼의 위치를 설정합니다.
unsigned int bufferNumber = 0;
// 마지막으로 정점 쉐이더의 상수 버퍼를 바뀐 값으로 바꿉니다.
deviceContext->VSSetConstantBuffers(bufferNumber, 1, &_constantBuffer);
// 픽셀 쉐이더에서 픽셀 텍스처 리소스를 설정합니다.
deviceContext->PSSetShaderResources(0, 1, &texture);
SetShaderParameters 함수는 렌더링을 수행하기 전에 모든 쉐이더 인자들을 설정합니다.
// 픽셀 쉐이더에 상수 버퍼를 쓰기 위해 잠급니다.
result = deviceContext->Map(_pixelBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if (FAILED(result))
{
return false;
}
// 픽셀 상수 버퍼에서 포인터를 얻어옵니다.
PixelBufferType* dataPtr2 = (PixelBufferType*)mappedResource.pData;
// 픽셀 버퍼에 색상을 복사합니다.
dataPtr2->pixelColor = pixelColor;
// 잠금을 풉니다.
deviceContext->Unmap(_pixelBuffer, 0);
// 픽셀 쉐이더의 픽셀 상수 버퍼의 위치를 조정합니다.
bufferNumber = 0;
// 픽셀 쉐이더에 값을 업데이트 해줍니다.
deviceContext->PSSetConstantBuffers(bufferNumber, 1, &_pixelBuffer);
return true;
}
여기에 렌더링 하기 전에 글자 색을 설정합니다. 우선 상수 버퍼를 잠그고 색상을 설정한 뒤 버퍼 잠금을 해제합니ㅏㄷ. 픽셀 쉐이더에서 상수 버퍼 위치를 잡아주면 사용할 준비가 완료 됩니다.
그 외의 내용은 TextureShaderClass와 같습니다.
TextClass
TextClass.h
#pragma once
class FontClass;
class FontShaderClass;
class TextClass : public AlignedAllocationPolicy<16>
{
private:
struct SentenceType
{
ID3D11Buffer *vertexBuffer, *indexBuffer;
int vertexCount, indexCount, maxLength;
float red, green, blue;
};
struct VertexType
{
XMFLOAT3 position;
XMFLOAT2 texture;
};
public:
TextClass();
TextClass(const TextClass& other);
~TextClass();
bool Initialize(ID3D11Device* device, ID3D11DeviceContext* deviceContext, HWND hwnd, int screenWidth, int screenHeight, XMMATRIX baseViewMatrix);
void Shutdown();
bool Render(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX orthoMatrix);
private:
bool InitializeSentence(SentenceType** sentence, int maxLength, ID3D11Device* device);
bool UpdateSentence(SentenceType* sentence, char* text, int positionX, int positionY, float red, float green, float blue, ID3D11DeviceContext* deviceContext);
void ReleaseSentence(SentenceType** sentence);
bool RenderSentence(ID3D11DeviceContext* deviceContext, SentenceType* sentence, XMMATRIX worldMatrix, XMMATRIX orthoMatrix);
private:
FontClass* _font = nullptr;
FontShaderClass* _fontShader = nullptr;
int _screenWidth = 0;
int _screenHeight = 0;
XMMATRIX _baseViewMatrix;
SentenceType* _sentence1;
SentenceType* _sentence2;
};
TextClass 클래스는 모든 2D문자열을 그리는 일을 담당 합니다. FontClass와 FontShaderClass를 사용하여 2D 문자열을 화면에 그립니다.
struct SentenceType
{
ID3D11Buffer *vertexBuffer, *indexBuffer;
int vertexCount, indexCount, maxLength;
float red, green, blue;
};
SenteceType 구조체는 각 문장의 렌더링 정보를 저장하는데 사용됩니다.
struct VertexType
{
XMFLOAT3 position;
XMFLOAT2 texture;
};
VertexType은 FontClass의 VertexType와 일치해야 합니다.
TextClass.cpp
#include "stdafx.h"
#include "FontClass.h"
#include "FontShaderClass.h"
#include "TextClass.h"
TextClass::TextClass()
{
}
TextClass::TextClass(const TextClass& other)
{
}
TextClass::~TextClass()
{
}
bool TextClass::Initialize(ID3D11Device* device, ID3D11DeviceContext* deviceContext, HWND hwnd, int screenWidth, int screenHeight, XMMATRIX baseViewMatrix)
{
bool result;
// 스크린의 가로 세로 크기를 저장합니다.
_screenWidth = screenWidth;
_screenHeight = screenHeight;
// 뷰행렬을 저장합니다.
_baseViewMatrix = baseViewMatrix;
// 폰트 오브젝트를 만듭니다.
_font = new FontClass();
if (_font == nullptr)
{
return false;
}
// 폰트 오브젝트를 초기화 합니다.
result = _font->Initialize(device, "Data/fontdata.txt", L"Data/font.dds");
if(result == false)
{
MessageBox(hwnd, L"Could not initialize ths font object", L"Error", MB_OK);
return false;
}
// 폰트 쉐이더 객체를 만듭니다.
_fontShader = new FontShaderClass();
if (_fontShader == nullptr)
{
return false;
}
// 폰트 쉐이더를 초기화 합니다.
result = _fontShader->Initialize(device, hwnd);
if (result == false)
{
MessageBox(hwnd, L"Could not initialize ths fontShader object", L"Error", MB_OK);
return false;
}
// 첫번째 문장을 초기화 합니다.
result = InitializeSentence(&_sentence1, 16, device);
if (result == false)
{
return false;
}
// 문장의 정점 버퍼와 새로운 문자열의 정보를 업데이트 합니다.
result = UpdateSentence(_sentence1, "Hello", 100, 100, 1.0f, 1.0f, 1.0f, deviceContext);
if (result == false)
{
return false;
}
// 두번째 문장을 초기화 합니다.
result = InitializeSentence(&_sentence2, 16, device);
if (!result)
{
return false;
}
// 문장의 정점 버퍼와 새로운 문자열의 정보를 업데이트 합니다.
result = UpdateSentence(_sentence2, "Good Bye", 100, 200, 1.0f, 1.0f, 0.0f, deviceContext);
if (result == false)
{
return false;
}
return true;
}
void TextClass::Shutdown()
{
ReleaseSentence(&_sentence1);
ReleaseSentence(&_sentence2);
if (_fontShader)
{
_fontShader->ShutDown();
delete _fontShader;
_fontShader = 0;
}
if (_font)
{
_font->ShutDown();
delete _font;
_font = 0;
}
}
bool TextClass::Render(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX orthoMatrix)
{
bool result;
// 첫번째 문장을 그립니다.
result = RenderSentence(deviceContext, _sentence1, worldMatrix, orthoMatrix);
if (result == false)
{
return false;
}
// 두번째 문장을 그립니다.
result = RenderSentence(deviceContext, _sentence2, worldMatrix, orthoMatrix);
if (result == false)
{
return false;
}
return true;
}
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;
// 새로운 문장 오브젝트를 만듭니다.
*sentence = new SentenceType;
if (*sentence == nullptr)
{
return false;
}
// 센텐스 버퍼를 Null로 초기화 합니다.
(*sentence)->vertexBuffer = 0;
(*sentence)->indexBuffer = 0;
// 최대 길이를 설정합니다.
(*sentence)->maxLength = maxLength;
// 정점 갯수를 설정합니다.
(*sentence)->vertexCount = maxLength * 6;
// 인덱스 갯수를 설정합니다.
(*sentence)->indexCount = (*sentence)->vertexCount;
// 정점 배열을 만듭니다.
vertices = new VertexType[(*sentence)->vertexCount];
if (vertices == nullptr)
{
return false;
}
indices = new unsigned long[(*sentence)->indexCount];
if (indices == nullptr)
{
return false;
}
// 정점 배열을 0으로 처음에 조기화 합니다.
memset(vertices, 0, (sizeof(VertexType) * (*sentence)->vertexCount));
// 인덱스 배열을 초기화 합니다.
for (int i = 0; i < (*sentence)->indexCount; i++)
{
indices[i] = i;
}
// 동적 정점 버퍼의 Desc를 설정합니다.
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;
vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;
result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &(*sentence)->vertexBuffer);
if (FAILED(result))
{
return false;
}
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;
// 정점 데이터에 서브리소스 포인터를 전달합니다.
indexData.pSysMem = indices;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;
// 인덱스 버퍼를 만듭니다.
result = device->CreateBuffer(&indexBufferDesc, &indexData, &(*sentence)->indexBuffer);
if (FAILED(result))
{
return false;
}
SAFE_DELETE_ARRAY(vertices);
SAFE_DELETE_ARRAY(indices);
return true;
}
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;
// 색상을 저장합니다.
sentence->red = red;
sentence->green = green;
sentence->blue = blue;
// 문장의 글자 갯수를 전달합니다
numLetters = (int)strlen(text);
// 오버플로우를 검사합니다.
if(numLetters > sentence->maxLength)
{
return false;
}
// 정점 버퍼를 만듭니다.
vertices = new VertexType[sentence->vertexCount];
if (vertices == nullptr)
{
return true;
}
// 정점 버퍼를 0으로 초기화 합니다.
memset(vertices, 0, (sizeof(VertexType) * sentence->vertexCount));
// 처음 그릴 스크린의 화면 픽셀을 계산합니다.
drawX = (float)(((_screenWidth / 2) * -1) + positionX);
drawY = (float)((_screenHeight / 2) - positionY);
// 문장읠 텍스트 그리고 그릴 위치로부터 정점 버퍼를 빌드 할 폰트 클래스를 사용합니다.
_font->BuildVertexArray((void*)vertices, text, drawX, drawY);
// 버텍스 버퍼를 잠급니다.
result = deviceContext->Map(sentence->vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if (FAILED(result))
{
return false;
}
// 정점 버퍼 안에 있는 포인터를 할당 받습니다.
verticesPtr = (VertexType*)mappedResource.pData;
// 버텍스 버퍼로부터 데이터를 복사합니다.
memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * sentence->vertexCount));
// 버텍스 버퍼의 잠금을 해제합니다.
deviceContext->Unmap(sentence->vertexBuffer, 0);
SAFE_DELETE_ARRAY(vertices);
return true;
}
void TextClass::ReleaseSentence(SentenceType** sentence)
{
if (*sentence)
{
// 정점 버퍼를 해제합니다.
SAFE_RELEASE((*sentence)->vertexBuffer);
SAFE_RELEASE((*sentence)->indexBuffer);
SAFE_DELETE((*sentence));
}
}
bool TextClass::RenderSentence(ID3D11DeviceContext* deviceContext, SentenceType* sentence, XMMATRIX worldMatrix, XMMATRIX orthoMatrix)
{
unsigned int stride, offset;
XMFLOAT4 pixelColor;
bool result;
// 정점 버퍼의 stride 와 offset을 설정합니다.
stride = sizeof(VertexType);
offset = 0;
// 렌더링 할수 있게 인풋 어셈블러 안에 있는 active된 버텍스 버퍼를 설정합니다.
deviceContext->IASetVertexBuffers(0, 1, &sentence->vertexBuffer, &stride, &offset);
// 렌더링 할수 있게 인풋 어셈블러 안에 있는 active된 인덱스 버퍼를 설정합니다.
deviceContext->IASetIndexBuffer(sentence->indexBuffer, DXGI_FORMAT_R32_UINT, 0);
// 이 정점 버퍼로부터 렌더링 할수 있게 준 primitive타입을 세팅합ㄴ.다
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// 입력한 문장 색상을 픽셀 컬러 정점을 만듭니다.
pixelColor = XMFLOAT4(sentence->red, sentence->green, sentence->blue, 1.0f);
// 폰트 쉐이더를 사용해서 렌더링 합니다.
result = _fontShader->Render(deviceContext, sentence->indexCount, worldMatrix, _baseViewMatrix, orthoMatrix, _font->GetTexture(), pixelColor);
if (result == false)
{
return false;
}
return true;
}
bool TextClass::Initialize(ID3D11Device* device, ID3D11DeviceContext* deviceContext, HWND hwnd, int screenWidth, int screenHeight, XMMATRIX baseViewMatrix)
{
bool result;
// 스크린의 가로 세로 크기를 저장합니다.
_screenWidth = screenWidth;
_screenHeight = screenHeight;
// 뷰행렬을 저장합니다.
_baseViewMatrix = baseViewMatrix;
화면 크기와 기본 뷰 행렬을 저장합니다. 이것들은 후에 텍스트를 화면에 렌더링 할때 사용됩니다.
// 폰트 오브젝트를 만듭니다.
_font = new FontClass();
if (_font == nullptr)
{
return false;
}
// 폰트 오브젝트를 초기화 합니다.
result = _font->Initialize(device, "Data/fontdata.txt", L"Data/font.dds");
if(result == false)
{
MessageBox(hwnd, L"Could not initialize ths font object", L"Error", MB_OK);
return false;
}
// 폰트 쉐이더 객체를 만듭니다.
_fontShader = new FontShaderClass();
if (_fontShader == nullptr)
{
return false;
}
// 폰트 쉐이더를 초기화 합니다.
result = _fontShader->Initialize(device, hwnd);
if (result == false)
{
MessageBox(hwnd, L"Could not initialize ths fontShader object", L"Error", MB_OK);
return false;
}
글꼴, 폰트 쉐이더를 초기화 합니다.
// 첫번째 문장을 초기화 합니다.
result = InitializeSentence(&_sentence1, 16, device);
if (result == false)
{
return false;
}
// 문장의 정점 버퍼와 새로운 문자열의 정보를 업데이트 합니다.
result = UpdateSentence(_sentence1, "Hello", 100, 100, 1.0f, 1.0f, 1.0f, deviceContext);
if (result == false)
{
return false;
}
// 두번째 문장을 초기화 합니다.
result = InitializeSentence(&_sentence2, 16, device);
if (!result)
{
return false;
}
// 문장의 정점 버퍼와 새로운 문자열의 정보를 업데이트 합니다.
result = UpdateSentence(_sentence2, "Good Bye", 100, 200, 1.0f, 1.0f, 0.0f, deviceContext);
if (result == false)
{
return false;
}
return true;
}
두 문장을 생성하고 초기화 합니다.
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;
// 새로운 문장 오브젝트를 만듭니다.
*sentence = new SentenceType;
if (*sentence == nullptr)
{
return false;
}
// 센텐스 버퍼를 Null로 초기화 합니다.
(*sentence)->vertexBuffer = 0;
(*sentence)->indexBuffer = 0;
// 최대 길이를 설정합니다.
(*sentence)->maxLength = maxLength;
// 정점 갯수를 설정합니다.
(*sentence)->vertexCount = maxLength * 6;
// 인덱스 갯수를 설정합니다.
(*sentence)->indexCount = (*sentence)->vertexCount;
// 정점 배열을 만듭니다.
vertices = new VertexType[(*sentence)->vertexCount];
if (vertices == nullptr)
{
return false;
}
indices = new unsigned long[(*sentence)->indexCount];
if (indices == nullptr)
{
return false;
}
// 정점 배열을 0으로 처음에 조기화 합니다.
memset(vertices, 0, (sizeof(VertexType) * (*sentence)->vertexCount));
// 인덱스 배열을 초기화 합니다.
for (int i = 0; i < (*sentence)->indexCount; i++)
{
indices[i] = i;
}
InitializeSentence함수는 문장을 저장하고 그리기 위해 빈 정점 버퍼로 이루어진 SentenceType 형의 변수를 생성합니다.
maxLength인자는 정점 버퍼가 얼마나 커야 하는지를 결정합니다. 모든 문장은 관련된 정점/인덱스 버퍼를 가지며 버퍼들은 이 함수의 앞부분에서 초기화 됩니다.
// 동적 정점 버퍼의 Desc를 설정합니다.
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;
vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;
result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &(*sentence)->vertexBuffer);
if (FAILED(result))
{
return false;
}
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;
// 정점 데이터에 서브리소스 포인터를 전달합니다.
indexData.pSysMem = indices;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;
// 인덱스 버퍼를 만듭니다.
result = device->CreateBuffer(&indexBufferDesc, &indexData, &(*sentence)->indexBuffer);
if (FAILED(result))
{
return false;
}
SAFE_DELETE_ARRAY(vertices);
SAFE_DELETE_ARRAY(indices);
return true;
}
문장의 내용을 언제나 바꿀 수 있도록 정점 버퍼의 description을 작성할 때 Usage 항목을 Dynamic으로 설정합니다.
인덱스 버퍼는 그 내용이 바뀌지 않기 떄문에 일반적인 정점 버퍼로 생성합니다.
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;
UpdateSentence 함수는 입력된 문장에 맞게 정점 버퍼의 내용을 바꿉니다. Map 함수와 Unmap함수, 그리고 memcpy 함수를 사용하여 정점 버퍼를 수정합니다.
// 색상을 저장합니다.
sentence->red = red;
sentence->green = green;
sentence->blue = blue;
// 문장의 글자 갯수를 전달합니다
numLetters = (int)strlen(text);
// 오버플로우를 검사합니다.
if(numLetters > sentence->maxLength)
{
return false;
}
// 정점 버퍼를 만듭니다.
vertices = new VertexType[sentence->vertexCount];
if (vertices == nullptr)
{
return true;
}
// 정점 버퍼를 0으로 초기화 합니다.
memset(vertices, 0, (sizeof(VertexType) * sentence->vertexCount));
문장의 색상과 길이를 정합니다.
// 스크린에 그려질 첫번째 위치를 계산합니다.
drawX = (float)(((_screenWidth / 2) * -1) + positionX);
drawY = (float)((_screenHeight / 2) - positionY);
// 문장의 텍스트 그리고 그릴 위치로부터 정점 버퍼를 빌드 할 폰트 클래스를 사용합니다.
_font->BuildVertexArray((void*)vertices, text, drawX, drawY);
화면에 그려질 첫번째 위치와 정점 배열을 만듭니다.
// 버텍스 버퍼를 잠급니다.
result = deviceContext->Map(sentence->vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if (FAILED(result))
{
return false;
}
// 정점 버퍼 안에 있는 포인터를 할당 받습니다.
verticesPtr = (VertexType*)mappedResource.pData;
// 버텍스 버퍼로부터 데이터를 복사합니다.
memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * sentence->vertexCount));
// 버텍스 버퍼의 잠금을 해제합니다.
deviceContext->Unmap(sentence->vertexBuffer, 0);
SAFE_DELETE_ARRAY(vertices);
return true;
}
정점 배열의 정보를 문장 정점 버퍼로 복사합니다.
bool TextClass::RenderSentence(ID3D11DeviceContext* deviceContext, SentenceType* sentence, XMMATRIX worldMatrix, XMMATRIX orthoMatrix)
{
unsigned int stride, offset;
XMFLOAT4 pixelColor;
bool result;
// 정점 버퍼의 stride 와 offset을 설정합니다.
stride = sizeof(VertexType);
offset = 0;
// 렌더링 할수 있게 인풋 어셈블러 안에 있는 active된 버텍스 버퍼를 설정합니다.
deviceContext->IASetVertexBuffers(0, 1, &sentence->vertexBuffer, &stride, &offset);
// 렌더링 할수 있게 인풋 어셈블러 안에 있는 active된 인덱스 버퍼를 설정합니다.
deviceContext->IASetIndexBuffer(sentence->indexBuffer, DXGI_FORMAT_R32_UINT, 0);
// 이 정점 버퍼로부터 렌더링 할수 있게 준 primitive타입을 세팅합ㄴ.다
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// 입력한 문장 색상을 픽셀 컬러 정점을 만듭니다.
pixelColor = XMFLOAT4(sentence->red, sentence->green, sentence->blue, 1.0f);
// 폰트 쉐이더를 사용해서 렌더링 합니다.
result = _fontShader->Render(deviceContext, sentence->indexCount, worldMatrix, _baseViewMatrix, orthoMatrix, _font->GetTexture(), pixelColor);
if (result == false)
{
return false;
}
return true;
}
RenderSentence 함수는 문장의 정점/인덱스 버퍼를 입력 어셈블러에 넣고 FontShaderClass를 호출해서 입력된 버퍼들을 그리게 합니다. _baseViewMatrix가 아니라 현재의 뷰행렬을 사용한다는것을 주의 해주세요. 이를 통해 현재 카메라가 어디에 있는지에 상관없이 화면의 같은 위치에 글자가 그려지게 됩니다. 마찬가지로 2D좌표에 표기사는 것이기 때문에 정규 투영 행렬 대신 정사영 행렬을 사용합니다.
D3DClass
블렌딩 상태를 통합하기 위해 D3DClass 클래스도 수정되었습니다. 블렌딩은 글자가 3D 물체들을 배경으로 조화롭게 그려지도록 합니다. 만약 블렌딩을 켜지 않는다면 텍스트 뒤에 검은 삼각형이 있는것을 보게 될 것입니다. 반대로 블렌딩이 켜져 있다면 오로지 글자와 관련된 픽셀들만 화면에 나타나고 나머지 부분의 삼각형은 투명 처리됩니다. 여기서는 그에 관해 더 자세히 다루지 않고 적당히 글씨만 표시될 정도의 블렌딩을 사용할 것입니다.
#pragma once
class D3DClass
{
public:
D3DClass();
D3DClass(const D3DClass& other);
~D3DClass();
bool Initialize(int screenWidth, int screenHieght, bool vsync, HWND hwnd, bool fullScreen, float screenDepth, float screenNear);
void Shutdown();
void BeginScene(float red, float green, float blue, float alpha);
void EndScene();
ID3D11Device* GetDevice();
ID3D11DeviceContext* GetDeviceContext();
void GetProjectionMatrix(XMMATRIX& projectionMatrix);
void GetWorldMatrix(XMMATRIX& worldMatrix);
void GetOrthoMatrix(XMMATRIX& orthoMatrix);
void GetVideoCardInfo(char* cardName, int& memory);
void TurnZBufferOn();
void TurnZBufferOff();
void TurnOnAlphaBlending();
void TurnOffAlphaBlending();
private:
bool _vsyncEnabled = false;
int _videoCardMemory = 0;
char _videoCardDescription[128] = {0,};
IDXGISwapChain* _swapChain = nullptr;
ID3D11Device* _device = nullptr;
ID3D11DeviceContext* _deviceContext = nullptr;
ID3D11RenderTargetView* _renderTargetView = nullptr;
ID3D11Texture2D* _depthStencilBuffer = nullptr;
ID3D11DepthStencilState* _depthStencilState = nullptr;
ID3D11DepthStencilView* _depthStencilView = nullptr;
ID3D11RasterizerState* _rasterState = nullptr;
XMMATRIX _projectionMatrix;
XMMATRIX _worldMatrix;
XMMATRIX _orthoMatrix;
ID3D11DepthStencilState* _depthDisabledStencilState = nullptr;
ID3D11BlendState* _alphaEnableBlendState = nullptr;
ID3D11BlendState* _alphaDisableBlendState = nullptr;
};
public:
void TurnOnAlphaBlending();
void TurnOffAlphaBlending();
private:
ID3D11BlendState* _alphaEnableBlendState = nullptr;
ID3D11BlendState* _alphaDisableBlendState = nullptr;
알파 블렌딩을 켜고 끄는 함수를 선언하고, 두개의 블렌딩 상태를 표현하는 변수를 만듭니다. _alphaEnableBlendingState변수는 알파 블렌딩을 사용하는 것이고, alphaDisableBlendingState 변수는 사용하지 않을 때 사용할 것입니다.
D3DClass.cpp
#include "stdafx.h"
#include "D3DClass.h"
D3DClass::D3DClass()
{
}
D3DClass::D3DClass(const D3DClass& other)
{
}
D3DClass::~D3DClass()
{
}
bool D3DClass::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullScreen, float screenDepth, float screenNear)
{
// 수직동기화 상태를 저장
_vsyncEnabled = vsync;
// DirectX 그래픽 인터페이스 펙토리 생성
IDXGIFactory* factory = nullptr;
if (FAILED(CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory)))
{
return false;
}
// 팩토리 객체를 사용하여 첫번째 그래픽 카드 인터페이스 어뎁터를 생성
IDXGIAdapter* adapter = nullptr;
if (FAILED(factory->EnumAdapters(0, &adapter)))
{
return false;
}
// 출력(모니터)에 대한 첫번째 어뎁터를 지정
IDXGIOutput* adapterOutput = nullptr;
if (FAILED(adapter->EnumOutputs(0, &adapterOutput)))
{
return false;
}
// 출력(모니터)에 대한 DXGI_FORMAT_R8G8B8A8_UNORM 표시 형식에 맞는 모드 수를 가져옴
unsigned int numModes = 0;
if (FAILED(adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL)))
{
return false;
}
// 가능한 모든 모니터와 그래픽 카드 조합을 저장할 리스트를 생성
DXGI_MODE_DESC* displayModeList = new DXGI_MODE_DESC[numModes];
if (!displayModeList)
{
return false;
}
// 모든 디스플레이 모드에 대한 리스트를 채움
if (FAILED(adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList)))
{
return false;
}
// 이제 모든 디스플레이 모드에 대해 화면 너비/노퓨이에 맞는 디스플레이 모드를 찾음
// 적합한 것을 찾으면, 모니터의 새로고침 비율의 분모와 분자 값을 저장
unsigned int numerator = 0;
unsigned int denominator = 0;
for (unsigned int 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;
}
}
}
// 비디오카드의 구조체를 얻음
DXGI_ADAPTER_DESC adapterDesc;
if (FAILED(adapter->GetDesc(&adapterDesc)))
{
return false;
}
// 비디오카드 메모리 용량 단위를 메가바이트 단위로 저장
_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024);
// 비디오카드의 이름을 저장
size_t stringLength = 0;
if(wcstombs_s(&stringLength, _videoCardDescription, 128, adapterDesc.Description, 128) != 0)
{
return false;
}
// 디스플레이 모드 리스트를 해제
SAFE_DELETE_ARRAY(displayModeList);
displayModeList = 0;
// 출력 어뎁터를 해제
adapterOutput->Release();
adapterOutput = 0;
// 어댑터를 해제
adapter->Release();
adapter = 0;
// 팩토리 객체를 해제
factory->Release();
factory = 0;
// 스왑체인 구조체를 초기화
DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
// 백버퍼를 한개만 사용하도록 저장
swapChainDesc.BufferCount = 1;
// 백버퍼의 넓이와 높이를 지정
swapChainDesc.BufferDesc.Width = screenWidth;
swapChainDesc.BufferDesc.Height = screenHeight;
// 32bit 서페이스 설정
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
// 백버퍼의 새로고침 비율을 설정
if (_vsyncEnabled)
{
swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator;
swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator;
}
else
{
swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
}
// 백버퍼의 사용 용도 지정
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
// 랜더링에 사용될 윈도우 핸들을 지정
swapChainDesc.OutputWindow = hwnd;
// 멀티샘플링을 끔
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
// 창모드 or 풀스크린 모드 설정
if (fullScreen)
{
swapChainDesc.Windowed = false;
}
else
{
swapChainDesc.Windowed = true;
}
// 스캔 라인 순서 및 크기를 지정하지 않음으로 설정
swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
// 출력된 다음 백버퍼를 비우도록 지정
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
// 추가 옵션 플래그를 사용하지 않음
swapChainDesc.Flags = 0;
// 피처레벨을 DirectX 11로 설정
D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
// 스왑 체인, Direct3D 장치 및 Direct3D 장치 컨텍스트를 만듦
if (FAILED(D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1,
D3D11_SDK_VERSION, &swapChainDesc, &_swapChain, &_device, NULL, &_deviceContext)))
{
return false;
}
// 백버퍼 포인터를 얻어옴
ID3D11Texture2D* backBufferPtr = nullptr;
if (FAILED(_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr)))
{
return false;
}
// 백버퍼 포인터로 랜더 타겟 뷰를 생성
if (FAILED(_device->CreateRenderTargetView(backBufferPtr, NULL, &_renderTargetView)))
{
return false;
}
// 백버퍼 포인터를 해제
backBufferPtr->Release();
backBufferPtr = 0;
// 깊이 버퍼 구조체를 초기화
D3D11_TEXTURE2D_DESC depthBufferDesc;
ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));
// 깊이 버퍼 구조체 작성
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;
// 설정된 깊이버퍼 구조체를 사용하여 깊이 버퍼 텍스쳐를 생성
if (FAILED(_device->CreateTexture2D(&depthBufferDesc, NULL, &_depthStencilBuffer)))
{
return false;
}
// 스텐실 상태 구조체를 초기화
D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));
// 스텐실 상태 구조체를 작성
depthStencilDesc.DepthEnable = true;
depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
depthStencilDesc.StencilEnable = true;
depthStencilDesc.StencilReadMask = 0xFF;
depthStencilDesc.StencilWriteMask = 0xFF;
// 픽셀 정면의 스텐실 설정
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;
// 픽셀 뒷면의 스텐실 설정
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;
// 깊이 스텐실 상태를 생성
if (FAILED(_device->CreateDepthStencilState(&depthStencilDesc, &_depthStencilState)))
{
return false;
}
// 깊이 스텐실 상태를 설정
_deviceContext->OMSetDepthStencilState(_depthStencilState, 1);
// 깊이 스텐실 뷰의 구조체를 초기화
D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));
// 깊이 스텐실 뷰 구조체를 설정
depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
depthStencilViewDesc.Texture2D.MipSlice = 0;
// 깊이 스텐실 뷰를 생성
if (FAILED(_device->CreateDepthStencilView(_depthStencilBuffer, &depthStencilViewDesc, &_depthStencilView)))
{
return false;
}
// 랜더링 대상 뷰와 깊이 스텐실 버퍼를 출력, 출력 렌터더 파이프 라인에 바인딩
_deviceContext->OMSetRenderTargets(1, &_renderTargetView, _depthStencilView);
// 그려지는 폴리곤과 방법을 결정할 래스터 구조체를 설정
D3D11_RASTERIZER_DESC rasterDesc;
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;
// 방금 작성한 구조체에서 래스터 라이저 상태를 만듦
if (FAILED(_device->CreateRasterizerState(&rasterDesc, &_rasterState)))
{
return false;
}
// 이제 래스터 라이저 상태를 설정
_deviceContext->RSSetState(_rasterState);
// 랜더링을 위해 뷰포트를 설정
D3D11_VIEWPORT viewport;
viewport.Width = (float)screenWidth;
viewport.Height = (float)screenHeight;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;
// 뷰포트를 생성
_deviceContext->RSSetViewports(1, &viewport);
// 투영 행렬을 설정합니다
float fieldOfView = 3.141592654f / 4.0f;
float screenAspect = (float)screenWidth/(float)screenHeight;
// 3D 랜더링을 위한 투영 행렬을 만듦
_projectionMatrix = XMMatrixPerspectiveFovLH(fieldOfView, screenAspect, screenNear, screenDepth);
// 월드 행렬을 항등 행렬로 초기화
_worldMatrix = XMMatrixIdentity();
// 2D 랜더링을 위한 직교 투영 행렬을 만듦
_orthoMatrix = XMMatrixOrthographicLH((float)screenWidth, (float)screenHeight, screenNear, screenDepth);
// 이제 2D 렌더링을 위한 Z 버퍼를 끄는 두번째 깊이 스텐실 상태를 만듭니다. 유일한 차이점은
// DepthEnable을 false로 설정하면 다름ㄴ 모든 매개 변수는 다른 깊이 스텐실 상태와 동일합니다.
D3D11_DEPTH_STENCIL_DESC depthDisabledStencilDesc;
ZeroMemory(&depthDisabledStencilDesc, sizeof(depthDisabledStencilDesc));
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;
// 장치를 사용하여 상태를 만듭니다.
if (FAILED(_device->CreateDepthStencilState(&depthDisabledStencilDesc, &_depthDisabledStencilState)))
{
return false;
}
// 블렌드 상태를 저장할 desc를 정의하고 비워줍니다.
D3D11_BLEND_DESC blendStateDescription;
ZeroMemory(&blendStateDescription, sizeof(D3D11_BLEND_DESC));
// 블렌드 상태를 설정합니다.
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;
// 블렌드 desc를 만듭니다.
if (FAILED(_device->CreateBlendState(&blendStateDescription, &_alphaEnableBlendState)))
{
return false;
}
// desc을 수정하여 알파 비활성화 블렌드 상태 desc을 생성합니다.
blendStateDescription.RenderTarget[1].BlendEnable = FALSE;
// 블렌드 desc를 만듭니다.
if (FAILED(_device->CreateBlendState(&blendStateDescription, &_alphaDisableBlendState)))
{
return false;
}
return true;
}
void D3DClass::Shutdown()
{
// 종료 전 윈도우 모드로 설정하지 않으면 스왑 체인을 해제 할 때 예외가 발생
if (_swapChain)
{
_swapChain->SetFullscreenState(false, NULL);
}
SAFE_RELEASE(_depthDisabledStencilState);
SAFE_RELEASE(_alphaDisableBlendState);
SAFE_RELEASE(_alphaEnableBlendState);
SAFE_RELEASE(_rasterState);
SAFE_RELEASE(_depthStencilView);
SAFE_RELEASE(_depthStencilState);
SAFE_RELEASE(_depthStencilBuffer);
SAFE_RELEASE(_renderTargetView);
SAFE_RELEASE(_deviceContext);
SAFE_RELEASE(_device);
SAFE_RELEASE(_swapChain);
}
void D3DClass::BeginScene(float red, float green, float blue, float alpha)
{
// 버퍼를 지울 새을 설정
float color[4] = {red, green, blue, alpha};
// 백버퍼를 지움
_deviceContext->ClearRenderTargetView(_renderTargetView, color);
// 깊이 버퍼를 지움
_deviceContext->ClearDepthStencilView(_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
}
void D3DClass::EndScene()
{
// 렌더링이 완료되었으므로 화면에 백 버퍼를 표시
if (_vsyncEnabled)
{
// 화면 새로고침 비율을 고정
_swapChain->Present(1, 0);
}
else
{
// 가능한 빠르게 출력
_swapChain->Present(0,0);
}
}
ID3D11Device* D3DClass::GetDevice()
{
return _device;
}
ID3D11DeviceContext* D3DClass::GetDeviceContext()
{
return _deviceContext;
}
void D3DClass::GetProjectionMatrix(XMMATRIX& projectionMatrix)
{
projectionMatrix = _projectionMatrix;
}
void D3DClass::GetWorldMatrix(XMMATRIX& worldMatrix)
{
worldMatrix = _worldMatrix;
}
void D3DClass::GetOrthoMatrix(XMMATRIX& orthoMatrix)
{
orthoMatrix = _orthoMatrix;
}
void D3DClass::GetVideoCardInfo(char* cardName, int& memory)
{
strcpy_s(cardName, 128, _videoCardDescription);
memory = _videoCardMemory;
}
void D3DClass::TurnZBufferOn()
{
_deviceContext->OMSetDepthStencilState(_depthStencilState, 1);
}
void D3DClass::TurnZBufferOff()
{
_deviceContext->OMSetDepthStencilState(_depthDisabledStencilState, 1);
}
void D3DClass::TurnOnAlphaBlending()
{
// Blend Factor를 설정합니다.
float blendFactor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
// 알파 블렌딩을 킵니다.
_deviceContext->OMSetBlendState(_alphaEnableBlendState, blendFactor, 0xffffffff);
}
void D3DClass::TurnOffAlphaBlending()
{
// Blend Factor를 설정합니다.
float blendFactor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
// 알파 블렌딩을 킵니다.
_deviceContext->OMSetBlendState(_alphaDisableBlendState, blendFactor, 0xffffffff);
}
// 블렌드 상태를 설정합니다.
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;
// 블렌드 desc를 만듭니다.
if (FAILED(_device->CreateBlendState(&blendStateDescription, &_alphaEnableBlendState)))
{
return false;
}
알파값이 적용된 블렌딩 상태를 만들기 위해 BlendEnable을 TRUE로 바꾸고 DestBlend를 D3D11_BLEND_INV_SRC_ALPHA로 설정합니다.
// desc을 수정하여 알파 비활성화 블렌드 상태 desc을 생성합니다.
blendStateDescription.RenderTarget[1].BlendEnable = FALSE;
// 블렌드 desc를 만듭니다.
if (FAILED(_device->CreateBlendState(&blendStateDescription, &_alphaDisableBlendState)))
{
return false;
}
알파값을 사용하지 않는 상태를 만들기 위해서 BlendEanble을 FALSE로 설정합니다.
void D3DClass::TurnOnAlphaBlending()
{
// Blend Factor를 설정합니다.
float blendFactor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
// 알파 블렌딩을 킵니다.
_deviceContext->OMSetBlendState(_alphaEnableBlendState, blendFactor, 0xffffffff);
}
void D3DClass::TurnOffAlphaBlending()
{
// Blend Factor를 설정합니다.
float blendFactor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
// 알파 블렌딩을 킵니다.
_deviceContext->OMSetBlendState(_alphaDisableBlendState, blendFactor, 0xffffffff);
}
TurnOnAlphaBlending은 OMSetBlendState함수와 _alphaEnableBlendState 변수를 사용하여 알파 블렌딩을 켜주고
TurnOffAlphaBlending은 그 반대로 _alphaDisableBlendState을 사용해서 꺼줍니다.
GraphicClass
GraphicClass.h
#pragma once
/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;
class D3DClass;
class CameraClass;
class TextClass;
class GraphicsClass
{
public:
GraphicsClass();
GraphicsClass(const GraphicsClass& other);
~GraphicsClass();
bool Initialize(int screenWidth, int screenHeight, HWND hwnd);
void Shutdown();
void Frame();
bool Render();
private:
D3DClass* _direct3D = nullptr;
CameraClass* _camera = nullptr;
TextClass* _text = nullptr;
};
GraphicClass.cpp
#include "stdafx.h"
#include "d3dclass.h"
#include "CameraClass.h"
#include "TextClass.h"
#include "GraphicsClass.h"
GraphicsClass::GraphicsClass()
{
}
GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}
GraphicsClass::~GraphicsClass()
{
}
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
// Direct3D 객체 생성
//_direct3D = (D3DClass*) SAFE_ALIGNED_NEW(sizeof(D3DClass), 16);
_direct3D = new D3DClass();
if (!_direct3D)
{
return false;
}
// Direct3D 객체 초기화
if (!_direct3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR))
{
MessageBox(hwnd, L"Could not initialze Direct3D", L"Error", MB_OK);
return false;
}
// _camera 객체 생성
_camera = new CameraClass();
if (_camera == nullptr)
{
return false;
}
// 카메라 포지션 변경
XMMATRIX baseViewMatrix;
_camera->SetPosition(0.0f, 0.0f, -5.0f);
_camera->Render();
_camera->GetViewMatrix(baseViewMatrix);
_text = new TextClass();
if (_text == nullptr)
{
return false;
}
// 텍스트 객체 초기화
if(_text->Initialize(_direct3D->GetDevice(), _direct3D->GetDeviceContext(), hwnd, screenWidth, screenHeight, baseViewMatrix) == false)
{
MessageBox(hwnd, L"Could not initialize the text object", L"Error", MB_OK);
return false;
}
return true;
}
void GraphicsClass::Shutdown()
{
if (_text)
{
_text->Shutdown();
SAFE_DELETE(_text);
}
// _camera 객체 반환
SAFE_DELETE(_camera);
// Direct3D 객체 반환
if (_direct3D)
{
_direct3D->Shutdown();
SAFE_DELETE(_direct3D);
}
}
void GraphicsClass::Frame()
{
_camera->SetPosition(0.0f, 0.0f, -10.0f);
}
bool GraphicsClass::Render()
{
// 씬을 그리기 위해 버퍼를 지웁니다.
_direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
// 카메라의 위치에 따라 뷰 행렬을 생성합니다.
_camera->Render();
// 카메라 및 d3d 객체에서 월드, 뷰 및 투영 행렬을 가져옵니다.
XMMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;
_camera->GetViewMatrix(viewMatrix);
_direct3D->GetWorldMatrix(worldMatrix);
_direct3D->GetProjectionMatrix(projectionMatrix);
_direct3D->GetOrthoMatrix(orthoMatrix);
// 모든 2D 렌더링을 시작하려면 Z 버퍼를 끕니다.
_direct3D->TurnZBufferOff();
// 텍스트를 렌더링 하기 전에 알파 블렌딩을 킵니다.
_direct3D->TurnOnAlphaBlending();
// 텍스트를 렌더링 합니다.
if (_text->Render(_direct3D->GetDeviceContext(), worldMatrix, orthoMatrix) == false)
{
return false;
}
// 텍스트 렌더링 후 알파 블렌딩을 끕니다.
_direct3D->TurnOffAlphaBlending();
// 모든 2D 렌더링이 완료되었으므로 Z버퍼를 다시 킵니다.
_direct3D->TurnZBufferOn();
// 버퍼의 내용을 화면에 출력합니다.
_direct3D->EndScene();
return true;
}
// 카메라 포지션 변경
XMMATRIX baseViewMatrix;
_camera->SetPosition(0.0f, 0.0f, -5.0f);
_camera->Render();
_camera->GetViewMatrix(baseViewMatrix);
_text = new TextClass();
if (_text == nullptr)
{
return false;
}
// 텍스트 객체 초기화
if(_text->Initialize(_direct3D->GetDevice(), _direct3D->GetDeviceContext(), hwnd, screenWidth, screenHeight, baseViewMatrix) == false)
{
MessageBox(hwnd, L"Could not initialize the text object", L"Error", MB_OK);
return false;
}
카메라 객체로부터 새로운 뷰 행렬을 생성하여 TextClass가 사용하도록 합니다. 이를 통하여 글자가 항상 화면의 같은 위치에 그려지게 됩니다.
그리고 TextClass의 객체를 초기화 해줍니다.
bool GraphicsClass::Render()
{
// 씬을 그리기 위해 버퍼를 지웁니다.
_direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
// 카메라의 위치에 따라 뷰 행렬을 생성합니다.
_camera->Render();
// 카메라 및 d3d 객체에서 월드, 뷰 및 투영 행렬을 가져옵니다.
XMMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;
_camera->GetViewMatrix(viewMatrix);
_direct3D->GetWorldMatrix(worldMatrix);
_direct3D->GetProjectionMatrix(projectionMatrix);
_direct3D->GetOrthoMatrix(orthoMatrix);
// 모든 2D 렌더링을 시작하려면 Z 버퍼를 끕니다.
_direct3D->TurnZBufferOff();
// 텍스트를 렌더링 하기 전에 알파 블렌딩을 킵니다.
_direct3D->TurnOnAlphaBlending();
텍스트가 배경과 조화롭게 그려지도록 알파 블렌딩을 켜줍니다.
// 텍스트를 렌더링 합니다.
if (_text->Render(_direct3D->GetDeviceContext(), worldMatrix, orthoMatrix) == false)
{
return false;
}
여기서 텍스트 객체의 Render함수를 호출하여 모든 문장들이 화면에 그려지도록 합니다. 그리고 2D이미지의 경우와 마찬가지로 렌더링을 수행하기 전에 Z버퍼를 해제하고 다 그리면 Z 버퍼를 킵니다.
// 텍스트 렌더링 후 알파 블렌딩을 끕니다.
_direct3D->TurnOffAlphaBlending();
// 모든 2D 렌더링이 완료되었으므로 Z버퍼를 다시 킵니다.
_direct3D->TurnZBufferOn();
// 버퍼의 내용을 화면에 출력합니다.
_direct3D->EndScene();
return true;
}
다시 알파 블렌딩을 꺼서 그리는 물체 뒤에 뭔가 있을 때 알파 블렌딩 효과가 나오지 않게 합니다.
결과화면

'DirectX' 카테고리의 다른 글
10. 2D 렌더링 (0) | 2023.01.31 |
---|---|
9. 정반사광 (0) | 2022.12.13 |
8. 주변광 (0) | 2022.12.06 |
7. Maya 2011 모델 불러오기 (2) | 2022.12.05 |
6. 3D 모델 렌더링 (0) | 2022.11.26 |