728x90

장치초기화의 코드 흐름이다.

  1. Device와 Swapchain 생성
  2. SwapChain에서 BackBuffer를 통해 RenderTargetView생성 후 OM(Output Merge)에 설정
  3. 뷰포트 설정
  4. RenderTarget Clear
  5. 그릴게 없으니 생략, Present로 전면 후면버퍼 교환

여기서 이제 그릴게 생겼으니 추가적으로 해주어야할게 생기게 되는데

  1. Input Assembler에 정점 정보 등록
  2. Vertex Shader 단계 추가
  3. Pixel Shader 단계 추가
  4. Draw Call

 

 

정점에 대한 정보를 등록한다는 말은 CPU메모리에서 GPU메모리로 복사를 수행한다는 말이며, 이 과정은 VertexBuffer를 생성하여 IASetVertexBuffers를 호출하는 과정으로 이루어진다.

HRESULT hr;
_vertexCount = static_cast<uint32>(vec.size());
uint32 bufferSize = _vertexCount * sizeof(Vertex);

D3D11_BUFFER_DESC bd = {};
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = bufferSize;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;

D3D11_SUBRESOURCE_DATA InitData = {};
InitData.pSysMem = &vec[0];
hr = DEVICE->CreateBuffer(&bd, &InitData, &_vertexBuffer);
CHECK_FAIL(hr, L"Failed Create Vertex Buffer");

// Set vertex buffer
uint32 stride = sizeof(Vertex);
uint32 offset = 0;
DEVICECTX->IASetVertexBuffers(0, 1, &_vertexBuffer, &stride, &offset);

// Set primitive topology
DEVICECTX->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
 

CHECK_FAIL은 그냥 임의로 만든 매크로 함수이니 무시해도 상관없다. D3D11_BUFFER_DESC 구조체를 통해 몇 가지 정보를 입력하고 BindFlags를 D3D11_BIND_VERTEX_BUFFER로 등록하여 VertexBuffer로 사용할 것이라고 명시해준다. D3D11_SUBRESOURCE_DATA 구조체는 버퍼를 만들 때 초기 데이터가 존재할 경우 사용하는 구조체이며 없다면 CreateBuffer함수에서 해당 위치를 nullptr로 지정해주면 된다. (대신 다른 방법으로 버퍼를 채워주긴 해야한다.) 각 인자의 자세한 내용은 MSDN을 참고하길 바란다. IASetVertexBuffer를 통해 버퍼를 등록하고, IASetPrimitiveTopology함수는 각 정점을 어떤식으로 연결할 것인지 알려주는 함수이고 TRIANGLELIST로 지정하면 삼각형으로 연결하겠다는 것이다. 이렇게 하면 정점 정보를 등록하는 것은 끝났다. 하지만 정점의 위치 정보만 넘겨줬을 뿐 아직 어떤 형식인지 어떻게 쓰일 것인지는 넘겨주지 않은 상태이다. 그래서 D3D11_INPUT_ELEMENT_DESC구조체를 통해 이를 알려주어야 한다. 이것은 Vertex Shader 단계에서 사용된다.

HRESULT hr;
CreateVertexShader(path, "VS_Main", "vs_5_0");
CreatePixelShader(path, "PS_Main", "ps_5_0");

// Define the input layout
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 },
};
uint32 numElements = ARRAYSIZE(layout);

// Create the input layout
hr = DEVICE->CreateInputLayout(layout, numElements, _vsBlob->GetBufferPointer(),
	_vsBlob->GetBufferSize(), &_vertexLayout);
_vsBlob->Release();
CHECK_FAIL(hr, L"Failed Create Input Layout");

// Set the input layout
DEVICECTX->IASetInputLayout(_vertexLayout);
 

CreateVertexShader, CreatePixelShader 함수는 DX sample sdk와 MSDN을 참고하면서 만들었다. 찾아보면 셰이더 파일을 컴파일하고 객체로 만드는 방법은 여러가지가 있으니 참고하길 바란다. 딱히 중요한 내용은 아니니 넘어가고, 셰이더 코드에는 VS_IN 구조체에 pos와 color 객체에 Input layout에서 지정한 POSITION과 COLOR가 있는 것을 볼 수 있는데 이것을 위해 Input layout을 작성했다고 보면되고 Input layout에서의 이름과 셰이더 코드의 이름이 정확히 일치하지 않으면 오류가 나니 주의해야 한다.

struct VS_IN
{
    float3 pos : POSITION;
    float4 color : COLOR;
};

struct VS_OUT
{
    float4 pos : SV_Position;
    float4 color : COLOR;
};

VS_OUT VS_Main(VS_IN input)
{
    VS_OUT output = (VS_OUT)0;

    output.pos = float4(input.pos, 1.f);
    output.color = input.color;

    return output;
}

float4 PS_Main(VS_OUT input) : SV_Target
{
    return input.color;
}
 

셰이더 문법 자체가 C언어와 유사해서 어느정도 해석이 가능할 수 있는데 쉽게 보면 VS_Main은 Vertex Shader단계 PS_Main은 Pixel Shader 단계라고 보면되고, 둘다 그냥 입력받은 값을 거의 그대로 출력하는 것을 확인할 수 있다. 각 셰이더를 등록할때는 DeviceContext를 통해 VSSetShader, PSSetShader함수로 지정할 수 있고, 예상할 수 있듯이 앞으로 사용할 헐셰이더, 지오메트리 셰이더도 HS, GS 와 같은 이름으로 존재한다. 이렇게까지하면 마지막으로 Draw Call만 남았다. 이 부분은 이제 Render Target에 말 그대로 그려달라는 요청을 하는 함수이고 여기서는 Draw함수를 썻지만 DrawIndexed, DrawIndexedInstanced 등 다양한 Draw함수들이 있는데 상황에 맞춰 사용하면 된다.

 

 

 

728x90

'C++ > DirectX' 카테고리의 다른 글

6. 색인 버퍼(Index Buffer)  (0) 2023.05.15
5. 상수 버퍼(Constant Buffer)  (0) 2023.05.15
3. 장치 초기화  (1) 2023.05.14
2. DX11의 렌더링 파이프라인  (0) 2023.05.14
1. WinAPI 기초  (0) 2023.05.14

+ Recent posts