OpenGL: Рендер в текстуру

Этот урок показывает, как можно рендерить экран в текстуру. В программе мы рендерим вращающийся куб в текстурную карту, которую затем накладываем на вращающийся квадрат. Вы не увидите вращающийся куб в 3д сцене, но увидите его на поверхности квадрата как обновляющуюся текстуру.

Сначала мы создадим пустую текстуру с данными RGB, инициализированными как 0.
Размер её будет 512×512. Больший размер (в зависимости от видеокарты) можен либо не работать, либо скушать много памяти, что затормозит всю программу. Далее мы сожмем данный вьюпорт до любого: 128×128, 256×256, или 512×512. Чем меньше размер, тем хуже будет качество текстуры. Установив вьюпорт, рендерим куб, биндим на него текстуру, и вызываем glCopyTexImage2D (). Эта чудесная функция копирует пикселные данные экрана в текущую забинденную текстуру.
Теперь мы очистим экран, не меняя буферы, и вернём экран в первоначальное разрешение.
Отныне мы можем использовать эту текстуру для наложения на квадрат. Больше деталей вы найдёте в коде.

Код взят из урока «Загрузка текстур».
Файлы texture.h, texture.cpp, main.h, init.cpp не изменены.

Файл main.cpp:

// Нам понадобится 2 текстуры, так что при обьявлении
// массива укажем двойку:
TextureImage textures[2];// Ниже создадим несколько переменных.
// Переменные, хранящие текущее вращение:
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);
// И прототип функции, создающей куб:
void CreateBox(int textureID, float x, float y, float z, float width,
float height,  float length, float uscale, float vscale);

// Теперь изменим код инициализации:
void Init(HWND hWnd)
{
g_hWnd = hWnd;
GetClientRect(g_hWnd, &g_rRect);
InitializeOpenGL(g_rRect.right, g_rRect.bottom);

Texture = new CTexture();

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

// Тут мы инициализируем новую текстуру, в которую будем рендерить.
// Мы передаем текстурный массив для сохранения, размер,
// канала (3 для РГБ), тип (РГБ) и ID текстуры.
CreateRenderTexture(textures, 512, 3, GL_RGB, 0);

// Грузим текстуру кирпича
Texture->LoadTexture(IL_BMP, «Brick.bmp»,&textures[1]);
}

// Напишем функцию для сохдания куба. Куб создается отекстуренным,
// если textureID > 0.

void CreateBox(int textureID, float x, float y, float z, float width,
float height,  float length, float uscale, float vscale)
{

// Функция создает куб с заданным вращением вокруг центральной точки.
// Первый параметр — id текстуры, или  < 0, если текстуры нет.
// Следующие параметры — центр, вращение и UV координаты.

// Проверим, передали ли нам верный id текстуры.
if(textureID >= 0) glBindTexture(GL_TEXTURE_2D, textures[textureID].texID);

// центрируем куб вокруг точки
x = x width  / 2;
y = y height / 2;
z = z length / 2;

// рисуем стороны
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();
}

// Ещё одна новая функция: создаёт пустую текстуру для рендера в неё
void CreateRenderTexture(TextureImage textureArray[], int size, int channels, int type, int textureID)
{
// указатель для сохранения данных текстуры
unsigned int *pTexture = NULL;

// Нужно создать пустую текстуру для рендера динамической текстуры.
// Чтобы сделать это, просто создаем массив для хранинея данных и
// передаем его OpenGL. Текстура хранится в видеокарте, так что мы можем
// уничтожить массив в любой момент.
// Эта функция принимает текстурный массив для сохранения текстуры,
// размер текстуры, высоту и ширину, каналы (1,3,4), тип (RGB, RGBA, etc)
// и текстурный ID.

// Инициализируем память под массив текстуры и привяжем к 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);

// Создаем текстуру и сохраняем в памяти
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);

// Сохранив текстуру с пом. OpenGL, мы можем удалить временные данные
delete [] pTexture;
}

// Внесём изменения в функцию WinProc: добавим обработку нескольких клавиш.
// Блок WM_KEYDOWN теперь выглядит так:
case WM_KEYDOWN:
switch(wParam) {
case VK_ESCAPE:
PostQuitMessage(0);
break;

case '1':   g_Viewport = 128; // Изменим размер экрана на 128
break;
case '2':   g_Viewport = 256; // Изменим размер экрана на 256
break;
case '3':   g_Viewport = 512; // Изменим размер экрана на 512
break;
}
break;
//////////////////////////////////////////////////////////////////////////

// И, наконец, изменим функцию RenderScene ():
void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();

//     Позиция     Направление    Верт. вектор
gluLookAt(0, 0, 4,     0, 0, 0,     0, 1, 0);       // отодвинем камеру назад

// Изменяем цвет фона на светло-синий. Мы изменим цвет экрана, когда будем
// отрисовывать куб для текстуры
glClearColor(0.0f, 0.0f, 1.0f, 1);      // Сделаем бекграунд синим

// Вращаем куб и квадрат вокруг оси Z
glRotatef(g_RotateY, 0.0f, 1.0f, 0.0f);     // Вращение по оси Y

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

// вращаем куб вокруг х и z
glRotatef(g_RotateX, 1.0f, 0.0f, 0.0f); // Вращение по оси X
glRotatef(g_RotateZ, 0.0f, 0.0f, 1.0f); // Вращение по оси Z

// В этой части мы собственно рендерим в текстуру. Сначала создадим маленький
// вьюпорт дял рендера. Таким образом мы не создадим текстуру больше
// нужного нам размера.
// Потом мы сможем рендерить с этого вьюпорта в текстуру.

// Обрежем вьюпорт до текущей величины экрана. У нас есть возможность
// изменять размер в реалтайме, нажимая 1, 2 или 3. Вы увидите разницу
// в качестве между 128, 256 и 512 размерами текстуры.
glViewport(0, 0, g_Viewport, g_Viewport);

// Теперь отрендерим куб в текстуру. Нам нужно просто создать куб с текущим
// вращением. Параметры — id текстуры, центр, угол и координаты UV.
CreateBox(1,    0, 0, 0,    1, 1, 1,    1, 1);

// До копирования экрана в текстуру нужно указать её вызовом glBindTexture ()
glBindTexture(GL_TEXTURE_2D,textures[0].texID);

// Настал момент, которого мы ждали — мы рендерим экран в текстуру.
// Передаем тип текстуры, детализацию, формат пиксела, x и y позицию старта,
// ширину и высоту для захвата, и границу. Если вы хотите сохранитьтолько часть
// экрана, это легко сделать изменением передаваемых параметров.
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);

// Изменяем цвет на черный
glClearColor(0.0f, 0.0f, 0.0f, 0.5);

glPopMatrix();  // Возвращаемся в исходную матрицу

// Теперь у нас есть правильная текстурная карта с вращающимся кубом.
// Наложим её на квадрат
glBindTexture(GL_TEXTURE_2D, textures[0].texID);

glBegin(GL_QUADS);

// Создадим текстурированный квадрат с только что отрендеренной
// текстурой на нём.
glTexCoord2f(   1, 0.0f);   glVertex3f(1, 1,  0);
glTexCoord2f(   1,    1);   glVertex3f(1,  1,  0);
glTexCoord2f(0.0f,    1);   glVertex3f( 1,  1,  0);
glTexCoord2f(0.0f, 0.0f);   glVertex3f( 1, 1,  0);

glEnd();

// Изменим углы вращения
g_RotateX += 1; g_RotateY += 2; g_RotateZ += 1;

SwapBuffers(g_hDC);
}

 

Готово!
После компиляции вы увидите вращающийся квадрат на синем фоне, на который наложена
текстура вращающегося куба на черном фоне. Иногда этот эффект оказывается очень полезным.

Понравилась статья? Поделиться с друзьями: