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

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

Изменено: 05.03.2008

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

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

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





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

Функция setMRT() разрешает использование для вывода нескольких цветовых буферов. Для этого требуется поддержка MRT - расширение ARB_draw_buffers или ATI_draw_buffers. Эти расширения вводят функцию glDrawBuffersARB() котрорая разрешает вывод в заданный набор буферов.

bool	GLFrameBuffer :: attachColorTexture (Texture * tex, int no)
{
	if ( frameBuffer == 0 )
		return false;

	if ( renderTargets[no] == tex )
		return true;

	unsigned currentFb;

	glGetIntegerv ( GL_FRAMEBUFFER_BINDING_EXT, (int *)& currentFb );
	
	if ( currentFb != frameBuffer )
		glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, frameBuffer );
		
	if ( tex->getTarget() != TT_TEXTURE_2D && tex->getTarget() != TT_TEXTURE_RECTANGLE &&
			(tex->getTarget() < GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB ||
				tex->getTarget() > GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB) )
		return false;

	if (no > (int)maxColorAttachment)
		return false;
		
	if (tex->isMipmap() && (tex->getTarget() != TT_TEXTURE_RECTANGLE))
		glGenerateMipmapEXT ( tex->getTarget() );

	glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + no,
		tex->getTarget(), tex->getId(), 0 );
	
	renderTargets[no] = tex;

	isOk();

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

	return true;
}

Как видно из названия, функция attachColorTexture ( Texture * tex, int no ) присоединяет к фреймбуфру текстуру, причем в случае FBO вторым параметром no задается номер цветового аттачмента, он может принимать значнеия от 0 до 7. Если присоединяемая текстура должна иметь мип-мап уровни, то для нее разрешается генерация мип-мапов функцией glGenerateMipmapEXT(). А теперь важное замечание, при частом ренеринге в FBO (несколько раз в кадре) использование генерации мипмапов может сильно сказываться на производительности. Для случая постобработки генерацию мип-мапов для текстур рендер-таргетов лучше сразу отключить.

bool GLFrameBuffer :: attachDepthTexture ( Texture * tex )
{
	if ( frameBuffer == 0 )
		return false;

	unsigned currentFb;
	
	glGetIntegerv ( GL_FRAMEBUFFER_BINDING_EXT, (int *)& currentFb );
	
	if ( currentFb != frameBuffer )
		glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, frameBuffer );

	glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
		tex->getTarget(), tex->getId(), 0 );
	
	depthTarget = tex;

	isOk();

	if ( currentFb != frameBuffer )
		glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, currentFb );
	
	return true;
}

Функция attachDepthTexture( Texture * tex, int no ) делает почти все то же смое что и предыдущая, но только присоединяет к фреймбуфру текстуру глубины.

bool	GLFrameBuffer :: detachColorTexture (unsigned target, int no)
{
	if ( frameBuffer == 0 )
		return false;

	unsigned currentFb;

	if (renderTargets[no]==NULL)
		FATAL("GLFrameBuffer.detachColorTexture: Try to detach unattached buffer!");

	glGetIntegerv ( GL_FRAMEBUFFER_BINDING_EXT, (int *)& currentFb );
	
	if ( currentFb != frameBuffer )
		glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, frameBuffer );

	if ( target != GL_TEXTURE_2D && target != TT_TEXTURE_RECTANGLE &&
		(target < GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB ||
			target > GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB) )
		return false;

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

	glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + no,
		target, 0, 0 );

	renderTargets[no] = NULL;

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

	return true;
}
//-------------------------------------------------------
bool	GLFrameBuffer :: detachDepthTexture ( unsigned target )
{
	if ( frameBuffer == 0 )
		return false;
		
	glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
		target, 0, 0 );

	depthTarget = NULL;
	return true;
}

Думаю не нужно объяснять что выполняют эти две функции. А вот следующая функция достойна отдельного рассмотрения.

bool GLFrameBuffer :: shareRenderBuffers (FrameBuffer * fb, int attachment)
{
	if (fb->getType() != FBTYPE_OGL_FBO)
		return false;

	// если размеры рендертаргетов не совпадают - возвращаем false
	if (fb->getWidth() != width || fb->getHeight() != height)
		return false;

	GLFrameBuffer * FB = static_cast<GLFrameBuffer*>(fb);

	int gl_attachment = 0;
	int buffer = 0;
	if ( attachment & depth16 )
	{
		gl_attachment = GL_DEPTH_ATTACHMENT_EXT;
		buffer = FB->depthBuffer;
	}
	else
	if ( attachment & depth24 )
	{
		gl_attachment = GL_DEPTH_ATTACHMENT_EXT;
		buffer = FB->depthBuffer;
	}
	else
	if ( attachment & depth32 )
	{
		gl_attachment = GL_DEPTH_ATTACHMENT_EXT;
		buffer = FB->depthBuffer;
	}
	
	if ( attachment & stencil1 )
	{
		gl_attachment = GL_STENCIL_ATTACHMENT_EXT;
		buffer = FB->stencilBuffer;
	}
	else
	if ( attachment & stencil4 )
	{
		gl_attachment = GL_STENCIL_ATTACHMENT_EXT;
		buffer = FB->stencilBuffer;
	}
	else
	if ( attachment & stencil8 )
	{
		gl_attachment = GL_STENCIL_ATTACHMENT_EXT;
		buffer = FB->stencilBuffer;
	}
	else
	if ( attachment & stencil16 )
	{
		gl_attachment = GL_STENCIL_ATTACHMENT_EXT;
		buffer = FB->stencilBuffer;
	}

	if (!gl_attachment || !buffer || !frameBuffer || !buffer)
		return false;

	unsigned currentFb;
	glGetIntegerv ( GL_FRAMEBUFFER_BINDING_EXT, (int *)& currentFb );
	
	if ( currentFb != frameBuffer )
		glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, frameBuffer );

	glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, gl_attachment,
		GL_RENDERBUFFER_EXT, buffer);

	isOk();

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

	return true;
}

Ранее я говорил, что для нескольких FBO существует возможность расшаривать (совместно использовать) рендербуферы. Функция shareRenderBuffers(FrameBuffer * fb, int attachment) как раз служит именно для этой цели. Она получает указатель на фреймбуфер и специальный флаг. Этот флаг указывает, какой тип рендербуфера (depth или stencil) из fb следует присоединить к данному фреймбуферу. Затем вызывается функция glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, gl_attachment, GL_RENDERBUFFER_EXT, buffer), которая присоединяет к текущему фреймбуферу рендербуфер buffer в качестве gl_attachment.

int GLFrameBuffer :: maxColorAttachemnts ()
{
	int n;
	glGetIntegerv (GL_MAX_COLOR_ATTACHMENTS_EXT, &n);
	return n;
}

И, наконец, функция maxColorAttachemnts() - просто возвращает максимальное для реализации OpenGL количество цветовых аттачментов.

Как я уже говорил, если реализация OpenGL не поддерживает расширение FBO, то от идеи создания на ней пост-эффектов отказываться не стоит. Выходом из этой ситуации может послужить использование расширения PBuffer взамен FBO. Листинги файлов реализации PBuffer-а вы можете найти в прилагаемых исходниках.

Отдельно стоит рассмотреть, как происходит создание и инициализация рендер-таргета в функции OpenGlView::createFrameBuffer(). Ниже приведен листинг этой функции.

FrameBuffer* OpenGlView :: createFrameBuffer(int theWidth, int theHeight,
		TexIntFmt texFormat, int theFlags ) const
{
	if (oglExt::isExtensionSupported("GL_EXT_framebuffer_object"))
	{
		FrameBuffer * fbo = (FrameBuffer *) new GLFrameBuffer(theWidth,
				theHeight, theFlags);
		if (!fbo->create())
		{
			delete fbo;
			fbo = NULL;
		}
		return fbo;
	}
	else
	if (oglExt::isExtensionSupported("WGL_ARB_pbuffer"))
	{
		int mode = 0;
	
		if (GetTexTYPE_ID(texFormat) == TTYPE_HALF_FLOAT)
			mode |= PBuffer::modeBufferHalfFloat;
		if (GetTexTYPE_ID(texFormat) == TTYPE_FLOAT)
			mode |= PBuffer::modeBufferFloat;
		if (GetTF_ID(texFormat) == PF_RGBA)
			mode |= PBuffer::modeAlpha;
	
		mode |= PBuffer::modeTexture2D;
		if (theFlags & FrameBuffer::stencil8)
			mode |= PBuffer::modeStencil;
		if (theFlags & FrameBuffer::depth16 || theFlags & FrameBuffer::depth24)
			mode |= PBuffer::modeDepth;

		OpenGLPBuffer * ret = new OpenGLPBuffer("", theWidth, theHeight, mode, texFormat);
		if (!ret->create())
		{
			delete ret;
			ret = NULL;
		}
		return ret;
	}
	else
	{
		FATAL("This Hardware does not support Render To Texture (RTT) :(...");
	}
	return NULL;
}

Как видно из кода этой функции, в зависимости от реализации OpenGL возможно создание фреймбуфера как на основе расширения FBO, так и на основе PBuffer. То, какой создастся объект, определяется в процессе выполнения программы и зависит от поддержки соответствующих расширений.

Класс PostprocFilter управляет процессом постобработки и его по большому счету стоило-бы рассмотреть более подробно, но в связи с тем, что от этого сильно возрастет объем статьи, приводить код я не стану, а опишу лишь несколько основных функций.

Ниже приведена часть исходного кода классов PostprocFilter и PostprocFilterPass.

3D\Tor_PostprocFilter.h

// структура для конфигурации текстуры
struct stTextureSetup
{
	stTextureSetup() : width(0),height(0), texture(NULL), texIntFormat(TIF_RGBA8), name("") {}
	int width;
	int height;
	Texture		*texture;
	TexIntFmt	texIntFormat;
	String name;

	void clear()
	{
		width	= height = 0;
		texture	= NULL;
		texIntFormat	= TIF_RGBA8;
		name	= ""; 		
	}
};
// структура для конфигурации шейдера
struct stShaderProgramSetup
{
	stShaderProgramSetup() : shader(NULL), name(""), fileName("") {}
	Tor::renderer::ShaderProgram * shader;
	String  name;
	String  fileName;
	VarsManager	variables;
	VarsManager	texUnits; //	[имя сэмплера]	[unit]

	void clear()
	{
		shader = NULL;
		name = ""; 
		fileName = "";
		variables.clear();
		texUnits.clear();			
	}
};
//-------------------------------------------------------
class  PostprocFilterPass
{
public:

	PostprocFilterPass(const String& Name, PostprocFilter * filter);
	virtual ~PostprocFilterPass();

	virtual void bind();
	virtual void unbind();

	virtual void drawScreenQuad();

	virtual ShaderProgram	*getShader();

	String  getName();

	void  addRenderTarget(FrameBuffer * r_target);
	void  addTexture(const String& tex_name, stTextureSetup &texture);
	void  setShaderProgramSetup(stShaderProgramSetup *sh);

	void  setValue(const String& valName, Var * var);

	FrameBuffer	*getOutRenderTarget();

private:
	void setTexture(const String& tex_name, stTextureSetup & tex);

private:
	int texturesCount;
	FrameBuffer		*outRenderTarget;
	stTextureSetup		inputTextures[MAX_INPUT_TEXTURES];
	stShaderProgramSetup	*shader;
	String			passName;
	PostprocFilter		*ppfilter;
};

Класс PostprocFilterPass задает один проход обработки изображения - один вывод квада в текущий фреймбуфер. Член класса outRenderTarget - выходной фреймбуфер в котором будет формироваться изображение. Если выходным фреймбуфером является задний буфер, то outRenderTarget имеет значение NULL. inputTextures[] - массив входных текстур, хранит указатели на используемые в этом проходе текстуры, shader - шейдер для постобработки, passName - имя этого прохода, ppfilter - ссылка на связанный с этим проходом пост-эффект. Функция bind() конфигурирует все необходимые для прохода объекты, устанавливает текущим фреймбуфер для вывода изображения, привязывает текстуры, обновляет uniform переменные шейдера и устанавливает шейдер для постобработки. drawScreenQuad() - отрисовывает квад на экран если outRenderTarget == NULL иначе - в outRenderTarget. addRenderTarget() - устанавливает фреймбуфер для этого прохода. addTexture() - добавляет текстуру к данному проходу. setShaderProgramSetup() - устанавливает шейдер. setValue() - устанавливает значение переменных шейдера. Последние четыре функции используются в основном при парсинге файла пост-эффекта.

class   PostprocFilter
{
public:

	PostprocFilter(const String& Name = "newFilter" );

	virtual ~PostprocFilter();

	virtual void  begin(const Camera * camera);
	virtual void  end();
	virtual void  process ();
	virtual void  setSequence(const String& seqName);

	void  clear();

	virtual  PostprocFilterPass*	getFilterPass(int passNum);
	virtual  PostprocFilterPass*	getFilterPass(const String& passName);
	virtual  PostprocFilterPass*	getCurrentFilterPass();

	String  getName() const;

	void  setInputTexture(const String& inTexName, Texture * texture);
	Texture  *getInputTexture(const String& inTexName);

	int  getPassesUsed();

private:
	void  addInputTexture(stTextureSetup & texture);
	void  setInputRenderTarget(FrameBuffer * input_render_target);

	virtual void  addSequence(const String& name, vector<int>& seq);
	virtual void  addFilterPass(PostprocFilterPass* pass);

private:
	FrameBuffer		*inputRenderTarget;
	stTextureSetup		inputTextures[MAX_INPUT_TEXTURES];
	PostprocFilterPass	*filterPass[MAX_FILTER_PASSES];
	int  passesUsed;
	int  currentPass;
	int  inputTexturesUsed;
	vector<int>	 currentSequence;
	map< String, vector<int> >	passSequences;
	String  name;

	// Все созданные в процессе компиляции объекты хранятся тут
	// для разруливания ситуации с их менеджментом.
	// При уничтожении фильтра уничтожаем их все.
	vector<FrameBuffer*>	frameBuffers;
	vector<Texture*>	textures;
	vector<ShaderProgram*>	shaders;

	// к сожалению пришлось подружить его с PostprocCompiler
	friend class	PostprocCompiler;
};

Член класса PostprocFilter - inputRenderTarget, предсталяет собой входной фреймбуфер в который будет отрисовыаться 3D сцена. Массив inputTextures[] - задает входные текстуры, текстуры которые загружаются с диска и содержат какое-либо изображение, например dudv карту для сдвига текстурных координат. Эти текстуры впоследствии могут использоваться внутри проходов. Массив filterPass[] хранит все испоьзуемые для этого пост-эффекта проходы. Для чего служат счетчики passesUsed, currentPass и inputTexturesUsed видно из их названия. Вектор currentSequence задает текущую последовательность проходов, она хранится в виде индексов проходов из массива filterPass[]. Функция begin(const Camera * camera) устанавливает текущим входной фреймбуфер для рендеринга 3D сцены. Функция end() в обязательном порядке должна вызываться после рендеринга 3D сцены, она отвязывает входной фреймбуфер. В функции process() выполняется вся постобработка, вот чать этой функции:

	...
	unsigned size = currentSequence.size();

	for (unsigned i=0; i < size; i++)
	{
		int fltrN = currentSequence[i];
		filterPass[fltrN]->bind();
		filterPass[fltrN]->drawScreenQuad();
		filterPass[fltrN]->unbind();
	}
	...

Класс PostprocCompiler выполняет всю основную работу по парсингу файла пост-эффекта и созданию всех необходимых объектов: текстур, шейдеров, рендер-таргетов и т.п. Думаю приводить код этого класса нестоит, он довольно объемный, и в любом случае его можно найти в прилагаемом к статье исходном коде. От PostprocCompiler нам понадобится лишь одна функция: PostprocFilter * PostprocCompiler::compile(const String& fileName) которая выполняет парсинг файла и создание пост-эффекта.

Список источников:

[OGL] OpenGL. Официальное руководство программиста
[Lun] Фрэнк Д. Луна. Введение в программирование трёхмерных игр с DirectX 9.0.
[Bor05] А.В. Боресков. Расширения OpenGL.
[Rost05] Р.Д. Рост. OpenGL Трехмерная графика и язык программирования шейдеров.
[CG] The Cg Tutorial - The Definitive Guide To Programmable Real-Time Graphics
[DXHDR] DirectX HDR Lighting sample. DirectX SDK (November 07).
[DXHDRa] DirectX HDR Pipeline sample. DirectX SDK (November 07).
[RM] Render Monkey 1.62: Depth of Field Effect
[Dev02] DEBEVEC P.: Image based lighting tutorial. In IEEE Computer Graphics and Applications (2002).
[Kawase04] Kawase, Masaki. Practical Implementation of High Dynamic Range Rendering. Presentation. Game Developers Conference 2004.

Ссылки на www источники:

Общие ресурсы:

[OGLORG] OpenGL: http://www.opengl.org
[OGLEXT] OpenGL Extensions: http://www.opengl.org/registry/
[NV] nVidia: http://www.developer.nvidia.com/page/home.html
[NVCg] nVidia cg: http://www.developer.nvidia.com/object/cg_tutorial_home.html
[NVSDK] nVidia SDK: http://developer.nvidia.com/object/sdk_home.html
[ATI] ATI: http://ati.amd.com/developer/index.html
[FBO] http://download.nvidia.com/developer/presentations/2005/GDC/OpenGL_Day/OpenGL_FrameBuffer_Object.pdf
[GD06] http://www.gamedev.ru/community/opengl/articles/fp16fp32
[GD06a] http://www.gamedev.ru/community/opengl/articles/framebuffer_object
[S3DFBO] http://www.steps3d.narod.ru/tutorials/framebuffer-object-tutorial.html

Ресурсы по реализации пост-эффектов:

[S3D06] http://www.steps3d.narod.ru/tutorials/hdr-tutorial.html
[Luk07] Christian Luksch Realtime HDR Rendering http://www.cg.tuwien.ac.at/research/publications/2007/Luksch_2007_RHR/Luksch_2007_RHR-RealtimeHDR%20.pdf
[GDN] http://www.gamedev.net/reference/articles/article2208.asp
[GDNа] http://www.gamedev.net/columns/hardcore/hdrrendering/
[NV04] http://download.nvidia.com/developer/presentations/2004/6800_Leagues/6800_Leagues_HDR.pdf
[KM] Kawase, Masaki. "Real-Time High Dynamic Range Image-Based Lighting".
[REM02] Reinhard, Erik, Mike Stark, Peter Shirley, and James Ferwerda. "Photographic Tone Reproduction for Digital Images". ACM Transactions on Graphics (TOG), Proceedings of the 29th Annual Conference on Computer Graphics and Interactive Techniques (SIGGRAPH), pp. 267-276. New York, NY: ACM Press, 2002.

 


Читайте продолжение этой статьи "Реализация процессора эффектов постобработки. Часть 2 - Создание пост-эффектов". Там же вы можете найти бинарник и исходный код приложения, которое демонстрирует несколько различных пост-эффектов.


Copyright © D'yachkov Vladimir, 2008. Все права на использование этой статьи принадлежат Дьячкову Владимиру (Автору). При цитировании где-либо любой части данной статьи ссылка на нее обязательна. Автор оставляет за собой право размещать эту статью где-либо, будь то в сети Интернет или в любом печатном издании.


Статья участвовала в конкурсе статей по программированию #2 (2007).

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