Этот урок построен на основе предыдущего. В нём мы добавим ещё немного реализма нашему миру: мы реализуем объемный туман (volumetric fog). Объемный туман используется во многих популярных играх, включая Quake. Эта технология позволит вам указать границы, которые будет занимать туман. Во многих играх эта технология используется для ограничения «бездонных пропастей», попробуйте сами заглянуть в какую-нибуть =)
Мы нарисуем туман, стелющийся над нашей поверхности, как будто это утро в какой-то долине =)
Конечно, яркое высокое солнце может испортить «утренний» эффект, но вы можете переместить солнце 😉

Мысль о создании объемного тумана может показаться страшной и сложной 😉 но фактически это так же легко, как, например, использование glTexCoord*(). Так как мы не будем создавать туман своими силами, например блендингом, а будем использовать хардварный туман OpenGL, нам нужно загрузить расширение «glFogCoordfEXT». Это будет делаться при инициализации программы, сразу после загрузки расширений для мультитекстурирования.

Шаги для включения обьемного тумана просты. Сначала мы инициализируем дым как обычно, затем установим новый параметр, используя glFogi() с новыми дефайнами, объявленными в main.h, затем просто вызовем glFogCoordfEXT() со значением глубины для каждой вершины, которая должна быть «затуманенной». Если передадим 0, туман проигнорируется. Чем выше значение, тем больше плотность дыма.

Файл main.h

// В начале файла добавим всё необходимое для обьемного дыма:

// Добавляем константы для передачи в glFogi()
#define GL_FOG_COORDINATE_SOURCE_EXT        0x8450
#define GL_FOG_COORDINATE_EXT           0x8451

// Обьявляем указатель на функцию для установки позиции обьемного тумана
typedef void (APIENTRY * PFNGLFOGCOORDFEXTPROC) (GLfloat coord);

 

Файл terrain.cpp

// С прошлого урока в этот файл мы добавим обьемный туман, используя glFogCoordfEXT().
// В программе вы сможете изменять положение тумана клавишами +/-.
// Также добавим в файл новую функцию: SetFogCoord(), принимающую глубину тумана и текущее
// y-значение вершины, к которой привязан туман.
// На самом деле «обьемный туман» — нифига не обьемный туман, но он изменяет цвета
// вершин так, что создается полное ощущение тумана.// В начале файла добавим:// Глубина тумана
extern float g_FogDepth;// Указатель на функцию-расширение для установки глубины вершины
extern PFNGLFOGCOORDFEXTPROC glFogCoordfEXT;

///////////////////////////////// SET FOG COORD \\\\*
/////
/////   Новая функция: Устанавливает обьемный туман для текущей вершины
/////   с указанной глубиной
/////
///////////////////////////////// SET FOG COORD \\\\*

void SetFogCoord(float depth, float height)
{
// Функция принимает глубину тумана и высоту текущей вершины. Если высота
// больше глубины, дым для этой вершины не установлен (0), но если высота
// ниже глубины, рассчитываем значение тумана. Посколько более высокое
// число, передаваемое в glFogCoordfEXT(), выведет больше тумана, нам нужно
// вычести глубину из высоты, затем сделать это значение отрицательным.
// Иначе будет больше тумана сверху, чем снизу.

float fogY = 0;

// Проверим, не больше ли высота вершины, чем значение глубины
if(height > depth)
fogY = 0;
// Иначе вычисляем глубину тумана для текущей вершины
else
fogY = (height depth);

// Применяем координаты тумана для этой вершины, используя указатель на функцию-расширение
glFogCoordfEXT(fogY);
}

/////////////////////////////////////////////////////////////////////////////////////////////////
//
// Немного изменим void RenderHeightMap():
//

// Найдите код    glBegin( GL_TRIANGLE_STRIP);
// После него идёт цикл for(…), перепишем его:

for ( X = 0; X <= MAP_SIZE; X += STEP_SIZE )
{
if(bSwitchSides)
{
for ( Y = MAP_SIZE; Y >= 0; Y -= STEP_SIZE )
{
// Get the (X, Y, Z) value for the bottom left vertex
x = X;
y = Height(pHeightMap, X, Y );
z = Y;

/////// * /////////// * /////////// * NEW * /////// * /////////// * /////////// *

// Устанавливаем коорд-ты тумана этой вершины
SetFogCoord(g_FogDepth, (float)y);

/////// * /////////// * /////////// * NEW * /////// * /////////// * /////////// *

// Устанавливаем коорд-ты тумана этой вершины
SetTextureCoord( (float)x, (float)z );
glVertex3i(x, y, z);

x = X + STEP_SIZE;
y = Height(pHeightMap, X + STEP_SIZE, Y );
z = Y;

/////// * /////////// * /////////// * NEW * /////// * /////////// * /////////// *

// Устанавливаем коорд-ты тумана этой вершины
SetFogCoord(g_FogDepth, (float)y);

/////// * /////////// * /////////// * NEW * /////// * /////////// * /////////// *

SetTextureCoord( (float)x, (float)z );
glVertex3i(x, y, z);
}
}
else
{
for ( Y = 0; Y <= MAP_SIZE; Y += STEP_SIZE )
{
x = X + STEP_SIZE;
y = Height(pHeightMap, X + STEP_SIZE, Y );
z = Y;

/////// * /////////// * /////////// * NEW * /////// * /////////// * /////////// *

// Устанавливаем коорд-ты тумана этой вершины
SetFogCoord(g_FogDepth, (float)y);

/////// * /////////// * /////////// * NEW * /////// * /////////// * /////////// *

SetTextureCoord( (float)x, (float)z );
glVertex3i(x, y, z);

x = X;
y = Height(pHeightMap, X, Y );
z = Y;

/////// * /////////// * /////////// * NEW * /////// * /////////// * /////////// *

// Устанавливаем коорд-ты тумана этой вершины
SetFogCoord(g_FogDepth, (float)y);

/////// * /////////// * /////////// * NEW * /////// * /////////// * /////////// *

SetTextureCoord( (float)x, (float)z );
glVertex3i(x, y, z);
}
}

bSwitchSides = !bSwitchSides;
}

//////////////////////////////////////////////////////////////////////////////////////////

 

Теперь main.cpp:

// Где-нибуть вверху добавьте:

// Расширение: указатель на функцию, устанавливающую глубину тумана для вершины
PFNGLFOGCOORDFEXTPROC glFogCoordfEXT = NULL;

// Хранит глубину тумана
float g_FogDepth = 50.0f;

///////////////////////////////////////////////////////////////////////////////////////////
//
// Добавляем следующий код в функцию Init():
//

// Нужно проверить, поддерживает ли видеокарта хардварный туман. Мы делаем
// одни и те же действия для каждого расширения. Сначала говорим wglGetProcAddress(),
// какие расширения нам нужны, и получаем указатель на функцию. После этого
// проверяется, поддерживает ли драйвер видеокарты эту функцию.

// Получаем указатель на функцию нужного расширения
glFogCoordfEXT  = (PFNGLFOGCOORDFEXTPROC) wglGetProcAddress(«glFogCoordfEXT»);

// Прежде чем его использоваьт, проверяем, поддерживается ли он видеокартой.
if(!glFogCoordfEXT)
{
// Выводим ошибку и выходим.
MessageBox(g_hWnd, «Your current setup does not support volumetric fog», «Error», MB_OK);
PostQuitMessage(0);
}

// Если мы дошли досюда, значит видеокарта поддерживает обьемный туман. Теперь
// нужно его инициализировать. Так же, как и при обычном тумане, включаем GL_FOG,
// передаём его цвет, и его «плотность». Будет передана новая информация в glFogi(),
// новые обьявленные флаги, которые сообщат OpenGL, что мы хотим использовать
// вершинный туман. Заметьте, мы не используем GL_FOG_DENSITY, оно не
// возымеет действия.

// Установим цвет тумана с полной альфой
float fogColor[4] = {0.8f, 0.8f, 0.8f, 1.0f};

glEnable(GL_FOG);               // Вкл. туман
glFogi(GL_FOG_MODE, GL_LINEAR);         // Устанавливаем режим в GL_LINEAR
glFogfv(GL_FOG_COLOR, fogColor);        // Передаём OpenGL цвет тумана
glFogf(GL_FOG_START, 0.0);          // Начальное значение глубины — 0
glFogf(GL_FOG_END, 50.0);           // Конечное — 50

// Теперь сообщаем OpenGL, что используем расширение для обьемного тумана.
// Для каждой вершины, которой нужна привязка дыма, вызываем glFogCoordEXT()
// со значением глубины. Эти флаги обьявлены в main.h и их нет в стандартных
// хидерах OpenGL.

glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FOG_COORDINATE_EXT);

///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Чтобы сделать урок интереснее, я добавил возможность изменять высоту
// тумана над землей клавишами +/-.

case VK_ADD:        // Если нажали +

g_FogDepth += 1;        // Увеличиваем высоту

if(g_FogDepth > 200)        // Но не более 200
g_FogDepth = 200;
break;

case VK_SUBTRACT:   // Если нажали —

g_FogDepth -= 1;        // Уменьшаем высоту

if(g_FogDepth < 0)      // Не менее 0
g_FogDepth = 0;

break;

 

Видите, нового кода совсем немного — зато какой красивый и полезный эффект =)

Исходные коды к уроку