Реализация bumpmapping'а - построение базиса касательного пространства, быстрая нормализация векторов

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

Нахождение базиса касательного пространства для набора треугольников

В рассматриваемых ранее примерах попиксельного освещения с учетом карты нормалей (bumpmap) возникала необходимость задания для каждой вершины всех выводимых граней базиса касательного пространства - векторов t, b и n.

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

Проще всего дело обстоит с вектором внешней нормали n - в ряде случаев он просто явно задается для каждой вершины. Если же он не задан, то его легко можно найти по координатам вершин.

Если у нас задан треугольник v0,v1,v2, то нормаль в точке v0 легко можно найти через векторное произведение ребер:

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

Обратите также внимание, что эта формула вообще дает нормаль ко всему треугольнику, а не нормаль в какой-то вершине.

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

Полученное значение нормали должно быть нормировано для дальнейшего использования.

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

Рассмотрим сначала идеальный случай - пусть у нас есть гладкая поверхность с заданным на ней законом задания текстурных координат:

Пусть также задана точка v0, соответствующая текстурным координатам (s0,t0).

Тогда рассмотрим на этой поверхности линии линии, вдоль которых одна из текстурных координат будет постоянна. Нас интересует две таких линии - вдоль одной величина s принимает значение s0, а вдоль второй величина t принимает значение t0.

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

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

Рассмотрим теперь, как можно найти эти касательные вектора без явного нахождения самих этих линий.

В силу невырожденности закона задания текстурных координат этот закон можно (хотя бы локально, т.е. в малой окрестности точки v0) обратить, т.е. записать в виде:

Тогда интересующие нас касательные вектора будут равны (x's,y's,z's,) и (x't,y't,z't,).

Вернемся обратно к треугольнику v0,v1,v2, считая, что в каждой его вершине явно заданы текстурные координаты (si,ti), используемые для обращения к карте нормалей.

Считая, что пространственные и текстурные координаты связаны некоторым законом (рассматриваемого ранее вида), можно приближенно записать следующие разностные отношения:

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

Для решения каждой из этих систем можно с успехом применить известное правило Крамера, т.е. записать интересующие нас производные x's,x't в виде отношения двух определителей.

Можно упростить запись решения, путем введения следующих двух векторов:

Тогда каждый из необходимых определителей будет являться (с точностью до знака) одной из компонент векторного произведения векторов e0 и e1.

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

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

void	computeTangents ( Vertex& v0, const Vertex& v1, const Vertex& v2 )
{
	Vector3D	e0 ( v1.pos.x - v0.pos.x, v1.tex.x - v0.tex.x, v1.tex.y - v0.tex.y );
	Vector3D	e1 ( v2.pos.x - v0.pos.x, v2.tex.x - v0.tex.x, v2.tex.y - v0.tex.y );
	Vector		cp = e0 ^ e1;

	if ( fabs ( cp.x ) > EPS )
	{
		v0.t.x = -cp.y / cp.x;
		v0.b.x = -cp.z / cp.x;
	}
	else
	{
		v0.t.x = 0;
		v0.b.x = 0;
	}

	e0.x = v1.pos.y - v0.pos.y;
	e1.x = v2.pos.y - v0.pos.y;
	cp   = e0 ^ e1;

	if ( fabs ( cp.x ) > EPS )
	{
		v0.t.y = -cp.y / cp.x;
		v0.b.y = -cp.z / cp.x;
	}
	else
	{
		v0.t.y = 0;
		v0.b.y = 0;
	}

	e0.x = v1.pos.z - v0.pos.z;
	e1.x = v2.pos.z - v0.pos.z;
	cp   = e0 ^ e1;

	if ( fabs ( cp.x ) > EPS )
	{
		v0.t.z = -cp.y / cp.x;
		v0.b.z = -cp.z / cp.x;
	}
	else
	{
		v0.t.z = 0;
		v0.b.z = 0;
	}

	if ( ( v0.t ^ v0.b ) & v0.n ) < 0 )
		v0.t = -v0.t;
}

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

Быстрая нормализация векторов

Способ нормирования векторов с использованием нормирующей кубической карты, хотя и прост, но требует использования текстурного блока и обращения к текстуре.

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

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

Возьмем за основу известную формулу (основанную на разложении по формуле Тейлора с точностью до первого члена).

Тогда, если обозначить t=(v,v)-1, то мы получим простую приближенную формулу

Естественно, что эта формула дает лишь приближенное значение и тем точнее, чем ближе длина вектора v к единице.

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