상세 컨텐츠

본문 제목

정점, 인덱스를 이용한 렌더링

DirectX

by 부레두 2024. 6. 19. 22:22

본문

0. 개요

랜더링 파이프 라인을 개념적으로 숙지했다면, 직접 파이프 라인을 구성하고 VS(정점 쉐이더)와 PS(픽셀 쉐이더)를 정의하고 3차원 도형을 만들어 최종적으로 와이어프레임 도형을 출력한다.

 

이론적으로 하나하나 설명하기엔 이해가 부족할 수 있기 때문에 순서적으로 작성한 코드와 내용을 서술한다.


1. 정점과 입력 배치

ID3D11InputLayout 설치

더보기

간단히 정점 구조체를 정의 한 후, 정의 된 정점들이 어떤 용도로 쓰이는지 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);

2. 인덱스 설정, 인덱스 버퍼

적은 정점 정보로 많은 기본도형을 그릴 수 있게 만들어주는 인덱스를 사용하기 위해선 인덱스 전용 버퍼도 생성해야 한다.

더보기
// 인덱스 버퍼 구조체 서술
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);

3. 렌더링 상태 설정

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()));
    }
}

4. 결과물

인덱스 없이 정점 정보로만 그린 사각형

 

IA의 토폴로지(위상 구조)만 변경해 다시 그린 경우

 

두 개의 정점 버퍼와 인덱스 버퍼를 쌍으로 그린 2개의 삼각형

 

버텍스와 인덱스를 이용해 만든 큐브의 WireFrame버전과 Solid 버전

'DirectX' 카테고리의 다른 글

Texture(1) - UV 좌표  (0) 2024.07.08
Grid 그리기  (0) 2024.06.24
벡터 대수  (0) 2024.06.08
랜더링 파이프 라인 개요  (0) 2024.06.08
장치 초기화  (0) 2024.06.07

관련글 더보기