Эффект сгорания объекта как в Doom 3

Одним из очень красивых эффектов в Doom 3 является "сгорание" убитого противника - он сгорает как сигарета.

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

Рассмотрим простейший вариант реализации данного эффекта.

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

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

В результате этого каждой точке растрового представления объекта можно сопоставить величину b, являющуюся разностью проинтерполированного вдоль грани значения и параметра самой программы.

Задачей фрагментной программы будет при b<0 возвращать исходный цвет (например цвет текстуры, соответствующей данному фрагменту). При b>1 фрагмент должен просто отбрасывать, а отрезку [0,1] сопоставляются различные фазы процесса сгорания.

За счет изменения параметра фрагментной программы мы будет перемещать фронт огня по поверхности объекта.

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

Единственными операциями, которые дают возможность программисту использовать условия являются KIL, CMP, MAX, MIN, SGE и SLT.

Команде KIL позволяет легко отбросить те фрагменты, для которых b>1:

ADD  b2, one, -b;       # break if b > 1
KIL  b2.x;

Все остальное ветвление мы будем реализовывать при помощи команд SLT и LRP.

Команда SLT позволяет нам определить нужно ли использовать нормальную текстуру или же цвет сгорания. Для этого достаточно произвести линейную интерполяцию между цветом огня, соответствующему текущему значению bи цветом текстуры. В качестве параметра интерполяция надо использовать значение, возвращаемое командой SLT:

SLT  tf.xyz, b, 0;                     # now tf.x = 1 if burn < 0, otherwise 0
LRP  result.color.rgb, tf, color, cf;  # choose either color or cf

Для получение цвета, соответствующего процессу сгорания удобно также воспользоваться командой LRP:

LRP  cf, b, brightYellow, darkRed;     # get burn color

В результате мы приходим к следующей фрагментной программе:

!!ARBfp1.0
#
# simple burn fragment shader
# on entry:
#     fragment.texcoord [0] - texture coordinates
#
#     texture [0] - decal map
#
#     program.local [0].x - burn coordinate
#

ATTRIB	t            = fragment.texcoord [1];
PARAM	burn         = program.local [0];
PARAM	one          = 1;
PARAM	two          = 2;
PARAM	darkRed      = { 0.5, 0, 0, 1 };
PARAM	brightYellow = { 1,   1, 0, 1 };

TEMP	b, b2, tf, cf, dots, color, h2, l2, temp, diffuse, dist, atten;

ADD		b, t, -burn;
MOV		b.y, b.x;
MOV		b.z, b.x;

ADD		b2, one, -b;                        # break if b > 1
KIL		b2.x;

TEX		color, fragment.texcoord [0], texture [0], 2D;	# get decal color
SLT		tf.xyz, b, 0;                       # now tf.x = 1 if burn < 0, otherwise 0
LRP		cf, b, brightYellow, darkRed;       # get burn color
LRP		result.color.rgb, tf, color, cf;
MOV		result.color.a, 1;

END

Однако непосредственное применение данной программы приводит к слишком ровной границе процесса горения, что не всегда красиво выглядит.

Эффект сгорания

Рис 1. Эффект сгорания, полученный при помощи приведенной выше программы.

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

Соответствующая фрагментная программа приводится ниже, в ней шумовая текстура передается в первом текстурном блоке.

!!ARBfp1.0
#
# simple burn fragment shader
# on entry:
#     fragment.texcoord [0] - texture coordinates
#
#     texture [0] - decal map
#
#     program.local [0].x - burn coordinate
#

ATTRIB	t            = fragment.texcoord [1];
PARAM	burn         = program.local [0];
PARAM	one          = 1;
PARAM	two          = 2;
PARAM	darkRed      = { 0.5, 0, 0, 1 };
PARAM	brightYellow = { 1,   1, 0, 1 };

TEMP	offs, b, b2, tf, cf, dots, color, h2, l2, temp, diffuse, dist, atten, mask;

                                            # get burn noise
MUL		temp, fragment.texcoord [0], 30;
TEX		offs, temp, texture [1], 2D;
MAD		offs, offs, 3, -1.5;

                                            # apply it
ADD		b, t, -burn;

MOV		b.y, b.x;
MOV		b.z, b.x;
ADD		b, b, offs;                         # bias threshold with noise

ADD		b2, one, -b;                        # break if b > 1
KIL		b2.x;
                                            # get decal color
TEX		color, fragment.texcoord [0], texture [0], 2D;

SLT		tf.xyz, b, 0;                       # now tf.x = 1 if burn < 0, otherwise 0
LRP		cf, b, brightYellow, darkRed;       # get burn color

LRP		result.color.rgb, tf, color, cf;
MOV		result.color.a, 1;

END

Ниже приводятся изображения, полученные при помощи данной программы:

Эффект сгорания с добавлением шумовой функции

Эффект сгорания с добавлением шумовой функции

По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows, Linux и Mac OS X.

Используются технологии uCoz