Array ( )
Вход:




Главная | OpenGL | GLSL | AI | Сеть | Примеры | Библиотека

OpenGL: Маскинг и режим ortho






Возможно, вы когда-либо хотели отобразить только часть текстуры, не изменяя
полигона, на который она наложена - например, деревья, кусты, частицы,
или, например, интерфейс. Данный урок научит вас этой важной технологии.
Вы могли раньше экспериментировать с блендингом. Например: glBlendFunc(GL_SRC_ALPHA),
с включенным блендингом (glEnable(GL_BLEND_FUNC)) - смешает текстуру с бекграундом.
Чем ближе цвет на текстуре к черному, тем меньше прозрачности. Если цвет на текстуре
белый, это место будет прозрачным, и будет отображено всё, что находится за ним.
Например, если у вас есть картинка дерева с черным фоном, будет отображено только дерево, без
черного бекграунда. Проблема в том, что так как само дерево не белое, оно тоже будет
выглядеть полупрозрачным. Это нормально с частицами, но если вы хотите, чтобы
картинка была чистой и красивой, вам нужно либо изображение с альфа-каналом, либо
наложение маски (masking). Маскинг делается так, что отображается только та часть
изображения, которая закрашена черным на маске. То есть вы должны иметь 2 картинки:
одну - нормальную, вторую - черно-белую, содержащую маску. Детали будут разьяснены
внизу.

Для демонстрации маскинга мы нарисуем круг прицела. Вокруг мы нарисуем комнату - она
будет видна сквозь прозрачную часть прицела. Если вы посмотрите битмап маски прицела,
то увидите, что его центр - белый, что значит, что он НЕ будет отрисован.

Контроль:
Вверх-Вниз, w/s - zoom, приближение и отдаление прицела.
Влево-Вправо, a/d - вращение по кругу.
Пробел - вкл/выкл NightVision
ESC - выход.


Кроме изучения маскинга будет затронут режим ortho. Мы уже использовали его,
начиная с урока "Подсчет FPS", но теперь я обьясню его подробно.



Исходные коды взяты из урока "загрузка текстур".
Файлы init.cpp, main.h, texture.cpp, texture.h остаются без изменений.

Файл main.cpp:


// В начале увеличим количество текстур до 4:
TextureImage textures[4];

// Создадим переменную для хранения угла вращения:
float rotateY = 0;
// Переменная, переключающая нас в режим ночноги видения и обратно:
bool bNightVision = false;
// Значение ZOOM:
float Zoom=0;



// Изменим функцию Init(): загрузим четыре текстуры и сделаем пространство
// синим, а не черным, как обычно:
void Init(HWND hWnd)
{
    g_hWnd = hWnd;
    GetClientRect(g_hWnd, &g_rRect);
    InitializeOpenGL(g_rRect.right, g_rRect.bottom);

    // Инициализируем текстурный класс и загружаем текстуры.
    Texture = new CTexture();

    Texture->LoadTexture(IL_BMP, "Wall.bmp", &textures[0]);
    Texture->LoadTexture(IL_BMP, "floor.bmp", &textures[1]);
    Texture->LoadTexture(IL_BMP, "CrossHairMask.bmp", &textures[2]);
    Texture->LoadTexture(IL_BMP, "CrossHair.bmp", &textures[3]);

    // Вместо стандартного черного, делаем пространство синим
    glClearColor(.4f, 0, 1, 1);
}

// Добавим функцию обработки ввода:
void KeyPressed()
{
    // Как и в уроках камеры, мы используем отдельную функцию для контроля
    // движения, т.к. WinProc() значительно медленнее.

    // Клавиши зума
    if(GetKeyState(VK_UP) & 0x80 || GetKeyState('W') & 0x80) {

        // Проверим, не превысили ли мы порог (13), если нет - увеличиваем зум
        if(g_zoom < 13) g_zoom += 0.5f;
    }
    if(GetKeyState(VK_DOWN) & 0x80 || GetKeyState('S') & 0x80) {

        // Убедимся, что мы не зумимся назад, если нет - уменьшаем зум
        if(g_zoom > 0)  g_zoom -= 0.5f;
    }

    // Клавиши вращения
    if(GetKeyState(VK_LEFT) & 0x80 || GetKeyState('A') & 0x80) {
        // Уменьшим угол вращения мира
        g_rotateY -= 2;
    }
    if(GetKeyState(VK_RIGHT) & 0x80 || GetKeyState('D') & 0x80) {
        // Увеличим угол вращения мира
        g_rotateY += 2;
    }
}



//////////////
// В функции MainLoop() сразу после RenderScene(); добавьте вызов:
KeyPressed();
//////////////



// Тут начинаются вещи, с которыми мы обычно не имели дела - матрица проекции.
// С вызовами функций OrthoMode() и PerspectiveMode() происходит замена между
// 3д точками и 2д точками. Это значит, что вместо передавания OpenGL трехмерных
// координат, в ortho-режиме мы передаем ему пикселные координаты экрана.
// Это очень полезный режим, поскольку оберегает нас от огромных рассчетов,
// когда нам нужно рисовать интерфейс поверх 3д сцены. Когда вы вращаете или
// хоть как-то изменяете положение камеры, вслед за ней нужно изменять положение
// интерфейса, что очень раздражает.
// В самом начале вам нужно установить требуемую глубину, то есть нужно найти
// значение Z, с которым 2д картинка лучше всего смотрится. Если значение будет
// неподходящим, картинка может, например, стать пиксельной, что не есть гут.
// Кроме того, это работает намного быстрее glDrawPixels, и вам не нужно трогать
// растеризацию, плюс в можете использовать прозрачность и другие эффекты OpenGL.


///////////////////////////////// ORTHO MODE \\\\\\\\\\\\\\\\*
/////
/////           Новая функция: переключение в 2D режим
/////
///////////////////////////////// ORTHO MODE \\\\\\\\\\\\\\\\*

void OrthoMode(int left, int top, int right, int bottom)
{
    // Эта функция принимает координаты квадрата для нашего 2д экрана.
    // Ниже нам нужно включить матрицу проекции. Чтобы это сделать,
    // вызываем glMatrixMode(GL_PROJECTION), чтобы указать, какой нам нужен режим.
    // Далее мы загружаем новую матрицу, чтобы всё инициализировалось до того, как
    // мы перешли в режим ortho. Перейдя в 2д режим вызовом glOrtho(), мы изменяем
    // матрицу на GL_MODELVIEW, которая подготавливает нас к рендеру мира, используя
    // матрицу моделей. Также мы инициализируем новую матрицу моделей для 2д рендера,
    // прежде чем что-то рисовать.

    // Переключаемся на матрицу проекции, чтобы перейти в режем ortho, не perspective
    glMatrixMode(GL_PROJECTION);

    // Входим в новую матрицу, чтобы потом просто выйти из неё,
    // для возвращения в режим перспективы
    glPushMatrix();

    // Сбросим нашу текущую матрицу
    glLoadIdentity();

    // Передадим OpenGL экранные координаты. Лево, право, низ, верх.
    // Последние 2 параметра - ближняя и дальняя плоскости.
    glOrtho( left, right, bottom, top, 0, 1 );

    // Переключимся в modelview, чтобы рендерить общую картинку
    glMatrixMode(GL_MODELVIEW);

    // Обнулим текущую матрицу
    glLoadIdentity();
}



///////////////////////////////// PERSPECTIVE MODE \\\\\\\\\\\\\\\\*
/////
/////       Новая функция: возвращает нас из 2д режима в 3д.
/////
///////////////////////////////// PERSPECTIVE MODE \\\\\\\\\\\\\\\\*

void PerspectiveMode()
{
    // На самом деле, эта ф-я не переключается в режим перспективы, мы не
    // используем gluPerspective(), но если вы вспомните OrthoMode(), мы
    // работаем в новой матрице. Всё, что нам надо - выйти из этой матрицы,
    // и вернутся и изначальную матрицу перспективы. Итак, перед выходом из
    // матрицы, нам нужно сказать OpenGL, какую матрицу мы хотим использовать
    // - изменением текущей матрицы на перспективу. Потом мы можем вернутся
    // в матрицу modelview и рендерить всё как обычно.

    // Входим в режим матрицы проекции
    glMatrixMode( GL_PROJECTION );

    // Выходим из последней матрицы, возвращаясь в матрицу с режимом перспективы.
    glPopMatrix();

    // Возвращаемся в матрицу моделей
    glMatrixMode( GL_MODELVIEW );

    // Теперь мы должны быть в нормальном 3д режиме с перспективой
}


///////////////////////////////// DRAW ROOM \\\\\\\\\\\\\\\\*
/////
/////           Новая функция: рисует стены и пол
/////
///////////////////////////////// DRAW ROOM \\\\\\\\\\\\\\\\*

void DrawRoom()
{
    // Чтобы лучше продемонстрировать прозрачность прицела, я создал стены и пол,
    // на которые можно смотреть. Мы наложим текстуры пола и стены на созданные
    // квадраты. Наша камера находится в центре мира, а стены
    // - на 15 единиц по бокам. Так как квадраты большие, нам нужно повторять
    // текстуры несколько раз вместо растягивания на всю плоскость.
    // Вот почему координаты U и V более единицы.

    // Биндим текстуру на стены
    glBindTexture(GL_TEXTURE_2D,  textures[0].texID);

    // 4 длинных квадрата - стены
    glBegin(GL_QUADS);

    ////////////////////// Задняя стена ///////////////////////
        glTexCoord2f(0.0f, 2.0f);   glVertex3f(-15, 1, -15);
        glTexCoord2f(0.0f, 0.0f);   glVertex3f(-15, -1, -15);
        glTexCoord2f(12.0f, 0.0f);  glVertex3f(15, -1, -15);
        glTexCoord2f(12.0f, 2.0f);  glVertex3f(15, 1, -15);
    ////////////////////// Передняя стена ///////////////////////
        glTexCoord2f(0.0f, 2.0f);   glVertex3f(-15, 1, 15);
        glTexCoord2f(0.0f, 0.0f);   glVertex3f(-15, -1, 15);
        glTexCoord2f(12.0f, 0.0f);  glVertex3f(15, -1, 15);
        glTexCoord2f(12.0f, 2.0f);  glVertex3f(15, 1, 15);
    ////////////////////// Левая стена ///////////////////////
        glTexCoord2f(0.0f, 2.0f);   glVertex3f(-15, 1, -15);
        glTexCoord2f(0.0f, 0.0f);   glVertex3f(-15, -1, -15);
        glTexCoord2f(12.0f, 0.0f);  glVertex3f(-15, -1, 15);
        glTexCoord2f(12.0f, 2.0f);  glVertex3f(-15, 1, 15);
    ////////////////////// Правая стена ///////////////////////
        glTexCoord2f(0.0f, 2.0f);   glVertex3f(15, 1, -15);
        glTexCoord2f(0.0f, 0.0f);   glVertex3f(15, -1, -15);
        glTexCoord2f(12.0f, 0.0f);  glVertex3f(15, -1, 15);
        glTexCoord2f(12.0f, 2.0f);  glVertex3f(15, 1, 15);
    // Стены нарисовали.
    glEnd();

    // Теперь нам нужно нарисовать пол. Мы повторяем текстуру по полу
    // 16 раз в каждую сторону. Передаём 16.0f в текстурных координатах.

    // Биндим текстуру пола
    glBindTexture(GL_TEXTURE_2D,  textures[1].texID);

    // Квадрат - пол
    glBegin(GL_QUADS);
        glTexCoord2f(0.0f, 16.0f);  glVertex3f(-15, -1, -15);
        glTexCoord2f(0.0f, 0.0f);   glVertex3f(-15, -1, 15);
        glTexCoord2f(16.0f, 0.0f);  glVertex3f(15, -1, 15);
        glTexCoord2f(16.0f, 16.0f); glVertex3f(15, -1, -15);
    // Нарисовали пол
    glEnd();
}



//////////////////////
// Ещё одна вещь: добавим в WinProc обработку пробела для переключения
// режима нормального/ночного видения. Теперь блок WM_KEYDOWN выглядит так:

    case WM_KEYDOWN:
        switch(wParam) {    // Если нажата клавиша
            case VK_ESCAPE: // Если нажат ескейп
                PostQuitMessage(0); // Выходим
                break;

            case VK_SPACE:  // Если нажат пробел
                bNightVision = !bNightVision; // Вкл/выкл ночное видение
                break;

        }
        break;
////////////////////////////




////////////////////////////////////////////////////////
//
// И, наконец, главный участок нашей программы:

void RenderScene()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
 
    // Вместо использования класса камеры я написал небольшой кусок кода попроще,
    // чтобы продемонстрировать пример зума вперед-назад. В идеале у нас будет
    // позиция камеры и несколько обьектов "камера" на одной линии. Тогда при зуме
    // можно будет просто переключаться между ними.
 
        // Позиция       Направление     Верт. вектор
    gluLookAt(0, 0, Zoom,     0, 0, Zoom + 1,     0, 1, 0);
 
    // Вращаем мир вокруг оси Z
    glRotatef(rotateY , 0, 1, 0);
 
    // Текстурированные стены и пол
    DrawRoom();
 
 
    // Прежде, чем рисовать текстуру прицела, нужно перейти в режим ortho. Мы передаем
    // ширину и высоту экрана, чтобы использовать его правые и нижние координаты
    OrthoMode(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
 
    // Если ночное видение включено, окрашиваем мир в зеленый цвет, иначе - в нормальный.
    if(bNightVision)
        glColor4f(0, 1, 0, 1);
    else
        glColor4f(1, 1, 1, 1);
 
 
    ///////////////////// MASKING ///////////////////////////////
 
    // Когда комната нарисована, начинаем наш маскинг. Чтобы всё работало,
    // НЕОБХОДИМО ОТКЛЮЧИТЬ ТЕСТ ГЛУБИНЫ. Иначе работать не будет.
    // Прежде, чем рендерить нормальную картинку, рендерим её маску.
    // Метод блендинка будет следующим: (GL_DST_COLOR, GL_ZERO).
 
    // Тест глубины ДОЛЖЕН быть отключен
    glDisable(GL_DEPTH_TEST);
 
    // Включаем блендинг
    glBlendFunc(GL_DST_COLOR,GL_ZERO);
    glEnable(GL_BLEND);
 
    // Биндим текстуру маски к нашему 2д квадрату.
    glBindTexture(GL_TEXTURE_2D,  textures[2].texID);
 
    // Отображаем 2д квадрат с маской
    glBegin(GL_QUADS);
        // Обратите внимание, что находясь в 2д режиме, мы используем glVertex2f(),
        // передавая не вершины, а экранные координаты. Это делает отрисовку интерфейса
        // значительно легче. Похоже всё это на обычную 2д графику.
        // Текстурные координаты остаются без изменений.
 
        // Отображаем верхнюю левую точку 2д рисунка
        glTexCoord2f(0.0f, 1.0f);   glVertex2f(0, 0);
        // Нижнюю левую
        glTexCoord2f(0.0f, 0.0f);   glVertex2f(0, SCREEN_HEIGHT);
        // Нижнюю правую
        glTexCoord2f(1.0f, 0.0f);   glVertex2f(SCREEN_WIDTH, SCREEN_HEIGHT);
        // Верхнюю правую
        glTexCoord2f(1.0f, 1.0f);   glVertex2f(SCREEN_WIDTH, 0);
    // Нарисовали
    glEnd();
 
    // Отобразив маску на экране, нам нужно установить новый режим блендинга.
    // Этот режим будет осуществлять нужный нам режим маскинга. Потом мы просто
    // рендерим прозрачную картинку поверх маски, и будет достигнут
    // желаемый эффект.
 
    // Включаем режим блендинга "one to one"
    glBlendFunc(GL_ONE,GL_ONE);
 
    // Биндим текстуру прицела к следующему 2д квадрату
    glBindTexture(GL_TEXTURE_2D,  textures[3].texID);
 
    // 2д квадрат с текстурой прицела
    glBegin(GL_QUADS);
        // верх лево
        glTexCoord2f(0.0f, 1.0f);   glVertex2f(0, 0);
        // низ лево
        glTexCoord2f(0.0f, 0.0f);   glVertex2f(0, SCREEN_HEIGHT);
        // низ право
        glTexCoord2f(1.0f, 0.0f);   glVertex2f(SCREEN_WIDTH, SCREEN_HEIGHT);
        // верх право
        glTexCoord2f(1.0f, 1.0f);   glVertex2f(SCREEN_WIDTH, 0);
    // нарисовали
    glEnd();
 
    // закончив, мы можем отключить блендинг и включить тест глубины
    glDisable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);
 
    // Вернемся назад в режим перспективы.
    PerspectiveMode();
 
    SwapBuffers(g_hDC);
}



Полезный эффект, верно? Вы можете заметить, что края прицела несколько размазаны.
Это потому, что мы берём маленькую текстуру и растягиваем её на весь экран. Я сделал
маленькую текстуру, так как некоторые старые видеокарты не поддерживают текстуры,
бОльшие, чем 512x512.

Давайте рассмотрим процесс ещё раз.

Маскирование.
Техника маскрирования позволяет вам, например, вывести на прямоугольник рисунок
дерева с прозрачным фоном вместо сплошного. Это достигается созданием маски, типа 2Д,
накладывающейся перед самим изображением, а после этого накладывается основная текстура,
прозрачная в местах, обозначенных наложенной перед ней маской. Обратная сторона
медали - то, что вам нужно дважды рендерить изображение. Я предлагаю вместо
этого использовать текстуры с альфа-каналом.

Режим Ortho.
Режим Ortho - режим проекции, позволяющий рисовать примитивы в 2Д. Вместо
glVertex3f() используется glVertex2f(), в которую передаются экранные 2д координаты
вместо мировых 3д. Это очень полезно, если имеешь дело с интерфейсом или чем-то,
что должно оставатся на экране независимо от движения камеры.

Итак, как так получается, что положение картинки на экране остается неизменным независимо
от движения камеры? На самом деле всё просто. Вспомните, в OrthoMode() мы вызвали
новую матрицу прямо перед вызовом glLoadIdentity(). Мы сделали так, что текущая матрица,
с которой мы работаем, НЕ задета сбросом новой матрицы. Но после сброса матрицы уже
НЕТ вращения или перемещения, применённых к предидущей матрице, вплоть до выхода из
матрицы вызовом glPopMatrix(). Проще говоря, мы как бы говорим "Таймаут, мне нужно
стартовать свежую матрицу, но не удаляйте предидущую, так как я вернусь в неё через секунду".

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

Надеюсь, урок кому-нибуть помог!






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




Комментарии:

Войдите, чтобы оставить комментарий:












Яндекс.Метрика
 Яндекс цитирования.