이전 포스트에서 실행 기반을 다졌으니 이제 실제 동작을 위한 기본적인 렌더러를 제작하려고 합니다. 그러기 위해서 일단 렌더러의 추상화 작업을 해주어야 합니다. 

이렇게 추상화 작업을 해주어야지 구현과 사용이 분리되어 차후에 렌더러가 수정되더라도 사용자 부분에 영향이 안가고 유지보수가 좋아집니다. 또 한, DirectX 11 이든 OpenGL 이든 다른 렌더러가 추가 되더라도 사용자 부분에서는 구현 부분에 대해 알필요없이 사용이 가능하므로 확장성 또한 좋아지겠죠.

class ev_IRenderer
{
public:
	ev_IRenderer( void ) {};
	virtual ~ev_IRenderer( void ) {};

public:
	virtual HRESULT Initialize( HWND hRenderWnd ) = 0;
	virtual HRESULT Destroy( void ) = 0;
	virtual void Render( ev_CRenderNodePtr spRenderNode ) = 0;
};

대략 위와 같은 모습을 띄고 있습니다. 클래스 이름은 인터페이스를 뜻하는 I를 붙입니다. 그리고 순수 가상함수가 있구요(추상 클래스니까요). 눈여겨 볼점은 Render 함수에서 그려질 객체를 직접 받는다는 겁니다. 보통 DirectX 초중급 책자들을 보면 객체를 그릴때 객체 내에서 그리는 경우가 많습니다. 예로 메시를 그린다 하면 메시 클래스 내에 Render 함수가 있고 메시 내에서 Device를 얻어다가 직접 그리죠.

이렇게 될 경우 이 메시 클래스는 해당 렌더러에 완전 종속되게 되고 DirectX 11을 지원하게 되거나 OpenGL을 지원하거나 또는 같은 DirectX 9를 쓰더라도 렌더 방식이 달라지거나 하면 (지연 렌더링 같이) 하면 수정이 불가피해집니다. 게다가 동시에 여러 렌더러를 지원하기도 힘들어지죠.

그래서 그리는 것을 해당 렌더러에서 그리게 하고 메시 클래스는 렌더러에서 해당 객체를 그리기 위한 데이터만 제공하도록 할 예정입니다.

다음은 위의 추상 클래스를 상속받아 만든 DirectX 9 Deferred Renderer Class 의 대략적인 모습이니다.

class ev_CRendererDX9Deferred : ev_IRenderer
{
public:
	ev_CRendererDX9Deferred( void );
	virtual ~ev_CRendererDX9Deferred( void );

public:
	virtual HRESULT Initialize( HWND hRenderWnd );
	virtual HRESULT Destroy( void ) ;
	virtual void Render( ev_CRenderNodePtr spRenderNode );

private:
	LPDIRECT3D9			m_pD3D;
	LPDIRECT3DDEVICE9	m_pD3DDevice;
	D3DCAPS9			m_D3DCaps;

	bool				m_bIsWireFrame;
};

대략 이처럼 나오겠죠. 가상 함수들을 해당 렌더러에 맞게 내부를 구현해줍니다. 또 한, 외부에서는 해당 렌더러가 DirectX 9/10/11을 사용할지 OpenGL을 사용할지 알 수 없습니다. 그렇기 때문에 Matrix/Vector 같은 3D객체를 그리기 위한 데이터등은 모두 자체 포맷으로 제작을 합니다. DirectX의 의존성을 없애기 위해 D3D 데이터 포맷을 사용하지 않죠.

렌더러 클래스의 구현이 끝나면 이제 사용만 해주면 됩니다. 사용을 할때는 인터페이스 클래스격인 ev_IRenderer로 사용하면 됩니다.

// 대략 적인 흐름도

// 환경에 맞는 렌더러 생성
ev_IRenderer* m_pRenderer = (ev_IRenderer*)new ev_CRendererDX9Deferred();

// 렌더러 초기화 ( 화면을 그릴 윈도우의 핸들을 넘겨준다 )
m_pRenderer->Initialize( (HWND)hRenderView.ToPointer() );

// 메인 루프
MSG msg;
ZeroMemory( &msg, sizeof(msg) );
while( WM_QUIT != msg.message )
{
	// 종료 플래그 검사
	if( m_bExitFlag )
		break;

	// 메시지큐에 메시지가 있으면 메시지 처리
	if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
	{
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	}
	else
	{
		// 객체 렌더링
		m_pRenderer->Render( /*그릴 객체를 넘겨줌*/ );
	}
}

// 렌더러 소멸
Safe_Delete( m_pRenderer );
실제로는 씬노드( SceneNode )와 씬노드를 관리할 씬 매니저를 통해 렌더러에 그려질 객체등을 넘겨주겠지만, 대략적인 흐름만 파악하기 위해 위 처럼 코딩하였습니다. 이 후에 씬노드쪽도 새로 구현이 되면 그에 관해서도 포스팅 하겠습니다.

댓글 유도를 위한 의미없는 짤방

+ Recent posts