그래픽스(DirectX)

[DirectX11] 삼각형 그리기 (렌더링 파이프라인)

gamzachips 2023. 4. 29.

 

기본 2D 삼각형을 그리는 과정을 정리한다. 

 

 

Vertex data 정의

먼저 정점을 정의한다. 우선 위치와 색상 정보만 가지고 있게 만들자.

struct Vertex
{
	Vec3 position;
	Color color;
};
 

 

geometry 만들기

이 정점들로 이루어진 도형(geometry)를 정의한다. 정점들의 vector로 만들자

vector<Vertex> _vertices;
 
_vertices.resize(3);
_vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
_vertices[0].color = Color(1.f, 0.f, 0.f, 1.f);
_vertices[1].position = Vec3(0.f, 0.5f, 0.f);
_vertices[1].color = Color(0.f, 1.f, 0.f, 1.f);
_vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
_vertices[2].color = Color(0.f, 0.f, 1.f, 1.f);
 

 

 

Vertex Buffer 만들기

이러한 vertex의 data를 GPU로 넘겨주기 위한 버퍼를 만든다

ComPtr<ID3D11Buffer> _vertexBuffer = nullptr;
 
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_IMMUTABLE;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth = (uint32)(sizeof(Vertex) * _vertices.size());

D3D11_SUBRESOURCE_DATA data;
ZeroMemory(&data, sizeof(data));
data.pSysMem = _vertices.data();

_device->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
 

 

버퍼에 넘겨줄 설명서(DESC)를 만들어서 넘겨줘야 한다.

Usage는 버퍼를 CPU,GPU가 읽거나 쓸 수 있는지 여부를 지정한다.

BindFlags는 어떤 용도의 버퍼인지를 지정한다.

그리고 우리의 vertex data의 주소 정보를 넘겨준다.

 

 

 

 

HLSL 파일 작성

다음 과정을 진행하기 위해서 셰이더 파일(HLSL 파일)이 필요하다.

 

먼저 VS 단계에서 input으로 들어갈 데이터를 정의한다.

여기서 POSITION, COLOR 라는 이름은 위에서 정의했던 정점의 이름과 같게 만들어준다.????확인필요

struct VS_INPUT
{
	float4 position : POSITION;
	float4 color : COLOR;
};
 

 

VS단계에서 output으로 나올 데이터를 정의한다.

여기서 'SV_' 키워드는 system value의 약자로, HLSL 시스템에서 예약되어있는 이름임을 나타낸다.

'SV_POSITION'은 VS단계의 output으로서 RS 단계를 위해 넘겨줄 최종 위치임을 알려주게 된다.

struct VS_OUTPUT
{
	float4 position : SV_POSITION;
	float4 color : COLOR;
};
 

 

VS 함수를 작성한다. (진입점)

VS함수는 각 정점에 대해서 실행되며, 여기서는 정점의 위치에 대한 연산이 주로 이루어진다.

VS_OUTPUT VS(VS_INPUT input)
{
	VS_OUTPUT output;
	output.position = input.position;
	output.color = input.color;

	return output;
}
 

 

VS단계가 끝나면, 자동으로 RS단계가 진행된다. (설정을 변경해줄 수는 있음)

RS단계에서는 정점들 사이의 삼각형 내부를 체크하고, 그 안의 픽셀들의 색상을 보간한다.

 

이후 PS단계로 넘어가며, 여기서는 색상에 대한 연산이 이루어지고 그 결과를 반환한다.

float4 PS(VS_OUTPUT input) : SV_Target
{
	return input.color;
}
 

 

 

shader 로드하기

셰이더 파일 정보를 가져올 Blob과, 셰이더를 담을 VertexShader, PixelShader COM객체를 만든다

ComPtr<ID3D11VertexShader> _vertexShader = nullptr;
ComPtr<ID3DBlob> _vsBlob = nullptr;

ComPtr<ID3D11PixelShader> _pixelShader = nullptr;
ComPtr<ID3DBlob> _psBlob = nullptr;
 
void LoadShaderFromFile(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob)
{
	const uint32 compileFlag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;

	HRESULT hr = ::D3DCompileFromFile(
		path.c_str(),
		nullptr,
		D3D_COMPILE_STANDARD_FILE_INCLUDE,
		name.c_str(),
		version.c_str(),
		compileFlag,
		0,
		blob.GetAddressOf(),
		nullptr);

	CHECK(hr);
}
 

 

아래는 VertexShader 생성하는 과정이고, PixelShader에 대해서도 마찬가지로 진행한다.

Blob에 셰이더 파일을 읽어온 후에 Shader 객체에 정보를 넣어준다.

LoadShaderFromFile(L"Shader1.hlsl", "VS", "vs_5_0", _vsBlob);
HRESULT hr = _device->CreateVertexShader(_vsBlob->GetBufferPointer(), _vsBlob->GetBufferSize(), nullptr, _vertexShader.GetAddressOf());
CHECK(hr);
 

 

 

 

InputLayout 만들기

데이터를 어떻게 읽어야하는지를 묘사하는 Input Layout을 만든다.

ComPtr<ID3D11InputLayout> _inputLayout = nullptr;
 
D3D11_INPUT_ELEMENT_DESC layout[] =
{
	{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};

const int32 count = sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC);
_device->CreateInputLayout(layout, count, _vsBlob->GetBufferPointer(), _vsBlob->GetBufferSize(), _inputLayout.GetAddressOf());
 

여기까지 하면 준비는 끝났다!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

이제 만들어준 COM객체들을 가지고 파이프라인 단계에 연결(?)해주면 된다.

 

 

IA단계

우리의 도형에 대한 정보(VertexBuffer)를 넘겨주고, 어떻게 읽어야하는지에 대한 개요(InputLayout)를 넘겨준다. 그리고 정점들을 가지고 삼각형을 만들어달라고 Topology를 설정해준다.

uint32 stride = sizeof(Vertex);
uint32 offset = 0;
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(), &stride, &offset); 
_deviceContext->IASetInputLayout(_inputLayout.Get());
_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
 

VS단계

VertexShader를 넘겨준다

_deviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0);
 

PS단계

PixelShader를 넘겨준다

_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
 

OM단계

Draw. 그린다!!! 정점 개수를 알려준다.

_deviceContext->Draw(_vertices.size(), 0);
 

 

 

 

참고자료 (소스코드 출처)

인프런 Rookiss님 멘토링 강의

 

댓글