DirectX/10.0/Direct3D/Font Engine
Writing text onto the screen is a pretty important function of any application. Rendering text in DirectX 11 requires that you first know how to render 2D images. As we covered that topic in the previous tutorial this tutorial will just build on that knowledge.
The first thing you are going to need is your own font image. I built a really simple one with the characters I wanted and put it on a 1024x16 DDS texture:
As you can see it has the basic characters needed and they are all on a single texture file. We can now build a simple font engine which uses an index into that texture to draw individual characters to the screen as needed. How we do that in DirectX 11 is we build a square out of two triangles and then render the single character from the texture onto that square. So if we have a sentence we figure out the characters we need, create a square for each of them, and then render the characters to those squares. After doing that we render all the squares onto the screen to the location that forms the sentence. This is the same method we used in the previous tutorial to render a 2D image to the screen.
Now to index the texture we will need a text file that gives the location and size of each character in the texture. This text file will allow the font engine to quickly grab the pixels it needs out of the texture to form a character that it can render. Below is the index file for this font texture.
This format of the file is: [Ascii value of character] [The character] [Left Texture U coordinate] [Right Texture U Coordinate] [Pixel Width of Character]
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.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
With both the index file and texture file we have what we need to build the font engine. If you need to build your own index file just make sure that each character is only separated by spaces and you can write a bitmap parser yourself to create TU and TV coordinates based on where there are no blank spaces.
Remember that different users will run your application in different resolutions. One size font is not going to be clearly readable on all resolutions. So you may want to make 3-4 different font sizes and use certain ones for certain resolutions to fix this problem.
Framework
editAs we will want to encapsulate the font functionality in its own set of classes we will add some new classes to our frame work. The updated frame work looks like the following:
In this tutorial we have three new classes called TextClass, FontClass, and FontShader class. FontShaderClass is the shader for rendering fonts similar to how TextureShaderClass was used for rendering bitmap images in the previous tutorial. FontClass holds the font data and constructs vertex buffers needed for rendering strings. TextClass contains the vertex and index buffers for each set of text strings that need to be rendered to the screen, it uses FontClass to create the vertex buffer for the strings and then uses FontShaderClass to render those buffers.
Fontclass.h
editWe will look at the new FontClass first. This class will handle the texture for the font, the font data from the text file, and the function used to build vertex buffers with the font data. The vertex buffers that hold the font data for individual sentences will be in the TextClass and not inside this class.
//////////////////////////////////////////////////////////////////////////////// // 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:
The FontType structure is used to hold the indexing data read in from the font index file. The left and right are the TU texture coordinates. The size is the width of the character in pixels.
struct FontType { float left, right; int size; };
The VertexType structure is for the actual vertex data used to build the square to render the text character on. The individual character will require two triangles to make a square. Those triangles will have position and texture data only.
struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; }; public: FontClass(); FontClass(const FontClass&); ~FontClass(); bool Initialize(ID3D11Device*, char*, WCHAR*); void Shutdown(); ID3D11ShaderResourceView* GetTexture();
BuildVertexArray will handle building and returning a vertex array of triangles that will render the character sentence which was given as input to this function. This function will be called by the new TextClass to build vertex arrays of all the sentences it needs to render.
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
edit/////////////////////////////////////////////////////////////////////////////// // Filename: fontclass.cpp /////////////////////////////////////////////////////////////////////////////// #include "fontclass.h"
The class constructor initializes all the private member variables for the FontClass to null.
FontClass::FontClass() { m_Font = 0; m_Texture = 0; } FontClass::FontClass(const FontClass& other) { } FontClass::~FontClass() { }
Initialize will load the font data and the font texture.
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 will release the font data and the font texture.
void FontClass::Shutdown() { // Release the font texture. ReleaseTexture(); // Release the font data. ReleaseFontData(); return; }
The LoadFontData function is where we load the fontdata.txt file which contains the indexing information for the texture.
bool FontClass::LoadFontData(char* filename) { ifstream fin; int i; char temp;
First we create an array of the FontType structure. The size of the array is set to 95 as that is the number of characters in the texture and hence the number of indexes in the fontdata.txt file.
// Create the font spacing buffer. m_Font = new FontType[95]; if(!m_Font) { return false; }
Now we open the file and read each line into the array m_Font. We only need to read in the texture TU left and right coordinates as well as the pixel size of the character.
// 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> m_Font[i].left; fin >> m_Font[i].right; fin >> m_Font[i].size; } // Close the file. fin.close(); return true; }
The ReleaseFontData function releases the array that holds the texture indexing data.
void FontClass::ReleaseFontData() { // Release the font data array. if(m_Font) { delete [] m_Font; m_Font = 0; } return; }
The LoadTexture function reads in the font.dds file into the texture shader resource. This will be the texture we take the characters from and write them to their own square polygons for rendering.
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 function releases the texture that was used for the font.
void FontClass::ReleaseTexture() { // Release the texture object. if(m_Texture) { m_Texture->Shutdown(); delete m_Texture; m_Texture = 0; } return; }
GetTexture returns the font texture interface so the font graphics can be rendered.
ID3D11ShaderResourceView* FontClass::GetTexture() { return m_Texture->GetTexture(); }
BuildVertexArray will be called by the TextClass to build vertex buffers out of the text sentences it sends to this function as input. This way each sentence in the TextClass that needs to be drawn has its own vertex buffer that can be rendered easily after being created. The vertices input is the pointer to the vertex array that will be returned to the TextClass once it is built. The sentence input is the text sentence that will be used to create the vertex array. The drawX and drawY input variables are the screen coordinates of where to draw the sentence.
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;
The following loop will now build the vertex and index arrays. It takes each character from the sentence and creates two triangles for it. It then maps the character from the font texture onto those two triangles using the m_Font array which has the TU texture coordinates and pixel size. Once the polygon for that character has been created it then updates the X coordinate on the screen of where to draw the next character.
// 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
editThe font vertex shader is just a modified version of the texture vertex shader in the previous tutorial that was used to render 2D images. The only change is the vertex shader name.
//////////////////////////////////////////////////////////////////////////////// // 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
edit//////////////////////////////////////////////////////////////////////////////// // Filename: font.ps //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // ///////////// Texture2D shaderTexture; SamplerState SampleType;
We have a new constant buffer that contains the pixelColor value. We use this to control the color of the pixel that will be used to draw the font text.
cbuffer PixelBuffer { float4 pixelColor; }; ////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; };
The FontPixelShader first samples the font texture to get the pixel. If it samples a pixel that is black then it is just part of the background triangle and not a text pixel. In this case we set the alpha of this pixel to zero so when the blending is calculated it will determine that this pixel should be see-through. If the color of the input pixel is not black then it is a text pixel. In this case we multiply it by the pixelColor to get the pixel colored the way we want and then draw that to the screen.
//////////////////////////////////////////////////////////////////////////////// // 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
editThe FontShaderClass is just the TextureShaderClass from the previous tutorial renamed with a couple code changes for rendering fonts.
//////////////////////////////////////////////////////////////////////////////// // 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; };
We have a new structure type to match the PixleBuffer in the pixel shader. It contains just the pixel color of the text that will be rendered.
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;
The FontShaderClass has a constant buffer for the pixel color that will be used to render the text fonts with color.
ID3D11Buffer* m_pixelBuffer; }; #endif
Fontshaderclass.cpp
edit//////////////////////////////////////////////////////////////////////////////// // Filename: fontshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "fontshaderclass.h" FontShaderClass::FontShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_constantBuffer = 0; m_sampleState = 0;
We initialize the pixel color constant buffer to null in the class constructor.
m_pixelBuffer = 0; } FontShaderClass::FontShaderClass(const FontShaderClass& other) { } FontShaderClass::~FontShaderClass() { } bool FontShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result;
Initialize loads the new font vertex shader and pixel shader HLSL files.
// 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 calls ShutdownShader which releases the font shader related pointers and data.
void FontShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
Render will set the shader parameters and then draw the buffers using the font shader. Notice the is the same as TextureShaderClass except that it takes in the new pixelColor parameter.
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; }
The InitializeShader function loads the new HLSL font vertex and pixel shaders as well as the pointers that interface with the shader.
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;
The name of the vertex shader has been changed to 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; }
The name of the pixel shader has been changed to 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 vertex shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. // This setup needs to match the VertexType structure in the ModelClass and in the shader. polygonLayout[0].SemanticName = "POSITION"; polygonLayout[0].SemanticIndex = 0; polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[0].InputSlot = 0; polygonLayout[0].AlignedByteOffset = 0; polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[0].InstanceDataStepRate = 0; polygonLayout[1].SemanticName = "TEXCOORD"; polygonLayout[1].SemanticIndex = 0; polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT; polygonLayout[1].InputSlot = 0; polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[1].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &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; }
Here we setup the new pixel color constant buffer that will allow this class to set the pixel color in the pixel shader.
// 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; }
The ShutdownShader function releases all the shader related data.
void FontShaderClass::ShutdownShader() {
The new pixel color constant buffer is released here.
// 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 writes any shader compilation errors to a text file that can be reviewed in the event of a failure in compilation.
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 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; }
The SetShaderParameters function sets all the shader variables before rendering.
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);
Here is where we set the pixel color before rendering. We lock the pixel constant buffer and then set the pixel color inside it and unlock it again. We set the constant buffer position in the pixel shader and it is ready for use.
// 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 draws the prepared font vertex/index buffers using the font shader.
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
editThe TextClass handles all the 2D text drawing that the application will need. It renders 2D text to the screen and uses the FontClass and FontShaderClass to assist it in doing so.
//////////////////////////////////////////////////////////////////////////////// // 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 is the structure that holds the rendering information for each text sentence.
struct SentenceType { ID3D11Buffer *vertexBuffer, *indexBuffer; int vertexCount, indexCount, maxLength; float red, green, blue; };
The VertexType must match the one in the 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;
We will use two sentences in this tutorial.
SentenceType* m_sentence1; SentenceType* m_sentence2; }; #endif
Textclass.cpp
edit/////////////////////////////////////////////////////////////////////////////// // Filename: textclass.cpp /////////////////////////////////////////////////////////////////////////////// #include "textclass.h"
The class constructor initializes the private member variables to 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;
Store the screen size and the base view matrix, these will be used for rendering 2D text.
// Store the screen width and height. m_screenWidth = screenWidth; m_screenHeight = screenHeight; // Store the base view matrix. m_baseViewMatrix = baseViewMatrix;
Create and initialize the font object.
// 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 and initialize the font shader object.
// 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; }
Create and initialize the two strings that will be used for this tutorial. One string says Hello in white at 100, 100 and the other says Goodbye in yellow at 100, 200. The UpdateSentence function can be called to change the contents, location, and color of the strings at any time.
// 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; }
The Shutdown function will release the two sentences, the font object, and the font shader object.
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 will draw the two sentences to the screen.
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; }
The InitializeSentence function creates a SentenceType with an empty vertex buffer which will be used to store and render sentences. The maxLength input parameter determines how large the vertex buffer will be. All sentences have a vertex and index buffer associated with them which is initialized first in this function.
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; iindexCount; i++) { indices[i] = i; }
During the creation of the vertex buffer description for the sentence we set the Usage type to dynamic as we may want to change the contents of the sentence at any time.
// 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; }
The index buffer is setup as a normal static buffer since the contents will never need to change.
// 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 changes the contents of the vertex buffer for the input sentence. It uses the Map and Unmap functions along with memcpy to update the contents of the vertex buffer.
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;
Set the color and size of the sentence.
// 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 starting location to draw the sentence on the screen at.
// 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);
Build the vertex array using the FontClass and the sentence information.
// 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);
Copy the vertex array information into the sentence vertex buffer.
// 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 is used to release the sentence vertex and index buffer as well as the sentence itself.
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; }
The RenderSentence function puts the sentence vertex and index buffer on the input assembler and then calls the FontShaderClass object to draw the sentence that was given as input to this function. Notice that we use the m_baseViewMatrix instead of the current view matrix. This allows us to draw text to the same location on the screen each frame regardless of where the current view may be. Likewise we use the orthoMatrix instead of the regular projection matrix since this should be drawn using 2D coordinates.
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
editWe have also modified the D3DClass in this tutorial to incorporate blending states. Blending allows the font to blend with the 3D objects in the background. If we don't turn on blending we see the black triangles behind the text. But with blending turned on only the pixels for the text show up on the screen and the rest of the triangle is completely see-through. I won't get into great detail about blending here but a simple blend was needed for this tutorial to work correctly.
//////////////////////////////////////////////////////////////////////////////// // 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();
We have two new functions for turning on and off alpha blending.
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;
We have two new blending states. m_alphaEnableBlendingState is for turning on alpha blending and m_alphaDisableBlendingState is for turning off alpha blending.
ID3D11BlendState* m_alphaEnableBlendingState; ID3D11BlendState* m_alphaDisableBlendingState; }; #endif
D3dclass.cpp
editWe will just cover the functions that have changed in this class since the previous tutorial.
//////////////////////////////////////////////////////////////////////////////// // 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;
Set the two new blending states to 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;
We have a new description variable for setting up the two new blend states.
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; }
First initialize the blend state description.
// Clear the blend state description. ZeroMemory(&blendStateDescription, sizeof(D3D11_BLEND_DESC));
To create an alpha enabled blend state description change BlendEnable to TRUE and DestBlend to D3D11_BLEND_INV_SRC_ALPHA. The other settings are set to their default values which can be looked up in the Windows DirectX Graphics Documentation.
// 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;
We then create an alpha enabled blending state using the description we just setup.
// Create the blend state using the description. result = m_device->CreateBlendState(&blendStateDescription, &m_alphaEnableBlendingState); if(FAILED(result)) { return false; }
Now to create an alpha disabled state we change the description we just made to set BlendEnable to FALSE. The rest of the settings can be left as they are.
// Modify the description to create an alpha disabled blend state description. blendStateDescription.RenderTarget[0].BlendEnable = FALSE;
We then create an alpha disabled blending state using the modified blend state description. We now have two blending states we can switch between to turn on and off alpha blending.
// 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); }
Release the two new blending states.
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; }
The first new function TurnOnAlphaBlending allows us to turn on alpha blending by using the OMSetBlendState function with our m_alphaEnableBlendingState blending state.
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; }
The second new function TurnOffAlphaBlending allows us to turn off alpha blending by using the OMSetBlendState function with our m_alphaDisableBlendingState blending state.
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
edit//////////////////////////////////////////////////////////////////////////////// // 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"
We now include the new TextClass header file.
#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;
There is a new private variable for the TextClass object.
TextClass* m_Text; }; #endif
Graphicsclass.cpp
editWe will just look at the functions that have changed since the previous tutorial.
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h" GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0;
Initialize the new TextClass object to null in the class constructor.
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; }
We create a new view matrix from the camera object for the TextClass to use. It will always use this view matrix so that the text is always drawn in the same location on the screen.
// 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);
Here we create and initialize the new TextClass object.
// 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() {
Here we release the TextClass object.
// 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();
Here we turn on alpha blending so the text will blend with the background.
// Turn on the alpha blending before rendering the text. m_D3D->TurnOnAlphaBlending();
We call the text object to render all its sentences to the screen here. And just like with 2D images we disable the Z buffer before drawing and then enable it again after all the 2D has been drawn.
// Render the text strings. result = m_Text->Render(m_D3D->GetDeviceContext(), worldMatrix, orthoMatrix); if(!result) { return false; }
Here we turn off alpha blending so anything else that is drawn will not alpha blend with the objects behind it.
// 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; }
Summary
editNow we are able to render colored text to any location of the screen.
To Do Exercises
edit1. Recompile the code and ensure you get a white "Hello" written to 100x100 on your screen as well as a yellow "Goodbye" beneath it.
2. Change the pixel color, location, and content of the sentences.
3. Create a third sentence structure and have it render also.
4. Comment out the blending calls in the GraphicsClass::Render function and set m_D3D->BeginScene(0.0f, 0.0f, 1.0f, 1.0f); in the GraphicsClass::Render function. This will show why blending is needed.
5. Add the blending calls back into the GraphicsClass::Render function and keep m_D3D->BeginScene(0.0f, 0.0f, 1.0f, 1.0f); in the GraphicsClass::Render function to see the difference.