DX의 렌더링 파이프라인을 사용하는 것은 각각의 파이프라인 단계에 맞는 적절한 쉐이더를 사용하여 렌더링 작업을 요청하는 것과 같다. 이를 위해 필요한 것이 바로 파이프라인에서 사용할 쉐이더를 만들어 DX에게 알려주는 것이다.
1. 쉐이더 컴파일
쉐이더는 GPU에서 실행되는 프로그램이다. DX에서는 쉐이더를 컴파일하여 생성된 실행 파일을 파이프라인에 묶어서 GPU에서 사용하게 만들 수 있다. 쉐이더를 컴파일하는 방법은 다음과 같다.
// https://learn.microsoft.com/ko-kr/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dcompilefromfile
HRESULT D3DCompileFromFile(
[in] LPCWSTR pFileName,
[in, optional] const D3D_SHADER_MACRO *pDefines,
[in, optional] ID3DInclude *pInclude,
[in] LPCSTR pEntrypoint,
[in] LPCSTR pTarget,
[in] UINT Flags1,
[in] UINT Flags2,
[out] ID3DBlob **ppCode,
[out, optional] ID3DBlob **ppErrorMsgs
);
pFileName
: 컴파일을 하려고 하는 쉐이더 파일의 경로pDefines
: 쉐이더에서 사용할 매크로를 설정pInclude
: 쉐이더에서 사용할include
설정pEntrypoint
: 쉐이더의 시작 지점을 설정pTarget
: 쉐이더의 버전 지정Flags1, Flags2
: 컴파일 옵션 지정ppCode
: 컴파일된 쉐이더를 저장할 블롭ppErrorMsgs
: 컴파일 과정에서 오류가 발생했을 경우 관련 정보를 저장할 블롭
d3dcompiler.h
에서 제공하는 D3DCompileFromFile
함수를 사용하면 특정 경로에 있는 쉐이더(.hlsl
)을 컴파일할 수 있다. 컴파일된 쉐이더는 ID3DBlob
형태로 DX에서 관리할 수 있다. ID3DBlob
은 실행 파일과 같은 데이터를 저장하기 위한 용도로 사용된다.
ppErrorMsgs
에는 관련된 오류 정보가 저장되는데, 확인을 하려면 다음과 같은 코드를 사용하면 된다.
HRESULT hr = D3DCompileFromFile(...);
if(FAILED(hr)) {
if((hr & D3D11_ERROR_FILE_NOT_FOUND) != 0) {
// 파일이 없음
}
if(errorBlob) {
cout << "error: " << (char*)errorBlob->GetBufferPointer() << endl;
}
}
2. 버텍스 쉐이더 만들기
버텍스 쉐이더는 DX의 파이프라인에서 Input Assembler 단계를 지나면 바로 나오는 단계이다. Input Assembler 단계에서는 GPU에게 전달하려고 하는 데이터들이 모이게 된다. 이후 곧바로 버텍스 쉐이더가 실행되는데, Input Assembler를 통해 전달 받은 데이터가 어떤 형식으로 들어오는지 GPU는 모르기 때문에 인풋 레이아웃(InputLayout)을 함께 만들어 전달해주어야 한다.
DX에서는 D3D11_INPUT_ELEMENT_DESC
구조체를 정의하여 인풋 레이아웃을 만들 수 있다. 위치와 색깔 정보를 가지고 있는 버텍스에 대한 인풋 레이아웃은 다음과 같이 만들 수 있다.
typedef struct D3D11_INPUT_ELEMENT_DESC {
LPCSTR SemanticName;
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset;
D3D11_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D11_INPUT_ELEMENT_DESC;
SemanticName
: 쉐이더에서 사용되는 유효한 변수 이름. 이를 통해 GPU는 해당 데이터가 정점인지, 색깔인지 등을 알 수 있다.SemanticIndex
: 동일한SemanticName
이 여러 개 있을 경우, 이를 구분하기 위한 인덱스를 의미한다.Format
: GPU에 전달할 데이터의 타입을 정의한다.InputSlot
: GPU에 최대 16개의 버퍼를 동시에 전달할 수 있는데, 여러 개 중에서 어떤 버퍼를 사용할지 나타내는 인덱스다.AlignedByteOffset
: 접근하려고 하는 데이터의 시작 위치를 나타낸다.InputSlotClass
:InputSlot
에서 전달하려고 하는 데이터의 식별 클래스를 나타낸다. 일반적으로D3D11_INPUT_PER_VERTEX_DATA
를 사용한다.InstanceDataStepRate
: 인스턴스화에 사용한다. 지금은 0을 사용한다.
struct Vertex {
// float3
Vector3 position;
// float3
Vector3 color;
}
vector<D3D11_INPUT_ELEMENT_DESC> inputElems =
{
{"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},
}
인풋 레이아웃을 만들었다면, 앞에서 만든 컴파일된 쉐이더와 함께 디바이스에서 CreateVertexShader
함수를 호출해주면 된다.
// https://learn.microsoft.com/ko-kr/windows/win32/api/d3d11/nf-d3d11-id3d11device-createvertexshader
HRESULT CreateVertexShader(
[in] const void *pShaderBytecode,
[in] SIZE_T BytecodeLength,
[in, optional] ID3D11ClassLinkage *pClassLinkage,
[out, optional] ID3D11VertexShader **ppVertexShader
);
pShaderBytecode
: 컴파일된 쉐이더를 나타내는 포인터를 넣어준다.BytecodeLength
: 컴파일된 쉐이더의 사이즈를 넣어준다.pClassLinkage
: 클래스 연결 인터페이스 포인터다. NULL을 넣어준다.ppVertexShader
: 생성된 버텍스 쉐이더를 받을 변수를 넣어준다.
CreateVertexShader
를 사용해서 GPU에서 사용할 버텍스 쉐이더를 만들었다면, 이제 버텍스 쉐이더에서 어떤 형식의 데이터를 사용할지 지정하기 위해 인풋 레이아웃을 만들어야 한다. 인풋 레이아웃은 버텍스 쉐이더를 위해 사용하는 것이기 때문에 컴파일된 쉐이더를 함께 사용해준다.
// https://learn.microsoft.com/ko-kr/windows/win32/api/d3d11/nf-d3d11-id3d11device-createinputlayout
HRESULT CreateInputLayout(
[in] const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
[in] UINT NumElements,
[in] const void *pShaderBytecodeWithInputSignature,
[in] SIZE_T BytecodeLength,
[out, optional] ID3D11InputLayout **ppInputLayout
);
pInputElementDescs
: 앞서 정의한 인풋 레이아웃 구조체를 넣어준다.NumElements
: 인풋 레이아웃 배열의 크기를 넣어준다.pShaderBytecodeWithInputSignature
: 컴파일된 쉐이더를 넣어준다.BytecodeLength
: 컴파일된 쉐이더의 크기를 넣어준다.ppInputLayout
: 생성된 인풋 레이아웃을 받을 변수를 넣어준다.
3. 픽셀 쉐이더 만들기
픽셀 쉐이더는 래스터라이즈를 통해 결정된 각 픽셀의 색들을 후처리하는 단계이다. GPU에서 실행되는 쉐이더는 서로 독립적인 프로그램이지만, 앞 단계의 특정 쉐이더에서 가공한 데이터를 다음 단계에 있는 쉐이더에서 받아 사용할 수 있다. 그렇기 때문에 픽셀 쉐이더를 만들 때에는 인풋 레이아웃을 함께 만들지 않아도 된다.
픽셀 쉐이더를 만드는 과정은 버텍스 쉐이더를 만드는 과정과 유사하다. 픽셀 쉐이더 파일을 컴파일하여 블롭으로 만들고, 이를 이용해서 픽셀 쉐이더를 만들면 된다. 픽셀 쉐이더는 GPU 내부의 메모리 공간을 차지하는 프로그램이기 때문에 디바이스를 통해 생성할 수 있다.
// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11device-createpixelshader
HRESULT CreatePixelShader(
[in] const void *pShaderBytecode,
[in] SIZE_T BytecodeLength,
[in, optional] ID3D11ClassLinkage *pClassLinkage,
[out, optional] ID3D11PixelShader **ppPixelShader
);
CreatePixelShader
의 매개 변수는 버텍스 쉐이더를 만드는 함수와 동일한 역할을 한다.
'그래픽스 > DirectX11' 카테고리의 다른 글
조명 (0) | 2024.05.08 |
---|---|
노멀 벡터 변환 (0) | 2024.05.08 |
버텍스 버퍼, 인덱스 버퍼, 컨스턴트 버퍼 (0) | 2024.04.22 |
RenderTargetView의 의미 (0) | 2024.04.19 |
DXGI_SWAP_CHAIN_DESC에 관하여 (1) | 2024.04.19 |