프로그래밍/공부

[DirectX] 렌더링 파이프라인 - (1)

Sik.K 2023. 1. 4. 16:33

DirectX에서 렌더링, 즉 화면에 결과물을 출력하기 위해선 렌더링 파이프라인이라는 과정을 거쳐야 한다.

 

렌더링 파이프라인이란 화면에 그래픽스 결과물을 출력하기 위한 구조를 의미한다.

 

각 과정의 Output은 다음 과정의 Input이다.

 

DrawCall -> IA(Input Assembler) -> VS(Vertex Shader) -> RS(Rasterizer) -> PS(Pixer Shader) -> OM(Output Merger)

 

더 상세한 과정이 있지만 기초적인 출력의 결과물을 확인하기 위해선 위의 과정은 반드시 필수적이다.

 

이 게시물에서는 IA에 대한 개념을 확인하고자 한다.

 

IA(Input Assembler)

 

쉽게 정의하면 정점에 대한 정보를 입력 받는 구간이다.

 

DX11에는 ID3D11로 시작하는 인터페이스 클래스가 있다.  종류는 차후에 알아볼 예정이지만 IA에서 사용하는 부분은 ID3D11Device와 ID3D11DeviceContext이다.

 

ID3D11Device는 실제 Vertex의 정보를 담을 Buffer를 생성하는 역할을 가진다.

 

//CreateVertexBuffer
    {
        D3D11_BUFFER_DESC desc;
        desc = { 0 };
        desc.Usage = D3D11_USAGE_DEFAULT; //버퍼를 읽고 쓰는 방법
        desc.ByteWidth = sizeof(VertexPC) * 4; //버퍼 크기 (바이트)입니다.
        desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; //버퍼가 파이프 라인에 바인딩되는 방법을 식별하십시오

        D3D11_SUBRESOURCE_DATA data = { 0 };
        //하위 리소스를 초기화하기위한 데이터를 지정합니다.
        data.pSysMem = Vertex;
        //초기화 데이터의 포인터.

        //버퍼 만들기
        HRESULT hr = D3D->GetDevice()->CreateBuffer(&desc, &data, &fillVertexBuffer);
        assert(SUCCEEDED(hr));
    }

 

기본적인 VertexBuffer를 생성하는 desc를 작성하면 위와 같다.

 

desc라는 건 단순하게 생각하면 설계도를 의미한다. VertexBuffer를 생성하기 위한 설계도를 작성하여 이를 함수로 넘겨주면 VertexBuffer를 생성하는 것이다.

 

// 멤버 변수 의미

 

ByteWidth - UINT 형식이며 버퍼의 크기(바이트)를 의미한다.

 

Usage - D3D11_USAGE라는 형식을 사용하며 버퍼를 읽고 쓰는 방법을 식별하며 가장 일반적인 값은 D3D11_USAGE_DEFAULT; 이다.

 

BindFlags - UINT 형식이며 버퍼가 파이프라인에 바인딩되는 방법을 식별한다. 상세한 내용은 추후에 다루겠다.

 

CPUAccessFlags - UINT 형식이며 CPU 액세스 플래그에 대해 WRITE 혹은 READ를 설정한다. 마찬가지로 상세한 내용은 추후에 다루겠다.

 

MiscFlags - 기타 플래그를 의미하며 사용하지 않을 경우 0을 입력한다.

 

StructureByteStride - 버퍼가 구조화된 버퍼를 나타내는 경우 버퍼 구조의 각 요소 크기를 의미한다. 이는 구조적 버퍼에 대한 개념을 이해해야 하며 이는 추후에 작성하겠다.

 

위의 변수의 값을 설정한 desc을 이용해 VertexBuffer를 작성한다.

 

 

렌더링 파이프라인은 GPU를 사용하여 연산을 한다. 앞서 나는 개인적으로 작성한 VertexPC라는 구조체의 Vertex라는 변수에 정점에 대한 정보를 저장해두었는데 이를 곧바로 GPU로 넘기는 것이 불가능하기 때문에 D3D11_SUBRESOURCE_DATA라는 구조체를 통한 변수를 생성하여 이 변수를 매개변수로 Create 함수로 넘겨주어야 한다.

 

 

이후에 IA 단계에서는 여기서 작성한 VertexBuffer와 Primitive Topology라는 것을 설정하여 넘겨주어야 한다.

 

Primitive Topology - 기본 도형 위상 구조

 

어려운 단어로 이름이 정의되어 있지만 쉽게 말하면 정점을 이용해 어떻게 그림을 그릴지에 대한 방법을 의미한다.

 

이는 enum을 통해 열거형으로 작성이 되어 있으며 이에 대한 정보는 링크로 대체하겠다.

 

https://learn.microsoft.com/ko-kr/windows/uwp/graphics-concepts/primitive-topologies

 

기본 토폴로지 - UWP applications

Direct3D는 여러 개의 기본 토폴로지를 지원하는데 이 토폴로지는 점 목록, 선 목록, 삼각형 스트립 등 파이프라인이 꼭짓점을 해석 및 렌더링하는 방식을 정의합니다.

learn.microsoft.com

 

 

VertexBuffer를 생성하고, 어떻게 그릴 지에 대한 Primitive Topology를 설정했다면 이를 IA 단계에 넘겨주는 것으로 과정이 종료된다.

 

이를 위해 존재하는 것이 바로 DeviceContext이며, 각 렌더링 과정에 바인딩 해주는 역할을 담당한다.

 

오브젝트 Rect라는 클래스에 내부에 존재하는 Render 함수가 있다고 가정해보자.

 

void ObRect::Render
{
	basicShader->Set();
    
    UINT stride = sizeof(VertexPC);
    UINT offset = 0;
   
    D3D->GetDC()->IASetVertexBuffers(
    0,
    1,
    &vertexBuffer,
    &stride,
    &offset
    );
    
    D3D->GetDC()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
    D3D->GetDC()->Draw(4,0);
}

 

초기에 UINT 자료형으로 stride와 offset 변수를 선언해준다.

 

밑에 보면 GetDC로 호출한 DeviceContext 내부에 있는 IASetVertexBuffers와 IASetPrimitiveTopology 함수를 호출하여 매개변수를 넘겨주는데, 첫 번째 함수에서 넘겨주는 것이 우리가 처음에 desc로 만든 VertexBuffer이며, 두 번째 함수에서 넘겨주는 것이 바로 PrimitiveTopology이다.

 

이 과정을 거치면 렌더링 파이프라인의 첫 단계인 IA 단계가 끝이 나게 되고 이후에 이 Output이 VS의 Input으로 넘어가게된다.