렌더링 파이프 라인이란?
3차원으로 만들어진 모델(Texture, Material, Animation, Geometry 등등)을 2차원 스크린 좌표계까지 투영하는 과정의 프로세스를 자세하게 표현한 것입니다.
컴퓨터의 메모리에 존재하는 3D Resource가 모니터에 출력되는 과정이 렌더링 파이프 라인을 따르게 됩니다.
초록색 부분은 Fixed Function(고정 단계) : 미리 정해진 특정한 연산들만 수행이 가능하고, 실행 시점에서 기능은 바꿀 수 없지만 상태 객체라는 개념을 이용하여 설정들의 변경이 가능합니다.
빨간색 부분은 Programmable 단계로 HLSL로 shader 프로그래밍이 가능한 단계.
렌더링 파이프라인의 핵심 5단계는
IA(Input Assembler) - VS(Vertex Shader) - RS(Rasterizer State) - PS(Pixel Shader) - OM(Output Merger)입니다.
1. Input Assembler
이 단계에서는 Index Buffer, Vertex Buffer를 GPU에게 꽂아주는 단계입니다.
Index Buffer란?
Vertex Buffer와 관련이 있는 버퍼입니다.
버퍼는 프로그래밍에서 GPU자원을 뜻합니다.
개별적으로 데이터를 하나하나 보내기 보다(대역폭에 부담이 가기 때문에) 하나의 구조체로 묶어서 보내기 위한 개념입니다.
Vertex Buffer에 있는 각 정점들이 연결되는 순서를 기록하기 위한 버퍼입니다.
이런 인덱스 버퍼를 GPU에게 전달해주면 GPU는 전달받은 인덱스 버퍼를 통해 Vertex Buffer에 필요한 정점들을 빠르게 찾아 낼 수 있습니다.
Index Buffer 사용하는 이유?
Vertex Buffer에 정점들에 대한 데이터를 보관할 때 정점들을 삼각형 단위로 쪼개서 보관해서 GPU에게 넘겨주는데
위처럼 하나의 사각형을 그리는 임무를 맡은 GPU가 정점을 읽어서 그릴려고 할 때
빨간색 원부터 0 -> 1 -> 2 읽고 다시 초록색 2-> 1 -> 3 읽어들여서 삼각형 + 삼각형 그려서 사각형을 만드는 것보다
파란색 0 -> 1 -> 2 -> 3으로 빠르게 읽어라는 설명서(Index Buffer)를 보고 읽는다면 속도가 더 빠를 것입니다.
(추가적으로 D3D11_PRIMITIVE_TOPOLOGY의 값을 D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST로 설정하는게 거의 공식? 이라고 합니다. 즉 삼각형으로 각 정점들을 그리는게 무언의 약속 느낌?)
실제로 이런 쌩데이터를 DX에서 먼저 입력을 해주어야하는데 코드를 작성하면 아래와 같이
// index라는 주석에서 0, 1, 2, 2, 1, 3순으로 인덱스를 셋팅한다음에 이것을 나중에 Index Buffer에 설정해줍니다.
void GeometryHelper::CreateRectangle(shared_ptr<Geometry<VertexTextureData>> geometry)
{
// Vertex
// 13
// 02
vector<VertexTextureData> verticise;
verticise.resize(4);
verticise[0].position = Vec3(-0.5f, -0.5f, 0.0f);
verticise[0].uv = {0.f, 1.f};
verticise[1].position = Vec3(-0.5f, 0.5f, 0.f);
verticise[1].uv = {0.f, 0.f};
verticise[2].position = Vec3(0.5f, -0.5f, 0.f);
verticise[2].uv = {1.f, 1.f};
verticise[3].position = Vec3(0.5f, 0.5f, 0.f);
verticise[3].uv = {1.f, 0.f};
geometry->SetVertices(verticise);
// Index
vector<uint32> indices = { 0, 1, 2, 2, 1, 3 };
geometry->SetIndices(indices);
}
Vertex Buffer란?
어떤 모델(리소스)의 정점 데이터 정보들을 들고 있는 버퍼입니다. 3D 구체를 Vertex Buffer에 담는다고 하면은
아래의 구체의 동글빼이? 가 구체의 각 정점을 담당합니다. 이 정점 위치 정보들을 하나하나 Vertex Buffer에 넣어주어야 합니다. (나중에는 일정한 데이터 형태의 파일을 파싱해서 빠르게 담게 될 것입니다)
2. Vertex Shader
두번 째가 Vertex Shader 단계입니다. 줄여서 VS단계라고 하겠습니다.
VS단계에서는 Vertex Buffer에 있는 정점 데이터들을 3D공간을 변환시켜주는 작은 프로그램 입니다. VS단계에서 여러 행렬 연산을 합니다.
대표적으로 WVP 행렬 연산을 수행합니다.
World Matrix(SRT행렬 연산) * View Matrix(카메라 행렬 연산) * Projection Matrix(투영)을 담당합니다.
VS_OUTPUT VS(VS_INPUT input)
{
VS_OUTPUT output;
// WVP
float4 position = mul(input.position, matWorld);
position = mul(position, matView);
position = mul(position, matProjection);
output.position = position;
output.uv = input.uv;
if (useAnimation)
{
// 500 / 1000
output.uv *= spriteSize / textureSize;
output.uv += spriteOffset / textureSize;
}
return output;
}
이때 CPU에서 연산을 수행한 WVP데이터를 Shader에 넘겨주기 위해, 상수 데이터를 보내주는 버퍼가 하나 필요한데 이것이 '상수 버퍼'입니다.
Constant Buffer는 WVP같은 행렬을 Shader로 넘겨주고 각 정점에 곱해주는 역할을 수행합니다.
위의 코드는 제 프로젝트 파일의 default.hlsl라는 shader프로그램에 작성되어 있는 코드인데요
잠깐 HLSL의 개념을 보면은 아래와 같습니다.
HLSL (High Level Shader Language)
.hlsl라는 확장자를 가지는 파일은 DirectX11 에서 사용하는 정점 및 shader프로그램을 작성할 때 사용하는 프로그래밍 언어입니다.(c언어와 유사)
hlsl 프로그램 파일은 전역변수, 타입 정의, Vertex Shader, Pixel Shader, Geometry Shader로 구성되어 있습니다.
이어서 위의 구체에 대한 정보(Vertex Buffer)는 리소스로 관리되면서 '불변'하는 데이터 입니다.
template <typename T>
void Create(const vector<T>& vertices)
{
_stride = sizeof(T);
_count = static_cast<uint32>(vertices.size());
D3D11_BUFFER_DESC desc;
Z(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_IMMUTABLE;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth = (uint32)(_stride * _count);
D3D11_SUBRESOURCE_DATA data;
Z(&data, sizeof(data));
data.pSysMem = vertices.data();
H hr = _device->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
C(hr);
}
desc.Usgae의 D3D11_USAGE_IMMUTABLE로 만들어서 CPU, GPU는 수정할 수 없습니다.
이동같은 것을 처리하기 위해서 이동에 필요한 '정보'를 받아서 Shader에 넘겨주어 각 정점에 곱할 수 있게 해줍니다.
VS라는 Vertex Shader 진입접은 .hlsl파일의 속성 -> HLSL Compiler -> General에서 아래와 같이 설정하였습니다.
3. RS (Rasterizer State)
이 단계에서 처리하는 것들은
- 보이지 않는 primitive제거 (은면 제거, 컬링 처리)
- 좌표의 뷰포트 전환
- 시제 테스트
- 깊이 바이어스 계산
- 프리미티브들을 렌더 타겟 상의 텍셀 단위로 변경
- 멀티 샘플링이나 필 모드의 설정에 따른 처리
등이 있습니다.
간단하게 말하면 Vertex Shader가 넘겨준 부분에 해당하는 모든 픽셀을 '판별'하는 단계입니다.
RS를 생성하는 코드는 아래와 같습니다.
void RasterizerState::Create()
{
D3D11_RASTERIZER_DESC desc;
Z(&desc, sizeof(desc));
desc.FillMode = D3D11_FILL_SOLID; // 기본 모드 (중요)
// D3D11_FILL_WIREFRAME 와이어 프레임 모드
desc.CullMode = D3D11_CULL_BACK; // 기본 모드 (중요)
desc.FrontCounterClockwise = false; // 기본 모드 (중요)
H hr = _device->CreateRasterizerState(&desc, _rasterizerState.GetAddressOf());
C(hr);
}
기본 모드로 FillMode는 SOLID, CullMode는 Cull Back (뒷면은 그리지 않겠다), FrontCounterClockWise는 false로 설정하였는데 이는 CullBack에서 앞뒤 판변을 하는 설정값입니다.
a가 물체를 바라볼 때 시계 방향으로 바라본다면 b가 바라 볼 때의 a가 바라보는 시계방향은 자신이 생각한 시계방향의 반대 방향일 것입니다.
FrontCounterClockWise는 false는 시계 방향으로 봐서 앞뒤를 판별하겠다라고 설정하는 부분입니다.
추가적으로
Sampler State
유한한 픽셀 크기를 가질 수 밖에 없는 Texture의 각 픽셀 사이를 어떻게 처리할 것인지 결정하는 State.
BlendState
렌더링 파이프라인에서 Pixel Shader를 거쳐 생성된 단편이 렌더 타겟에 어떻게 적용될지를 결정하는 상태값입니다.
4. PS (Pixel Shader)
그리고자 하는 오브젝트(모델, 리소스)에 색상을 입힐 때 사용하는 작은 프로그램입니다.
보여지는 모든 픽셀들에 대해 GPU에서 연산이 됩니다.
색상을 입히고(coloring), Texture를 입히고(texturing), 광원 효과(lighting)등등이 해당 단계에 들어갑니다.
5. OM (Output Merger)
렌더링 파이프라인의 마지막 단계라고 보시면 되겠습니다.
위의 렌더링 파이프 라인을 진행하면서 변경된 부분이나 설정값들을 Render Target View에 그리기 전에 최종적으로 병합 하는 과정정도로 보시면 되겠습니다.
즉 저희 모니터에 보여주는 단계입니다.
Render Target은 말 그대로 Render할 Target인데요 아래의 윈도우 창이 제가 그릴 Render Target View입니다.
이곳에 물체를 그렸다가 회색으로 채우고(화면을 다시 비우는 과정, 색은 무방) 다시 그릴 물체들을 이곳에 그립니다.
여기에는 SwapChain이라는 개념이 들어가는데 아래 블로그를 참고 해주시면 되겠습니다.
https://handhp1.tistory.com/10
정리하자면
- 이런식으로 Render Begin -> PipeLine -> Reder End 가 반복되면서 게임에 존재하는 물체들을 사용자에게 보여주는 것이고(이 과정이 얼마나 빠르냐에 따라 FPS가 결정됨. 1 -> 2 -> 3 과정 한번을 1초에 얼마나 반복하냐 입니다. 게임에서 60FPS정도가 나오려면 1프레임은 대략 16.66ms?? 정도 되는거 같습니다.)
- 렌더링 파이프 라인 과정은 크게 IA - VS - RS - PS - OM 단계로 이루어져 있다. 이정도가 되겠습니다.
'컴퓨터 그래픽스 > DirectX' 카테고리의 다른 글
NavMesh 만들기 (2) | 2024.12.07 |
---|---|
[Animation] 애니매이션을 만들어보기 (정리글) (1) | 2024.12.06 |
[Animation] 스키닝 애니매이션이란? (Skinning Animation) (1) | 2024.11.10 |
View Spcae Matrix 만들기 (0) | 2024.07.31 |
[DX] Multi Sampling (0) | 2023.08.23 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!