이제 본격적으로 Directx 11에서 그리기 연산이 어떻게 이루어지는 지 살펴보자.

우선 위처럼 하나의 정육면체 큐브를 만든다고 했을 때 이 큐브가 어떻게 만들어지는 지 생각해보자.

이 큐브도 렌더링파이프라인을 모두 거쳐서 나오게 될 것이다.

우선 IA 단계에서 정점과 인덱스 정보를 묘사하는 아이인 Input Layout을 통해 넘겨주고 이를 VS단계에서 카메라에 어떻게 보이게 되는지 행렬곱 연산을 적용시켜 줄 것이다. 그리고 RS단계에서 색상을 보간해줘서 위와 같이 보이게 해야할 것 이다. 그렇게 해주려면 정점과 색상에 대한 정보가 있어야 할 것이다.

 

코드를 살펴보자면 우선  vertexBuffer와 indexBuffer에 정보를 채워서 GPU쪽에 보내줘야한다.

ComPtr<ID3D11Buffer> _vertexBuffer;
ComPtr<ID3D11Buffer> _indexBuffer;

 

버퍼로 전달하고 싶은 정보를 구조체로 정의하고 정보를 채워준다음 버퍼 정보 정의해서 하나의 버퍼를 만들어 주면 된다.

이때 큐브모양을 만들어 줄 것이기 때문에 정점을 8개로 만들어주었다.

struct Vertex
{
	XMFLOAT3 Pos;
	XMFLOAT4 Color;
};

// Create vertex buffer
Vertex vertices[] =
{
	{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4((const float*)&Colors::White)   },
	{ XMFLOAT3(-1.0f, +1.0f, -1.0f), XMFLOAT4((const float*)&Colors::Black)   },
	{ XMFLOAT3(+1.0f, +1.0f, -1.0f), XMFLOAT4((const float*)&Colors::Red)     },
	{ XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4((const float*)&Colors::Green)   },
	{ XMFLOAT3(-1.0f, -1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Blue)    },
	{ XMFLOAT3(-1.0f, +1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Yellow)  },
	{ XMFLOAT3(+1.0f, +1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Cyan)    },
	{ XMFLOAT3(+1.0f, -1.0f, +1.0f), XMFLOAT4((const float*)&Colors::Magenta) }
};

D3D11_BUFFER_DESC vbd;
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof(Vertex) * 8;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
vbd.MiscFlags = 0;
vbd.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA vinitData;
vinitData.pSysMem = vertices;
HR(_device->CreateBuffer(&vbd, &vinitData, _vertexBuffer.GetAddressOf()));

 

이렇게 정점만 넘겨줘도 그려줄 수는 있지만 삼각형으로 그리다보면 겹치는 부분이 많이 생기기 때문에 인덱스 정보를 

같이 넘겨준다. 인덱스 버퍼도 버텍스 버퍼와 동일한 방식으로 만들어주면 된다.

// Create the index buffer

uint32 indices[] = {
	// front face
	0, 1, 2,
	0, 2, 3,

	// back face
	4, 6, 5,
	4, 7, 6,

	// left face
	4, 5, 1,
	4, 1, 0,

	// right face
	3, 2, 6,
	3, 6, 7,

	// top face
	1, 5, 6,
	1, 6, 2,

	// bottom face
	4, 0, 3,
	4, 3, 7
};

D3D11_BUFFER_DESC ibd;
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = sizeof(uint32) * 36;
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
ibd.MiscFlags = 0;
ibd.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = indices;
HR(_device->CreateBuffer(&ibd, &iinitData, _indexBuffer.GetAddressOf()));

 

그리고 shader를 사용할 때 Com객체처럼 사용하는데 이때 이름을 맞춰주면 가져오고 세팅할 때 연동이 돼서 편하게 사용

할 수 있다.

_fxWorldViewProj = _fx->GetVariableByName("gWorldViewProj")->AsMatrix();

_fxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));

 

VS단계를 보면 이렇게 정점이 월드 뷰 프로젝션 변환 행렬이 곱채져서 반환된다는 것을 볼 수 있다.

cbuffer cbPerObject
{
	float4x4 gWorldViewProj;
};

struct VertexIn
{
	float3 PosL  : POSITION;
    float4 Color : COLOR;
};

struct VertexOut
{
	float4 PosH  : SV_POSITION;
    float4 Color : COLOR;
};

VertexOut VS(VertexIn vin)
{
	VertexOut vout;
	
	// Transform to homogeneous clip space.
	vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);
	
	// Just pass vertex color into the pixel shader.
    vout.Color = vin.Color;
    
    return vout;
}

 

그 다음 물체가 어떻게 생겼는지 묘사해주는 Input Layout 단계이다. 이때 쉐이더의 구조체 변수와 클래스 변수와 이름과 구조를 동일하게 맞춰주어야 한다.

void BoxDemo::BuildVertexLayout()
{
	// Create the vertex input layout.
	D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
	{
		{"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}
	};

	// Create the input layout
	D3DX11_PASS_DESC passDesc;
	_tech->GetPassByIndex(0)->GetDesc(&passDesc);

	HR(_device->CreateInputLayout(vertexDesc, 2, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, _inputLayout.GetAddressOf()));
}

 

그 다음에는 렌더타켓뷰와 깊이 스텐실뷰를 클리어해주는 것으로 그림 그릴 도화지를 밀어주고 IA단계의 인풋레이아웃을 묘사한 부분을 실제 DeviceContext와 연결시켜주고 물체를 어떤 기본 물체로 그려줄지 세팅하는 토폴로지 단계를 하는 것을 볼 수 있다. 정점버퍼와 인덱스버퍼도 연결시켜준다.

_deviceContext->IASetInputLayout(_inputLayout.Get());
_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

uint32 stride = sizeof(Vertex);
uint32 offset = 0;
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(), &stride, &offset);
_deviceContext->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);

 

이렇게 해준다음 월드,뷰,프로젝션 변환행렬을 쉐이더쪽에 정보를 넘겨주고 드려주면 된다. 인덱스 버퍼를 통해 그려주는 DrawIndexed를 사용하는 것을 볼 수 있다.

그려준 도화지를 Present를 통해 제출한다.

// Set constants
XMMATRIX world = ::XMLoadFloat4x4(&_world);
XMMATRIX view = ::XMLoadFloat4x4(&_view);
XMMATRIX proj = ::XMLoadFloat4x4(&_proj);
XMMATRIX worldViewProj = world * view * proj;

_fxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));

D3DX11_TECHNIQUE_DESC techDesc;
_tech->GetDesc(&techDesc);
for (uint32 p = 0; p < techDesc.Passes; ++p)
{
	_tech->GetPassByIndex(p)->Apply(0, _deviceContext.Get());

	// 36 indices for the box.
	_deviceContext->DrawIndexed(36, 0, 0);
}

HR(_swapChain->Present(0, 0));

 

이렇게 해주면 위의 큐브가 보이게 되는 것이다.

강의 링크

https://inf.run/1HYWy

 

학습 페이지

 

www.inflearn.com

 

파이프라인을 설명할 때는 처음부터 각 단계에 집중하기보다는 이 파이프라인으로 무엇을 해주는 지를 포괄적으로 

설명 하는 것이 좋다.

3D 게임을 만든다고 했을 때 3D 게임 세상을 2D화면으로 표현해주기 위해 변환을 해줘야하는 데 변환해주는 일련의 규칙적인 단계들을 모아서 렌더링 파이프라인이라고 한다. 파이프라인의 단계는 밑의 그림과 같다 이런 작업을 GPU를 통해 이루어지게 된다.

https://learn.microsoft.com/ko-kr/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline

 

● IA(Input Asssembler)단계

입력을 조립하는 단계로 정점을 건네주고 그 정점이 삼각형이나 선, 점인지 어떤식으로 구성되어 있는 지에 관한 정보와 

정점만을 건네주면 리소스가 너무 커지기 때문에 색인정보, 즉 인덱스 정보를 같이 넘겨준다. 이때는 좌표가 아직까지 로컬

좌표이다.

이때 로컬좌표는 자기 자신의 좌표계를 기준으로 하는 좌표로 자신의 원점을 중심으로 기하학적으로 어떻게 생겼는지에 관한 좌표정보이다.

 

● VS(Vertex Shader)단계

이 단계에서는 물체를 이루는 기본 도형인 삼각형의 정점별로 실행되는 단계로 World, View, Projection변환 행렬을 곱해주는 것으로 로컬좌표의 물체를 실제 월드에 배치할 수 있다.

 

● RS(Rasterizer State)단계

이 단계는 카메라의 값에 따라 그려주지 않아도 되는 오브젝트를 걸러주는 클래핑과 같은 작업과 후면으로 된 오브젝트는 그려주지 않는 등의 작업을 해주고 마지막으로 보간 작업을 해줘서 정점을 픽셀 단위로 만들어 준다.

● PS(Pixel Shader)단계

픽셀 단위로 넘어온 것의 색상을 정해주는 단계로 일반적인 물체의 색상 뿐만 아니라 여러가지 조명 연산이 적용된 색상까지 반영해주게 된다.

 

● OM(Ouput Merge)단계

색상까지 정해진 후에 텍스처나 렌더타켓을 그려주거나 렌더타켓 뷰를 설정해서 Swap Chain을 연결해준 다음 후면 버퍼 

전면 버퍼에 최종적으로 그려주게 되는 단계이다. 그려주고 Present를 해주면 화면에 출력 된다.

 

● 행렬

 

SRT연산에서 행렬연산을 많이 해주게 되는데 이때 x y z 에서 1을 붙여주는 이유는 선형 변환을 위해서 필요하다.

만약 이 부분이 0이 된다면 선형 이동을 하지 않는다. 방향벡터를 이용해서 방향만 적용할 때 이렇게 사용하면 회전

만 적용된다. 

1을 붙여주면 TransformCode 이고 0을 붙여주면 TransformNormal 이라고 보면 된다.

 

행렬 연산에서 우선 순위는 Scale Rotation(자전) Translation Roation(공전) Parent(계층) 이다.

 

+ Recent posts