Код этого урока взят из урока «загрузка текстур», кроме того, добавлен соответствующий код из уроков об 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 тоже подходит, если движение происходит быстро и далеко от камеры.
Надеюсь, урок вам пригодился.