본문 바로가기
서적 정리/DirectX11을 이용한 3D 게임 프로그래밍 입문

50.효과 프레임워크(Effect Framework)

by 민돌이2 2022. 2. 10.

효과 프레임워크는 특정한 렌더링 효과를 구현하는 데 쓰이는 셰이더 프로그램들과 렌더 상태들을 효과라고 부르는 단위로 조직화하고 관리하는 틀을 제공하는 편의용 코드 집합이다. 하나의 효과는 적어도 하나의 정점 셰이더와 픽셀 셰이더, 그리고 그 효과를 구현하는 데 필요한 렌더 상태들로 이루어진다.

 

 

1.효과 파일

일반적으로 셰이더들의 코드를 확장자가 .fx인 효과 파일에 담아둔다. 효과 파일은 보통의 텍스트 파일이다. C++ 코드를 담고 있는 .h나 .cpp 파일 역시 결국 텍스트 파일이다. 하나의 효과 파일은 셰이더 코드와 상수 버퍼의 내용뿐만 아니라, 적어도 하나의 기법(technique) 정의도 담는다. 그리고 하나의 기법은 적어도 하나의 패스(pass)를 포함한다.

  1. 효과 파일에서 식별자 technique11로 시작하는 섹션은 하나의 기법을 정의한다. 하나의 기법은 특정 렌더링 기법을 구현하는 데 쓰이는 하나 이상의 패스들로 구성된다. 각 패스마다 기하구조를 다른 방식으로 렌더링 하고, 각 패스의 결과를 특정 방식으로 결합함으로써 원하는 결과를 얻는다.
  2. 기법 정의 안에서 pass로 시작하는 섹션은 하나의 패스를 정의한다. 하나의 패스는 하나의 정점 셰이더와 픽셀 셰이더, 렌더 상태로 구성된다. 이런 구성 요소들은 이 패스에서 기하구조를 어떻게 처리하고 색을 입힐 것인지를 규정한다. 예외로 픽셀 셰이더가 없는 패스도 존재하는데, 오브젝트를 후면 버퍼에는 그리지 않고 깊이 버퍼만 갱신하는 경우이다.

다음은 효과 파일의 셰이더 코드이다.

struct cbPerObject
{
	float4x4 gWorldViewProj;
};

struct VertexIn
{
	float3 Pos : POSITION;
	float4 Color : COLOR:
};

struct VertexOut
{
	float4 PosH : SV_POSITION;
	float4 Color : COLOR;
};

VertexOut VS(VertexIn vertex)
{
	VertexOut output;

	output.PosH = mul(float4(vertex.Pos, 1.0f), gWorldViewProj); //동차 절단 공간으로 변환
	output.Color = vertex.Color; //정점 색상을 그대로 픽셀 셰이더로 전달

	return output;
}

float4 PS(VertexOut output) : SV_Target
{
	return output.Color;
}

RasterizerState WireframeRS
{
	FillMode = Wireframe;
	CullMode = Back;
	FrontCounterClockwise = fasle;
}

technique11 ColorTech
{
	pass P0
	{
		SetVertexShader(CompileShader(vs_5_0, VS()));
		SetPixelShader(CompileShader(ps_5_0, PS()));
		SetRasterizerState(WireframeRS);
	}
}

 

 

2.셰이더 컴파일하기

효과를 사용하려면 .fx 파일 안의 셰이더 프로그램들을 컴파일해야 한다. 이를 위해 d3dcompiler.h에 있는 D3DCompileFromFile 함수를 사용한다. 

D3DCompileFromFile(
	LPCWSTR pFileName,
	CONST D3D_SHADER_MACRO* pDefines,
	ID3DInclude* pInclude,
	LPCSTR pEntrypoint,
	LPCSTR pTarget,
	UINT Flags1,
	UINT Flags2,
	ID3DBlob** ppCode,
	ID3DBlob** ppErrorMsgs
    );

1.pFileName : 컴파일할 셰이더 소스 코드를 담고 있는 .fx 파일 이름
2.pDefines : 셰이더 매크로를 정의하는 배열. 사용하지 않는 경우 NULL로 설정해야 한다.
3.pInclude : 컴파일러가 포함되는 파일을 처리하는 데 사용하는 
4.pEntrypoint : 셰이더 프로그램의 진입점, 즉 셰이더 주 함수의 이름.
5.pTarget : 사용할 셰이더 버전을 뜻하는 문자열. Direct3D 11 효과의 경우 5.0(fx_5_0)을 사용한다.
6.Flags1 : 셰이더 코드의 컴파일 옵션. 결과 값은 컴파일러가 HLSL 코드를 컴파일하는 방법을 지정한다.
7.Flags2 : 효과 컴파일 옵션. 결과 값은 컴파일러가 효과를 컴파일하는 방법을 지정한다.
8.ppCode : 컴파일된 코드에 액세스하는 데 사용
9.ppErrorMsgs : 컴파일러 오류 메시지에 액세스하는 데 사용

 

효과 파일의 셰이더들을 성공적으로 컴파일했다면, d3dx11effect.h에 있는 D3DX11CreateEffectFromMemory 함수를 호출하여 효과 자체를 나타내는 ID3DXEffect11 인터페이스를 생성한다.

HRESULT D3DX11CreateEffectFromMemory(
	void* pData,
	SIZE_T DataLength,
	UINT FXFlags,
	ID3D11Device* pDevice,
	ID3DX11Effect** ppEffect
);

1.pData : 컴파일된 효과 자료를 가리키는 포인터
2.DataLength : 컴파일된 효과 자료의 byte 단위 크기
3.FXFlags : 효과 플래크들. D3DCompileFromFile 함수에서 Flag2에 지정한 것과 일치해야 한다.
4.pDevice : Direct3D 장치를 가리키는 포인터
5.ppEffect : 생성된 효과 파일을 가리키는 포인터 반환

 

아래의 코드는 효과를 컴파일하고 생성하는 과정이다.

DWORD shaderFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
shaderFlags |= D3D10_SHADER_DEBUG;
shaderFlags |= D3D10_SHADER_SKIP_OPTIMIZATION;
#endif

ID3D10Blob* compileShader = 0;
ID3D10Blob* compilationMsgs = 0;
HRESULT hr = D3DCompileFromFile(L"color.fx", 0, 0, 0, "fx_5_0", shaderFlags, 0, &compileShader, &compilationMsgs);
if (compilationMsgs != 0)
{
	MessageBoxA(0, (char*)compilationMsgs->GetBufferPointer(), 0, 0);
	ReleaseCOM(compilationMsgs);
}

if (FAILED(hr))
{
	DXTrace(__FILE__, (DWORD)__LINE__, hr, L"D3DX11CompileFromFIle", true);
}

ID3DX11Effect* mFX;
HR(D3DX11CreateEffectFromMemory(compileShader->GetBufferPointer(), compileShader->GetBufferSize(), 0, md3dDevice, &mFX));
ReleaseCOM(compileShader);

 

 

 

3.C++ 응용 프로그램에서 효과 객체 다루기

일반적으로 C++ 응용 프로그램은 상수 버퍼의 변수들을 갱신해야 한다.

아래의 코드는 상수 버퍼가 있을 때 효과 객체를 통해서 상수 버퍼 변수에 대한 포인터를 얻는 방법이다.

cbuffer cbPerObject
{
	float4x4 gWVP;
	float4 gColor;
	float gSize;
	int gIndex;
	bool gOptionOn;
};

ID3DX11EffectMatrixVariable* fxWVPVar;
ID3DX11EffectVectorVariable* fxColorVar;
ID3DX11EffectScalarVariable* fxSizeVar;
ID3DX11EffectScalarVariable* fxIndexVar
ID3DX11EffectScalarVariable* fxOptionOnVar;

fxWVPVar = mFX->GetVariableByName("gWVP")->AsMatrix();
fxColorVar = mFX->GetVariableByName("gColor")->AsVector();
fxSizeVar = mFX->GetVariableByName("gSize")->AsScalar();
fxIndexVar = mFX->GetVariableByName("gIndex")->AsScalar();
fxOptionOnVar = mFX->GetVariableByName("gOptionOn")->AsScalar();

ID3DX11Effect::GetVariableByName 메소드는 ID3DX11EffectVariable 형식의 포인터를 반환한다. 변수에 대한 포인터를 얻었다면 적절한 C++ 인터페이스를 통해서 변수의 값을 갱신할 수 있다.

fxWVPVar->SetMatrix((float*)&M); //M이 XMMATRIX 형식의 객체라고 가정
fxColorVar->SetFloatVector((float*)&v); //v가 XMVECTOR 형식의 객체라고 가정
fxSizeVar->SetFloat(5.0f);
fxIndexVar->SetInt(77);
fxOptionOnVar->SetBool(true);

이 호출들로 효과 객체의 내부 캐시가 갱신되는 것일 뿐, GPU 메모리에 있는 실제 상수 버퍼가 갱신되는 것은 아니다. 실직적인 갱신은 렌더링 패스(다음장 참고)를 수행할 때 일어난다. 위의 방식은 GPU 메모리를 여러 번 조금씩 갱신하는 대신 한 번에 일괄적으로 갱신하지만 비효율 적이다.

 

 

4.효과를 이용한 그리기

효과 기법을 이용해서 기하구조를 그리는 과정은 간단하다.

  1. 상수 버퍼의 변수들을 적절히 갱신한다.
  2. 루프로 기법의 각 패스를 훑으면서 각 패스를 적용한다.
//상수 설정
XMMATRIX world = XMLoadFloat4x4(&mWorld);
XMMATRIX view = XMLoadFloat4x4(&mView);
XMMATRIX proj = XMLoadFloat4x4(&mProj);
XMMATRIX worldViewProj = world * view * proj;

mfxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));
mTech->GetDesc(&techDesc);
for (UINT p = 0; i < techDesc.Passes; ++p)
{
	mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);

	md3dImmediateContext->DrawIndexed(36, 0, 0); //기하구조 그리기
}

위 코드에서 ID3DX11EffectTechnique::GetPassByIndex 메소드는 주어진 인덱스에 해당하는 패스를 나타내는 ID3DX11EffectPass 인터페이스를 가리키는 포인터를 반환한다. ID3DX11EffectPass::Apply 메소드는 GPU 메모리에 저장된 상수 버퍼를 새 변수 값들로 갱신하고, 패스에 설정된 셰이더 프로그램들을 파이프라인에 묶고, 패스에 설정된 렌더 상태들을 적용한다.

728x90

'서적 정리 > DirectX11을 이용한 3D 게임 프로그래밍 입문' 카테고리의 다른 글

54.파일에서 기하구조 불러오기  (0) 2022.02.15
53.도형 예제  (0) 2022.02.15
52.언덕 예제  (0) 2022.02.15
51.상자 예제  (0) 2022.02.15
49.렌더 상태(Render State)  (0) 2022.02.09
48.예제 픽셀 셰이더  (0) 2022.02.08
47.상수 버퍼(Constant Buffer)  (0) 2022.02.08
46.예제 정점 셰이더  (0) 2022.02.08

댓글