Array ( )
Вход:




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

OpenGL: Motion Blur






Код этого урока взят из урока "загрузка текстур", кроме того, добавлен соответствующий
код из уроков об Ortho mode и о рендере в текстуру. Так что перед работой с этим
уроком советую вам просмотреть предыдущие, если вы их не проходили =)

В этом уроке показана технология создания эффекта MotionBlur - размытого движения - довольно-таки
быстрым способом. Может быть, вы слышали о другом способе - создании буффера аккумуляции,
но очень уж это медленно для риал-тайм рендера. Более практичный способ - рендерить
текущий кадр в текстуру, а потом в режиме ortho рендерить текстуру на экран.
Изменить число рендеров на кадр можно будет клавишами ВЛЕВО и ВПРАВО. Чем больше значение,
тем более реалистично выглядит кадр.
Клавиши 1, 2 и 3 изменяют размер создаваемой текстуры.
Пробел останавливает и стартует вращение.

Итак, переходим к коду.
Файл main.cpp:
// Увеличим количество текстур до двух:
TextureImage textures[2];


// Добавим несколько переменных вверху файла.

// Первая будет хранить число рендеров/кадр:
int g_BlurRate = 50;
// Вторая - вкл/выкл вращения примитива:
bool g_bRotate = true;
// Эти три - значения вращения по разным осям:
float g_RotateX = 0, g_RotateY = 0, g_RotateZ = 0;
// Размер (разрешение) экрана, который будем рендерить в текстуру:
int g_Viewport = 512;
// А это - прототип функции, создаюшей пустую текстуру:
void CreateRenderTexture(TextureImage textureArray[],int size, int channels, int type, int textureID);


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

    Texture = new CTexture();

    // Инициализируем пустую текстуру для рендера. Передаём ID текстуры,
    // размер, каналы (3 для РГБ), тип (РГБ) и ID текстуры.
    CreateRenderTexture(textures, 512, 3, GL_RGB, 0);
    // Загрузим текстуру кирпичей в текстуру с ID 1
    Texture->LoadTexture(IL_BMP, "Brick.bmp", &textures[1]);
}


// Добавим функции для перехода в режим Ortho и обратно. Подробные обьяснения - в прошлых
// уроках.
void OrthoMode(int left, int top, int right, int bottom)
{
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    glOrtho( left, right, bottom, top, 0, 1 );

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void PerspectiveMode()                                     
{
    glMatrixMode( GL_PROJECTION );
    glPopMatrix();
    glMatrixMode( GL_MODELVIEW );
}


// Добавим функцию, создающую нашу вращающуюся фигуру:
void CreateBox(int textureID, float x, float y, float z, float width,
               float height,  float length, float uscale, float vscale)
{
    // Если ID текстуры >= 0, наложим текстуру:
    if(textureID >= 0) glBindTexture(GL_TEXTURE_2D, textures[textureID].texID);

    // Расположим центр в координатах x,y,z:
    x = x - width  / 2;
    y = y - height / 2;
    z = z - length / 2;

    // Начинаем рисовать фигуры в режиме GL_QUADS:
    glBegin(GL_QUADS);

        // Задняя сторона
        glTexCoord2f(0.0f,     0.0f); glVertex3f(x,y,   z);
        glTexCoord2f(0.0f,   vscale); glVertex3f(x,y + height, z);
        glTexCoord2f(uscale, vscale); glVertex3f(x + width, y + height, z);
        glTexCoord2f(uscale,   0.0f); glVertex3f(x + width, y,  z);

        // Передняя сторона
        glTexCoord2f(uscale,   0.0f); glVertex3f(x,y,z + length);
        glTexCoord2f(uscale, vscale); glVertex3f(x,y + height, z + length);
        glTexCoord2f(0.0f,   vscale); glVertex3f(x + width, y + height, z + length);
        glTexCoord2f(0.0f,     0.0f); glVertex3f(x + width, y,  z + length);

        // Нижняя сторона
        glTexCoord2f(uscale,   0.0f); glVertex3f(x,y,   z);
        glTexCoord2f(uscale, vscale); glVertex3f(x,y,   z + length);
        glTexCoord2f(0.0f,   vscale); glVertex3f(x + width, y,  z + length);
        glTexCoord2f(0.0f,     0.0f); glVertex3f(x + width, y,  z);

        // Верхняя сторона
        glTexCoord2f(uscale, vscale); glVertex3f(x,y + height,  z);
        glTexCoord2f(uscale,   0.0f); glVertex3f(x,y + height,  z + length);
        glTexCoord2f(0.0f,     0.0f); glVertex3f(x + width, y + height, z + length);
        glTexCoord2f(0.0f,   vscale); glVertex3f(x + width, y + height, z);

        // Левая сторона
        glTexCoord2f(uscale,   0.0f); glVertex3f(x,y,z);
        glTexCoord2f(0.0f,     0.0f); glVertex3f(x,y,   z + length);
        glTexCoord2f(0.0f,   vscale); glVertex3f(x,y + height,  z + length);
        glTexCoord2f(uscale, vscale); glVertex3f(x,y + height,  z);

        // Правая сторона
        glTexCoord2f(0.0f,     0.0f); glVertex3f(x + width, y,  z);
        glTexCoord2f(uscale,   0.0f); glVertex3f(x + width, y,  z + length);
        glTexCoord2f(uscale, vscale); glVertex3f(x + width, y + height, z + length);
        glTexCoord2f(0.0f,   vscale); glVertex3f(x + width, y + height, z);

    glEnd();
}

// Опишем реализацию CreateRenderTexture():
void CreateRenderTexture(TextureImage textureArray[],int size, int channels, int type, int textureID)
{
    // Создадим указатель на пустые данные текстуры
    unsigned int *pTexture = NULL;
 
    // Выделим и инициализируем память под данные текстуры, и направим
    // на них указатель pTexture
    pTexture = new unsigned int [size * size * channels];
    memset(pTexture, 0, size * size * channels * sizeof(unsigned int));
 
    // Передадим текстуру OpenGL и забиндим на ID текстуры:
    glGenTextures(1, &textureArray[textureID].texID);
    glBindTexture(GL_TEXTURE_2D, textureArray[textureID].texID);
 
    // Create the texture and store it on the video card
    // Создадим текстуру и сохраним её на видеокарте
    glTexImage2D(GL_TEXTURE_2D, 0, channels, size, size, 0, type, GL_UNSIGNED_INT, pTexture);
 
    // Установим кач-во текстуры
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
 
    // Сохранив текстуру на видеокарте, удалим данные:
    delete [] pTexture;
}


// Напишем новую функцию, рассчитывающую кадры:
bool AnimateNextFrame(int desiredFrameRate)
{
    static float lastTime = 0.0f;
    float elapsedTime = 0.0;

    // Текущее время в секундах
    float currentTime = GetTickCount() * 0.001f;

    // Получим прошедшее с предидущего кадра время
    elapsedTime = currentTime - lastTime;

    // Проверим, больше ли прошедшее время, чем (1 second / framesPerSecond)
    if( elapsedTime > (1.0f / desiredFrameRate) )
    {
        // Сбросим последнее время
        lastTime = currentTime;

        // Вернем TRUE, чтобы анимировать следующий кадр
        return true;
    }

    // Иначе сейчас анимация не нужна
    return false;
}

// Еще одна новая функция, рисующая motion blur:
void RenderMotionBlur(int textureID)
{
    // Эта функция создана для рисования текстуры с прозрачным фоном прямо перед
    // экраном, чтобы создать рекурсивный эффект смазывания. Мы начинаем, выключая
    // тест глубины, устанавливая режим прозрачности, а потом биндим отрендеренную
    // текстуру на экран. Далее устанавливаем уровень прозрачности в 90%.
    // Перед рендером квадрата, переходим в режим ortho. Отрендерив, возвращаемся
    // в режим перспективы, включаем тест глубины и выключаем текстурирование.

    // Входим в новую матрицу
    glPushMatrix();

        // Выключаем тест глубины
        glDisable(GL_DEPTH_TEST);

        // Включаем прозрачность и задаем её режим
        glBlendFunc(GL_SRC_ALPHA,GL_ONE);
        glEnable(GL_BLEND);

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

        // Установим альфа-прозрачность в 90%
        glColor4f(1, 1, 1, 0.9f);

        // Переходим в режим ortho
        OrthoMode(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

        // Рисуем квадрат с отрендеренной текстурой
        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();

        // Вернемся в режим перспективы
        PerspectiveMode();

        // Включим тест глубины и выключим прозрачность.
        glEnable(GL_DEPTH_TEST);
        glDisable(GL_BLEND);

    // Вернемся в оригинальную матрицу.
    glPopMatrix();
}



// Наконец, переходим к RenderScene():
void RenderScene()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    gluLookAt(0, 0, 10,     0, 0, 0,     0, 1, 0);

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

    glRotatef(g_RotateX, 1.0f, 0.0f, 0.0f); // Вокруг оси X
    glRotatef(g_RotateY, 0.0f, 1.0f, 0.0f); // Вокруг оси Y
    glRotatef(g_RotateZ, 0.0f, 0.0f, 1.0f); // Вокруг оси Z

    // Ниже рендерим в текстуру. Мы можем контролировать количество рендеров в секунду
    // нажатием влево-вправо. Чем больше рендеров в секунду, тем лучше качество.

    // Рендер в текстуру контролируется ф-ей AnimateNextFrame()
    if( AnimateNextFrame(g_BlurRate) )
    {
        // Установим вьюпорт в заданное разрешение. Изменяется клавишами 1,2,3
        glViewport(0, 0, g_Viewport, g_Viewport);

        // Тут мы перерисовываем текущую отрендеренную текстуру перед отрисовкой бокса.
        // Передаём ID текстуры.
        RenderMotionBlur(0);


        // Теперь нужно отрендерить в текстуру вращающийся куб. Нам нужно просто создать
        // куб, и он будет уже с заданным вращением.
        // параметры - ID текстуры, центр (x,y,z), смещение и UV координаты.
        CreateBox(1,    0, 0, 0,    1, 6, 1,    1, 3);

        // Прежде, чем копировать экран в текстуру, нужно указать текущую текстуру
        // вызовом glBindTexture
        glBindTexture(GL_TEXTURE_2D,textures[0].texID);

        // Рендерим экран в текстуру
        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, g_Viewport, g_Viewport, 0);

        // Очищаем экран и буфер глубины
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Возвращаем вьюпорт в нормальное состояние
        glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    }

    // Теперь, обновив отрендеренную текстуру, вывелем её на экран.
    RenderMotionBlur(0);

    // И нарисуем вращающийся куб
    CreateBox(1,    0, 0, 0,    1, 6, 1,    1, 3);

    // Если куб вращается, увеличим значения вращения
    if(g_bRotate)
    {
        g_RotateX += 2; g_RotateY += 3; g_RotateZ += 2;
    }

    SwapBuffers(g_hDC);
}


///////////////////////////////////////////////////////
//
// В начале WinProc добавим:
bool bUpdate = false;

// Изменим обработку клавиш.
// Блок WM_KEYDOWN в ф-ии WinProc должен выглядеть так:
    case WM_KEYDOWN:
        switch(wParam) {
            case VK_ESCAPE:
                PostQuitMessage(0);
                break;

            // Мы можем контролировать частоту смазывания клавишами ВЛЕВО-ВПРАВО.
            // Также мы можем останавливать вращение пробелом.

            case VK_LEFT:  // Если нажата ВЛЕВО

                g_BlurRate -= 10; // Уменьшим рендер в секунду
                // Убедимся, что не менее 10
                if(g_BlurRate < 10) g_BlurRate = 10;

                bUpdate = true; // Обновим информацию в заголовке
                break;

            case VK_RIGHT:  // если нажата ВПРАВОЛ

                g_BlurRate += 10;  // Увеличим рендер в секунду

                // Убедимся, что не превысили сотню:
                if(g_BlurRate > 100) g_BlurRate = 100;
                bUpdate = true; // Обновим информацию в заголовке
                break;

            case VK_SPACE:  g_bRotate = !g_bRotate; // вкл/выкл вращение
                break;

            case '1':   g_Viewport = 128;// Изменим разрешение до 128
                bUpdate = true;     // Обновим информацию в заголовке
                break;

            case '2':   g_Viewport = 256;       // Изменим разрешение до 256
                bUpdate = true; // Обновим информацию в заголовке
                break;

            case '3':   g_Viewport = 512;   // Изменим разрешение до 512
                bUpdate = true; // Обновим информацию в заголовке
                break;

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

// И в конец функции WinProc, перед строкой "return lRet", добавим:
    if(bUpdate)
    {
        static char strTitle[255] = {0};
        sprintf(strTitle,
            "Current Texture Renders Per Second: %d   Current Texture Size: %d",
            g_BlurRate, g_Viewport);

        SetWindowText(g_hWnd, strTitle);// Обновим информацию в заголовке
    }



Смазывание движения классно выглядит, да? Это так, но будьте осторожны - не используйте его
постоянно. Хоть этот метод и быстр, но всё, что вы хотите сделать смазанным, рисуется дважды.
А вообще, чем лучше видеокарта, тем более детализированными могут быть текстуры смазывания.
Если вы попробуете установить размер 128х128, увидете, что качество становится так себе.
Я рекомендую использовать как минимум 256х256. Хотя 128 тоже подходит, если движение происходит
быстро и далеко от камеры.

Надеюсь, урок вам пригодился.







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




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

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












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