Автор: Владимир Дьячков «Nikola Tesla»

Опубликовано: 05.03.2008

Изменено: 05.03.2008

Постоянная ссылка

Комментарии [0]

Реализация процессора эффектов постобработки. Часть 1 - Разработка фреймворка





Страницы: 1 2 3

Реализация фреймворка с использованием библиотеки OpenGL

Ниже представлены листинги некоторых файлов реализации рассмотренной архитектуры фреймворка для OpenGL 3D API. Все абстрактные классы, от которых наследуются классы реализаций, а также вспомогательные классы можно найти в прилагаемых к статье исходниках.

Так как центральным объектом в архитектуре фреймворка является фреймбуфер, то я рассмотрю его реализацию более подробно. Для начала рассмотрим абстрактный класс фреймбуфера.

3D\Tor_FrameBuffer.h

#include "3D\Tor_Texture.h"

//-------------------------------------------------------
namespace Tor
{
//-------------------------------------------------------
namespace renderer
{
//-------------------------------------------------------
// Тип реализации фреймбуфера
enum FrameBufferType
{
  FBTYPE_UNKNOWN = 0,
  FBTYPE_OGL_Copy2Texture = 1,	// через копирование в текстуру (не используется)
  FBTYPE_OGL_PBuffer = 2,	// через PBuffer
  FBTYPE_OGL_FBO = 3,		// через FBO
  FBTYPE_DX  = 4		// DirectX render target (не используется)
};
//-------------------------------------------------------
class FrameBuffer
{
protected:
	int flags;
	int width;
	int height;

public:
	FrameBuffer (int theWidth, int theHeight, int theFlags=0) :
				flags(theFlags), width(theWidth), height(theHeight) {}

	virtual ~FrameBuffer () {}
	
	int getWidth () const { return width; }	
	int getHeight () const { return height; }	
	
	virtual bool hasStencil () const = 0;	
	virtual bool hasDepth () const = 0;
	
	// проверяет все ли в порядке
	virtual bool	isOk   () const = 0;
	// создание всех 3D API объектов и их настрока
	virtual bool	create () = 0;
	// устанавливает этот фрейм-буфер текущим для следующего рендера
	virtual bool	bind   () = 0;
	// устанавливает вывод в back buffer
	virtual bool	unbind () = 0;
	
	// присоединяет текструру (no - номер аттачмента (если исп. FBO))
	virtual bool attachColorTexture (Texture * tex, int no=0)=0;
	virtual bool attachColorTextureCubeMap (Texture * tex, TexTarget cubeMapSide, int no=0)=0;
	
	// присоединяет текструру глубины (если исп. FBO)
	virtual bool attachDepthTexture ( Texture * tex ) = 0;
	virtual bool attachDepthTextureCubeMap (Texture * tex, TexTarget cubeMapSide) = 0;
	
	// отсоединяет текстуры
	virtual bool detachColorTexture (unsigned target, int no=0)=0;
	virtual bool detachDepthTexture (unsigned target)=0;
	
	// устанавливает рендеринг в несколько целей
	virtual bool setMRT (int targetsCount, unsigned * targets)=0;
	
	// возвращает указатель на присоединенную текстуру
	virtual Texture * getRenderTarget (int no=0)=0;
	virtual Texture * getDepthTarget ()=0;
	
	// рендеринг квада в этот фреймбуфер, размеры квада
	// соотв. размерам фреймбуфера
	virtual void drawScreenQuad (Texture * tex) = 0;
	virtual void drawScreenQuad (Texture * tex, const vec2f &minPoint, const vec2f &maxPoint)=0;

	virtual int getType() { return FBTYPE_UNKNOWN; }

	// только для FBO - расширивает рендербуферы между фреймбуферами
	virtual bool shareRenderBuffers (FrameBuffer * fb, int attachment)=0;

	enum
	{
		depth16 = 1,
		depth24 = 2,
		depth32 = 4,
		stencil1  = 16,
		stencil4  = 32,
		stencil8  = 64,
		stencil16 = 128
	};
	
	static int maxColorAttachemnts () {return 0;}
};
//-------------------------------------------------------
}	// namespace renderer
//-------------------------------------------------------
}	// namespace Tor
//-------------------------------------------------------

Более подробные комментарии к членам класса фреймбуфера будут даны на примере реализации фреймбуфера для OpenGL.

Реализация фреймбуфера на OpenGL через расширение FBO.

3D\FBO\Tor_GLFrameBuffer.h

#include "common\Tor_String.h"
#include "3D\Tor_FrameBuffer.h"
#include <vector>
#include <map>

using namespace std;

//-------------------------------------------------------
namespace Tor
{
//-------------------------------------------------------
namespace renderer
{
//-------------------------------------------------------
	class Texture;
//-------------------------------------------------------
class	GLFrameBuffer : public FrameBuffer
{
	unsigned	frameBuffer;
	unsigned	depthBuffer;
	unsigned	stencilBuffer;
	Texture*	renderTargets[8];
	Texture*	depthTarget;
	bool		supportMRT;
	unsigned	maxColorAttachment;

public:
	GLFrameBuffer (int theWidth, int theHeight, int theFlags = 0);
	virtual ~GLFrameBuffer ();
	
	virtual bool hasStencil () const
	{
		return stencilBuffer != 0;
	}
	
	virtual bool hasDepth () const
	{
		return depthBuffer != 0;
	}
	
	virtual bool isOk   () const;
	virtual bool create ();
	virtual bool bind   ();
	virtual bool unbind ();
	
	virtual bool attachColorTexture (Texture * tex, int no = 0);
	virtual bool attachColorTextureCubeMap (Texture * tex, TexTarget cubeMapSide, int no=0);
	virtual bool attachDepthTexture (Texture * tex);
	virtual bool attachDepthTextureCubeMap (Texture * tex, TexTarget cubeMapSide);
	
	virtual bool detachColorTexture (unsigned target, int no = 0);
	virtual bool detachDepthTexture (unsigned target);

	virtual bool setMRT ( int targetsCount, unsigned * targets  );
	virtual Texture * getRenderTarget ( int no = 0 );
	virtual Texture * getDepthTarget ();

	virtual void drawScreenQuad (Texture * tex);
	virtual void drawScreenQuad (Texture * tex, const vec2f &minPoint, const vec2f &maxPoint);

	virtual int getType() { return 	FBTYPE_OGL_FBO; }
	virtual bool shareRenderBuffers (FrameBuffer * fb, int attachment );

	virtual bool checkExtensions ();	
	static int maxColorAttachemnts();
};
//-------------------------------------------------------
}	// namespace renderer
//-------------------------------------------------------
}	// namespace Tor
//-------------------------------------------------------

Итак, класс GLFrameBuffer - реализация фреймбуфера для OpenGL с использованием расширения FBO. Основные члены класса: frameBuffer - идентификатор объекта FBO, depthBuffer - идентификатор рендербуфера для буфера глубины, stencilBuffer - идентификатор рендербуфера для буфера трафарета. В массиве renderTargets[8] хранятся указатели на присоединенные к FBO в качестве цветовых аттачментов текстуры, а в depthTarget - указатель на присоединенную текстуру глубины, если конечно она имеется. Функции hasStencil() и hasDepth() возвращают true если к FBO присоединены буфер трафарета и буфер глубины соответственно. Рассмотрим некоторые функции подробнее.

Фрагменты файла 3D\FBO\Tor_GLFrameBuffer.cpp

GLFrameBuffer :: GLFrameBuffer (int theWidth, int theHeight, int theFlags ) :
				FrameBuffer(theWidth, theHeight, theFlags)
{
	frameBuffer   = 0;
	depthBuffer   = 0;
	stencilBuffer = 0;
	memset( renderTargets,NULL,8*sizeof(Texture*) );
	supportMRT = false;
	maxColorAttachment = 1;
}
//-------------------------------------------------------
GLFrameBuffer :: ~GLFrameBuffer ()
{
	if ( depthBuffer != 0 )
		glDeleteRenderbuffersEXT ( 1, &depthBuffer );
		
	if ( stencilBuffer != 0 )
		glDeleteRenderbuffersEXT ( 1, &stencilBuffer );
		
	if ( frameBuffer != 0 )
		glDeleteFramebuffersEXT ( 1, &frameBuffer );
	
	LOGGER("GLFrameBuffer: FrameBuffer " << width << " x "<< height << " destroyed");
}

В конструкторе выполняется инициализация некоторых членов, например устанавливаются в 0 идентификаторы FBO и рендербуферов, очищается массив renderTargets[]. Также происходит вызов конструктора базового класса - FrameBuffer(theWidth,theHeight,theFlags), где theWidth и theHeight - ширина и высота создаваемого буфера, а theFlags - флаги говорящие о том нужно ли при создании фреймбуфера дополнительно создавать буфер глубины или трафарета. В деструкторе выполняется уничтожение всех использованных рендербуферов функцией glDeleteRenderbuffersEXT(), далее уничтожается сам объект FBO.

bool GLFrameBuffer :: create ()
{
	GLint maxRenderBufferSize = 0;

	LOGGER("GLFrameBuffer: Creating FrameBuffer");
	if ( !checkExtensions () )
	{
		LOGGER("GLFrameBuffer creation error:
				FrameBuffer Extensions does not supported");
		return false;
	}

	glGetIntegerv( GL_MAX_RENDERBUFFER_SIZE_EXT, &maxRenderBufferSize);

	if ( width <= 0 || height <= 0 ||
		width >= maxRenderBufferSize || height >= maxRenderBufferSize )
		return false;
		
	// Создаем объект фреймбуфера FBO
	glGenFramebuffersEXT ( 1, &frameBuffer );
	glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, frameBuffer );
	
	int depthFormat   = 0;
	int stencilFormat = 0;
	
	// теперь нужно создать и присоединить к FBO необходимые 
	// рендербуферы. в переменной flags находится информация 
	// о том какие необходимы рендербуферы
	if ( flags & depth16 )
		depthFormat = GL_DEPTH_COMPONENT16;
	else
	if ( flags & depth24 )
		depthFormat = GL_DEPTH_COMPONENT24_ARB;
	else
	if ( flags & depth32 )
		depthFormat = GL_DEPTH_COMPONENT32;
	
	if ( flags & stencil1 )
		stencilFormat = GL_STENCIL_INDEX1_EXT;
	else
	if ( flags & stencil4 )
		stencilFormat = GL_STENCIL_INDEX4_EXT;
	else
	if ( flags & stencil8 )
		stencilFormat = GL_STENCIL_INDEX8_EXT;
	else
	if ( flags & stencil16 )
		stencilFormat = GL_STENCIL_INDEX16_EXT;
	
	
	if ( depthFormat != 0 )
	{
	  // создаем рендербуфер глубины и присоединяем его к frameBuffer
	  glGenRenderbuffersEXT        ( 1, &depthBuffer );
	  glBindRenderbufferEXT        ( GL_RENDERBUFFER_EXT, depthBuffer );
	  glRenderbufferStorageEXT     ( GL_RENDERBUFFER_EXT, depthFormat, width, height );
	  glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT,  GL_DEPTH_ATTACHMENT_EXT,
	                               GL_RENDERBUFFER_EXT, depthBuffer );
	}
	
	if ( stencilFormat != 0 )
	{
	  // создаем рендербуфер трафарета и присоединяем его к frameBuffer
	  glGenRenderbuffersEXT        ( 1, &stencilBuffer );
	  glBindRenderbufferEXT        ( GL_RENDERBUFFER_EXT, stencilBuffer );
	  glRenderbufferStorageEXT     ( GL_RENDERBUFFER_EXT, stencilFormat, width, height);
	  glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT,  GL_STENCIL_ATTACHMENT_EXT,
	                               GL_RENDERBUFFER_EXT, stencilBuffer );
	}
	
	maxColorAttachment = maxColorAttachemnts ();
	
	glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, 0 );

	// проверяем доступность MRT
	supportMRT = isExtensionSupported("GL_ARB_draw_buffers") ||
					isExtensionSupported("GL_ATI_draw_buffers");

	return true;
}

Функция create() создает объект FBO. Сначала вызовом checkExtensions() проверяются все необходимые расширения. Затем проверяется максимально допустимый размер рендербуфера. Если он меньше требуемого размера, то сообщаем об ошибке. Далее создаются объект фреймбуфера, а также все необходимые рендербуферы.

bool GLFrameBuffer :: isOk () const
{
	unsigned currentFb;
	
	glGetIntegerv (GL_FRAMEBUFFER_BINDING_EXT, (int *)& currentFb );
	
	if ( currentFb != frameBuffer )
		glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, frameBuffer );
	
	GLenum	complete = glCheckFramebufferStatusEXT (GL_FRAMEBUFFER_EXT);

	if ( currentFb != frameBuffer )
		glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, currentFb );

	if (complete == GL_FRAMEBUFFER_COMPLETE_EXT)
		return true;
	
	switch (complete) 
	{                                          
		case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: 
		FATAL("FBO has one or several image attachments
				with different internal formats");
		break;
		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: 
		FATAL("FBO has one or several image attachments
				with different dimensions");
		break;
		case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT: 
		FATAL("FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT");
		break;
		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: 
		FATAL("FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
		break;
		case GL_FRAMEBUFFER_UNSUPPORTED_EXT:  
		FATAL("FBO format unsupported");		
		break;
		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT :
		FATAL("FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
		break;
		case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT :
		FATAL("FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
		break;
		case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT :
		FATAL("FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
	}

	FATAL("Unknown FBO error");

	return false;
}

Функция isOk () - проверяет статус объекта FBO. Это самая полезная функция. Если что-то в фреймбуфере неработает или работает неправильно всегда можно это проверить. glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) проверяет статус текущего FBO.

bool GLFrameBuffer :: bind ()
{
	if ( frameBuffer == 0 )
		if (!create())
		{
			FATAL("GLFrameBuffer.bind (): Unable to create GLFrameBuffer");
			return false;
		}

	glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, frameBuffer);
	glViewport (0, 0, width, height);

	int numColorAttachments = 0;
	unsigned attachments[8];
	for (unsigned i=0; i < 8; i++)
		if (renderTargets[i]!=NULL)
		{
			attachments[numColorAttachments] = GL_COLOR_ATTACHMENT0_EXT + i;
			numColorAttachments++;
		}
	setMRT(numColorAttachments,attachments);
	return true;
}

Функция bind() - установливает текущим объект фреймбуфера. Здесь, помимо того, что вызаывется функция привязки фреймбуфера glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBuffer), выполняется еще и установка размеров области вывода - glViewport( 0, 0, width, height ), о чем многие очень часто забывают. Так как размер текущего фреймбуфера может отличаться от размера буфера кадра, или других фреймбуферов, то если не обновить область вывода изображение будет отрисовываться некорректно. Также здесь разрешается возможность вывода в несколько цветовых буферов. Этим занимается функция setMRT(numColorAttachments, attachments), где в массиве attachments передаются номера цветовых буферов для вывода (возможны значения от 0 до 7, но в более продвинутых реализациях максимальное количество цветовых буферов может быть больше чем 8). MRT возможно задействовать лишь при рендеринге с использованием фрагментных шейдеров. Тогда во фрагментном шейдере результат нужно писать не в переменную gl_FragData, а в массив gl_FragData[].

bool GLFrameBuffer :: unbind ()
{
	if (frameBuffer == 0)
		return false;
		
	glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0);

	// later, after use it, need to change vieport dims
	
	return true;
}

Функция unbind() делает текущим нулевой фреймбуфер, тем самым устанавливает задний буфер для вывода на экран.

void GLFrameBuffer :: drawScreenQuad ( Texture * tex )
{
	glMatrixMode   ( GL_PROJECTION );
	glPushMatrix   ();
	glLoadIdentity ();

	glOrtho        ( 0, width, 0, height, -1, 1 );
	glMatrixMode   ( GL_MODELVIEW );
	glPushMatrix   ();
	glLoadIdentity ();

	glViewport(0, 0, width, height);

	bool depthMask = GlobalRendererState->depthWrite;
	bool depthTest = GlobalRendererState->depthTest;
	GlobalRendererState->setDepthTest(false);
	GlobalRendererState->setDepthWrites(false);

	if (tex)
		tex->Bind();

	glColor4f     ( 1, 1, 1, 1 );

	glBegin       ( GL_QUADS );

		glTexCoord2f ( 0, 0 );
		glVertex2f   ( 0, 0 );

		glTexCoord2f ( 1, 0 );
		glVertex2f   ( (float)width, 0 );

		glTexCoord2f ( 1, 1 );
		glVertex2f   ( (float)width, (float)height );

		glTexCoord2f ( 0, 1 );
		glVertex2f   ( 0, (float)height );

	glEnd   ();

	glMatrixMode ( GL_PROJECTION );
	glPopMatrix  ();
	
	glMatrixMode ( GL_MODELVIEW );
	glPopMatrix  ();

	GlobalRendererState->setDepthTest(depthTest);
	GlobalRendererState->setDepthWrites(depthMask);
}

Функция drawScreenQuad(Texture * tex) рисует в текущий фреймбуфер квад с заданной текстурой tex.

bool GLFrameBuffer :: setMRT (int targetsCount, unsigned * targets)
{
	if (!supportMRT)
		return false;

	GLuint  buffers [8];

	if (targetsCount>(int)maxColorAttachment)
		return false;

	for (int i=0; i < targetsCount; i++)
	{
		buffers[i] = GL_COLOR_ATTACHMENT0_EXT + targets[i];
	}
	glDrawBuffersARB (targetsCount, buffers);
	
	return true;
}
Страницы: 1 2 3