NeHe Tutorials Народный учебник по OpenGL
Урок 29. OpenGL

Blitter Function, RAW Texture Loading

 

Этот урок вначале был написан Андреасом Лоффлером. Он также написал весь оригинальный код HTML для этого урока. Через несколько дней после этого Роб Флетчер выслал мне Irix версию этого урока. Он переписал большинство кода. Так что я портировал Irix/GLUT код Роба в Visual C++/Win32. Затем я модифицировал код цикла обработки сообщений, и код работы в полноэкранном режиме. Когда программа свернута, она не будет использовать процессор. Большинство проблем возникавших при переключениях полноэкранного режима исчезнет (таких как пропадание изображения на экране).

 

Так что урок Андреаса теперь лучше чем когда-либо. После того как был модифицирован код, так же изменился и код HTML. Огромное спасибо Андреасу за то, что он начал эту работу! Спасибо Робу за модификации!

 

Давайте начнем... Мы определяем структуру для хранения информации о режиме экрана DMsaved. Мы будем использовать эту структуру, для того чтобы сохранить информацию о видеорежиме, прежде чем мы переключимся в полноэкранный режим. Больше об этот позже! Заметьте, что мы определяем переменную для одной текстуры (texture [1]).

 

#include <windows.h> // заголовочный файл для Windows

#include <stdio.h>   // заголовочный файл для стандартного ввода/вывода

#include <gl\gl.h>   // заголовочный файл для библиотеки OpenGL32

#include <gl\glu.h>  // заголовочный файл для библиотеки GLu32

 

HDC       hDC=NULL;        // Контекст устройства

HGLRC     hRC=NULL;        // Контекст рендеринга

HWND      hWnd=NULL;       // Дескриптор окна

HINSTANCE hInstance;       // Экземпляр приложения

 

bool      keys[256];       // Массив для работы с клавиатурой

bool      active=TRUE;     // Флаг активности приложения

bool      fullscreen=TRUE; // Флаг полноэкранного режима

 

DEVMODE    DMsaved;        // Сохранить предыдущие настройки экрана (НОВОЕ)

 

GLfloat    xrot;                  // X вращение

GLfloat    yrot;                  // Y вращение

GLfloat    zrot;                  // Z вращение

 

GLuint    texture[1];                // имя 1 текстуры

 

Теперь об интересном. Мы создаем структуру по имени TEXTURE_IMAGE. Структура содержит информацию о ширине изображения, высоте, и формате (число байт на пиксель). Поле структуры data - указатель на данные изображения.

 

typedef struct Texture_Image

{

  int width;                 // Ширина

  int height;                // Высота

  int format;                // Байт на пиксель

  unsigned char *data;       // данные текстуры

} TEXTURE_IMAGE;

 

Мы тогда создаем указатель по имени P_TEXTURE_IMAGE на структуру TEXTURE_IMAGE. Переменные t1 и t2 имеют тип P_TEXTURE_IMAGE, где P_TEXTURE_IMAGE - переопределенный тип указателя на TEXTURE_IMAGE.

 

typedef TEXTURE_IMAGE *P_TEXTURE_IMAGE; // Указатель на структуру изображения

 

P_TEXTURE_IMAGE t1;                  // Указатель на данные текстуры

P_TEXTURE_IMAGE t2;                  // Указатель на данные текстуры

 

LRESULT  CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Объявление WndProc

 

Код ниже выделяет память для текстуры. Когда мы вызовем этот код, мы передадим ему ширину, высоту и число байт на пиксель. Переменная ti – указатель на данные TEXTURE_IMAGE. Переменная c – указатель на данные типа unsigned char.

 

// Выделить память под структуру изображения и данных изображения

P_TEXTURE_IMAGE AllocateTextureBuffer( GLint w, GLint h, GLint f)

{

  P_TEXTURE_IMAGE ti=NULL;              // Указатель на структуру изображения

  unsigned char *c=NULL;                // Указатель на блок памяти для изображения

 

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

 

После распределения памяти, и проверки, мы можем заполнять структуру атрибутами изображения. Сначала мы задаем ширину (w), затем высоту (h) и наконец формат (f). Имейте в виду, что формат – число байт на пиксель.

 

  ti = (P_TEXTURE_IMAGE)malloc(sizeof(TEXTURE_IMAGE)); // Дайте пожалуйста одну структуру для изображения

 

  if( ti != NULL ) {

    ti->width  = w;   // Ширина

    ti->height = h;   // Высота

    ti->format = f;   // Формат

 

Теперь мы должны распределить память для данных изображения. Для вычисления размера нужного куска памяти надо просто умножить ширину изображения (w) на высоту изображения (h) и на формат (f – число байт на пиксель).

 

    c = (unsigned char *)malloc( w * h * f);

 

Делаем проверку, что все прошло удачно. Если значение в c - не равно NULL, мы присваиваем полю data значение переменной c.

 

Если память не выделена, то выскочит сообщение об ошибке и процедура вернет NULL.

 

    if ( c != NULL ) {

      ti->data = c;

    }

    else {

      MessageBox(NULL,"Не могу выделить память для буфера текстуры","BUFFER ERROR",MB_OK | MB_ICONINFORMATION);

      return NULL;

    }

  }

 

Если невозможно выделить память для структуры изображения, то так же выскочит сообщение об ошибке и процедура вернет NULL.

 

Если нет проблем, мы возвращаем ti - указатель на готовую структуру изображения. Ух... надеюсь, что все это понятно и имеет смысл.

 

  else

  {

    MessageBox(NULL,"Не могу выделить память для структуры изображения","IMAGE STRUCTURE ERROR",MB_OK | MB_ICONINFORMATION);

    return NULL;

  }

  return ti; // Вернем указатель на структуру изображения

}

 

Когда приходит время, то надо освободить память. Для этого служит код ниже, в котором происходит освобождение буфера текстуры и затем сброс памяти для структуры изображения. Переменная t - указатель на структуру данных TEXTURE_IMAGE, которую мы хотим освободить.

 

// Освободить память

void DeallocateTexture( P_TEXTURE_IMAGE t )

{

  if(t)

  {

    if(t->data)

    {

      free(t->data); // Освободить буфер

    }

 

    free(t); // Сброс текстуры

  }

}

 

Теперь мы будем читать наше RAW изображение (несжатые и не форматированные данные, так называемые “сырые” данные). Мы передаем имя файла и указатель на структуру изображения, в которую мы хотим загрузить изображение. Мы определяем несколько вспомогательных переменных, и затем вычисляем размер строки, умножая ширину нашего изображения на формат (число байт на пиксель). Если бы изображение было 256 пикселей в ширину и 4 байта на пиксель, то ширина строки была бы 1024 байта. Мы запоминаем ширину строки в stride.

 

Мы обнуляем указатель (p), и затем пытаемся открыть файл.

 

// Чтение RAW файла в готовый буфер.

// Поворот изображения сверху вниз. Возвращает 9, если сбой, или число прочитанных байт.

int ReadTextureData ( char *filename, P_TEXTURE_IMAGE buffer)

{

  FILE *f;

  int i,j,k,done=0;

  int stride = buffer->width * buffer->format; // Размер строки

  unsigned char *p = NULL;

 

  f = fopen(filename, "rb");  // Открыть файл

  if( f != NULL )             // Если файл существует

  {

 

Если файл существует, мы запускаем три цикла, чтобы прочитать нашу текстуру. Начинаем с нижней строки и сдвигаемся вверх по одной строке. Так как цикл начинается снизу, то зеркальный поворот будет выполнен правильно. RAW изображения сохраняются инвертированными вниз. Необходимо установить указатель в надлежащее место в буфере изображения. Каждый раз, когда мы сдвигаемся на следующую строку(i уменьшается) мы устанавливаем указатель на начало новой строки. Указатель data – показывает начало изображения, и для того чтобы перейти на следующую строку изображения, надо умножить номер текущей строки i на размер строки stride.

 

Во втором цикле j смещается слева (0) направо (ширина строки в пикселях, но не в байтах).

 

    for( i = buffer->height-1; i >= 0 ; i-- ) // Цикл по высоте (снизу вверх)

    {

      p = buffer->data + (i * stride );

      for ( j = 0; j < buffer->width ; j++ ) // Цикл по ширине

      {

 

В цикле по k читаем пиксель побайтно. Если формат (байт на пиксель) - 4,то k меняется от 0 до 2, где 2 равняется число байт на пиксель минус один (format -1). Так как большинство необработанных изображений не имеют альфа канала, а нам он нужен, то это та причина, по которой мы вычитаем единицу. Мы будем заполнять каждый 4-ый байт нашим значением альфа канала.

 

Заметьте, что в цикле, мы также увеличиваем указатель (p) и переменную done. Больше об этом позже.

 

В строке внутри цикла происходит чтение символа из нашего файла и запоминание его в буфере текстуры согласно текущего положения указателя. Если наше изображение имеет 4 байта на пиксель, первые 3 байта будут читаться из RAW файла (format-1), и 4-ый байт будет вручную установлен в 255. После того, как мы задаем 4-ый байт равным 255, мы сдвигаем указателя на один байт вперед, чтобы наш 4-ый байт не был перезаписан следующим байтом из файла.

 

После того, как все байты прочитались в пиксель, и прочитались все пиксели строке, и прочитались все строки, то все сделано! Мы можем закрывать файл.

 

        for ( k = 0 ; k < buffer->format-1 ; k++, p++, done++ )

        {

          *p = fgetc(f); // Чтение значения из файла и сохранение его в памяти

        }

        *p = 255; p++; // Установить 255 в альфа канал и увеличить указатель

      }

    }

    fclose(f); // Закрыть файл

  }

 

Если файл не открыт, то выскочит окно сообщения, о том, что файл не был открыт.

 

В конце мы возвращаем done. Если файл не был открыт, то done равно 0. Если все пошло удачно, то done равно числу прочитанных байтов из файла. Помните, мы увеличивались done, каждый раз, когда читали байт в цикле выше (цикл по k).

 

  else // Иначе

  {

    MessageBox(NULL,"Невозможно открыть файл изображения ","IMAGE ERROR",MB_OK | MB_ICONINFORMATION);

  }

  return done; // Возвращает число прочитанных байт

}

 

This shouldn't need explaining. By now you should know how to build a texture. tex is the pointer to the TEXTURE_IMAGE structure that we want to use. We build a linear filtered texture. In this example, we're building mipmaps (smoother looking). We pass the width, height and data just like we would if we were using glaux, but this time we get the information from the selected TEXTURE_IMAGE structure.

 

Далее код не нуждается в пояснениях. Вы должны знать, как создать текстуру. Переменная tex - указатель на структуру TEXTURE_IMAGE, которую мы хотим использовать. Мы создаем текстуру с линейной фильтрацией. В этом примере, мы задаем мипмапы (для улучшения изображения). Мы передаем ширину, высоту и данные изображения точно так же как, если мы использовали glaux, но на сей раз, мы получаем информацию из структуры TEXTURE_IMAGE.

 

void BuildTexture (P_TEXTURE_IMAGE tex)

{

  glGenTextures(1, &texture[0]);

  glBindTexture(GL_TEXTURE_2D, texture[0]);

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

  gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, tex->width, tex->height, GL_RGBA, GL_UNSIGNED_BYTE, tex->data);

}

 

Теперь код блиттера (функция пересылки изображения). Код блиттера очень мощный. Он позволяет Вам копировать любую прямоугольную часть текстуру (src) в приемную текстуру (dst). Вы можете комбинировать так много текстур, как Вы хотите, Вы можете задать альфа значение для смешивания.

 

Переменная src - структура TEXTURE_IMAGE используется как исходное изображение. Переменная dst - структура TEXTURE_IMAGE используется как приемное изображение. Переменная src_xstart – задает начало копирования данных из исходного изображения по оси X. Переменная src_ystart - задает начало копирования данных из исходного изображения по оси Y. Переменная src_width – задает ширину в пикселях области, которую Вы хотите копировать из исходного изображения. Переменная src_height - высота в пикселях области, которую Вы хотите скопировать из исходного изображения. Переменные dst_xstart и dst_ystart – задают место, куда вы хотите скопировать пиксели из исходного изображения в приемное изображение. Если blend – равно 1, то два изображения будут смешаны. Значение переменной alpha задает степень прозрачности изображения. Если значение 0, то ничего не будет скопировано, если alpha равно 255, то исходное изображение полностью перекроет приемное.

 

Затем задаем все вспомогательные переменные циклов, наряду с указателями для нашего исходного изображения (s) и приемного изображения (d). Проверяем диапазон значения alpha. Если есть выход за диапазон, то исправляем его. Тоже и для blend.

 

void Blit( P_TEXTURE_IMAGE src, P_TEXTURE_IMAGE dst, int src_xstart, int src_ystart, int src_width, int src_height,

     int dst_xstart, int dst_ystart, int blend, int alpha)

{

  int i,j,k;

  unsigned char *s, *d; // Исходное и приемное

 

  // Ограничиваем Alpha

  if( alpha > 255 ) alpha = 255;

  if( alpha < 0 ) alpha = 0;

 

  // Проверка флага Blend

  if( blend < 0 ) blend = 0;

  if( blend > 1 ) blend = 1;

 

Теперь мы должны задать значение указателей. Приемный указатель - адрес приемных данных плюс смещение начальной строки в приемном изображении, которое равно произведению начальной строки изображения (dst_ystart) на ширину приемного изображения и на формат изображения.

 

Тоже делается и для исходного изображения. Исходный указатель - адрес исходных данных плюс смещение начальной строки в исходном изображении (src_ystart), которое равно произведению начальной строки изображения (src_ystart) на ширину исходного изображения и на формат изображения.

 

Цикл по i от 0 до src_height, копирует все строки из исходного изображения.

 

  d = dst->data + (dst_ystart * dst->width * dst->format);

  s = src->data + (src_ystart * src->width * src->format);

 

  for (i = 0 ; i < src_height ; i++ ) // Цикл по высоте

  {

 

Мы уже задали значения указателей источника и приемника на нужные строки в каждом изображении. Теперь мы должны двигаться в правильном направлении слева направо в каждом изображении прежде, чем мы запустим блиттинг данных. Мы увеличиваем указатель на исходное изображение (s) на значение src_xstart, которое является началом по оси X исходных данных изображения умноженное на число байт на пиксель. Это перемещает указатель источника (s) на начальный пиксель по оси X (слева направо) в исходном изображении.

 

Тоже и для приемного изображения. Мы увеличиваем указатель на приемное изображение (d) на значение dst_xstart, которое является началом по оси X приемных данных изображения умноженное на число байт на пиксель. Это перемещает указатель источника (d) на начальный пиксель по оси X (слева направо) в приемном изображении.

 

После того, как мы вычислили, где в памяти мы хотим захватить наши пиксели от (s) и куда мы хотим переместить их в (d), мы запускаем цикл по j. Мы будем использовать цикл по j, чтобы перемещаться слева направо через исходное изображение.

 

    s = s + (src_xstart * src->format); // Сдвинемся на начало исходных данных в строке

    d = d + (dst_xstart * dst->format); // Сдвинемся на начало приемных данных в строке

    for (j = 0 ; j < src_width ; j++ )  // Цикл по ширине

    {

 

Цикл по k используется для обработки всех байт в пикселе. Заметьте, что при увеличении k указатели для источника и приемника также увеличиваются.

 

Внутри цикла мы проверяем, включено ли смешение. Если blend равно 1, то значит надо выполнить смешение пикселей, и мы делаем несколько причудливых операций, чтобы вычислить цвет наших смешанных пикселей. Значение приемника (d) будет равно исходному значению (s), умноженному на альфа-значение плюс текущее значение приемника (d) умноженное на 255 минус альфа-значение. Оператор сдвига (>>8) сдвигает значение в диапазон 0-255.

 

Если смешивание выключено (0), то мы копируем данные из исходного изображения непосредственно в приемное изображение. При этом смешивание не делается, и альфа-значение игнорируется.

 

      for( k = 0 ; k < src->format ; k++, d++, s++)

      {

        if (blend) // Смешивание

         // Умножить данные Src наalpha плюс данные Dst *(255-alpha)

         // Сохранить значение в диапазоне 0-255 с помощью сдвига >> 8

         *d = ( (*s * alpha) + (*d * (255-alpha)) ) >> 8;

        else

         *d = *s; // нет смешения, просто копируем

      }

    }

    d = d + (dst->width - (src_width + dst_xstart))*dst->format; // Добавить конец строки

    s = s + (src->width - (src_width + src_xstart))*src->format; // Добавить конец строки

  }

}

 

Код  InitGL() был изменен. Весь код ниже новый. Вначале выделяем память для изображения размером 256x256x4 байт. Переменная t1 укажет на выделенную память, если все пошло хорошо.

 

После выделения памяти для нашего изображения, мы пытаемся загрузить изображение. Мы передаем функции ReadTextureData() имя файла, который мы желаем открыть, вместе с указателем на нашу структуру изображения (t1).

 

Если изображение не загружено, выскочит окно сообщения на экран, чтобы дать понять пользователю, что была проблема, при загрузке текстуру.

 

Затем мы делаем тоже самое и для t2. Мы распределяем память, и пытаемся прочитать второе изображение. Если что-нибудь пойдет не так, как надо, выскочит окно сообщения.

 

int InitGL(GLvoid) // Вызов происходит после создания окна

{

  t1 = AllocateTextureBuffer( 256, 256, 4 ); // Взять структуру изображения

  if (ReadTextureData("Data/Monitor.raw",t1)==0) // Заполнить структуру данными

  { // Ничего не прочитали?

    MessageBox(NULL,"Не могу прочитать 'Monitor.raw'","TEXTURE ERROR",MB_OK | MB_ICONINFORMATION);

    return FALSE;

  }

 

  t2 = AllocateTextureBuffer( 256, 256, 4 ); // Второе изображение

  if (ReadTextureData("Data/GL.raw",t2)==0) // Заполнить структуру данными

  { // Ничего не прочитали?

    MessageBox(NULL," Не могу прочитать 'GL.raw'","TEXTURE ERROR",MB_OK | MB_ICONINFORMATION);

    return FALSE;

  }

 

Двигаемся дальше. Теперь пришло время использовать нашу функцию Blit(), чтобы объединить два изображения в одно.

 

Мы передаем Blit() два указателя t2 и t1, оба указатели на структуру TEXTURE_IMAGE (t2 - второе изображение, t1 - первое изображение).

 

Then we have to tell blit where to start grabbing data from on the source image. If you load the source image into Adobe Photoshop or any other program capable of loading .RAW images you will see that the entire image is blank except for the top right corner. The top right has a picture of the ball with GL written on it. The bottom left corner of the image is 0,0. The top right of the image is the width of the image-1 (255), the height of the image-1 (255). Knowing that we only want to copy 1/4 of the src image (top right), we tell Blit() to start grabbing from 127,127 (center of our source image).

 

Затем надо сообщить Blit(), где начать захватывать данные из исходного изображения. Если Вы загрузите исходное изображение в Adobe Photoshop, или любую другую программу, которая способна читать RAW изображения, то Вы увидите, что все изображение пустое, кроме правого верхнего угла. В нем есть изображение шара с надписью GL. Левый угол изображения - 0,0. Правый верхний угол равен ширине изображения-1 (255) и высоте изображения-1 (255). Зная, что мы хотим cкопировать 1/4 часть исходного изображения (право верх), мы сообщаем Blit() начать захват с 127,127 (центр нашего исходного изображения).

 

Затем мы сообщаем Blit(), сколько пикселей мы хотим копировать из нашей исходной точки вправо и вверх. Мы хотим захватить четвертую часть изображения. Наше изображение равно 256x256 пикселей, четверть его равна 128x128 пикселей. Вся исходная информация есть. Функция Blit() теперь знает, что надо скопировать от 127 по оси X до 127+128 (255) по оси X, и от 127 по оси Y до 127+128 (255) по оси Y.

 

Итак, Blit() знает, что копировать, и где получить данные для этого, но она не знает, куда поместить эти данные. Мы хотим вывести шар с GL, в середине нашего изображения монитора. Вы находите центр приемного изображения (256x256), который равен 128x128, и вычитаете половину ширины и высоты исходного изображения (128x128), что равно 64x64. Так (128-64) x (128-64) дает нам начальную точку 64,64.

 

И последнее, мы сообщаем нашей блиттер-функции, что мы хотим смешать два изображения (единица означает смешивание, а ноль означает не смешивание), и насколько смешать изображения. Если последнее значение - 0, то мы не смешиваем изображения. Если мы используем значение 127, то изображения смешиваются вместе в 50% пропорции, и если Вы используете 255, то изображение, которое Вы копируете, будет полностью прозрачно, и не будет видно вообще.

 

Пиксели копируются из изображения 2 (t2) в изображение 1 (t1). Смешанное изображение будет сохранено в t1.

 

  // Смешивание изображений: исходное, приемное изображение

  // исходные X & Y, исходные Width & Height, приемные X & Y, флаг смешивания

  // альфа-значение

  Blit(t2,t1,127,127,128,128,64,64,1,127); // блиттер

 

После того, как мы смешали два изображения (t1 и t2), мы создаем текстуру из объединенных изображений (t1).

 

После того, как текстура была создана, мы можем освободить память из двух структур TEXTURE_IMAGE.

 

Остальная часть кода уже стала привычным стандартом. Мы разрешаем наложение текстуры, тест глубины, и т.д.

 

  BuildTexture (t1);       // Загрузка текстуры в память

 

  DeallocateTexture( t1 ); // Очистка памяти

  DeallocateTexture( t2 );

 

  glEnable(GL_TEXTURE_2D); // Разрешить наложение текстуры

 

  glShadeModel(GL_SMOOTH); // Разрешить плавное сглаживание

  glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Фоновый цвет черный

  glClearDepth(1.0);       // Очистка буфера глубины

  glEnable(GL_DEPTH_TEST); // Тест глубины

  glDepthFunc(GL_LESS);    // Тп теста

 

  return TRUE;

}

 

Я не буду подробно пояснять код ниже. Мы сдвигаемся на 5 единиц в глубь экрана, выбираем текстуру, и рисуем текстурированный куб. Заметьте, что оба изображения теперь смешаны в одно изображение. Мы не выводим обе текстуры на кубе. Код блиттер делает это за нас.

 

GLvoid DrawGLScene(GLvoid)

{

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

  glLoadIdentity(); // Сброс просмотра

  glTranslatef(0.0f,0.0f,-5.0f);

 

  glRotatef(xrot,1.0f,0.0f,0.0f);

  glRotatef(yrot,0.0f,1.0f,0.0f);

  glRotatef(zrot,0.0f,0.0f,1.0f);

 

  glBindTexture(GL_TEXTURE_2D, texture[0]);

 

  glBegin(GL_QUADS);

    // Передняя грань

    glNormal3f( 0.0f, 0.0f, 1.0f);

    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);

    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);

    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);

    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);

    // Задняя грань

    glNormal3f( 0.0f, 0.0f,-1.0f);

    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);

    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);

    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);

    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);

    // Верхняя грань

    glNormal3f( 0.0f, 1.0f, 0.0f);

    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);

    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);

    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);

    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);

    // Нижняя грань

    glNormal3f( 0.0f,-1.0f, 0.0f);

    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);

    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);

    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);

    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);

    // Правая грань

    glNormal3f( 1.0f, 0.0f, 0.0f);

    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);

    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);

    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);

    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);

    // Левая грань

    glNormal3f(-1.0f, 0.0f, 0.0f);

    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);

    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);

    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);

    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);

  glEnd();

 

  xrot+=0.3f;

  yrot+=0.2f;

  zrot+=0.4f;

}

 

В код KillGLWindow() внесено ряд изменений. Во-первых, код переключения из полноэкранного режима назад к видеорежиму вашего рабочего стола теперь вначале KillGLWindow(). Если пользователь запустил программу в полноэкранном режиме, вначале, перед тем как уничтожить окно - пробуем переключиться назад в видеорежим рабочего стола. Если быстрый вариант не сработает, мы сбрасываем экран, используя информацию, сохраненную в DMsaved. Это должно восстановить оригинальный видеорежим.

 

GLvoid KillGLWindow(GLvoid) // Выполняет при уничтожении окна

{

  if (fullscreen)                  // Мы в полноэкранном?

  {

    if (!ChangeDisplaySettings(NULL,CDS_TEST)) { // Если это не работает

      ChangeDisplaySettings(NULL,CDS_RESET); // Еще раз (чтобы взять значения из реестра)

      ChangeDisplaySettings(&DMsaved,CDS_RESET); // Изменить разрешение используя

                                                 // сохраненные данные

    }

    else // все прошло

    {

      ChangeDisplaySettings(NULL,CDS_RESET); // Ничего не делать

    }

 

    ShowCursor(TRUE); // Показать курсор мыши

  }

 

    if (hRC)                              // Существует контекст рендеринга?

    {

        if (!wglMakeCurrent(NULL,NULL))   // Можно ли освободить DC и RC контексты?

        {

            MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",

                                                MB_OK | MB_ICONINFORMATION);

    }

      if (!wglDeleteContext(hRC))         // Можно ли уничтожить RC?

    {

            MessageBox(NULL,"Release Rendering Context Failed.",

                   "SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);

    }

  hRC=NULL;                               // Установим RC в NULL

    }

 

    if (hDC && !ReleaseDC(hWnd,hDC))      // Можно ли уничтожить DC?

    {

        MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",

                                              MB_OK | MB_ICONINFORMATION);

    hDC=NULL;                             // Установим DC в NULL

    }

    if (hWnd && !DestroyWindow(hWnd))     // Можно ли уничтожить окно?

    {

        MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK |

                                                      MB_ICONINFORMATION);

    hWnd=NULL;                            // Уствновим hWnd в NULL

    }

    if (!UnregisterClass("OpenGL",hInstance)) // Можно ли уничтожить класс?

    {

        MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | 

                                                          MB_ICONINFORMATION);

    hInstance=NULL;                       // Устанавливаем hInstance в NULL

    }

        KillFont();                       // Уничтожаем шрифт

}

 

Я сделал некоторые изменения и в CreateGLWindow. Эти изменения помогут устранить множество проблем, которые возникают при переключениях видеорежимов. Я включил первую часть CreateGLWindow(), чтобы вам было легче разобраться.

 

BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)

{

  GLuint    PixelFormat; // Для хранения результатов поиска

  WNDCLASS  wc;          // Структура класса окна

  DWORD    dwExStyle;    // Расширенный стиль окна

  DWORD    dwStyle;      // Стиль окна

 

  fullscreen=fullscreenflag; // Глобальный флаг

 

  hInstance    = GetModuleHandle(NULL);

  wc.style    = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;

  wc.lpfnWndProc    = (WNDPROC) WndProc; // Обработчик

  wc.cbClsExtra    = 0;

  wc.cbWndExtra    = 0;

  wc.hInstance    = hInstance;

  wc.hIcon    = LoadIcon(NULL, IDI_WINLOGO);

  wc.hCursor    = LoadCursor(NULL, IDC_ARROW);

  wc.hbrBackground  = NULL;

  wc.lpszMenuName    = NULL;

  wc.lpszClassName  = "OpenGL";

 

The big change here is that we now save the current desktop resolution, bit depth, etc. before we switch to fullscreen mode. That way when we exit the program, we can set everything back exactly how it was. The first line below copies the display settings into the DMsaved Device Mode structure. Nothing else has changed, just one new line of code.

 

Здесь есть существенное изменение: мы сохраняем текущие настройки видеорежима прежде, чем переключить видеорежим. Поэтому мы можем восстановить все обратно, когда выходим из программы. В первой строке ниже копируются параметры настройки дисплея в структуру режима дисплея DMsaved. Далее все тоже самое.

 

  // Сохранить настройки видеорежима (НОВОЕ)

  EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DMsaved);

 

  if (fullscreen) // Попробуем перейти в полноэкранный режим?

  {

    DEVMODE dmScreenSettings; // Режим работы

    memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); // Очистка для хранения установок

    dmScreenSettings.dmSize=sizeof(dmScreenSettings);      // Размер структуры Devmode

    dmScreenSettings.dmPelsWidth  = width;        // Ширина экрана

    dmScreenSettings.dmPelsHeight  = height;        // Высота экрана

    dmScreenSettings.dmBitsPerPel  = bits;          // Число бит на пиксель

    dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

 

    // Попробовать переключить режим и получить результат

    // Замечание: при CDS_FULLSCREEN исчезает панель задач.

    if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)

    {

      // Если не прошло, то запросить разрешение на работу в видеорежиме рабочего стола.

      if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)

      {

        fullscreen=FALSE; // Выбран оконный режим

      }

      else

      {

        // Сообщение о том, что работа программы завершается.

        MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);

        return FALSE;            // Return FALSE

      }

    }

  }

 

Функция WinMain() начинается так же. Спросим у пользователя разрешение на работу в полноэкранном режиме.

 

int WINAPI WinMain(

      HINSTANCE  hInstance,

      HINSTANCE  hPrevInstance,

      LPSTR      lpCmdLine,

      int        nCmdShow)

{

  MSG  msg; // Структура обработки сообщений

  BOOL  done=FALSE; // Переменная выхода из цикла

 

  // Спросить у пользователя какой режим он предпочитает

  if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?",

      "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)

  {

    fullscreen=FALSE; // Оконный режим

  }

 

  // Создать окно

  if (!CreateGLWindow("Andreas Lцffler, Rob Fletcher & NeHe's Blitter & Raw Image Loading Tutorial", 640, 480, 32, fullscreen))

  {

    return 0; // Выход, если окно не создано

  }

 

  while(!done) // Цикл пока done=FALSE

  {

    if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Ждем сообщений?

    {

      if (msg.message==WM_QUIT) // Получили сообщение о выходе?

      {

        done=TRUE; // тогда done=TRUE

      }

      else // Нет - обработать сообщений

      {

        TranslateMessage(&msg); // Транслировать

        DispatchMessage(&msg);  // Переслать

      }

    }

 

Я сделал некоторые изменения в коде ниже. Если программа - не активна (свернута), мы ждем первого сообщения программе с помощью команды WaitMessage(). Все останавливается, пока программа не получит сообщения (обычно это сообщение о развертывании окна). Спасибо Джиму Стронгу за это предложение.

 

    if (!active) // Программа не активна?

    {

      WaitMessage(); // Ожидать сообщения/Ничего не делать (НОВОЕ)

    }

 

    if (keys[VK_ESCAPE]) // Нажата Escape?

    {

      done=TRUE; // ESC сигнал на выход

    }

 

    if (keys[VK_F1]) // F1 нажата?

    {

      keys[VK_F1]=FALSE;

      KillGLWindow();

      fullscreen=!fullscreen;

      if (!CreateGLWindow("Andreas Lцffler, Rob Fletcher & NeHe's Blitter & Raw Image Loading Tutorial",640,480,16,fullscreen))

      {

        return 0;

      }

    }

 

    DrawGLScene();

    SwapBuffers(hDC);

  }

 

  // Выход

  KillGLWindow();

  return (msg.wParam);

}

 

Отлично! Теперь вы можете создавать крутые эффекты с применением смешивания. Используя текстурные буферы, как в этом уроке, вы можете создавать крутые эффекты, такие как плазма или вода. При объединении этих эффектов вместе Вы можете сделать почти фотореалистический ландшафт. Если что-то не работает в этом уроке, или у вас есть предложения, как улучшить урок, пошлите мне сообщение по электронной почте. Спасибо за то, что прочитали этот урок! Удачи Вам в создании ваших собственных интересных эффектов!

 

Andreas Lцffler &
Rob Fletcher
Jeff Molofee (NeHe)

PMG  5 марта 2005 (c)  Сергей Анисимов