목표 

  • DrawCall(드로우 콜) 기능 구현 
    DrawCall(드로우 콜)은 '이런 정보를 주었으니, 이런 방식으로 렌더링 해라'라고 커맨드 큐와 같은 컨테이너에 명령을 제출하고 그래픽카드에게 렌더링 명령을 내리는 것을 의미합니다.
  • Renderer Architecture
    화면에 렌더링을 해주는 특정 기능들의 집합, 렌더러의 목적과 기능, 설계 방식을 의미합니다.
  • Render Command Queue
    렌더링이 완료된 Scene들을 렌더러에 의한 요청에 의해 순차적으로 처리해주는 커맨드 큐 
  • RendererAPI
    렌더 과정이 아닌 렌더러가 사용하는 API들의 집합(일종의 래퍼 클래스)

 

드로우 콜과 렌더러의 관계

드로우 콜이란 우리가 화면에 어떤 기하학 형상을 그리기 위해 관련 정보를 렌더러에게 제공해주는 과정을 의미합니다. 즉, 드로우 콜은 렌더러가 어떠한 물체를 화면에 그리기 위해 수행하는 일련의 기능을 의미합니다. 그 과정에서 렌더러는 물체의 다양한 정보를 필요로 하게 됩니다. ( 버텍스, 인덱스 정보, 머티리얼, 빛의 반사율, 카메라 정보, 위치 정보, 주변 환경맵 등 ) 

객체가 가지는 정보와 렌더러가 가지는 정보의 구분

 렌더러는 다양한 정보를 이용해 화면에 형상을 렌더링해주지만, 모든 정보를 렌더러가 가지는 것은 아닙니다. 그러므로 렌더러가 가지는 정보와 객체가 가지는 정보를 구분할 수 있어야 합니다. 아직 카메라 정보나 기타 환경 맵, 빛의 정보를 배제하고 동일한 장면에서의 렌더링을 진행한다고 가정하고 씬과 같은 기능을 구현해나가려고 합니다. 즉, 렌더러에게 씬을 시작한다 알리고 씬에 필요한 여러 정보를 제공하는 방식을 구현해보려고 합니다. 

물체를 화면에 표현하는 방법

렌더러에게 장면을 시작한다고 알리고 환경, 위치, 유니폼, 셰이더 등을 렌더러에게 제공합니다. 해당 렌더러는 그에 대한 정보들을 활용해서 요청된 씬에 모든 메시들을 렌더링합니다. 모든 메시의 렌더링이 완료되면 렌더러가 해당 씬의 종료를 알립니다. 다만, 씬이 완료되어서 즉시 화면에 씬이 출력 되는 것이 아닌 일종의 렌더링 커맨드 큐에 씬에 대한 명령이 제출되도록 합니다.  이 커맨드 큐는 다른 렌더링 스레드에서 처리될 수도 있습니다. 

저는 앞으로 시작과 끝이 정해진 렌더링이 모두 완료된 장면(Scene)을 커맨드 큐에 제출하는 방식으로 렌더링 과정을 구축해갈 계획입니다. 이유는, 완성된 장면을 따로 처리가 가능해지면 오클루전 컬링과 같은 최적화 작업이 수월해지기 때문입니다. ( 뷰 밖의 객체들을 화면에서 제거하거나, 동일한 종류의 객체들을 묶는 등 )

Rendering code

void Application::Run()
{
    while ( m_Running )
    {
        { // ------ Scene Rendering start -------
            RenderCommand::SetClearColor( { 0.2f, 0.2f, 0.2f, 1.0f } );
            RenderCommand::Clear();
            
            Renderer::BeginScene();
            
            Renderer::Submit( m_SquareVertexArray );
            Renderer::Submit( m_VertexArray );

            Renderer::EndScene();
        } // ------ Scene Rendering end -------
    }
}

RenderCommand에서 화면을 지울 컬러를 설정하고 지워줍니다.  이후 렌더러 래퍼 클래스에서 각각의 VertexArray들을 렌더러에 제출합니다. 이후 렌더러는 내부에서 설정된 그래픽 API에 맞는 버전으로 DrawIndexed 를 수행합니다. 이후 EndScene() 함수를 호출해 렌더러 래퍼는 씬에 렌더링하는 작업을 종료합니다. 이 과정은 대부분 애플리케이션 레벨에서 이루어집니다.

 

RenderAPI

	/*
	RendererAPI는 OpenGL과 DirectX 버전 둘 다 갖게 될 것이고 
	RenderAPI와 다르게 Renderer들을 위한 API이다. 명칭상 차이점을 분명히 인식하자.
	*/
	class NORMAL_API RendererAPI
	{
	public:
		enum class API
		{
			None = 0,
			OpenGL, Vulkan,
			DirectX11, DirectX12,
		};

	public:
		virtual void SetClearColor( const glm::vec4& clearColor ) = 0;
		virtual void Clear() = 0;

		// Index Buffer를 이용해서 Scene에 Draw 명령을 수행하기 때문에 DrawIndexed라는 이름이 된다.
		virtual void DrawIndexed( const std::shared_ptr<VertexArray>& vertexArray ) = 0;

	public:
		inline static API GetCurrentAPI() { return m_GraphicAPI; }

	private:
		static API m_GraphicAPI;
	};

RenderAPI는 렌더링 과정에서 필요한 여러 호출들을 묶어놓은 일종의 래퍼 클래스입니다. 이 클래스를 직접 외부에서 참조해서 사용하진 않습니다. 그래픽 렌더링에 필요한 작업은 RenderCommand를 사용하는 Renderer가 대신 작업을 요청받아 수행하도록 했습니다. 프로그래머가 렌더링 요청을 할 땐 아래에 있는 Renderer 클래스와 RenderCommand 클래스를 사용하면 됩니다.

Renderer

	class NORMAL_API Renderer 
	{
	protected:
		explicit Renderer() = default;
	public:
		~Renderer() = default;

	public:
		static void BeginScene();
		static void EndScene();

		static void Submit( const std::shared_ptr<VertexArray>& vertexArray );

	public:
		inline static RendererAPI::API GetGraphicAPI() { return RendererAPI::GetCurrentAPI(); }
	};
	// cpp file
    
	void Renderer::Submit( const std::shared_ptr<VertexArray>& vertexArray )
	{
		vertexArray->Bind();
		RenderCommand::DrawIndexed( vertexArray );
	}

현재는 구현 초기단계라 Submit 까지만 구현되어있습니다.  Submit 함수는 인자로 받은 VertexArray를 BeginScene에서 받은 각각의 정보들(현재까지는 구현되어있지 않음), 예를 들어 셰이더, 매트릭스 정보등을 바탕으로 VertexArray를 처리해줍니다. 추상화된 DrawIndexed 내부에서 Bind를 수행하지 않고 Submit에서 수행을 하도록 한 이유는, DrawIndexed는 오로지 인덱스 버퍼를 이용해 정점들을 렌더링하는데에만 목적을 갖추었기 때문입니다. Bind와 같이 버퍼를 첨부하는 작업은 더 고수준의 작업이므로 해당 작업은 Submit에서 같이 수행해주도록 했습니다.

 

RenderCommand

	class NORMAL_API RenderCommand 
	{
	public:
		inline static void SetClearColor( const glm::vec4& clearColor )
		{
			s_RendererAPI->SetClearColor( clearColor );
		}

		inline static void Clear()
		{
			s_RendererAPI->Clear();
		}

		inline static void DrawIndexed( const std::shared_ptr<VertexArray>& vertexArray )
		{
			s_RendererAPI->DrawIndexed( vertexArray );
		}

	private:
		// TODO: 의도적인 1바이트 메모리 누수, 프로그램 수명이 다할 때 까지 남길 방법 강구 
		static RendererAPI* s_RendererAPI;
		// static std::unique_ptr<RendererAPI> s_RendererAPI;
	};

Renderer에서 보내온 VertexArray의 렌더링 요청은 RenderCommand로 제출됩니다. 아직 기능은 구현되어 있지 않지만, 추후에 렌더링 명령들은 이 커맨드 큐를 거쳐서 또 다른 렌더링 스레드로 전송될 예정입니다. 

 

Result