Бесплатная реклама

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

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

Изменено: 17.03.2008

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

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

Реализация процессора эффектов постобработки. Часть 2 - Создание пост-эффектов





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

HDR эффект, аккомодация глаза к интенсивности освещения

Тема HDR рендеринга (High Dynamic Range Rendering), на мой взгляд, достойна не одной статьи. Поэтому я не стану пытаться объять необъятное, тем более что размер статьи уже и так порядком великоват, а лишь вкратце опишу, как с помощью подобного фреймворка можно реализовать приемлемый HDR с эффектом аккомодации (приспособления) глаза к условиям освещения. Вот теперь-то и проявится вся мощь фреймворка. Что касается теоретической части HDR рендеринга, то можно порекомендовать несколько статей, например [Luk07], [REM02], [GDN] и [Dev02]. Хороший пример реализации HDR и аккомодации есть в DXSDK, пример HDR Lighting [DXHDR], а также HDR Pipeline [DXHDRa]. Поэтому я крайне рекомендую изучить сначала теоретическую часть, а потом преступать к реализации HDR на практике. Насчет особенностей реализации HDR на современном железе, пожалуй, я скажу пару слов, но все же вам не мешает обратиться к документации изготовителей видеокарт, чтобы уяснить все тонкости.

Для HDR рендеринга необходимо наличие аппаратной поддержки видеокартой рендеринга в floating-point текстуры, FP16 или FP32. На уровне OpenGL API такая возможность появляется фактически при наличии расширений GL_ARB_texture_float, GL_ATI_texture_float, и GL_ARB_color_buffer_float. Не стоит забывать, что для ряда видеокарт эти расширения имеют свои ограничения. Эти расширения вводят новые типы форматов текстур, а также позволяют использовать такие текстуры в качестве цветовых аттачментов FBO, или же задавать PBuffer-ы в FP формате и присоединять к ним FP текстуры. Расширение GL_ARB_color_buffer_float управляет приведением цвета вершин и фрагментов в единичный куб. Также существует ряд устаревших FP форматов вводимых расширениями GL_NV_float_buffer/WGL_NV_float_buffer, но они имеют ряд серьезных ограничений и пользоваться ими крайне нежелательно. Например, при рендеринге в такие текстуры невозможен блендинг, а также недоступна фильтрация таких текстур. Более подробно обо всех достоинствах и недостатках указанных расширений можно почитать в этой статье [GD06].

Стоит также заметить, что вариантов реализации HDR эффекта существует огромное количество, я лишь опишу один из них.

В общем случае процесс HDR рендеринга состоит из следующих этапов:

  • Рендеринг сцены в FP текстуру
  • Уменьшение изображения сцены
  • Получение средней яркости изображения
  • Bright-pass
  • Bloom эффект
  • Star эффект
  • Применение tone mapping-а и объединение эффектов

Дополнительно выполняются следующие этапы:

  • Адаптация яркости
  • Вычисление значения luminance key
Для начала нам понадобится отрендерить 3D сцену в FP16 текстуру. Затем нужно определить усредненную яркость (luminance) этого изображения, она вычисляется по следующей формуле:



      Lw(x,y) - это яркость текущего фрагмента изображения, она определяется как скалярное произведение цвета этого фрагмента на вектор весов определяющий вклад в интенсивность каждой компоненты цвета.

Существуют и другие методики вычисления средней яркости, например, на основании среднего квадратичного яркости всех фрагментов. Пожалуйста, используйте те, какие считаете нужными. Эта статья не рассчитана на то, чтобы в полной мере осветить все аспекты HDRR и сделать исчерпывающий анализ различных методик. Для этого вам лучше обратиться к соответствующей литературе или заняться самостоятельными изысканиями, здесь я лишь освещу базовые для большинства методик этапы и процедуры HDRR на основе одного из вариантов реализации.

Для вычисления усредненной (общей) яркости на первом этапе все изображение рендерится в текстуру размера 64х64. Причем производится 9 выборок из исходной текстуры и вычисляется логарифм яркости каждого фрагмента, затем эти значения для 9-ти выборок усредняются. В этом проходе также определяется максимальная яркость для этих 9-ти выборок, в дальнейшем она понадобится нам для финальной обработки. Описанную процедуру выполняет шейдер downLumLog.glsl. Ниже приведен его листинг. Результат записывается в текстуру, над которой впоследствии также производится обработка.

downLumLog.glsl

/*FRAGMENT_SHADER*/
const vec3 LUMINANCE_VECTOR  = vec3(0.2125, 0.7154, 0.0721);
uniform sampler2D DownSampler;
uniform vec2 samples[9];

void main(void)
{
	float	lum = 0.0;
	float	GreyValue = 0.0;
	float	maximum = 0.0;
	vec3	color;

	for (int i = 0; i < 9; i++)
	{
		color = texture2D(DownSampler, gl_TexCoord[0].xy + samples[i].xy).xyz;
		lum += log(dot(color,LUMINANCE_VECTOR) + 0.0001);
		GreyValue = max( color.r, max( color.g, color.b ) );
		maximum = max( maximum, GreyValue );
	}
	lum *= 0.111111;

	gl_FragColor = vec4(lum, maximum, 0.0, 1.0);
}

Затем это изображение уменьшается до размеров 16х16, производится down-sampling с 16-ю выборами в шейдере downLum.glsl. И опять вычисляется максимальная яркость из 16-ти выборок.

downLum.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D DownSampler;
uniform vec2 samples[16];

void main(void)
{
	float lum = 0.0;
	vec2  sample;
	vec4  color;
	float maximum = 0.0;
	for (int i = 0; i < 16; i++)
	{
		sample = vec2(samples[i].x,samples[i].y);
		color = texture2D(DownSampler, gl_TexCoord[0].xy + sample);
		maximum = max( maximum, color.g );
		lum += color.r;
	}
	lum *= 0.0625;
	
	gl_FragColor = vec4(lum, maximum, 0.0, 1.0);
}

Затем полученное на предыдущем этапе изображение уменьшается до размеров 4х4, и опять производится down-sampling с 16-ю выборами в шейдере downLum.glsl. И опять вычисляется максимальная яркость из16-ти выборок.

Далее остается вычислить общую яркость, этим занимается шейдер downLumExp.glsl. Все изображение при этом рендерится в текстуру 1х1.

downLumExp.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D DownSampler;
uniform vec2 samples[16];

void main(void)
{
	float lum = 0.0;
	float maximum = 0.0;
	vec4  color;
	vec2  sample;
	for (int i = 0; i < 16; i++)
	{
		sample = vec2(samples[i].x,samples[i].y);
		color = texture2D(DownSampler, gl_TexCoord[0].xy + sample);
		maximum = max( maximum, color.g );
		lum += color.r;
	}

	lum *= 0.0625;
	lum = exp(lum);

	gl_FragColor = vec4(lum, maximum, 0.0, 1.0);
}

Адаптированное значение яркости вычисляется исходя из значения яркости на предыдущем кадре и значения яркости полученного в текущем кадре. Адаптировать следует не только общую яркость, но и максимальную, иначе при tone mapping-е картинка будет иметь неприятные скачки яркости. Формула, по которой происходит адаптация:

      col = Alum + (Clum - Alum) * (1.0 - pow(0.98, 30.0 * dtime)),

где col - результирующая яркость, Alum - яркость, рассчитанная в предыдущем кадре, Clum - яркость, рассчитанная в текущем кадре, dtime - текущий интервал времени между соседними кадрами, оно вычисляется каждый кадр. Ниже приведен листинг шейдера адаптации.

adaptLum.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D CurLum;
uniform sampler2D AdaptLum;
uniform float dtime;

void main(void)
{
	vec3 Clum = texture2D(CurLum, vec2(0.5, 0.5)).xyz;
	vec3 Alum = texture2D(AdaptLum, vec2(0.5, 0.5)).xyz;
	vec3 col = Alum + (Clum - Alum) * (1.0 - pow(0.98, 30.0 * dtime));
	col.x = clamp(col.x, 0.25, 5.0);

	gl_FragColor = vec4(col.x,col.y, 0.0, 1.0);
}

Глаз имеет свойство не различать слабо освещенные участки, существует минимальная средняя яркость, которую может воспринимать человеческий глаз. Для этого clamp-ом специально ограничиваем яркость в диапазоне 0.25…5.0.

Если по каким-либо причинам предыдущий вариант адаптации яркости вас не устраивает, то можно попробовать следующий вариант:

      vec3 col = pow( pow( Alum, 0.25 ) + ( pow( Clum, 0.25 ) - pow( Alum, 0.25 ) ) * ( 1.0 - pow( 0.98, 30 * dtime ) ), 4.0);

Для придания сцене более натурального вида к исходному изображению применяется bloom эффект. Для этого, как и в случае простой реализации блума, исходное изображение сначала уменьшается, одновременно с этим производится bright-pass. Есть несколько видов bright-pass. Например, простое обнуление цвета фрагментов с низкой яркостью как это было рассмотрено ранее при реализации bloom эффекта. Ниже представлен листинг такого шейдера, выполняющего обнуление цвета фрагментов с недостаточной яркостью. Отсечение выполняется, когда какая-либо из компонент цвета меньше единицы. Для нормальной работы шейдера перед отрисовкой сцены необходимо с помощью функции glClampColorARB(GL_CLAMP_VERTEX_COLOR_ARB, GL_FALSE), входящей в расширение GL_ARB_color_buffer_float, запретить зажимать цвета вершин в диапазон [0..1].

downSampleHDR.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D DownSampler;
uniform vec2 samples[16];

vec4 SuppressLDR(vec4 c)
{
	if( c.r > 1.0 || c.g > 1.0 || c.b > 1.0 )
		return c;
	else
		return vec4( 0.0, 0.0, 0.0, 0.0 );
}

void main(void)
{
	vec3 col;
	vec2 sample;
	for (int i = 0; i < 16; i++)
	{
		sample = vec2(samples[i].x,samples[i].y);
		col += texture2D(DownSampler, gl_TexCoord[0].xy + sample).xyz;
	}
	col *= 0.0625;
	
	gl_FragColor = SuppressLDR( vec4(col, 1.0) );

}

Но есть более умный способ, использующий рассчитанную ранее адаптированную усредненную яркость. Данный способ используется в [DXHDR] и [Luk07]. Какой вам ближе - судите сами. В прилагаемой к статье демке имеется два варианта bright-pass. Ниже приведен листинг шейдера bright pass использующий адаптированную яркость.

brightPass.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D DownSampler;
uniform sampler2D ALumSampler;
uniform vec2  samples[16];
uniform float key;
const float BRIGHT_PASS_THRESHOLD = 5.5; // Threshold forBrightPass filter
const float BRIGHT_PASS_OFFSET    = 1.0; // Offset for BrightPass filter

void main(void)
{
	vec3 col;
	vec2 sample;
	for(int i = 0; i < 16; i++)
	{
		sample = vec2(samples[i].x,samples[i].y);
		col += texture2D(DownSampler, gl_TexCoord[0].xy + sample).xyz;
	}
	col *= 0.0625;
	
	float  ALum = texture2D(ALumSampler, vec2(0.5, 0.5) );

	// Determine what the pixel's value will be after tone-mapping occurs
	col.xyz *= key/(ALum.x + 0.001);
	
	// Subtract out dark pixels
	col.xyz -= BRIGHT_PASS_THRESHOLD;
	
	// Clamp to 0
	col = max(col, 0.0);
	
	// Map the resulting value into the 0 to 1 range. Higher values for
	// BRIGHT_PASS_OFFSET will isolate lights from illuminated scene 
	// objects.
	col.xyz /= (BRIGHT_PASS_OFFSET + col.xyz);

	gl_FragColor = vec4(col, 1.0);
}

После того как уменьшенное изображение прошло bright-pass, выполняется его размытие по методу Гаусса, сначала по x - координате, затем по y.

После нахождения адаптированной яркости и получения bloom текстуры, выполняется финальное тонирование, так называемый tone-mapping. Он служит для приведения результатов расчетов находящихся в HDR диапазоне к LDR (Low Dynamic Range) диапазону. Существует несколько видов tone-mapping. Простейший из них - линейный я вообще рассматривать не буду, он слишком примитивный и не дает приемлемых результатов. Также существует вариант использующий оператор Рейнхарда (Reinhard's operator), с ним изображение выглядит немного красивее. Но куда лучше смотрится модифицированный оператор Рейнхарда (The modified Reinhard's operator), используемый в [DXHDRa] и [Luk07].


где Lwhite2 - квадрат максимальной яркости изображения, а

где a - величина, задающая экспозицию в сцене. Чем она выше, тем больше засвечивается результирующее изображение. Существуют методики управляющие экспозицией в зависимости от яркости сцены, например на основе вычисления, так называемого luminance key, но в нашем упрощенном случае эта величина постоянна. Ниже приводится шейдер одного из возможных вариантов tone-mapping.

toneMapping.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D srcImage;
uniform sampler2D blurYImage;
uniform sampler2D adaptLumImage;
uniform float exposure;
const float blurBlendFactor = 0.5;
const float fGaussianScalar = 1.0;

void main(void)
{
	vec4 col = texture2D(srcImage, gl_TexCoord[0].xy);
	vec4 bloom = texture2D(blurYImage, gl_TexCoord[0].xy);
	vec4 lum = texture2D(adaptLumImage, vec2(0.5, 0.5));
	col += bloom * blurBlendFactor;

	float Lp = (exposure / lum.r ) * max( col.r, max( col.g, col.b ) );
	float LmSqr = (lum.g + fGaussianScalar * lum.g) * (lum.g + fGaussianScalar * lum.g);
	float toneScalar = ( Lp * ( 1.0 + ( Lp / ( LmSqr ) ) ) ) / ( 1.0 + Lp );
	col = col * toneScalar;
	gl_FragColor = vec4(col.x, col.y, col.z, 1.0);
}

Теперь о том, как все это реализуется с помощью фреймворка. Для этого нам понадобится десять рендер-таргетов, которые будут иметь формат RGBA_FLOAT16. Почему их так много я объясню ниже. Также необходимо настроить все описанные ранее шейдеры и проходы. Ниже приведен листинг файла конфигурации HDR эффекта.

mm_HDR.ppf

!----------------------------------
!	Postproc Filter	File
!----------------------------------
mm_HDR.ppf
{
   ! описание используемых рендер-таргетов
   render_target  sourceHDR
   {
	width		512
	height		512
	int_format	RGBA_FLOAT16	
	depth		24
	texture		0
   }
   render_target  brightPassSmall
   {
	width		256
	height		256
	int_format	RGBA_FLOAT16
	texture		0
   }
   render_target  XbrightPassSmallBlur
   {
	width		128
	height		128
	int_format	RGBA_FLOAT16
	texture		0
   }
   render_target  YbrightPassSmallBlur
   {
	width		128
	height		128
	int_format	RGBA_FLOAT16
	texture		0
   }
   render_target  luminance0
   {
	width		64
	height		64
	int_format	RGBA_FLOAT16
	texture		0
   }
   render_target  luminance1
   {
	width		16
	height		16
	int_format	RGBA_FLOAT16
	texture		0
   }
   render_target  luminance2
   {
	width		4
	height		4
	int_format	RGBA_FLOAT16
	texture		0
   }
   render_target  luminanceSwap0
   {
	width		1
	height		1
	int_format	RGBA_FLOAT16
	texture		0
   }
   render_target  luminanceSwap1
   {
	width		1
	height		1
	int_format	RGBA_FLOAT16
	texture		0
   }
   render_target  finalLuminance
   {
	width		1
	height		1
	int_format	RGBA_FLOAT16
	texture		0
   }

   ! описание используемых шейдеров
   shader  downsample
   {
	file		PostProc/brightPass.glsl
	texture	DownSampler	0
	texture	ALumSampler	1	
	var_vec2_array	samples	16	-0.0029296875	 -0.0029296875 -0.0009765625 
	-0.0029296875  0.0009765625 -0.0029296875 0.0029296875 -0.0029296875 
	-0.0029296875 -0.0009765625 -0.0009765625 -0.0009765625 0.0009765625 
	-0.0009765625 0.0029296875 -0.0009765625 -0.0029296875 0.0009765625 
	-0.0009765625 0.0009765625 0.0009765625 0.0009765625 0.0029296875 
	0.0009765625 -0.0029296875 0.0029296875 -0.0009765625 0.0029296875 
	0.0009765625 0.0029296875 0.0029296875 0.0029296875
   	var_float	key	0.5
   }
   shader  xblur
   {
	file		PostProc/xBlur.glsl
	texture	BlurXSampler	0
	var_float	blurness	0.25
   }
   shader  yblur
   {
	file		PostProc/yBlur.glsl
	texture	BlurYSampler	0
	var_float	blurness	0.25
   }  

   shader  toneMapping
   {
	file		PostProc/toneMapping.glsl
	texture	srcImage	0
	texture	blurYImage	1
	texture	adaptLumImage	2
	var_float	exposure	1.0
   }
   shader  downLumLog
   {
	file		PostProc/downLumLog.glsl
	texture		DownSampler	0
	var_vec2_array	samples 9 -0.0052083335 -0.0052083335 -0.0052083335 0.0 
	-0.0052083335 0.0052083335 0.0 -0.0052083335 0.0 0.0 0.0 0.0052083335 
	0.0052083335 -0.0052083335 0.0052083335 0.0 0.0052083335 0.0052083335
   }
   shader  downLum
   {
	file		PostProc/downLum.glsl
	texture		DownSampler	0
	var_vec2_array	samples 16 -0.0234375 -0.0234375 -0.0078125 -0.0234375 
	0.0078125 -0.0234375 0.0234375 -0.0234375 -0.0234375 -0.0078125 -0.0078125 
	-0.0078125 0.0078125 -0.0078125 0.0234375 -0.0078125 -0.0234375 0.0078125 
	-0.0078125 0.0078125 0.0078125 0.0078125 0.0234375 0.0078125 -0.0234375 
	0.0234375 -0.0078125 0.0234375 0.0078125 0.0234375 0.0234375 0.0234375	
   }
   shader  downLum2
   {
	file		PostProc/downLum.glsl
	texture		DownSampler	0
	var_vec2_array	samples 16 -0.09375 -0.09375 -0.03125 -0.09375 0.03125 
	-0.09375 0.09375 -0.09375 -0.09375 -0.03125 -0.03125 -0.03125 0.03125 
	-0.03125 0.09375 -0.03125 -0.09375 0.03125 -0.03125 0.03125 0.03125 
	0.03125 0.09375 0.03125 -0.09375 0.09375 -0.03125 0.09375 0.03125 
	0.09375 0.09375 0.09375	
   }
   shader  downExp
   {
	file		PostProc/downLumExp.glsl
	texture		DownSampler	0
	var_vec2_array	samples 16 -0.375 -0.375 -0.125 -0.375 0.125 -0.375 
	0.375 -0.375 -0.375  -0.125 -0.125 -0.125 0.125 -0.125 0.375 -0.125 
	-0.375 0.125 -0.125 0.125 0.125 0.125 0.375 0.125 -0.375 0.375 -0.125 
	0.375 0.125 0.375 0.375 0.375	
   }
   shader  adaptLum
   {
	file		PostProc/adaptLum.glsl
	texture		CurLum		0
	texture		AdaptLum	1
	var_float	dtime		0.007
   }

   ! Начальный рендер таргет
   begin_target		sourceHDR

   ! Используемые проходы
   pass	brightPass0
   {
	texture	DownSampler	sourceHDR	0
	texture	ALumSampler	luminanceSwap1	0
	target	brightPassSmall
	shader	downsample
   }
   pass	brightPass1
   {
	texture	DownSampler	sourceHDR	0
	texture	ALumSampler	luminanceSwap0	0
	target	brightPassSmall
	shader	downsample
   }
   pass	downLumLog
   {
	texture	DownSampler	sourceHDR	0
	target	luminance0
	shader	downLumLog
   }
   pass	downLum
   {
	texture	DownSampler	luminance0	0
	target	luminance1
	shader	downLum
   }
   pass	downLum2
   {
	texture	DownSampler	luminance1	0
	target	luminance2
	shader	downLum2
   }
   pass	downLumExp
   {
	texture	DownSampler	luminance2	0
	target	finalLuminance
	shader	downExp
   }
   pass	downLumAdapt0
   {
	texture	CurLum		finalLuminance	0
	texture	AdaptLum	luminanceSwap0	0
	target	luminanceSwap1
	shader	adaptLum
   }
   pass	downLumAdapt1
   {
	texture	CurLum		finalLuminance	0
	texture	AdaptLum	luminanceSwap1	0
	target	luminanceSwap0
	shader	adaptLum
   }
   pass	blurX
   {
	texture	BlurXSampler	brightPassSmall	0
	target	XbrightPassSmallBlur
	shader	xblur
   }
   pass	blurY
   {
	texture	BlurYSampler	XbrightPassSmallBlur	0
	target	YbrightPassSmallBlur
	shader	yblur
   }
   pass	toneMap0
   {
	texture	srcImage	sourceHDR		0
	texture	blurYImage	YbrightPassSmallBlur	0
	texture	adaptLumImage	luminanceSwap1		0
	target	backbuffer
	shader	toneMapping
   }
   pass	toneMap1
   {
	texture	srcImage	sourceHDR		0
	texture	blurYImage	YbrightPassSmallBlur	0
	texture	adaptLumImage	luminanceSwap0		0
	target	backbuffer
	shader	toneMapping
   }

   ! Используемые последовательности
   sequence	seq0
   {
	pass	downLumLog
	pass	downLum
	pass	downLum2
	pass	downLumExp
	pass	downLumAdapt0 	
	pass	brightPass0
	pass	blurX
	pass	blurY
	pass	toneMap0
   }

   sequence	seq1
   {
	pass	downLumLog
	pass	downLum
	pass	downLum2
	pass	downLumExp
	pass	downLumAdapt1
	pass	brightPass1
	pass	blurX
	pass	blurY
	pass	toneMap1
   }
}

Изначально рендеринг 3D сцены будет производиться в рендер-таргет sourceHDR. Для этого он специально имеет буфер глубины. luminance0, luminance1, luminance2, luminanceSwap0, luminanceSwap1, а также finalLuminance - это рендер-таргеты для вычисления усредненной яркости. Они имеют размеры 64х64, 16х16, 4х4, 1х1, 1х1, 1х1 соответственно. В первый из них, luminance0, производится рендеринг для определения логарифма яркости, при этом используется шейдер downLumLog.glsl, одновременно с этим происходит уменьшение исходного изображения. Второй и третий - усреднение яркости с использованием шейдера downLum.glsl. В finalLuminance пишется результирующая усредненная яркость, рассчитываемая в шейдере downLumExp.glsl. В luminanceSwap0 и luminanceSwap1 пишется адаптированная величина усредненной яркости, вычисляется она в шейдере adaptLum.glsl. Зачем нам 2 рендер-таргета? А дело вот в чем. Для получения адаптации во времени нам необходимо знать текущее значение яркости и значение адаптированной яркости на предыдущем кадре. Текущее значение получается из текстуры присоединенной к рендер-таргету finalLuminance, а адаптированное с предыдущего кадра - либо из текстуры luminanceSwap0, либо из luminanceSwap1, в зависимости от текущего кадра, результат, в свою очередь, пишется в текстуру рендер-таргета luminanceSwap1 или luminanceSwap0 соответственно. Думаю, не стоит объяснять, что luminanceSwap0 и luminanceSwap1 чередуются каждый кадр, т.е. в первом кадре адаптированное значение берется из текстуры luminanceSwap1 и пишется в luminanceSwap0, во втором кадре - наоборот - адаптированное значение берется из текстуры luminanceSwap0 и пишется в luminanceSwap1. О том, как реализовать такую возможность чередования рендер-таргетов с помощью фреймворка будет сказано ниже.

Рендер-таргет brightPassSmall - нужен в процессе создания текстуры блума, в него будет рисоваться изображение, обрабатываемое в bright pass. В XbrightPassSmallBlur и YbrightPassSmallBlur рисуется результат размытия изображения по X и Y координате соответственно.

Насчет используемых шейдеров вроде все сказал, их конфигурация в комментариях не нуждается. А вот на счет проходов и последовательностей стоит поговорить отдельно.

Проходы downLumLog, downLum, downLum2, и downLumExp вычисляют усредненную яркость исходного изображения находящегося в sourceHDR. Далее есть два прохода, downLumAdapt0 и downLumAdapt1, по сути, они делают одно и то же, а именно вычисляют адаптированную яркость. Только в downLumAdapt0 в качестве текстуры адаптированной яркости на предыдущем кадре (сэмплер AdaptLum) выступает текстура с luminanceSwap0, и результат пишется в рендер-таргет luminanceSwap1, а в downLumAdapt1 - все совсем наоборот. В downLumAdapt1 в качестве AdaptLum выступает текстура с luminanceSwap1, а в качестве рендер-таргета - luminanceSwap0. Для того чтобы адаптация заработала, остается чередовать через кадр эти два прохода. Проходы brightPass0 и brightPass1, выполняют bright pass над исходным изображением. Им также требуется адаптированная усредненная яркость, только для brightPass0 она будет браться из текстуры рендер-таргета luminanceSwap1, а для brightPass0 - из luminanceSwap0. Эти проходы, как и проходы downLumAdapt0 и downLumAdapt1 также необходимо чередовать через кадр. Процедура tone-mapping выполняется как финальный проход, в котором результат выводится в задний буфер. Здесь, как и в случае с brightPass0 и brightPass1, также используется адаптированная яркость, поэтому существуют два прохода: toneMap0 и toneMap1, которые также нужно чередовать.

Как осуществляется чередование проходов? Для этого составляются несколько последовательностей. В нашем случае необходимо чередовать проходы через кадр, поэтому создаем две последовательности. Одна будет применяться при обработке в четном кадре, а вторая - в нечетном. Затем в коде приложения необходимо будет лишь чередовать эти последовательности каждый кадр. Вот пример подобного кода:

PostprocFilter	*pp_filter;
static bool seq = true;
...
if (seq)
	pp_filter->setSequence("SEQ0");
else
	pp_filter->setSequence("SEQ1");

seq = !seq;
...

где "SEQ0" и "SEQ1" - имена последовательностей в файле пост-эффекта.

Ниже показаны результаты применения HDR эффекта при разном уровне адаптации глаза.

Рис. 9. HDR эффект Рис. 9. HDR эффект
Рис. 9. HDR эффект. Картина 1 - изображение, полученное при взгляде на освещенную часть
сцены после длительного нахождения в темном помещении.
Картина 2 - спустя некоторое время произошла адаптация оптики к условиям освещения

ЗАКЛЮЧЕНИЕ

Плюсы предлагаемого решения:

  • рассмотренный подход существенно упрощает внедрение пост-эффектов в графический движок или приложение, использующее ускоренную трехмерную графику;
  • в процессе постобработки и загрузки пост-эффектов нет 3D API зависимого кода, удобно расширять для использования нескольких 3D API;
  • создание новых эффектов, а также их настройка не требует изменения кода и перекомпиляции приложения;
  • можно создавать практически любые современные эффекты;
  • гибкая система по сравнению с фиксированной в коде реализацией, все настраивается дизайнером;
  • дизайнер может не только настраивать параметры пост-эффектов, но и создавать новые эффекты;
  • все создается и настраивается дизайнером практически без контроля со стороны программиста (программист только пишет шейдеры для пост-эффектов).

Минусы:

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

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

И хотя этот фреймворк далеко не идеален, и некоторые его моменты требуют отдельного обсуждения, я всегда готов выслушать ваши предложения и пожелания, пишите nikola_tesla@inbox.ru, h-o-h@mail.ru или стучите в аську: 1-408-402.

ПРИЛОЖЕНИЕ

Демо версия + исходный код

Демо версия: 3D_post_effects_bin.zip (10 Мб)
Исходный код: 3D_post_effects_src.zip (0.5 Мб)

В прилагаемых к статье исходниках находится проект для Microsoft Visual C++ Codename Orcas Express Edition. Для компиляции демки вам понадобится желательно свежий DirectX SDK, т.к. демка использует DirectInput, в связи с этим вам, возможно, нужно будет скорректировать пути в настройках проекта.

Немного о работе демки

Не стремитесь сразу скомпилировать проект, сначала посмотрите саму демку. Бинарник демки находится в директории BIN, и включает в себя несколько пост-эффектов. Каждый bat файл в этой директории конфигурирует фреймворк на выполнение одного из множества пост-эффектов.

За основу демки и фреймворка взята часть моего графического движка Rotor Engine. Часть исходных кодов этого движка я любезно предоставляю вам для некоммерческого использования. Вся отрисовка сцены и применение к ней пост-эффекта внутри движка выполняется в функции TestWorld::render(), она находится в файле GAME\Tor_TestWorld.cpp.

Через файлы конфигурации (.cfg) можно настроить разрешение экрана, тип фильтрации текстур, настройки положения камеры, а также установить отладочный режим, в котором на экране можно посмотреть содержимое всех рендер-таргетов после постобработки.

И еще, код движка не оттестирован на 100%, некоторые возможности могут работать некорректно, или вообще не работать, движок находится пока на стадии разработки. Тех, кто обнаружит какие-либо ошибки в коде фреймворка, убедительная просьба сообщить об этом мне.

Прилагаемый к статье исходный код вы можете использовать в своих некоммерческих проектах с закрытым или открытым исходным кодом. Данный код поставляется "как есть" без каких-либо гарантий и поддержки. При использовании исходного кода где-либо в своих проектах или при его модификации вы обязаны сообщить об этом мне. Использовать в коммерческих целях прилагаемый к статье исходный и бинарный код без согласования со мной запрещено.

СПИСОК ИСТОЧНИКОВ

[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.

 


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


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


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

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