랜더링 파이프 라인을 개념적으로 숙지했다면, 직접 파이프 라인을 구성하고 VS(정점 쉐이더)와 PS(픽셀 쉐이더)를 정의하고 3차원 도형을 만들어 최종적으로 와이어프레임 도형을 출력한다.
이론적으로 하나하나 설명하기엔 이해가 부족할 수 있기 때문에 순서적으로 작성한 코드와 내용을 서술한다.
간단히 정점 구조체를 정의 한 후, 정의 된 정점들이 어떤 용도로 쓰이는지 Direct3D에게 알려줄 ID3D11InputLayout 객체를 통해 전달해야 한다. 이 객체는 정점의 갯수만큼 존재해야 되기 때문에 Vector같은 자료구조에 담아 사용한다.
std::vector<D3D11_INPUT_ELEMENT_DESC> inputLayoutDesc;
// 각 정점 구조체의 성분을 서술
for (D3D11_SIGNATURE_PARAMETER_DESC& paramDesc : params)
{
D3D11_INPUT_ELEMENT_DESC elementDesc;
elementDesc.SemanticName = paramDesc.SemanticName; // 성분에 부여된 문자열 이름, 반드시 유효한 변수 이름
elementDesc.SemanticIndex = paramDesc.SemanticIndex; // 텍스쳐 좌표가 여러개일 경우 구별을 위한 번호 지정
elementDesc.InputSlot = 0; // 원소가 공급될 정점 버퍼 슬롯의 인덱스 DX에선 0~15 16개를 지원
elementDesc.AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; // 정점 성분과 시작 위치 사이의 오프셋 (거리)
elementDesc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; // 고정, 다른 값은 고급 기법인 인스턴싱에 사용
elementDesc.InstanceDataStepRate = 0; // 일단 0, 다른 값은 인스턴싱에 사용
// Format = 정점 성분의 자료 형식을 표시
if (paramDesc.Mask == 1)
{
if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32)
elementDesc.Format = DXGI_FORMAT_R32_UINT;
else if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32)
elementDesc.Format = DXGI_FORMAT_R32_SINT;
else if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32)
elementDesc.Format = DXGI_FORMAT_R32_FLOAT;
}
else if (paramDesc.Mask <= 3)
{
if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32)
elementDesc.Format = DXGI_FORMAT_R32G32_UINT;
else if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32)
elementDesc.Format = DXGI_FORMAT_R32G32_SINT;
else if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32)
elementDesc.Format = DXGI_FORMAT_R32G32_FLOAT;
}
else if (paramDesc.Mask <= 7)
{
if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32)
elementDesc.Format = DXGI_FORMAT_R32G32B32_UINT;
else if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32)
elementDesc.Format = DXGI_FORMAT_R32G32B32_SINT;
else if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32)
elementDesc.Format = DXGI_FORMAT_R32G32B32_FLOAT;
}
else if (paramDesc.Mask <= 15)
{
if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_UINT32)
elementDesc.Format = DXGI_FORMAT_R32G32B32A32_UINT;
else if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_SINT32)
elementDesc.Format = DXGI_FORMAT_R32G32B32A32_SINT;
else if (paramDesc.ComponentType == D3D_REGISTER_COMPONENT_FLOAT32)
elementDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
}
InputLayout 인터페이스를 설치 후, CreateInputLayout 메서드를 호출해 입력 배치를 생성한다.
const void* pCode = effectVsDesc->pBytecode;
UINT pCodeSize = effectVsDesc->BytecodeLength;
if (inputLayoutDesc.size() > 0)
{
ID3D11InputLayout* inputLayout = NULL;
HRESULT hr = D3D::GetDevice()->CreateInputLayout
(
&inputLayoutDesc[0] // 정점 구조체를 서술하는 ELEMENT_DESC들의 배열
, inputLayoutDesc.size() // 등록된 배열의 원소갯수
, pCode // 정점 쉐이더를 컴파일해 얻은 바이트 코드를 가리키는 포인터
, pCodeSize // 바이트 코드의 크기
, &inputLayout // 생성된 입력 배치를 이 포인터를 통해 리턴
);
Check(hr);
return inputLayout;
}
세번째 매개변수인 pShaderBytecodeWithInputSignature는 정점 쉐이더가 매개변수로 입력 받은 정점 성분들의 이름과 내용등이 서로 동일한지 체크할 떄 필요한 변수이다. 정점 구조체에 정의된 변수명과 입력된 정점 정보의 변수 정보가 다르면 에러가 발생한다.
위 과정을 따라 입력된 정점 배치 정보들을 장치에 바인딩하는 것이다.
mDeviceContext->IASetInputLayout(InputLayout);
적은 정점 정보로 많은 기본도형을 그릴 수 있게 만들어주는 인덱스를 사용하기 위해선 인덱스 전용 버퍼도 생성해야 한다.
// 인덱스 버퍼 구조체 서술
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_BUFFER_DESC));
desc.ByteWidth = sizeof(UINT) * 6;
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
// 인덱스 버퍼를 초기화할 자료를 지정
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = indices;
// 인덱스 버퍼 생성
device->CreateBuffer(&desc, &subResource, &indexBuffer));
그 후 이렇게 생성한 버퍼를 IA에 바인드 해주고 인덱스로 그리라는 명령을 실행한다.
device->IASetIndexBuffer(indexBuffer, DXGI_FORMAT_R32_UINT, 0);
device->DrawIndexed(0, 1, 6);
Direct3D에는 파이프라인 '렌더 상태'라 부르는 집합으로 묶어 관리하는 상태 변수들이 존재한다.
1. ID3D11RasrerizerState : RS 단계를 구성하는 설정들을 모은 집합
2. ID3D11BlendState : 혼합 연산을 구성하는데 쓰이는 설정들을 모은 집합
3. ID3D11DepthStencilState : 깊이와 스텐실 판정을 구성하는데 쓰이는 설정 집합
이부분의 내용은 대체로 고급 기능에 사용되기 때문에 자주 쓰이진 않지만 Fill(채우기), Cull(전후면 선별), FrontCounterClockwise(전후면 방향 설정)등의 기능이 있다.
아무것도 설정하지 않는 기본 상태도 잘 그려지기 때문에 이거 저거 만져보다 잘못된 결과가 나온다면 RSSetState(0);을 호출하면 된다.
device->RSSetState(StateBlock->RSRasterizerState);
device->OMSetDepthStencilState(StateBlock->OMDepthStencilState, StateBlock->OMStencilRef);
device->OMSetBlendState(StateBlock->OMBlendState, StateBlock->OMBlendFactor, StateBlock->OMSampleMask);
더불어 ID3DX11Effect라는 하나의 렌더링 기법을 캡슐화한 개념이 등장한다.
렌더링 패스를 만들어 하나의 정점, 픽셀 세위더, 렌더 상태등을 지정해 컴파일 시점에 매개변수 변경을 통한 다양한 쉐이더 베리에이션을 구현 할 수 있다.
technique11 T0
{
pass P0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader(CompileShader(ps_5_0, PS_R()));
}
pass P1
{
SetRasterizerState(FillMode_Wireframe);
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader(CompileShader(ps_5_0, PS_R()));
}
}
인덱스 없이 정점 정보로만 그린 사각형
IA의 토폴로지(위상 구조)만 변경해 다시 그린 경우
두 개의 정점 버퍼와 인덱스 버퍼를 쌍으로 그린 2개의 삼각형
버텍스와 인덱스를 이용해 만든 큐브의 WireFrame버전과 Solid 버전
Texture(1) - UV 좌표 (0) | 2024.07.08 |
---|---|
Grid 그리기 (0) | 2024.06.24 |
벡터 대수 (0) | 2024.06.08 |
랜더링 파이프 라인 개요 (0) | 2024.06.08 |
장치 초기화 (0) | 2024.06.07 |