Чтобы не считать честно распределение плотности тумана для каждого пикселя, как обычно, вводят некоторую аппроксимацию, что в нашем случае приводит к понятию "вертексного тумана" (vertex fog). Т.е. мы рассчитываем распределение тумана для вершин полигонов находящихся в объеме тумана, а затем полученные значения интерполируем по поверхности полигона.

Программеры Unreal решили, что линейной интерполяции им будет недостаточно и что обязательно нужно использовать честные формулы. В результате в Анрыле мы имеем так называемые "карты тумана" (fog maps) - дополнительные текстуры, которые аддитивно накладываются на полигоны и создают тем самым эффект тумана. Основная фишка фогмэпов состоит в том, что их нужно
перерассчитывать каждый кадр, что влечет за собой громоздкие вычисления (несколько тысяч линейных интегралов по объему каждый кадр), поэтому чтобы хоть как-то сэкономить время, фогмэпы рассчитываются в небольших разрешениях и накладываются с помощью дополнительных конвейеров мультитекстурирования.

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

Чтобы совсем не загрузиться математикой прибегнем к упрощениями, которыми пользовался Кармак. Во-первых объем тумана, должен быть параллелепипедом ориентированным так, чтобы его стороны были перпендикулярны координатным осям, а во-вторых, содержимое объема тумана может быть видно только через одну грань этого объема. Если подобные ограничения тебя не устраивают, уверен, ты сможешь их обойти 😉

Допустим наша сцена состоит из одного браша заполненного туманом и нескольких брашей геометрии:

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

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

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

// структура описывающая плоскость
typedef struct {
float normal[3]; // нормаль к поверхности (A, B, C)
float dist; // dist = A*x + B*y + C*z, где (x, y, z) - это
// координаты любой точки принадлежащей плоскости
}

// вертекс (x, y, z)
typedef float vec3_t[3];

// p - плоскость пересечение с которой находим;
// start, end - начальные и конечные координаты луча;
// out - искомая точка пересечения плоскости и луча;
//
int GetIntersectionPoint (plane_t p, vec3_t start, vec3_t end, vec3_t out)
{
float dist1, dist2, dot;

// найдем расстояние от start до плоскости
dist1 = start[0]*p.normal[0] + start[1]*p.normal[1] + start[2]*p.normal[2];
dist1 -= p.dist;

// найдем расстояние от end до плоскости
dist2 = end[0]*p.normal[0] + end [1]*p.normal[1] + end[2]*p.normal[2];
dist2 -= p.dist;

// если луч не пересекает плоскость, то выходим
if (dist1*dist2 > 0) return 0;

// найдем точку пересечения
dot = dist1 / (dists1 - dist2);
out[0] = start[0] + dot*(end[0] - start[0]);
out[1] = start[1] + dot*(end[1] - start[1]);
out[2] = start[2] + dot*(end[2] - start[2]);

return 1;
}

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

// fogcolor - цвет тумана;
// density - плотность тумана, т.е. минимальная толщина слоя
// тумана, когда он становится непрозрачным;
// start - точка вхождения луча в браш тумана;
// end - вершина в тумане к которой проводили луч;
// color - результирующий цвет;
//
void CalcVertexFogging (vec3_t fogcolor, float density, vec3_t start, vec3_t end, vec3_t color)
{
float dist, alpha;

// найдем расстояни между точками - путь луча в тумане
end[0] -= start[0];
end[1] -= start[1];
end[2] -= start[2];

dist = sqrt (end[0]*end[0] + end[1]*end[1] + end[2]*end[2]);

// рассчитаем прозрачность тумана
alpha = min(1.0, dist/density);

// и результирующий цвет
color[0] = alpha * fogcolor[0];
color[1] = alpha * fogcolor[1];
color[2] = alpha * fogcolor[2];
}

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

Графические API и вертексный туман

Вертексный туман официально поддерживается такими низкоуровневыми графическими API как DirectX и OpenGL. Для этого, к атрибутам вершин: координатам, цвету, координатам текстуры, координатам нормали и т.д. был добавлен еще один атрибут - координата тумана (fog coordinate), которая позволяет непосредственно задать значение плотности тумана для каждой вершины. И затем, при построении изображения, API использует эту координату и текущие установки тумана (цвет, плотность) для отрисовки полигонов с необходимыми цветовыми изменениями, вносимыми туманом.

В OpenGL использование координат тумана возможно только для драйверов поддерживающих расширение GL_EXT_fog_coord. Перспективная коррекция для тумана, равно как и для текстур контролируется параметром GL_PERSPECTIVE_CORRECTION_HINT. Прототип функции для задания значения координаты тумана имеет следующий вид: void glFogCoord[fd]EXT (T coord);

Для DirectX инициализация вертексного тумана осуществляется практически так же, как и пиксельного, нужно только для метода SetRenderState, вместо константы D3DRS_FOGTABLEMODE передать D3DRS_FOGVERTEXMODE. А чтобы самому задать значение плотности тумана для вертекса, нужно вычислить прозрачность тумана, как это показано выше, и поместить полученное значение в альфа-компоненту отраженного (specular) цвета вершины.

Вычисления и рендеринг вертексного тумана для карточек, поддерживающих модуль T&L, выполняется аппаратно, что нельзя не поприветствовать бурными аплодисментами. Ура, товарищи!

Check Also

DDoS на Bluetooth. Разбираем трюк, который поможет отключить чужую колонку

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

1 комментарий

  1. Аватар

    09.10.2014 at 21:21

Оставить мнение