성공할 게임개발자

[DirectX 11] 7. 2D 랜더링 & 글꼴 엔진 본문

DirectX 11

[DirectX 11] 7. 2D 랜더링 & 글꼴 엔진

fn000 2025. 2. 25. 14:44

2D 랜더링 아키텍처

 

 

글꼴 렌더링 아키텍처

이 두가지를 혼합하여 2D 랜더링으로 텍스처를 업로드하고 글꼴엔진도 같이 업로드 할것임. 이글에서는 그렇고

이 두가지와 충돌처리, 상수버퍼를 활용해서 

대략적으로 만들 게임에 대한 아이디어가 나왔는데 바로 가위,바위,보 게임임 쇼츠보다가 생각해낸건데 플레이어 1,2,3은 가위,바위,보 중 하나를 가지고 시작함. 

플레이가 시작되면 맵에 랜덤으로 돌아다니는 가위 바위 보 와 맞 닿았을 때, 내가 지면 삭제되거나 다른 동일한 개체로 이동하고 이기면 나와 대결한 개체를 내 문양으로 바꿈. 

결과적으로 바운더리 내의 모든 문양을 내 문양으로 만들면 승리. 간단한 게임임

3인 게임이라 밸런스도 맞을 거 같고...

 

 

 

 

2D 랜더링과 글자를 랜더링한 결과물

여튼 결과는 이렇다.

눈여겨 봐야할점은 

 

글자를 랜더링하기위해

fontShaderClass, FontClass, TextClass를 사용했다는 것이고

 

2D랜더링을 위해

TextureShaderClass, BitmapClass를 사용했다는 점이다. 기계에 기능을 추가하듯 해당 클래스들을 생성하고 applicationClass와 D3DClass 등에서 요소들을 잘 이어주면 작동한다는 소리이다.

 

 

2D 렌더링

 

 

BitmapClass

BitmapClass는 화면에 그리는데 필요한 각 이미지를 표현하는데 사용됨. 따라서 모든 2D 이미지에 대해 각각의 BitmapClass를 만들어주어야함  이 클래스는 3D객체 대신 2D 이미지를 다루는 ModelClass의 변형임

 

////////////////////////////////////////////////////////////////////////////////
// Filename: bitmapclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _BITMAPCLASS_H_
#define _BITMAPCLASS_H_

//////////////
// INCLUDES //
//////////////
#include <directxmath.h>
using namespace DirectX;

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"

////////////////////////////////////////////////////////////////////////////////
// Class name: BitmapClass
////////////////////////////////////////////////////////////////////////////////
class BitmapClass
{
private:
	struct VertexType
	{
		XMFLOAT3 position;
		XMFLOAT2 texture;
	};

public:
	BitmapClass();
	BitmapClass(const BitmapClass&);
	~BitmapClass();

	bool Initialize(ID3D11Device*, ID3D11DeviceContext*, int, int, char*, int, int);
	void Shutdown();
	bool Render(ID3D11DeviceContext*,int ,int);

	int GetIndexCount();
	ID3D11ShaderResourceView* GetTexture();

	void SetRenderLocation(int, int);

private:
	bool InitializeBuffers(ID3D11Device*);
	void ShutdownBuffers();
	bool UpdateBuffers(ID3D11DeviceContext*, int, int);
	void RenderBuffers(ID3D11DeviceContext*);

	bool LoadTexture(ID3D11Device*, ID3D11DeviceContext*, char*);
	void ReleaseTexture();

private:
	ID3D11Buffer* m_vertexBuffer, * m_indexBuffer;
	int m_vertexCount, m_indexCount, m_screenWidth, m_screenHeight, m_bitmapWidth, m_bitmapHeight, m_renderX, m_renderY, m_prevPosX, m_prevPosY;
	TextureClass* m_Texture;
};

#endif

2D 이미지에는 단순히 위치 벡터텍스처 좌표만이 필요함

struct VertexType
{
	XMFLOAT3 position;
	XMFLOAT2 texture;
};

 

또한 3D모델과는 달리 BitmapClass에서는 화면 크기, 이미지크기, 이전에 그려졌던 위치를 기억해야함.

 

 

BitmapClass.cpp

bool BitmapClass::Initialize(ID3D11Device* device, ID3D11DeviceContext* deviceContext, int screenWidth, int screenHeight, char* textureFilename, int renderX, int renderY)
{
	bool result;


	// Store the screen size.
	m_screenWidth = screenWidth;
	m_screenHeight = screenHeight;

	// Store where the bitmap should be rendered to.
	m_renderX = renderX;
	m_renderY = renderY;

	// Initialize the vertex and index buffer that hold the geometry for the bitmap quad.
	result = InitializeBuffers(device);
	if (!result)
	{
		return false;
	}

	// Load the texture for this bitmap.
	result = LoadTexture(device, deviceContext, textureFilename);
	if (!result)
	{
		return false;
	}

	return true;
}

Initialize함수에서 화면 크기와 이미지 크기를 저장하고 InitializeBuffers로 DirectX 11 그래픽 파이프라인에 버퍼(vertex buffer, index buffer)를 생성하고 설정함. 보다 정확히 말하면 GPU가 사용할 수 있는 정점 및 인덱스 버퍼를 생성하고 이를 GPU의 메모리에 할당하는 단계

 

bool BitmapClass::InitializeBuffers(ID3D11Device* device)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D11_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;
	int i;


	// Initialize the previous rendering position to negative one.
	m_prevPosX = -1;
	m_prevPosY = -1;

	// 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];

	// Create the index array.
	indices = new unsigned long[m_indexCount];

	// 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;
	}

	// Set up the description of the dynamic 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 finally create the vertex buffer.
	result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
	if (FAILED(result))
	{
		return false;
	}

	// Set up the description of the 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;
}

버퍼를 설정, 생성하고 GPU에 넘겨줌

 

 

bool BitmapClass::LoadTexture(ID3D11Device* device, ID3D11DeviceContext* deviceContext, char* filename)
{
	bool result;


	// Create and initialize the texture object.
	m_Texture = new TextureClass;

	result = m_Texture->Initialize(device, deviceContext, filename);
	if (!result)
	{
		return false;
	}

	// Store the size in pixels that this bitmap should be rendered at.
	m_bitmapWidth = m_Texture->GetWidth()/2;
	m_bitmapHeight = m_Texture->GetHeight()/2;

	return true;
}

텍스처를 1/2로 가져와서 (임의로 그렇게 그냥 정함) 텍스처 리소스를 GPU메모리에 로드하는 과정. 이 과정 이후 텍스처를 펙셀 셰이더에 바인딩해야함.

 

 

BitmapClass::Render

bool BitmapClass::Render(ID3D11DeviceContext* deviceContext, int positionX, int positionY)
{
	bool result;


	// Update the buffers if the position of the bitmap has changed from its original position.
	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;
}

applicationclass.cpp의 render함수에서 bitmapclass의 render함수를 실행해서 버퍼를 업데이트하고 그래픽 파이프라인에 바인딩하는 역할을 함. 즉 렌더링을 위한 준비단계

 

 

UpdateBuffers

bool BitmapClass::UpdateBuffers(ID3D11DeviceContext* deviceContent, int positionX, int positionY)
{
	float left, right, top, bottom;
	VertexType* vertices;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	VertexType* dataPtr;
	HRESULT result;


	// If the position we are rendering this bitmap to hasn't changed then don't update the vertex buffer.
	if ((m_prevPosX == positionX) && (m_prevPosY == positionY))
	{
		return true;
	}

	// If the rendering location has changed then store the new position and update the vertex buffer.
	m_prevPosX = positionX;
	m_prevPosY = positionY;

	// Create the vertex array.
	vertices = new VertexType[m_vertexCount];

	// 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;

	// Load the vertex array with data.
	// First triangle.
	vertices[0].position = XMFLOAT3(left, top, 0.0f);  // Top left.
	vertices[0].texture = XMFLOAT2(0.0f, 0.0f);

	vertices[1].position = XMFLOAT3(right, bottom, 0.0f);  // Bottom right.
	vertices[1].texture = XMFLOAT2(1.0f, 1.0f);

	vertices[2].position = XMFLOAT3(left, bottom, 0.0f);  // Bottom left.
	vertices[2].texture = XMFLOAT2(0.0f, 1.0f);

	// Second triangle.
	vertices[3].position = XMFLOAT3(left, top, 0.0f);  // Top left.
	vertices[3].texture = XMFLOAT2(0.0f, 0.0f);

	vertices[4].position = XMFLOAT3(right, top, 0.0f);  // Top right.
	vertices[4].texture = XMFLOAT2(1.0f, 0.0f);

	vertices[5].position = XMFLOAT3(right, bottom, 0.0f);  // Bottom right.
	vertices[5].texture = XMFLOAT2(1.0f, 1.0f);

	// Lock the vertex buffer.
	result = deviceContent->Map(m_vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if (FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the constant buffer.
	dataPtr = (VertexType*)mappedResource.pData;

	// Copy the data into the vertex buffer.
	memcpy(dataPtr, (void*)vertices, (sizeof(VertexType) * m_vertexCount));

	// Unlock the vertex buffer.
	deviceContent->Unmap(m_vertexBuffer, 0);

	// Release the pointer reference.
	dataPtr = 0;

	// Release the vertex array as it is no longer needed.
	delete[] vertices;
	vertices = 0;

	return true;
}

 

DirectX 11에서 동적으로 정점버퍼를 업데이트하여 비트맵의 새로운 위치를 반영하는 역할을함. 이동이없으면 계산을 생략하여 성능이 올라감.

6개의 정점을 사용하여 삼각형 두개를 만들어 랜더링할 사각형을 만들어주고

 

// Lock the vertex buffer.
result = deviceContent->Map(m_vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if (FAILED(result))
{
	return false;
}

 

Map() 함수를 사용하여 GPU에 있는 기존 정점 버퍼 데이터를 CPU에서 수정할 수 있도록 설정

D3D11_MAP_WRITE_DISCARD 모드를 사용하여 기존 데이터를 폐기하고 새로운 데이터를 작성

mappedResource.pData가 GPU 메모리 내의 정점 데이터 주소를 가리킴

memcpy()를 사용하여 새롭게 생성한 정점 데이터를 GPU 메모리에 복사

 

이 과정을 통해 입력 어셈블러(IA) 단계에서 사용될 정점 데이터를 업데이트하는 역할을함

 

 

 

D3DClass

 ID3D11BlendState* m_alphaEnableBlendingState;
 ID3D11BlendState* m_alphaDisableBlendingState;

2D 랜더링 시 z축 버퍼는 필요없으므로 2D용 깊이 스텐실 상태 변수를 설정하고 2D 랜더링 시 이를 해제/활성화 할 수 있는 상태의 함수를 만들어야함

 

 

// 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;
}

D3DClass의 Initialize함수에서 깊이 스텐실을 비활성화하는 구조체의 description을 초기화하는 단계

depthDisabledStencilDesc.DepthEnable = false; 로 설정하여 깊이 버퍼를 비활성화

 

가장 이상적인 매커니즘은 3D렌더링을 수행하고 Z버퍼를 끄고 2D렌더링을 수행하고 다시 Z버퍼를 키는 것

 

 

 

글꼴엔진

 

텍스처

 

인덱스 파일  [글자의 아스키코드] [글자] [왼쪽 U좌표] [오른쪽 U좌표] [글자의 픽셀 너비]

 

글자를 그리기 위해서는 글자의 텍스처를 인덱스를 이용해 그려냄. 2D렌더링과 마찬가지로 구조가 거의 유사함. 그럴 수 밖에 없는 것이 2D에서도 텍스처, 인덱스를 이용하여 특정 위치에 사각형을 그려내었고 글자도 마찬가지로 이와같은 과정을 거치기 때문

 

적용 순서는 위의 2D와 같다.

application->initialize

textclass->initialize->initializebuffer

 

이 외에는 2D하듯이 코드를 만들면 된다.

 

 

해본김에 마우스 커서도 렌더링 하였다. 마우스 커서는 systemclass에서 application에게 준 m_input 객체를 커서 비트맵에 넘겨주는 방식으로 구현하였다.

 

다음 목표

1. 충돌처리

2. 마우스 가두기

3. 게임 시퀀스 짜기

'DirectX 11' 카테고리의 다른 글

[DirectX 11] 6. 정반사광, 주변광  (0) 2025.02.20
[DirectX 11] 5. 조명  (0) 2025.02.15
[DirectX 11] 4. 텍스쳐  (0) 2025.02.14
[DirectX 11] 3. 버퍼, 셰이더, HLSL  (0) 2025.02.13
[DirectX 11] 2. DirectX 초기화  (1) 2025.02.12