Чтобы не считать честно распределение плотности тумана для каждого пикселя, как обычно, вводят некоторую аппроксимацию, что в нашем случае приводит к понятию «вертексного тумана» (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, выполняется аппаратно, что нельзя не поприветствовать бурными аплодисментами. Ура, товарищи!

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

  1. 09.10.2014 at 21:21

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

Check Also

Windows 10 против шифровальщиков. Как устроена защита в обновленной Windows 10

Этой осенью Windows 10 обновилась до версии 1709 с кодовым названием Fall Creators Update …