본문 바로가기

그래픽스

[그래픽스] 렌더링 파이프라인

Geometry 정의

물체를 그리기 위해 물체의 정보가 있어야 한다.

vertices 정보 ( position, color, normal 등) 와 indices 정보를 넣어준다.

 

 

Buffer 생성

GPU로 데이터를 옮기기 위해 

정점 정보를 가진 VertexBuffer, index 정보를 가진 IndexBuffer, 변환 정보를 가진 ConstantBuffer을 생성한다.

 

 

CreateVertexBuffer

Geometry 정의할 때 만든 vertices 벡터를 GPU에서 사용하기 위한 D3D11Buffer로 만드는 과정이다.

D3D11_BUFFER_DESC 구조체를 통해 D3D11Buffer의 정보를 세팅한다.

정점 정보는 GPU에서 수정할 일이 없으므로 Usage를 IMMUTABLE (GPU에서 읽기만 가능) 로 설정한다.

그리고 GPU로 어떤 데이터를 어떻게 보낼지 D3D11_SUBRESOURCE_DATA 구조체를 통해 설정한다.

vertexBufferData.pSysMem = vertices.data();는 vertices의 시작점부터 데이터를 보내라는 뜻이다.

CreateIndexBuffer도 형식은 CreateVertexBuffer와 같다.

 

 

CreateConstantBuffer

차이점은 매프레임마다 변환 정보를 CPU에서 GPU로 보내줘야 하기 때문에 Usage를 DYNAMIC으로 설정하고

CPUAccessFlags를 CPU_ACCESS_WRITE로 설정한다. CPU가 GPU에 쓸(write) 수 있다는 의미이다.

 

 

INPUT_ELEMENT_DESC

이제 쉐이더를 만들어야 하는 데, 어떤 데이터를 넣어줘야 하는지 작성해야 한다.

 vector<D3D11_INPUT_ELEMENT_DESC> inputElements = {
        {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
        {"COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 4 * 3, D3D11_INPUT_PER_VERTEX_DATA, 0},
    };

위 코드는 정점에 위치 정보와 색 정보를 넣어주는 예시이다. 각각 float 3개를 가지는 형태로 되어있으므로

R32(4바이트),G32(4바이트),B32(4바이트)_FLOAT으로 되어 있고 POSITION은 0, COLOR은 POSITION이 4*3바이트의 

크기이고 그 다음에 들어가므로 그 위치인 4*3을 넣어주고 있다.

 

 

CreateVertexShader, CreateInputLayout

D3DCompileFromFile함수를 통해 쉐이더를 컴파일하고 난 데이터를 임시버퍼 ShaderBlob, ErrorBlob에 저장한다.

그리고 ShaderBlob을 이용해 VertexShader와 InputLayout을 생성한다. InputLayout은 VertexShader에 어떤 데이터가 들어가는지 지정해준다. (float3 pos, float2 texcoord...) 그래서 vertexshader와 같이 만든다.

 

 

------- 여기까지가 Initialize에서 진행되는 상황이고 이제 Update에서 ConstantBuffer에 변환 행렬을 집어넣는다.

 

 

CPU에서 물체가 어떻게 움직일지 계산하고 GPU로 ConstantBuffer로  행렬을 보내는 것이 일반적이다.

그런데 DirectX Simple Math (CreateRotation, CreateTranslation..)는 Row-Major을 사용하고 HLSL은

Column-Major을 사용한다. 그래서 ConsantBuffer로 행렬을 보낼 때 Transpose를 해서 보내야 한다.

 

 

 

Model 변환 (World)

    m_constantBufferData.model = Matrix::CreateScale(0.5f) * Matrix::CreateRotationY(rot) *
                                 Matrix::CreateTranslation(Vector3(0.0f, -0.3f, 1.0f));
    m_constantBufferData.model = m_constantBufferData.model.Transpose();

DirectX Simple Math는 scale, rotation, translation변환을 함수로 row-major 행렬로  만들어준다. 

그 행렬을 곱한 것들을 constantBuffer의 model에 저장할 때 Transpose한다.

 

 

시점 변환(View)

 m_constantBufferData.view =
        XMMatrixLookAtLH({0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f});
    m_constantBufferData.view = m_constantBufferData.view.Transpose();

시점 변환은 카메라는 가만히 있고 model 행렬을 역으로 곱해주는 방식으로 진행된다.

XMMatrixLookAtLH 함수를 통해 간단하게 실행할 수 있는데, 카메라의 위치, 카메라가 바라보는 방향 (look), 

카메라의 위쪽 방향 (up)을 넣어주면 시점 변환을 할 수 있다.

 

 

투영 변환(Projection)

 const float aspect = AppBase::GetAspectRatio();
    if (m_usePerspectiveProjection) {
        const float fovAngleY = 70.0f * XM_PI / 180.0f;
        m_constantBufferData.projection =
            XMMatrixPerspectiveFovLH(fovAngleY, aspect, 0.01f, 100.0f);
    } else {
        m_constantBufferData.projection =
            XMMatrixOrthographicOffCenterLH(-aspect, aspect, -1.0f, 1.0f, 0.1f, 10.0f);
    }
    m_constantBufferData.projection = m_constantBufferData.projection.Transpose();

투영 변환은 직교인지 원근인지에 따라 다른 함수를 사용하는데, 원근일 경우

시야각, 비율, near/far clip plane을 넣고 직교일 경우엔 상,하,좌,우와 near/far clip plane을 넣어준다.

 

 

    template <typename T_DATA>
    void UpdateBuffer(const T_DATA &bufferData, ComPtr<ID3D11Buffer> &buffer) {
        D3D11_MAPPED_SUBRESOURCE ms;
        m_context->Map(buffer.Get(), NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms);
        memcpy(ms.pData, &bufferData, sizeof(bufferData));
        m_context->Unmap(buffer.Get(), NULL);
    }

업데이트 될때 마다 바뀌는 ConstantBuffer을 CPU에서 GPU로 복사해줘야 한다.

 

 

------- Update가 끝나면 Render이 시작된다.

 

 

Input Assembler(IA)

입력된 정점들을 출력할 수 있는 최소 단위까지 모아 결합한다.

예를 들어 정점 3개를 모아 삼각형 하나로 결합시키는 것이다.

 

Vertex Shader(VS)

그리고 ConstantBuffer에서 받은 변환 정보를 이용해

정점들의 변환을 진행한다.

 

Tessellation, GeometryShader

입력 받은 정점들을 더 자세히 만들어 주는 것이다.

단순한 모델 -> 상세한 모델 

 

Rasterization(RS)

기하 정보를 픽셀의 집합으로 만든다.

 

Pixel Shader(PS)

픽셀 단위로 색깔을 결정한다.

 

Output Merger Stage(OM)

실제 색을 결정한다.

ex) 겹쳐 있을 경우 어떤 색으로 표현할지 결정

 

렌더링할 때 함수들의 순서가 렌더링 파이프라인의 순서와 같지는 않다. 

Set함수들은 렌더링을 어떻게 해야할지 옵션만 설정해 주는 것이며 그 옵션을 사용해 렌더링하는 것은

DrawIndexed 함수가 호출된 이후에 진행된다.

 

m_context->RSSetViewports(1, &m_screenViewport);

먼저 Rasterize 단계에서 사용되는 Viewport를 설정한다.

 

  m_context->ClearRenderTargetView(m_renderTargetView.Get(), clearColor);
  m_context->ClearDepthStencilView(m_depthStencilView.Get(),
                                     D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

그리고 프레임을 그리기전에 RenderTargetView와 DepthStencilView를 지워 이전에 그렸던 것을 지워준다.

 

    m_context->OMSetRenderTargets(1, m_renderTargetView.GetAddressOf(), m_depthStencilView.Get());
    m_context->OMSetDepthStencilState(m_depthStencilState.Get(), 0);

RenderTargetView와 DepthStencilView를 다시 설정해주고 DepthStencilState까지 설정해준다.

 

   m_context->VSSetShader(m_colorVertexShader.Get(), 0, 0);
   m_context->VSSetConstantBuffers(0, 1, m_constantBuffer.GetAddressOf());
   m_context->PSSetShader(m_colorPixelShader.Get(), 0, 0);
   m_context->RSSetState(m_rasterizerSate.Get());

그 후 VSShader, ConstantBuffer, PSShader을 세팅해주고 RasterizerState를 세팅한다.

 

    m_context->IASetInputLayout(m_colorInputLayout.Get());
    m_context->IASetVertexBuffers(0, 1, m_vertexBuffer.GetAddressOf(), &stride, &offset);
    m_context->IASetIndexBuffer(m_indexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
    m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    m_context->DrawIndexed(m_indexCount, 0, 0);

InputLayout, VertexBuffer, IndexBuffer을 설정한다. 그리고 PrimitiveTopology를 설정해야 하는데 정점을 보내는 방식이다.

TRIANGLELIST는 정점을 세개씩 묶어 삼각형의 형태로 보내는 것이고 TRIANGLESTRIP은 삼각형을 하나 더 그릴 때마다 index를 한개씩만 추가하면 다음 삼각형을 그려주는 형식이다. DrawIndexed를 실행하면 렌더링이 시작된다.

마지막에 swapchain->Present를 실행하면 화면에 렌더링한 결과를 보여준다.