원문: http://www.rastertek.com/dx11tut11.html
화면에 2D 이미지를 그리는 것은 정말 유용합니다. 예를 들어 대부분의 유저 인터페이스(UI), 스프라이트 시스템, 텍스트 엔진들은 2D 이미지들로 만들어져 있습니다. DirectX 11에서는 도형에 2D 이미지들을 매핑하고 정사영 행렬을 이용하는 방식으로 2D 이미지를 그릴 수 있게 해 줍니다.
2D 화면 좌표계
2D 이미지를 화면에 그리기 위해서는 화면의 X,Y좌표를 계산해야 합니다. DirectX에서는 스크린의 중앙이 (0, 0)입니다. 그리고 중앙을 기준으로 왼쪽과 아래쪽 화면이 음수 좌표가 되고, 오른쪽과 위쪽 화면 좌표는 양수 방향이 됩니다. 예를 들어 1024x768 해상도의 화면이 있다면 화면 경계의 좌표는 다음과 같습니다.
올바르게 2D 렌더링을 하기 위해서는 이 화면의 좌표계를 기준으로 이루어진다는 것, 그리고 유저의 윈도우/스크린 크기를 알아야 한다는 것을 기억하기 바랍니다.
DirectX 11에서 Z버퍼 해제하기
2D 화면을 그리기 위해서는 Z버퍼를 사용하지 않아야 합니다. 그래야 해당 픽셀에 올바로 새로운 색상을 덮어쓰는 것이 가능해집니다. 뒤쪽부터 그리기 시작해서 맨 앞을 나중에 그리는 화가 알고리즘(painter's algorithm)에 따라 여러분이 원하는 결과물을 그릴 수 있을 것입니다. 일단 2D 화면을 다 그렸다면 Z버퍼를 켜서 다시 3D 객체를 그릴 수 있습니다.
Z버퍼를 켜고 끄기 위해서는 이전의 깊이 스텐실 상태와 같지만 DepthEnable 변수만 false로 지정된 두번째 깊이 스텐실 상태를 만들어야 합니다. 그 다음에는 OMSetDepthStencilState 함수를 사용하여 두 상태를 바꿔치기함으로 Z버퍼를 켜고 끌 수 있게 됩니다.
동적 정점 버퍼(Dynamic vertex buffer)
새로 소개하게 될 또다른 컨셉은 바로 동적 정점 버퍼입니다. 지금까지의 튜토리얼에서는 정적 정점 버퍼(Static vertex buffer)만을 사용했었습니다. 정적 정점 버퍼의 문제점은 버퍼 내부의 값을 바꿀 수 없다는 것입니다. 동적 정점 버퍼는 필요하다면 매 프레임마다 정점 버퍼의 내용을 바꿀 수 있게 해 줍니다. 정적 정점 버퍼보다는 속도가 떨어지지만 이전에는 할 수 없었던 추가 기능이 지원된다는 이점이 있는 것이지요.
2D 렌더링에서 동적 정점 버퍼를 사용하는 이유는 종종 이미지를 화면 내 다양한 위치로 옮겨야 하는 경우가 있기 때문입니다. 좋은 예로 마우스 포인터가 있습니다. 마우스 포인터는 수시로 움직이기 때문에 위치를 표시하는 정점 데이터 역시 그 값이 자주 바뀌어야 합니다.
기억해야 할 두 가지 점이 있습니다. 동적 정점 버퍼는 정적인 것보다 속도가 뒤쳐지기 때문에 필요한 경우에만 사용해야 합니다. 그리고 매 프레임마다 정적 정점 버퍼를 없애고 다시 생성하는 것 역시 지양해야 합니다. 이렇게 되면 그래픽 카드를 거의 완전히 잠그는 효과가 있기 때문에(Nvidia는 그렇지 않지만 ATI 그래픽카드에서 그런 현상이 발생합니다) 동적 정점 버퍼를 쓰는 것보다 성능이 현저히 떨어지게 됩니다.
DirectX 11에서의 정사영(Orthographic projection)
2D 이미지를 그리기 위해 필요한 마지막 컨셉은 바로 일반 3D 투영 행렬이 아닌 정사영 행렬을 사용하는 것입니다. 이를 통해 2D 화면의 좌표에 그릴 수 있게 됩니다. 이미 이전 튜토리얼에서 Direct3D를 초기화할 때 이 행렬을 만들었다는 것을 알고 계실 겁니다.
// Create an orthographic projection matrix for 2D rendering. D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth);
프레임워크
이 튜토리얼의 코드는 이전 튜토리얼에 기초해 있습니다. 이번 튜토리얼의 가장 큰 차이점은 ModelClass가 BitmapClass로 교체되었고 LightShaderClass 대신 TextureShaderClass를 다시 사용한다는 것입니다. 구조는 다음과 같습니다.
Bitmapclass.h
BitmapClass는 화면에 그리는데 필요한 각 이미지를 표현하는 데 사용될 것입니다. 따라서 모든 2D 이미지에 대해 각각 BitmapClass를 만들어 주어야 합니다. 이 클래스는 3D 객체 대신 2D 이미지를 다루는 ModelClass의 변형이라는 것을 참고하시기 바랍니다.
//////////////////////////////////////////////////////////////////////////////// // Filename: bitmapclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _BITMAPCLASS_H_ #define _BITMAPCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "textureclass.h" //////////////////////////////////////////////////////////////////////////////// // Class name: BitmapClass //////////////////////////////////////////////////////////////////////////////// class BitmapClass { private:
각 비트맵 이미지는 여전히 3D 객체처럼 그려지는 도형입니다. 2D 이미지에는 단지 위치 벡터와 텍스쳐 좌표만이 필요합니다.
struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; }; public: BitmapClass(); BitmapClass(const BitmapClass&); ~BitmapClass(); bool Initialize(ID3D11Device*, int, int, WCHAR*, int, int); void Shutdown(); bool Render(ID3D11DeviceContext*, int, int); int GetIndexCount(); ID3D11ShaderResourceView* GetTexture(); private: bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); bool UpdateBuffers(ID3D11DeviceContext*, int, int); void RenderBuffers(ID3D11DeviceContext*); bool LoadTexture(ID3D11Device*, WCHAR*); void ReleaseTexture(); private: ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; int m_vertexCount, m_indexCount; TextureClass* m_Texture;
3D 모델과는 달리 BitmapClass에서는 화면 크기, 이미지 크기, 이전에 그려졌던 위치를 기억해야 합니다. 따라서 이 정보들을 추적하는 전용 변수를 더합니다.
int m_screenWidth, m_screenHeight; int m_bitmapWidth, m_bitmapHeight; int m_previousPosX, m_previousPosY; }; #endif
Bitmapclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: bitmapclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "bitmapclass.h"
클래스 생성자에서는 모든 전용 포인터들을 초기화합니다.
BitmapClass::BitmapClass() { m_vertexBuffer = 0; m_indexBuffer = 0; m_Texture = 0; } BitmapClass::BitmapClass(const BitmapClass& other) { } BitmapClass::~BitmapClass() { } bool BitmapClass::Initialize(ID3D11Device* device, int screenWidth, int screenHeight, WCHAR* textureFilename, int bitmapWidth, int bitmapHeight) { bool result;
Initialize 함수에서 화면 크기와 이미지 크기를 저장합니다. 렌더링을 할 정확한 정점 위치가 필요하기 때문입니다. 이미지의 픽셀들은 텍스쳐의 그것처럼 정확하게 일치할 필요는 없습니다. 아무 크기나 지정할 수 있고 어떤 크기의 텍스쳐든지 지정할 수 있습니다.
// Store the screen size. m_screenWidth = screenWidth; m_screenHeight = screenHeight; // Store the size in pixels that this bitmap should be rendered at. m_bitmapWidth = bitmapWidth; m_bitmapHeight = bitmapHeight;
이전 렌더링 위치 변수는 -1로 초기화합니다. 이 변수는 이전 위치를 기억하는 용도이기 때문에 중요합니다. 만약 이전 프레임과 비교하여 위치가 변하지 않았다면 동적 정점 버퍼를 바꾸지 않기 때문에 성능의 향상을 꾀할 수 있습니다.
// Initialize the previous rendering position to negative one. m_previousPosX = -1; m_previousPosY = -1;
버퍼를 생성하고 비트맵 이미지의 텍스쳐 역시 로드됩니다.
// Initialize the vertex and index buffers. result = InitializeBuffers(device); if(!result) { return false; } // Load the texture for this model. result = LoadTexture(device, textureFilename); if(!result) { return false; } return true; }
Shutdown 함수는 정점 버퍼와 인덱스 버퍼, 그리고 비트맵 이미지에 사용된 텍스쳐를 해제합니다.
void BitmapClass::Shutdown() { // Release the model texture. ReleaseTexture(); // Shutdown the vertex and index buffers. ShutdownBuffers(); return; }
Render 함수에서는 2D 이미지의 버퍼를 그래픽 카드에 넣습니다. 또한 입력으로 이미지가 화면에 그려질 위치를 받습니다. UpdateBuffers 함수가 위치를 지정하는 데 사용됩니다. 이전 프레임과 위치가 바뀌었다면 동적 정점 버퍼의 정점들의 위치를 새로운 위치로 갱신합니다. 그렇지 않으면 UpdateBuffers 함수를 그냥 지나칩니다. RenderBuffers 함수를 수행한 뒤 최종적으로 그릴 정점/인덱스 버퍼를 준비합니다.
bool BitmapClass::Render(ID3D11DeviceContext* deviceContext, int positionX, int positionY) { bool result; // Re-build the dynamic vertex buffer for rendering to possibly a different location on the screen. result = UpdateBuffers(deviceContext, positionX, positionY); if(!result) { return false; } // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(deviceContext); return true; }
GetIndexCount 함수는 2D 이미지의 정점 수를 리턴합니다. 언제나 6을 반환합니다.
int BitmapClass::GetIndexCount() { return m_indexCount; }
GetTexture 함수는 2D 이미지에 사용하는 텍스쳐 자원에 대한 포인터를 반환합니다. 세이더에서는 이 함수를 호출하여 버퍼를 그릴 때 이미지에 접근합니다.
ID3D11ShaderResourceView* BitmapClass::GetTexture() { return m_Texture->GetTexture(); }
InitializeBuffer 함수는 2D이미지를 그리는데 사용하는 정점/인덱스 버퍼를 만드는 데 사용됩니다.
bool BitmapClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; int i;
두 개의 삼각형을 만들어야 하기 때문에 정점 수를 6으로 설정합니다. 인덱스 역시 같습니다.
// Set the number of vertices in the vertex array. m_vertexCount = 6; // Set the number of indices in the index array. m_indexCount = m_vertexCount; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; } // Initialize vertex array to zeros at first. memset(vertices, 0, (sizeof(VertexType) * m_vertexCount)); // Load the index array with data. for(i=0; i<m_indexCount; i++) { indices[i] = i; }
여기서 ModelClass와 큰 차이가 생깁니다. 여기서 동적 정점 버퍼를 생성하여 필요하다면 정점 버퍼 내부의 값을 수정할 수 있게 합니다. 동적이라는 속성을 지정하기 위하여 description의 Usage 변수를 D3D11_USAGE_DYNAMIC으로 하고 CPUAccessFlags를 D3D11_CPU_ACCESS_WRITE로 합니다.
// Set up the description of the static vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_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; // Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; }
인덱스 버퍼의 내용은 정점의 위치가 바뀌어도 언제나 같은 여섯개의 정점을 가리키기 때문에 인덱스 버퍼를 동적 버퍼로 만들지는 않습니다.
// Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; } // Release the arrays now that the vertex and index buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; }
ShutdownBuffers 함수는 정점/인덱스 버퍼를 해제합니다.
void BitmapClass::ShutdownBuffers() { // Release the index buffer. if(m_indexBuffer) { m_indexBuffer->Release(); m_indexBuffer = 0; } // Release the vertex buffer. if(m_vertexBuffer) { m_vertexBuffer->Release(); m_vertexBuffer = 0; } return; }
UpdateBuffers 함수는 매 프레임마다 불려 필요할 경우 동적 정점 버퍼의 내용을 새 위치로 변경합니다.
bool BitmapClass::UpdateBuffers(ID3D11DeviceContext* deviceContext, int positionX, int positionY) { float left, right, top, bottom; VertexType* vertices; D3D11_MAPPED_SUBRESOURCE mappedResource; VertexType* verticesPtr; HRESULT result;
이미지의 위치가 이전과 비교하여 달라졌는지 확인합니다. 바뀌지 않았다면 버퍼를 수정할 필요가 없으므로 그냥 빠져나갑니다. 이 확인과정은 많은 연산을 경감시켜줍니다.
// If the position we are rendering this bitmap to has not changed then don't update the vertex buffer since it // currently has the correct parameters. if((positionX == m_previousPosX) && (positionY == m_previousPosY)) { return true; }
만약 이미지 위치가 바뀌었다면 다음번에 그려질 위치를 위해 새로운 위치를 기록합니다.
// If it has changed then update the position it is being rendered to. m_previousPosX = positionX; m_previousPosY = positionY;
이미지의 네 변의 위치가 계산됩니다. 이에 관한 설명은 서두에 설명했었습니다.
// Calculate the screen coordinates of the left side of the bitmap. left = (float)((m_screenWidth / 2) * -1) + (float)positionX; // Calculate the screen coordinates of the right side of the bitmap. right = left + (float)m_bitmapWidth; // Calculate the screen coordinates of the top of the bitmap. top = (float)(m_screenHeight / 2) - (float)positionY; // Calculate the screen coordinates of the bottom of the bitmap. bottom = top - (float)m_bitmapHeight;
좌표가 계산되면 임시로 정점 배열을 만들고 새로운 정점 위치를 채워넣습니다.
// Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Load the vertex array with data. // First triangle. vertices[0].position = D3DXVECTOR3(left, top, 0.0f); // Top left. vertices[0].texture = D3DXVECTOR2(0.0f, 0.0f); vertices[1].position = D3DXVECTOR3(right, bottom, 0.0f); // Bottom right. vertices[1].texture = D3DXVECTOR2(1.0f, 1.0f); vertices[2].position = D3DXVECTOR3(left, bottom, 0.0f); // Bottom left. vertices[2].texture = D3DXVECTOR2(0.0f, 1.0f); // Second triangle. vertices[3].position = D3DXVECTOR3(left, top, 0.0f); // Top left. vertices[3].texture = D3DXVECTOR2(0.0f, 0.0f); vertices[4].position = D3DXVECTOR3(right, top, 0.0f); // Top right. vertices[4].texture = D3DXVECTOR2(1.0f, 0.0f); vertices[5].position = D3DXVECTOR3(right, bottom, 0.0f); // Bottom right. vertices[5].texture = D3DXVECTOR2(1.0f, 1.0f);
Map과 memcpy 함수를 사용하여 정점 배열의 내용을 정점 버퍼로 복사합니다.
// Lock the vertex buffer so it can be written to. result = deviceContext->Map(m_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) * m_vertexCount)); // Unlock the vertex buffer. deviceContext->Unmap(m_vertexBuffer, 0); // Release the vertex array as it is no longer needed. delete [] vertices; vertices = 0; return true; }
RenderBuffers 함수는 셰이더가 gpu에서 정점/인덱스 버퍼를 사용할 수 있도록 설정합니다.
void BitmapClass::RenderBuffers(ID3D11DeviceContext* deviceContext) { unsigned int stride; unsigned int offset; // Set vertex buffer stride and offset. stride = sizeof(VertexType); offset = 0; // Set the vertex buffer to active in the input assembler so it can be rendered. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0); // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; }
이어지는 함수는 2D 이미지로 그릴 텍스쳐를 로드합니다.
bool BitmapClass::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 함수는 로드된 텍스쳐를 해제합니다.
void BitmapClass::ReleaseTexture() { // Release the texture object. if(m_Texture) { m_Texture->Shutdown(); delete m_Texture; m_Texture = 0; } return; }
D3dclass.h
D3DClass 클래스는 Z버퍼를 켜고 끌 수 있도록 수정되었습니다.
//////////////////////////////////////////////////////////////////////////////// // 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 GetVideoCardInfo(char*, int&);
D3DClass 클래스에 2D 이미지를 그릴 때 Z버퍼를 켜고 끄는 함수를 만듭니다.
void TurnZBufferOn(); void TurnZBufferOff(); private: bool m_vsync_enabled; int m_videoCardMemory; char m_videoCardDescription[128]; 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;
또한 2D 렌더링을 위한 새로운 깊이 스텐실 상태 변수를 만듭니다.
ID3D11DepthStencilState* m_depthDisabledStencilState; }; #endif
D3dclass.cpp
여기서는 텍스쳐 튜토리얼에서 달라진 부분들을 설명하겠습니다.
//////////////////////////////////////////////////////////////////////////////// // Filename: d3dclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "d3dclass.h" D3DClass::D3DClass() { m_swapChain = 0; m_device = 0; m_deviceContext = 0; m_renderTargetView = 0; m_depthStencilBuffer = 0; m_depthStencilState = 0; m_depthStencilView = 0; m_rasterState = 0;
새로운 깊이 스텐실 상태 변수를 null로 초기화합니다.
m_depthDisabledStencilState = 0; } D3DClass::D3DClass(const D3DClass& other) { } D3DClass::~D3DClass() { } 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, stringLength; DXGI_MODE_DESC* displayModeList; DXGI_ADAPTER_DESC adapterDesc; int error; 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; // 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; } } } // Get the adapter (video card) description. result = adapter->GetDesc(&adapterDesc); if(FAILED(result)) { return false; } // Store the dedicated video card memory in megabytes. m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024); // Convert the name of the video card to a character array and store it. error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128); if(error != 0) { return false; } // 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);
여기서 깊이 스텐실 상태 변수의 description을 작성합니다. 새 변수 descripton은 DepthEnable이 2D 렌더링을 위해 false로 세팅됩니다.
// 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; } 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); }
Shutdown함수에서는 새로운 깊이 스텐실 상태를 해제하는 명령을 넣습니다.
if(m_depthDisabledStencilState) { m_depthDisabledStencilState->Release(); m_depthDisabledStencilState = 0; } if(m_rasterState) { m_rasterState->Release(); m_rasterState = 0; } if(m_depthStencilView) { m_depthStencilView->Release(); m_depthStencilView = 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; } void D3DClass::BeginScene(float red, float green, float blue, float alpha) { float color[4]; // Setup the color to clear the buffer to. color[0] = red; color[1] = green; color[2] = blue; color[3] = alpha; // Clear the back buffer. m_deviceContext->ClearRenderTargetView(m_renderTargetView, color); // Clear the depth buffer. m_deviceContext->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0); return; } void D3DClass::EndScene() { // Present the back buffer to the screen since rendering is complete. if(m_vsync_enabled) { // Lock to screen refresh rate. m_swapChain->Present(1, 0); } else { // Present as fast as possible. m_swapChain->Present(0, 0); } return; } ID3D11Device* D3DClass::GetDevice() { return m_device; } ID3D11DeviceContext* D3DClass::GetDeviceContext() { return m_deviceContext; } void D3DClass::GetProjectionMatrix(D3DXMATRIX& projectionMatrix) { projectionMatrix = m_projectionMatrix; return; } void D3DClass::GetWorldMatrix(D3DXMATRIX& worldMatrix) { worldMatrix = m_worldMatrix; return; } void D3DClass::GetOrthoMatrix(D3DXMATRIX& orthoMatrix) { orthoMatrix = m_orthoMatrix; return; } void D3DClass::GetVideoCardInfo(char* cardName, int& memory) { strcpy_s(cardName, 128, m_videoCardDescription); memory = m_videoCardMemory; return; }
아래 두 함수들은 Z버퍼를 켜고 끄는 일을 합니다. Z버퍼를 켜는 것은 원래의 깊이 스텐실 상태를 사용합니다. 그리고 Z버퍼를 끄는 것은 방금 depthEnable를 false로 설정한 새로운 깊이 스텐실 상태를 사용합니다. 일반적으로 3D 렌더링을 수행한 후에 Z버퍼를 끄고 2D 렌더링을 한 뒤 다시 Z버퍼를 키는 것이 가장 이상적입니다.
void D3DClass::TurnZBufferOn() { m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1); return; } void D3DClass::TurnZBufferOff() { m_deviceContext->OMSetDepthStencilState(m_depthDisabledStencilState, 1); return; }
Graphicsclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _GRAPHICSCLASS_H_ #define _GRAPHICSCLASS_H_ /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "d3dclass.h" #include "cameraclass.h" #include "textureshaderclass.h"
여기에 BitmapClass의 헤더 파일을 인클루드합니다.
#include "bitmapclass.h" ///////////// // GLOBALS // ///////////// const bool FULL_SCREEN = true; const bool VSYNC_ENABLED = true; const float SCREEN_DEPTH = 1000.0f; const float SCREEN_NEAR = 0.1f; //////////////////////////////////////////////////////////////////////////////// // Class name: GraphicsClass //////////////////////////////////////////////////////////////////////////////// class GraphicsClass { public: GraphicsClass(); GraphicsClass(const GraphicsClass&); ~GraphicsClass(); bool Initialize(int, int, HWND); void Shutdown(); bool Frame(); private: bool Render(float); private: D3DClass* m_D3D; CameraClass* m_Camera; TextureShaderClass* m_TextureShader;
전용 BitmapClass 변수를 추가합니다.
BitmapClass* m_Bitmap; }; #endif
Graphicsclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: graphicsclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "graphicsclass.h" GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_TextureShader = 0;
비트맵 객체를 null로 초기화합니다.
m_Bitmap = 0; } GraphicsClass::GraphicsClass(const GraphicsClass& other) { } GraphicsClass::~GraphicsClass() { } bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; // Create the Direct3D object. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Initialize the Direct3D object. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Set the initial position of the camera. m_Camera->SetPosition(0.0f, 0.0f, -10.0f); // Create the texture shader object. m_TextureShader = new TextureShaderClass; if(!m_TextureShader) { return false; } // Initialize the texture shader object. result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK); return false; }
BitmapClass 객체를 생성하고 초기화합니다. 이때 텍스쳐로 seafloor.dds 파일을 사용하고 크기를 256x256으로 합니다. 이 크기는 굳이 실제 텍스쳐 사이즈와 일치할 필요 없이 자유롭게 바꿀 수 있습니다.
// Create the bitmap object. m_Bitmap = new BitmapClass; if(!m_Bitmap) { return false; } // Initialize the bitmap object. result = m_Bitmap->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight, L"../Engine/data/seafloor.dds", 256, 256); if(!result) { MessageBox(hwnd, L"Could not initialize the bitmap object.", L"Error", MB_OK); return false; } return true; } void GraphicsClass::Shutdown() {
BitmapClass 객체도 이 함수에서 해제합니다.
// Release the bitmap object. if(m_Bitmap) { m_Bitmap->Shutdown(); delete m_Bitmap; m_Bitmap = 0; } // Release the texture shader object. if(m_TextureShader) { m_TextureShader->Shutdown(); delete m_TextureShader; m_TextureShader = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the D3D object. if(m_D3D) { m_D3D->Shutdown(); delete m_D3D; m_D3D = 0; } return; } bool GraphicsClass::Frame() { bool result; static float rotation = 0.0f; // Update the rotation variable each frame. rotation += (float)D3DX_PI * 0.005f; if(rotation > 360.0f) { rotation -= 360.0f; } // Render the graphics scene. result = Render(rotation); if(!result) { return false; } return true; } bool GraphicsClass::Render(float rotation) { 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 world, view, projection, and ortho matrices from the camera and d3d objects. m_Camera->GetViewMatrix(viewMatrix); m_D3D->GetWorldMatrix(worldMatrix); m_D3D->GetProjectionMatrix(projectionMatrix);
또한 2D 렌더링을 하기 위해 정사영 행렬을 구합니다. 일반 투영행렬 대신 이 행렬을 전달할 것입니다.
m_D3D->GetOrthoMatrix(orthoMatrix);
2D 렌더링을 시작하기 전에 Z버퍼를 끕니다.
// Turn off the Z buffer to begin all 2D rendering. m_D3D->TurnZBufferOff();
그리고 나서 화면의 (100, 100) 위치에 비트맵을 그립니다. 위치는 임의로 바꿀 수 있습니다.
// Put the bitmap vertex and index buffers on the graphics pipeline to prepare them for drawing. result = m_Bitmap->Render(m_D3D->GetDeviceContext(), 100, 100); if(!result) { return false; }
일단 정점/인덱스 버퍼가 준비되었다면 텍스쳐 셰이더를 이용해 그리게 됩니다. 2D 렌더링을 수행하기 위해 projectionMatrix 대신 orthoMatrix를 인자로 보냈다는 점을 주의하기 바랍니다. 또한 뷰 행렬의 내용이 계속 바뀌는 것이라면 2D 렌더링만을 위한 기본 뷰 행렬을 따로 만들어 사용해야 합니다. 이 튜토리얼에서는 카메라가 고정되어 있기 때문에 일반 뷰 행렬을 사용해도 같은 결과가 나옵니다.
// Render the bitmap with the texture shader. result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Bitmap->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, m_Bitmap->GetTexture()); if(!result) { return false; }
모든 2D 렌더링이 끝났다면 다음 프레임에서 3D객체를 그리기 위해 다시 Z버퍼를 킵니다.
// 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; }
마치면서
새로운 2D 개념과 함께 화면에 2D 이미지를 그릴 수 있게 되었습니다. 이를 통해 UI와 글꼴 시스템을 그려내는 기초가 됩니다.
연습 문제
1. 소스 코드를 다시 컴파일하고 화면상의 (100, 100) 위치에 2D 이미지가 그려지는지 확인해 보십시오.
2. 이미지가 그려지는 위치를 바꿔 보십시오.
3. GraphicsClass에서 m_Bitmap->Initialize 함수로 이미지의 크기를 바꿔 보십시오.
4. 2D 이미지에 사용되는 텍스쳐를 바꿔 보십시오.
소스 코드
Visual Studio 2010 프로젝트: dx11tut11.zip
소스 코드: dx11src11.zip
실행 파일: dx11exe11.zip
'강좌번역 > DirectX 11' 카테고리의 다른 글
DirectX11 Tutorial 13: Direct Input (1) | 2013.03.25 |
---|---|
DirectX11 Tutorial 12: 글꼴 엔진 (15) | 2013.03.22 |
DirectX11 Tutorial 10 - 정반사광 (1) | 2013.03.11 |
DirectX11 Tutorial 9 - 주변광 (0) | 2013.02.10 |
DirectX11 Tutorial 8 - 마야 2011 모델 불러오기 (16) | 2013.02.09 |