Этот урок показывает, как можно рендерить экран в текстуру. В программе мы рендерим вращающийся куб в текстурную карту, которую затем накладываем на вращающийся квадрат. Вы не увидите вращающийся куб в 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);
}

 

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