Graphics Pipeline

Shader Stage 들이 직접 코딩할 수 있는 부분이고, 나머지는 API 내부에서 처리해준다.

우리가 다룰 부분은 Vertex Shader와 Pixel Shader이다.

  • Vertex shader: 각 vertex에 적용할 수 있는 transformation이나 lighting 등을 수행
  • Pixel(fragment) shader : 각 pixel마다 색상을 계산하여 처리

 

 

 

 

Input-Assembler(IA) Stage

◇ CPU가 제공한 정보들을 primitive 형태로 모아준다.

  • 일반적으로 primitive는 삼각형을 의미한다. 
  • 관련해서 우리가 해줘야 할 일들은 다음과 같다.
    • 1) Create input buffers
    • 2) Create the input-layer object
    • 3) Bind objects to the input-assembler stage
    • 4) Specify the primitive type
    • 5) Call draw methods

 

 

 

IA Stage  - 1) Create input buffers

IA Stage에 전달해야 하는 버퍼 : Vertex buffer, Index buffer

 

Create a Vertex Buffer

 

1. Vertex 구조체를 만든다.

2. Vertex에 기하 정보(position)를 담은 메모리를 할당한다.

// header
struct SimpleVertex
{
	XMFLOAT3 Pos;
};

// global variables
ID3D11Buffer* g_pVertexBuffer = nullptr;

///////////////////////////////

SimpleVertex sVertices[] =
    {
        {XMFLOAT3 (0.0f, 0.5f, 0.5f)},
        {XMFLOAT3 (0.5f, -0.5f, 0.5f)},
        {XMFLOAT3 (-0.5f, -0.5f, 0.5f)},
    };

 

3. Buffer Description(버퍼 정보들)을 만들어준다. 

  • ByteWidth는 buffer의 크기, BindFlags는 어떤 buffer인지를 나타냄.

4. SUBRESOURCE_DATA를 만든다

  • 실제 데이터 정보에 대한 포인터를 전달해줌.
D3D11_BUFFER_DESC bd = {};
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = sizeof(SimpleVertex) * 3;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;


D3D11_SUBRESOURCE_DATA initData = {};
initData.pSysMem = sVertices;

 

5. CreateBuffer() 함수를 호출한다.

  • D3DDevice에 buffer를 만드는 것을 요청한다.
  • 함수 인자 값으로 앞서 만든 buffer description, subresource data, 그리고 buffer의 주소를 넣어준다.
hr = g_pd3dDevice->CreateBuffer(
    &bd,
    &initData,
    &g_pVertexBuffer
    );

if (FAILED(hr))
    return hr;

 

 

 Create a Index Buffer

같은 방식으로 Index Buffer도 만들어준다. 

 

1. Buffer를 만들고 index 정보를 저장한다.

2. Buffer description을 생성한다. 이때 flag를 Index buffer로 해야한다.

ID3D11Buffer* g_pIndexBuffer = nullptr;

///////////////////////////////

WORD sIndices[] = { 0, 1, 2 };

bd = {};
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = sizeof(WORD) * 3;
bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
bd.CPUAccessFlags = 0;

 

3. SUBRESOURCE_DATA를 만든다.

4. CreateBuffer() 함수를 호출한다.

initData = {};
initData.pSysMem = sIndices;

hr = g_pd3dDevice->CreateBuffer(
    &bd,
    &initData,
    &g_pIndexBuffer
);

if (FAILED(hr))
    return hr;

 

 

 

 

IA Stage  - 2)  Create the Input-Layout Object

  Input-Layout ObjectIA Stageinput에 대한 정보(앞에서 만든 buffer )를 전달해주는 역할을 한다.

  • Input data에 대한 설명을 해주는 input-element description을 만들어 준다.
  • 하나의 vertex는 position, normal, uv 등의 element 정보를 가질 수 있음. 하지만 지금은 position만 사용할 예정이기 때문에 이를 첫번째 파라미터로 명시해줬다.
  • 2번째 파라미터 : semantic index
  • 3번째 파라미터 : element 데이터의 format = 입력 데이터 타입에 따라 명시해주면 된다.
ID3D11InputLayout* g_pVertexLayout = nullptr;

///////////////////////////////

ID3DBlob* pVertexShaderBlob = nullptr;
D3D11_INPUT_ELEMENT_DESC layouts[] =
{
    {"POSITION",
    0,
    DXGI_FORMAT_R32G32B32_FLOAT,
    0,
    0,
    D3D11_INPUT_PER_VERTEX_DATA,
    0
    },
};

UINT uNumElements = ARRAYSIZE(layouts);

 

  • 4번째 파라미터 : input slot

IA Stage에는 여러 개의 vertex buffer가 들어갈 수 있다.(= 오브젝트가 여러 개인 경우)

각 n개의 vertex buffer를 하나의 슬롯으로 할당하여 IA의 input으로 들어간다. 따라서 Input-layout object에서는 몇 번째 slot을 다룰지에 대한 정보가 필요하다. 이게 4번째 파라미터 값이다.

  • 5번째 파라미터 : offset = 데이터를 어디서부터 읽을지 

이후 D3DDevice에세 InputLayout을 만들어달라고 요청한다.

  • CreateInputLayout() 파라미터 : layout / element 수 / vertex shader의 포인터 / vertex shader의 크기 / out 파라미터
  • Blob : shader 코드 데이터를 가리킨다고 생각하면 된다.
hr = g_pd3dDevice->CreateInputLayout(
    layouts,
    uNumElements,
    pVertexShaderBlob->GetBufferPointer(),
    pVertexShaderBlob->GetBufferSize(),
    &g_pVertexLayout
);

pVertexShaderBlob->Release();

if (FAILED(hr))
    return hr;

 

 

 

 

 

 

IA Stage  - 3)  Bind Objects to the IA Stage

  지금까지 만들어준 input buffer들과 input layout objectIA stagebinding 해준다.

  • Stride : vertex buffer에서 하나의 vertex 정보 크기 = SimpleVertex 구조체의 크기
  • Offset : vertex buffer에서 시작 지점
UINT uStride = sizeof(SimpleVertex);
UINT uOffset = 0;

g_pImmediateContext->IASetVertexBuffers(
    0, 		// slot
    1, 		// buffer의 개수
    &g_pVertexBuffer, 	// vertex buffer
    &uStride, 	    	// stride
    &uOffset	 	// offset
);

g_pImmediateContext->IASetIndexBuffer(
    g_pIndexBuffer,	 // index buffer
    DXGI_FORMAT_R16_UINT,	 // format of the index data
    0		 	 // offset
);

///////////////////////////

g_pImmediateContext->IASetInputLayout(g_pVertexLayout);

 

 

 

 

 

IA Stage  - 4)  Specify the Primitive Type

  IA stage에서 어떤 primitive type으로 assemble할 지에 대해 설정
앞서 언급한 것 처럼 삼각형 primitive로 설정해줬다.
g_pImmediateContext->IASetPrimitiveTopology(
    D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST
);

 

 

 

 

IA Stage  - 5)  Call Draw Method

  • Binding이 된 후 draw 함수를 호출해서 그려 달라고 요청한다. 
  • Draw 요청을 하게 되면, DirectX가 그래픽스 파이프라인을 쭉 실행하여 화면에 출력해 준다.
  • Draw 함수 종류
    • Draw(): Index buffer 없이 vertex에 따라 draw
    • DrawIndexed(): index buffer를 이용하여 index 순서대로 vertex그려준다.

 

 

 

 

Vertex Shader(VS) Stage

vertextransformation, skinning, per-vertex lighting 과 같은 처리들을 해준다.

  • vertex 개수만큼 반복 실행된다. = VS의 input도 vertex 하나이고, output도 vertex 하나이다.
  • VS는 하나의 vertex를 입력으로 하고, 하나의 vertex를 출력하는 함수라고 생각하면 된다.
  • 그리고 앞으로 이 함수를 우리가 정의해 줄 예정이다.

 

 

 

Vertex Shader

  • Shader는 High-Level Shading Language(HLSL)이라는 언어로 작성된다. C++이 아닌 HLSL로 따로 작성하는 이유는 GPU에서 Matrix와 Vector 연산을 훨씬 빠르게 처리할 수 있기 때문이다.
  • VS에서 가장 중요한 일은 좌표계 사이를 transform 해주는 것이다.

 

  • input과 output은 float4 형태의 벡터(x,y,z,w)
  • POSITION : input parameter의 sementic
  • SV_POSITION : 함수의 반환값. 이때 SV는 System Value를 의미한다. = Clip-space의 position

 

 

 

 

Rasterizer Stage

3차원 좌표계에 있는 vertex들을 화면에 그리기 위한 2D pixel(raster image)로 변환해준다.

3D graphic을 2D pixel에 mapping 시키는 단계

 

Rasterization

  • 화면은 2차원 grid로 pixel로 되어있고 각 pixel들은 색깔을 가지고 있다. 이 pixel들을 칠해서 모니터에 화면이 만들어진다.
  • 아래 그림에서 왼쪽은 3개의 vertex로 구성된 삼각형이고 이를 rasterization 하면 오른쪽 그림처럼 된다.
  • GPU는 어떤 pixel이 삼각형이 포함되고, 칠해져야 하는지 여부를 계산하게 된다. 이후 칠해져야 하는 pixel들만 pixel shader 단계를 진행하게 된다.

 

 

 

 

Pixel Shader(PS) Stage

  최종 pixel의 색깔을 결정하는 단계 

  • Per-pixel 단위로 Lighting이나 post-processing 같은 처리를 해준다.
  • Input : pixel의 색상을 결정할 정보들
  • Output : 계산 후 결정된 최종 pixel 색상 정보 
float4 PS(float4 Pos : SV_POSITION) : SV_Target
{
    return float4(1.0f, 1.0f, 0.0f, 1.0f);
}
  • 마찬가지로 input과 output은 float 4 타입이다.
  • Input의 semantic : SV_POSITION = vertex shader의 output
  • Output으로 color(RGBA)를 내보낸다. = SV_Target.

 

 

 

 

Creating the Shaders

D3DCompileFromFile 함수의 인자로 파일 이름이 들어가고, shader가 실행될 entry pointVS로 설정해준다. 이후 컴파일된 결과를 ppBlobOut으로 받아온다.
    DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;

#if defined (DEBUG) || defined(_DEBUG)
    dwShaderFlags |= D3DCOMPILE_DEBUG;
    dwShaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
    
    ID3DBlob* pErrorBlob = nullptr;
    hr = D3DCompileFromFile(
    “VS.hlsl”, 	// FileName
    nullptr, 		// shader macros
    nullptr, 		// include files
    “VS”,		// Entry point
    “vs_5_0”,		// shader target
    dwShaderFlags, 	// flag1 
    0, 		// flag2
    ppBlobOut, 		// ID3DBlob out
    &pErrorBlob		// error blob out
    );

 

컴파일 한 뒤에는 Vertex Shader Object를 만들어준다. 이 과정 역시 Device가 요청하게 된다.

  • 파라미터 : 컴파일된 shader의 포인터 / shader의 size / Class link(nullptr) / shader를 가리키는 포인터( ID3D11VertexShaer*)
ID3D11VertexShader*  g_pVertexShader = nullptr;

///////////////////////////////

    hr = g_pd3dDevice->CreateVertexShader(
        pVertexShaderBlob->GetBufferPointer(), 
        pVertexShaderBlob->GetBufferSize(), 
        nullptr, 
        &g_pVertexShader);

    if (FAILED(hr))
    {
        pVertexShaderBlob->Release();
        return hr;
    }

 

Pixel Shader도 마찬가지로 만들어준다.

ID3D11PixelShader*  g_pPixelShader = nullptr;

///////////////////////////////

    hr = g_pd3dDevice->CreatePixelShader(
    	pPixelShaderBlob->GetBufferPointer(), 
    	pPixelShaderBlob->GetBufferSize(), 
    	nullptr, 
    	&g_pPixelShader);

    if (FAILED(hr))
    {
        pPixelShaderBlob->Release();
        return hr;
    }

 

 

 

 

 

Setting the Shaders

이제 만들어둔 shader를 pipeline에 binding 해준다.

g_pImmediateContext->VSSetShader(g_pVertexShader, nullptr, 0);
g_pImmediateContext->PSSetShader(g_pPixelShader, nullptr, 0);

 

 

 

 

 

다음 포스팅에서는 3D Space와 Transformation 과정에 대해 자세히 다룰 예정이다.

 

 

Reference KHU 강형엽 교수님 강의의 실습 수업을 수강하며 정리한 내용입니다.

+ Recent posts