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

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

Изменено: 17.03.2008

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

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

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





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

Пост-эффекты с доступом к текстуре глубины: эффект глубины пространства (DOF)

Рассмотрим пару эффектов использующих данные о глубине сцены. Если реализация OpenGL не поддерживает FBO, то текстуру глубины так просто получить не удастся. Если поддерживается только PBuffer, то сцену необходимо будет рендерить 2 раза, один раз для получения текстуры цвета, а второй - для получения текстуры глубины, или же можно обойтись одним проходом, но копировать содержимое буфера глубины в текстуру стандартными средствами OpenGL. Можно расширить существующий фреймворк и ввести такую поддержку. На данном этапе рендеринг в текстуру глубины поддерживается данным фреймворком только через расширение FBO.

Эффект глубины пространства, или DOF (Depth Of Field) эффект реализуется несколькими способами, рассмотрим один из них основанный на получении данных о глубине сцены из текстуры глубины. Во-первых, нам понадобится размыть исходное изображение. Во-вторых, в финальном проходе, если фрагмент находится не в фокусе, то его цвет должен браться из размытой текстуры, если же он находится в фокусе, то из исходной текстуры. Если описать более точно, то исходная текстура и размытая смешиваются, причем величина, по которой определяется вклад в итоговый цвет той или иной текстуры, определяется исходя из того, насколько близко к фокусу располагается текущий фрагмент.

Ниже приведен шейдер заблуривания изображения методом Гаусса по X координате.

xBlur.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D BlurXSampler;
uniform float blurness;
void main(void)
{
    vec2 tx  = gl_TexCoord [0].xy;
    vec2 dx  = vec2 (0.01953,0.0)*blurness;
    vec2 sdx = dx;
    vec4 sum = texture2D (BlurXSampler, tx) * 0.134598;

    sum += (texture2D(BlurXSampler,tx+sdx) + texture2D(BlurXSampler,tx-sdx))*0.127325;
    sdx += dx;
    sum += (texture2D(BlurXSampler,tx+sdx) + texture2D(BlurXSampler,tx-sdx))*0.107778;
    sdx += dx;
    sum += (texture2D(BlurXSampler,tx+sdx) + texture2D(BlurXSampler,tx-sdx))*0.081638;
    sdx += dx;
    sum += (texture2D(BlurXSampler,tx+sdx) + texture2D(BlurXSampler,tx-sdx))*0.055335;
    sdx += dx;
    sum += (texture2D(BlurXSampler,tx+sdx) + texture2D(BlurXSampler,tx-sdx))*0.033562;
    sdx += dx;
    sum += (texture2D(BlurXSampler,tx+sdx) + texture2D(BlurXSampler,tx-sdx))*0.018216;
    sdx += dx;
    sum += (texture2D(BlurXSampler,tx+sdx) + texture2D(BlurXSampler,tx-sdx))*0.008847;
    sdx += dx;

    gl_FragColor = sum;

}

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

dof.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D FullSampler;
uniform sampler2D Blur;
uniform sampler2DShadow DepthSampler;

uniform float range;
uniform float focus;

void main(void)
{
	float far = 20000.0;
	float near = 0.1;

	float depth = shadow2D(DepthSampler, gl_TexCoord[0].xyz).r;

	float z_c1 = far*near/(far-near); 
	float z_c2=(far+near)/(2.0*(far-near)); 
	depth = z_c1/(-z_c2-0.5+depth);

	vec4 sharp = texture2D(FullSampler, gl_TexCoord[0].xy);
	vec4 blur  = texture2D(Blur, gl_TexCoord[0].xy);

	gl_FragColor = mix(sharp, blur, clamp(range * abs(focus + depth), 0.0, 1.0));

}

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

Есть несколько нюансов в описании файла пост-эффекта связанных с получением текстуры глубины. Во-первых, во входной рендер-таргет добавляется запись depth_texture 24, что означает присоединение к FBO текстуры глубины с точностью 24 бита. Во-вторых - это указание текстуры глубины для сэмплера шейдера. Для этого вместо номера цветового аттачмента рендер-таргета указывается значение depth:
texture DepthSampler temp0 depth

Ниже представлен файл конфигурации DOF эффекта. Необходимо применить три прохода, первый для размывания изображения по X координате, второй для размывания по Y. Третий проход - непосредственно DOF эффект.

!----------------------------------
!	Postproc Filter	File
!----------------------------------
mm_dof.ppf
{
   ! описание используемых рендер-таргетов
   render_target  temp0
   {
	width		512
	height		512
	int_format	RGBA8
	texture		0
	depth_texture	24
   }

   render_target  temp1
   {
	width		256
	height		256
	int_format	RGBA8
	texture		0
   }

   render_target  temp2
   {
	width		512
	height		512
	int_format	RGBA8
	texture		0
   }

   render_target  temp3
   {
	width		256
	height		256
	int_format	RGBA8
	texture		0
   }

   ! описание используемых шейдеров
   shader  blurx
   {
	file		PostProc/xBlur.glsl
	texture		BlurXSampler	0
	var_float	blurness	0.125
   }
   shader  blury
   {
	file		PostProc/yBlur.glsl
	texture		BlurYSampler	0
	var_float	blurness	0.125
   }  
   shader  dof
   {
	file		PostProc/dof.glsl
	texture		FullSampler	0
	texture		Blur		1
	texture		DepthSampler	2

	var_float	range	0.18
	var_float	focus	5.0;
   }

   begin_target	temp0

   pass	p1
   {
	texture	BlurXSampler	temp0	0
	target	temp2
	shader	blurx
   }
   pass	p2
   {
	texture	BlurYSampler	temp2	0
	target	temp3
	shader	blury
   }
   pass	p3
   {
	texture	FullSampler	temp0	0
	texture	Blur		temp3	0
	texture	DepthSampler	temp0	depth
	target	backbuffer
	shader	dof
   }

   sequence	seq0
   {
	pass	p1
	pass	p2
	pass	p3
   }
}
Рис. 6. Результат работы эффекта Depth Of Field
Рис. 7. Результат работы эффекта Depth Of Field

 

Пост-эффекты с доступом к текстуре глубины: эффект колебания горячего воздуха

В этом пост-эффекте нам понадобится произвольная текстура из файла для искажения текстурных координат для имитации колебания горячего воздуха, для этого подойдет так называемая dudv карта. Еще нам понадобится время как параметр для прокрутки, скроллинга (scrolling) этой текстуры. Текстура глубины нужна для того, чтобы ограничить распространение эффекта только на те фрагменты исходного изображения, которые удалены от камеры дальше определенного расстояния или ввести зависимость степени искажений от расстояния до камеры. Нужен всего один шейдер и один проход по изображению. Во фрагментном шейдере из текстуры глубины получаем глубину сцены и преобразуем ее к линейному диапазону. Из текстуры искажения получаем вектор смещения текстурных координат distort, причем текстурные координаты для доступа к текстуре искажения предварительно масштабируются и скролятся во времени. Вектор смещения distort приводится к диапазону -1...1, а затем умножается на глубину, тем самым, обеспечивая необходимую зависимость искажения текстурных координат от глубины сцены. Затем вектор distort используется для искажения текстурных координат при доступе к основной текстуре, где находится изображение сцены.

heat.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D FullSampler;
uniform sampler2DShadow DepthSampler;
uniform sampler2D DistortSampler;

uniform float time;

void main(void)
{
	float	far = 20000.0;
	float	near = 0.1;

	float depth = shadow2D(DepthSampler, gl_TexCoord[0].xyz).r;
	
	float z_c1 = far*near/(far-near); 
	float z_c2=(far+near)/(2.0*(far-near)); 
	depth = -z_c1/(-z_c2-0.5+depth);
	depth = clamp(depth,0.0,50.0);

	vec2 tc = gl_TexCoord[0].xy*6.0;
	tc.y -= time*0.2;
	vec2 distort = texture2D(DistortSampler, tc).xy;
	distort = 2.0*distort - 1.0;
	distort *= depth;
	distort *= 0.001;
	vec4 color = texture2D(FullSampler, gl_TexCoord[0].xy + distort);
	gl_FragColor = color;
}

В этом эффекте используется секция input_texture для задания текстуры из файла для искажений. При задании единственного прохода p1 для DistortSampler указывается имя этой текстуры.

!----------------------------------
!	Postproc Filter	File
!----------------------------------
mm_heat.ppf
{
   ! описание используемых входных текстур
   input_texture  tex0
   {
	file	water_n.dds
   }

   ! описание используемых рендер-таргетов
   render_target  temp0
   {
	width		512
	height		512
	int_format	RGBA8
	texture		0
	depth_texture	24
   }

   ! описание используемых шейдеров
   shader  heat
   {
	file		PostProc/heat.glsl
	texture		FullSampler	0
	texture		DepthSampler	1
	texture		DistortSampler	2
	var_float	time	0.0
   }

   begin_target	temp0

   pass	p1
   {
	texture	FullSampler	temp0	0
	texture	DepthSampler	temp0	depth
	texture	DistortSampler	tex0
	target	backbuffer
	shader	heat
   }

   sequence	seq0
   {
	pass	p1
   }
}

Оценить этот эффект удается только в динамике, поэтому во имя экономии времени и бумаги скриншот этого эффекта приводить не стану, смотрите демку heat_effect.bat. Для него, как и для предыдущего эффекта требуется поддержка FBO.

 

Sun Shafts

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

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

shaftsGenCry.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D FullSampler;
uniform sampler2DShadow DepthSampler;

float saturate(float val)
{
	return clamp(val,0.0,1.0);
}

void main(void)
{
	float sceneDepth = shadow2D(DepthSampler, gl_TexCoord[0].xyz).r;
	vec4  scene	= texture2D(FullSampler, gl_TexCoord[0].xy);
	float fShaftsMask = (1.0 - sceneDepth);

	gl_FragColor	= vec4( scene.xyz * saturate(sceneDepth), fShaftsMask );
}

То, что делает шейдер shaftsGenCry.glsl объяснять не нужно, тут и так все ясно.

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

// позиция источника
vec4f	sun_pos (10000.0,3000.0,1500.0,1.0);
// получаем транспонированную модельно-видовую матрицу
mat44f	mv = view->getTranform(GL_TRANSPOSE_MODELVIEW_MATRIX);
// получаем транспонированную проекционную матрицу
mat44f	pr = view->getTranform(GL_TRANSPOSE_PROJECTION_MATRIX);

// выполняем преобразования
sun_pos = mv*sun_pos;
sun_pos = pr*sun_pos;
sun_pos.x *= 0.5;
sun_pos.y *= 0.5;

// нормализуем координаты
sun_pos.x /= sun_pos.w;
sun_pos.y /= sun_pos.w;
sun_pos.z /= sun_pos.w;

// и самое интересное, в четвертую компоненту вектора позиции передаем 
// скалярное произведение нормализованного вектора позиции источника 
// и вектора направления камеры в мировом простанстве
// оно нам понадобится для создания эффекта затухания шафтов в 
// зависимости от угла между направлением на источник и вектором взгляда
sun_pos.w = math::dot( vec3f(sunPos.normalize()), camera.getViewDir());

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

shaftsCry.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D FullSampler;
uniform sampler2D BlurSampler;
uniform vec4 sun_pos;
const vec4 ShaftParams = vec4(0.1,2.0,0.1,2.0);

float saturate(float val)
{
	return clamp(val,0.0,1.0);
}

vec4 blendSoftLight(vec4 a, vec4 b)
{
	vec4 c = 2.0 * a * b + a * a * (1.0 - 2.0 * b);
	vec4 d = sqrt(a) * (2.0 * b - 1.0) + 2.0 * a * (1.0 - b);
   
	//return ( b < 0.5 )? c : d;
	return any(lessThan(b, vec4(0.5,0.5,0.5,0.5)))? c : d;
}

void main(void)
{
	vec2  sunPosProj = sun_pos.xy;
	float sign = sun_pos.w;

	vec2  sunVec = sunPosProj.xy - (gl_TexCoord[0].xy - vec2(0.5,0.5));
	float sunDist = saturate(sign) * saturate(1.0 - saturate (length(sunVec) * ShaftParams.y));

	sunVec *= ShaftParams.x * sign;

	vec4 accum;
	vec2 tc = gl_TexCoord[0].xy;

	tc += sunVec;
	accum = texture2D(BlurSampler, tc);
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.875;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.75;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.625;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.5;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.375;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.25;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.125;

	accum  *= 0.25 * vec4(sunDist,sunDist,sunDist,1.0);
	
 	accum.w += 1.0 - saturate(saturate(sign*0.1+0.9));

	vec4	cScreen = texture2D(FullSampler, gl_TexCoord[0].xy);      
	vec4	cSunShafts = accum;

	float fShaftsMask = saturate(1.00001- cSunShafts.w) * ShaftParams.z * 2.0;
        
	float fBlend = cSunShafts.w;

	vec4 sunColor = vec4(0.9,0.8,0.6,1.0);

	accum =  cScreen + cSunShafts.xyzz * ShaftParams.w * sunColor * ( 1.0 - cScreen );
	accum = blendSoftLight(accum, sunColor * fShaftsMask * 0.5 + 0.5);

	gl_FragColor = accum;
}

sunPosProj - позиция источника в нормализованном пространстве, sunVec - это направление на источник, может быть отрицательным, масштабируется парметром ShaftParams.x для получения необходимой длины шафтов и расстояния сэмплирования текстуры в направлении sunVec, sunDist - расстояние до источника но уже на плоскости. В шейдере осуществляется 8 выборок в направлении sunVec с постепенным затуханием вклада цвета к периферии. Затем полученный цвет усредняется и смешивается с исходным изображением. Тут многое добавлено от себя для получения приемлемого результата, так что смотрите сами, точное сходство с кризисом не гарантируется, но зато дает очень даже красивый эффект. Ниже приведен файл пост-эффекта, думаю, он в комментариях не нуждается.

mm_CrysisSunShafts.ppf

!----------------------------------
!	Postproc Filter	File
!	Crysis Sun Shafts
!----------------------------------
mm_CrysisSunShafts.ppf
{
   ! описание используемых рендер-таргетов
   render_target  srcImage
   {
	width		512
	height		512
	format		RGBA
	texture		0
	depth_texture	24
   }

   render_target  depth0
   {
	width	128
	height	128
	format	RGB
	texture	0
   }
   render_target  depth1
   {
	width	64
	height	64
	format	RGB
	texture	0
   }

   ! описание используемых шейдеров
   shader  getDepth
   {
	file		PostProc/shaftsGenCry.glsl
	texture		FullSampler	0
	texture		DepthSampler	1
   }
   shader  combine
   {
	file		PostProc/shaftsCry.glsl
	texture		FullSampler	0
	texture		BlurSampler	1
	var_vec4	sun_pos		1000.0 300.0 150.0 1.0
   }
   shader  blur
   {
	file		PostProc/PingPong.glsl
	texture		BlurSampler	0
   }
  
   ! начальный рендер-таргет
   begin_target		srcImage

   pass	r2Depth
   {
	texture	FullSampler	srcImage	0
	texture	DepthSampler	srcImage	depth
	target	depth0
	shader	getDepth
   }
   pass	blurDepth0
   {
	texture	BlurSampler	depth0		0
	target	depth1
	shader	blur
   }
   pass	blurDepth1
   {
	texture	BlurSampler	depth1		0
	target	depth0
	shader	blur
   }
   pass	Final
   {
	texture	FullSampler	srcImage	0
	texture	BlurSampler	depth1		0
	target	backbuffer
	shader	combine
   }

   sequence	seq0
   {
	pass	r2Depth
	pass	blurDepth0
	pass	blurDepth1
	pass	Final
   }
}
Рис. 8. Sun Shafts - без комментариев

Рис. 8. Sun Shafts - без комментариев

Рис. 8. Sun Shafts - без комментариев
Рис. 8. Sun Shafts - без комментариев

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

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

shafts.glsl

/*FRAGMENT_SHADER*/
uniform sampler2D FullSampler;
uniform sampler2D BlurSampler;
uniform vec4 sun_pos;
const vec4 ShaftParams	= vec4(0.05,1.0,0.05,1.0);
const vec4 sunColor	= vec4(0.9,0.8,0.6,1.0);

float saturate(float val)
{
	return clamp(val,0.0,1.0);
}

void main(void)
{
	vec2  sunPosProj = sun_pos.xy;
	float sign = sun_pos.w;

	vec2  tc = gl_TexCoord[0].xy;

	vec2  sunVec = sunPosProj.xy - tc + vec2(0.5,0.5);
	float sunDist = saturate(sign) * (1.0 - saturate(dot(sunVec,sunVec) * ShaftParams.y));

	sunVec *= ShaftParams.x * sign;

	tc += sunVec;
	vec4 accum = texture2D(BlurSampler, tc);
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.875;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.75;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.625;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.5;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.375;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.25;
	tc += sunVec;
	accum += texture2D(BlurSampler, tc) * 0.125;

	accum  *= 0.25 * sunDist;

	vec4 cScreen = texture2D(FullSampler, gl_TexCoord[0].xy);
	accum = cScreen + accum * ShaftParams.w * sunColor * ( 1.0 - cScreen );

	gl_FragColor = accum;
}

mm_SunShafts.ppf

!----------------------------------
!	Postproc Filter	File
!	SunShafts
!----------------------------------
mm_sunshafts.ppf
{
   ! описание используемых рендер-таргетов
   render_target  srcImage
   {
	width		512
	height		512
	int_format	RGBA8
	depth		24
	texture		0
   }

   render_target  depth0
   {
	width		256
	height		256
	int_format	RGBA8
	texture		0
   }
   render_target  depth1
   {
	width		128
	height		128
	int_format	RGBA8
	texture		0
   }

   ! описание используемых шейдеров
   shader  combine
   {
	file		PostProc/shafts.glsl
	texture		FullSampler	0
	texture		BlurSampler	1
	var_vec4	sun_pos		1000.0 300.0 150.0 1.0
   }
   shader  blur
   {
	file		PostProc/PingPong.glsl
	texture		BlurSampler	0
   }
  
   ! начальный рендер-таргет
   begin_target		srcImage

   pass	r2Depth
   {
	texture	BlurSampler	srcImage	0
	target	depth0
	shader	blur
   }
   pass	blurDepth0
   {
	texture	BlurSampler	depth0	0
	target	depth1
	shader	blur
   }
   pass	blurDepth1
   {
	texture	BlurSampler	depth1	0
	target	depth0
	shader	blur
   }
   pass	Final
   {
	texture	FullSampler	srcImage	0
	texture	BlurSampler	depth0	0
	target	backbuffer
	shader	combine
   }

   sequence	seq0
   {
	pass	r2Depth
	pass	blurDepth0
	pass	blurDepth1
	pass	Final
   }
}
Рис. 9. Sun Shafts вариант 2

Рис. 9. Sun Shafts вариант 2

Рис. 9. Sun Shafts вариант 2
Рис. 9. Sun Shafts вариант 2

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