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

Texture Filters, Lighting & Keyboard Control

В этом уроке я научу вас, как использовать три разных режима фильтрации текстур. Я научу вас, как перемещать объект, используя клавиши на клавиатуре, и я также преподам вам, как применить простое освещение в вашей OpenGL сцене. В этом уроке много материала, поэтому, если предыдущие уроки вам непонятны, вернитесь и посмотрите их вновь. Важно иметь хорошее понимание основ прежде, чем Вы перепрыгнете на этот код.


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

 

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

#include <stdio.h>    // Заголовочный файл для стандартного ввода/вывода (ДОБАВИЛИ)

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

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

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

 

HDC    hDC=NULL;      // Служебный контекст GDI устройства

HGLRC  hRC=NULL;      // Постоянный контекст для визуализации

HWND   hWnd=NULL;     // Содержит дискриптор для окна

HINSTANCE hInstance;  // Содержит данные для нашей программы

 

bool keys[256];       // Массив, использующийся для сохранения состояния клавиатуры

bool active=TRUE;     // Флаг состояния активности приложения (по умолчанию: TRUE)

bool fullscreen=TRUE; // Флаг полноэкранного режима (по умолчанию: полноэкранное)

 

Строки ниже новые. Мы собираемся добавлять три логических переменных. Тип BOOL означает, что переменная может только быть ИСТИННА (TRUE) или ЛОЖЬ (FALSE). Мы создаем переменную называемую light, чтобы отслеживать, действительно ли освещение включено или выключено. Переменные lp и fp используются, для отслеживания нажатия клавиш 'L' и 'F'. Я объясню, почему нам нужны эти переменные позже. Пока, запомните, что они необходимы.

 

BOOL light;      // Свет ВКЛ / ВЫКЛ

BOOL lp;         // L нажата?

BOOL fp;         // F нажата?

 

Теперь нам нужны пять переменных, которые будут управлять следующими параметрами: углом по оси X (xrot), углом по оси Y (yrot), скоростью вращения ящика по оси X (xspeed), и скоростью вращения ящика по оси Y (yspeed). Мы также создадим переменную z, которая будет управлять, погружением ящика в экран (по оси Z).

 

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

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

GLfloat xspeed;       // X скорость вращения

GLfloat yspeed;       // Y скорость вращения

 

GLfloat z=-5.0f;      // Сдвиг вглубь экрана

 

Теперь мы зададим массивы, которые мы будем использовать для освещения. Мы будем использовать два разных типа света. Первый тип света называется фоновым светом. Фоновый свет не имеет никакого определенного направления. Все объекты в вашей сцене будут освещены фоновым светом. Второй тип света - диффузный свет. Диффузный свет создается при помощи вашего источника света и отражается от поверхности объекта в вашей сцене. Любая поверхность объекта, на которую падает прямо свет, будет очень яркой и области, куда свет падает под углом, будут темнее. Это создает хороший эффект оттенения на сторонах нашей корзины.

 

Свет создается так же как цвет. Если первое число - 1.0f, а следующие два - 0.0f, мы получим ярко красный свет. Если третье число - 1.0f, и первые два - 0.0f, мы будем иметь ярко синий свет. Последнее число - альфа значение. Мы оставим его пока 1.0f.

Поэтому в строке ниже, мы зададим значение белого фонового света половиной интенсивности (0.5f). Поскольку все числа - 0.5f, мы получим свет средней яркости между черным (выключен свет) и белым (полная яркость). Смешанные в равных значениях красный, синий и зеленый дадут оттенки от черного (0.0f) до белого (1.0f). Без фонового света пятна, где нет диффузного света, будут очень темными.

 

GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f }; // Значения фонового света ( НОВОЕ )

 

В следующей строке мы зададим значение для супер-яркой, полной интенсивности диффузного света. Все значения - 1.0f. Это означает, что свет настолько яркий, насколько мы можем получить его. Диффузный свет эти яркое пятно спереди ящика.

 

GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f }; // Значения диффузного света ( НОВОЕ )

 

Наконец мы зададим позицию света. Первые три числа совпадают с тремя первыми аргументами функции glTranslate. Первое число смещение влево или вправо по плоскости x, второе число - для перемещения вверх или вниз по плоскости y, и третье число для перемещения к или от экрана по плоскости z. Поскольку мы хотим чтобы наш свет, падал прямо на переднею часть ящика, мы не сдвигаемся влево или вправо, поэтому первое значение - 0.0f (нет движения по x), мы не хотим двигаться вверх или вниз, поэтому второе значение - 0.0f. Третье значение мы зададим так, чтобы свет был всегда перед ящиком. Поэтому мы поместим свет вне экрана по отношению к наблюдателю. Давайте примем, что стекло на вашем мониторе - 0.0f по плоскости z. Мы позиционируем свет в 2.0f по плоскости z. Если бы Вы могли бы фактически видеть свет, он бы плавал перед стеклом вашего монитора. Делая, таким образом, единственный способ, когда бы свет оказался позади ящика, был бы тот, если бы ящик был также перед стеклом вашего монитора. Конечно, если бы ящик был уже не позади стекла вашего монитора, Вы больше не видели ящик, поэтому тогда не имеет значения, где свет. Это имеет смысл?


Нет никакого простого реального способа разъяснить третий параметр. Вы должны понять, что -2.0f ближе к Вам чем -5.0f и что -100.0f ДАЛЕКО в экране. Как только Вы берете 0.0f, изображение становится настолько большим, это заполняет весь монитор. Как только Вы устанавливаете положительные значения, изображение больше не появляется на экране по той причине, что объект "пропал с экрана". Вот это я подразумеваю, когда я говорю вне экрана. Объект - все еще есть, но Вы уже не можете больше его видеть.


Оставьте последнее число в 1.0f. Это говорит OpenGL о том, что данные координаты - позиция источника света. Более подробно об этом я расскажу в одном из следующих уроков.

 

GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };     // Позиция света ( НОВОЕ )

 

Переменная filter должна отслеживать, каким образом будут отображаться текстуры. Первая текстура (texture[0]) использует gl_nearest (без сглаживания). Вторая текстура (texture[1]) использует фильтрацию gl_linear, которая немного сглаживает изображение. Третья текстура (texture[2]) использует текстуры с мип-наложением (mipmapped, или множественное наложение), что повышает качество отображения. Переменная filter будет равняться 0, 1 или 2 в зависимости от текстуры, которую мы хотим использовать. Мы начинаем с первой текстуры.


Объявление GLuint texture[3] создает место для хранения трех разных текстур. Текстуры будут сохранены в texture[0], texture[1] и texture[2].

 

GLuint filter;         // Какой фильтр использовать

GLuint texture[3];     // Место для хранения 3 текстур

 

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    // Декларация WndProc

 

Сейчас мы загрузим картинку (bitmap, или растровый (побитный) образ изображения), и создадим три различных текстуры из нее. В этом уроке используется библиотека glaux для загрузки картинки, поэтому проверьте, что Вы подключили библиотеку glaux прежде, чем Вы пробуете скомпилировать код. Я знаю, что Delphi, и Visual C++ имеют библиотеку glaux. Я не уверен, что она есть в других языках. Я собираюсь объяснить только новые строки кода, если Вы видите не прокомментированную строку, и Вы не понимаете, что она делает, посмотрите шестой урок. Там объясняется загрузка, и формирования образов текстуры из картинок очень подробно.


Сразу же после кода выше, и до ReSizeGLScene (), мы добавим следующую секцию кода. Это - тот же самый код, который мы использовали в уроке 6 для загрузки картинки. Ничего не изменилось. Если Вы не понимаете их, прочитайте шестой урок. Там этот код объяснен подробнее.

 

AUX_RGBImageRec *LoadBMP(char *Filename)     // Загрузка картинки

{

 FILE *File=NULL;          // Индекс файла

 

 if (!Filename)            // Проверка имени файла

 {

  return NULL;             // Если нет вернем NULL

 }

 

 File=fopen(Filename,"r"); // Проверим существует ли файл

 

 if (File)                 // Файл существует?

 {

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

  return auxDIBImageLoad(Filename); // Загрузка картинки и вернем на нее указатель

 }

 return NULL;              // Если загрузка не удалась вернем NULL

}

 

В этой секции кода загружается картинка (вызов кода выше) и производится конвертирование ее в 3 текстуры. Переменная Status используется, чтобы следить, действительно ли текстура была загружена и создана.

 

int LoadGLTextures()                      // Загрузка картинки и конвертирование в текстуру

{

 int Status=FALSE;                        // Индикатор состояния

 

 AUX_RGBImageRec *TextureImage[1];        // Создать место для текстуры

 

 memset(TextureImage,0,sizeof(void *)*1); // Установить указатель в NULL

 

Теперь мы загружаем картинку и конвертируем ее в текстуру. Выражение TextureImage[0]=LoadBMP ("Data/Crate.bmp ") будет вызывать наш код LoadBMP(). Файл по имени Crate.bmp в каталоге Data будет загружен. Если все пройдет хорошо, данные изображения сохранены в TextureImage[0], Переменная Status установлена в TRUE, и мы начинаем строить нашу текстуру.

 

 // Загрузка картинки, проверка на ошибки, если картинка не найдена - выход

 if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))

 {

  Status=TRUE;       // Установим Status в TRUE

 

Теперь, мы загрузили данные изображения в TextureImage [0], мы будем использовать эти данные для построения 3 текстур. Строка ниже сообщает OpenGL, что мы хотим построить три текстуры, и мы хотим, чтобы текстура была сохранена в texture[0], texture[1] и texture[2].

 

glGenTextures(3, &texture[0]);     // Создание трех текстур

 

В шестом уроке, мы использовали линейную фильтрацию образов текстур. Это способ фильтрации требует много мощности процессора, но текстуры при этом выглядят реалистичными. Первый тип текстуры, которую мы собираемся создать в этом уроке, использует GL_NEAREST. Этот тип текстуры не использует фильтрацию. Требуется очень мало мощности процессора, и качество плохое. Если вы когда-нибудь видели игру, где текстуры как будто состоят из блоков, они, вероятно, используют этот тип текстуры. Единственное применение этого типа текстуры для проектов, которые будут запускаться на медленных компьютерах.


Заметьте, что мы используем GL_NEAREST, и для MIN и для MAG. Вы можете смешивать использование GL_NEAREST с GL_LINEAR, и текстура будет смотреться немного лучше, но мы заинтересованы в быстродействии, поэтому мы будем использовать везде низкое качество. Фильтр MIN_FILTER используется, когда изображение рисуемого полигона меньше, чем первоначальный размер текстуры. Фильтр MAG_FILTER используется, когда изображение рисуемого полигона больше, чем первоначальный размер текстуры.

 

  // Создание текстуры с фильтром по соседним пикселям

  glBindTexture(GL_TEXTURE_2D, texture[0]);

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); // ( НОВОЕ )

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // ( НОВОЕ )

  glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY,

   0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

 

Следующая текстура, которую мы построим, имеет тот же самый тип текстуры, которую мы использовали в уроке шесть. Линейный режим фильтрации. Изменилось только то, что мы сохраняем эту текстуру в texture[1] вместо texture[0], потому что здесь она вторая текстура. Если бы мы сохранили ее в texture[0] как ранее, она затерла бы текстуру GL_NEAREST (первая текстура).

 

  // Создание текстуры с линейной фильтрацией

  glBindTexture(GL_TEXTURE_2D, texture[1]);

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

  glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY,

   0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

 

Теперь новый способ создания текстуры. Мип-наложение! Вы, возможно, замечали, что, когда изображение на экране очень маленькое, пропадает множество мелких деталей. Орнаменты, которые выглядят вначале отлично, смотрятся отвратительно. Когда Вы говорите OpenGL построить текстуру с мип-наложением, OpenGL будет строить разного размера высококачественные текстуры. Когда Вы выводите текстуру с мип-наложением на экран, OpenGL будет выбирать НАИБОЛЕЕ ЛУЧШУЮ для отображения текстуру из построенных текстур (текстура с таким размером, как размер полигона на экране, т.е. наиболее детальная) и нарисует ее на экран вместо масштабирования первоначального изображения (что и вызывает потерю качества).

 

Я рассказал в уроке шесть про ограничение на размер текстур в OpenGL по ширине и высоте текстуры в 64,128,256, и т.д. Для gluBuild2DMipmaps Вы можете использовать картинки любых размеров при формировании текстуры с мип-наложением. OpenGL будет автоматически изменять размер ее задавая правильные ширину и высоту.


Поскольку это - текстура номер три, мы собираемся хранить эту текстуру в texture[2]. Поэтому, теперь мы имеем texture[0], которая не имеет никакой фильтрации, texture[1], которая использует линейную фильтрацию, и texture[2], которая использует текстуру с мин-наложением. Мы закончили построение текстур в этом уроке.

 

  // Создание Текстуры с Мип-Наложением

  glBindTexture(GL_TEXTURE_2D, texture[2]);

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST); // ( НОВОЕ )

 

Следующая строка строит текстуру с мип-наложением. Мы создаем 2D текстуру, используя три цвета (красный, зеленый, синий) (red, green, blue).  TextureImage[0]->sizeX - ширина картинки, TextureImage[0]->sizeY - высота картинки, GL_RGB означает, что мы используем цвета в порядке Красный, Зеленый, Синий (Red, Green, Blue). GL_UNSIGNED_BYTE означает что данные, из которых состоит текстура из байтов, и TextureImage[0]->data указатель на растр картинки, из которого мы строим текстуру.

 

  gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY,

   GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); // ( НОВОЕ )

 }

 

Теперь мы можем освободить память, которую мы использовали для картинок. Мы проверяем, была ли картинка сохранена в TextureImage[0]. Если да, то мы проверяем, были ли данные сохранены. Если данные были сохранены, мы удаляем их. Тогда мы освобождаем структуру изображения, уверенные, что любая используемая память освобождена.

 

 if (TextureImage[0])           // Если текстура существует

 {

  if (TextureImage[0]->data)    // Если изображение текстуры существует

  {

   free(TextureImage[0]->data); // Освобождение памяти изображения текстуры

  }

 

  free(TextureImage[0]);        // Освобождение памяти под структуру

 }

 

Наконец мы возвращаем статус. Если все прошло OK, переменная Status будет TRUE. Если что-нибудь прошло не так, как надо, Status будет FALSE.

 

 return Status;        // Возвращаем статус

}

 

Теперь мы загружаем текстуры, и инициализируем параметры настройки OpenGL. В первой строке InitGL загружаются текстуры, используя код выше. После того, как текстуры созданы, мы разрешаем 2D наложение текстуры с помощью glEnable (GL_TEXTURE_2D). Режим закрашивания (shade) задается как сглаженное закрашивание. Цвет фона задан как черный, мы разрешаем тест глубины, затем мы разрешаем хорошие перспективные вычисления.

 

int InitGL(GLvoid)             // Все настройки для OpenGL делаются здесь

{

 if (!LoadGLTextures())        // Переход на процедуру загрузки текстуры

 {

  return FALSE;                // Если текстура не загружена возвращаем FALSE

 }

 

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

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

 glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Черный фон

 glClearDepth(1.0f);           // Установка буфера глубины

 glEnable(GL_DEPTH_TEST);      // Разрешить тест глубины

 glDepthFunc(GL_LEQUAL);       // Тип теста глубины

 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Улучшенные вычисления перспективы

 

Теперь мы задаем освещение. Строка ниже задает интенсивность фонового света, которое light1 будет давать. В начале этого урока мы задали интенсивность фонового света в LightAmbient. Значения, которые мы поместили в массив, будут использованы (фоновый свет половиной интенсивности).

 

glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);    // Установка Фонового Света

 

Затем мы задаем интенсивность диффузного света, который источник света номер один будет давать. Мы задали интенсивность диффузного света в LightDiffuse. Значения, которые мы поместили в этот массив, будут использованы (белый свет полной интенсивности).

 

glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);    // Установка Диффузного Света

 

Теперь мы задаем позицию источника света. Мы поместили позицию в LightPosition. Значения, которые мы поместили в этот массив, будут использованы (справо в центре передней грани, 0.0f по x, 0.0f по y, и 2 единицы вперед к наблюдателю {выходит за экран} по плоскости z).

 

 glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);   // Позиция света

 

Наконец, мы разрешаем источник света номер один. Мы не разрешили GL_LIGHTING, поэтому Вы еще не увидите никакого освещения. Свет установлен, и позиционирован, и даже разрешен, но пока мы не разрешили GL_LIGHTING, освещение не будет работать.

 

 glEnable(GL_LIGHT1); // Разрешение источника света номер один

 return TRUE;         // Инициализация прошла OK

}

 

В следующей секции кода, мы нарисуем текстуру, наложенную на куб. Я буду комментировать только несколько строк, потому что они новые. Если Вы не понимаете не прокомментированные строки, вернитесь к уроку номер шесть.

 

int DrawGLScene(GLvoid)        // Здесь мы делаем все рисование

{

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

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

 

Следующих трех строках кода куб с наложенной текстурой позиционируется и вращается. Вызов glTranslatef (0.0f, 0.0f, z) перемещает куб на значение z по плоскости z (от наблюдателя или к наблюдателю). Вызов glRotatef (xrot, 1.0f, 0.0f, 0.0f) использует переменную xrot, чтобы вращать куб по оси X. Вызов glRotatef (yrot, 1.0f, 0.0f, 0.0f) использует переменную yrot, чтобы вращать куб по оси Y.

 

 glTranslatef(0.0f,0.0f,z);      // Перенос В/Вне экрана по z

 glRotatef(xrot,1.0f,0.0f,0.0f); // Вращение по оси X на xrot

 glRotatef(yrot,0.0f,1.0f,0.0f); // Вращение по оси Y по yrot

 

Следующая строка подобна строке, которую мы использовали в уроке шесть, но вместо связывания к texture[0], мы привязываем texture[filter]. Если мы нажимаем клавишу 'F', значение в filter увеличится. Если значение больше чем два, переменная filter сбрасывается в ноль. Когда программа запускается, filter будет установлена в ноль. Это все равно, что glBindTexture (GL_TEXTURE_2D, texture[0]). Если мы нажимаем "F" еще раз, переменная filter будет равна единице, что фактически glBindTexture (GL_TEXTURE_2D, texture[1]). Используя, переменную filter мы можем выбирать любую из трех текстур, которые мы создали.

 

 glBindTexture(GL_TEXTURE_2D, texture[filter]);    // Выбор текстуры основываясь на filter

 

 glBegin(GL_QUADS);       // Начало рисования четырехугольников

 

glNormal3f новая функция в моих уроках. Нормаль - линия, берущая начало из середины полигона под 90 углом градусов. Когда Вы используете освещение, Вы должны задать нормали. Нормаль сообщает OpenGL, в каком направлении у полигона лицевая часть, какое направление верхнее. Если Вы не задали нормали, могут происходить сверхъестественные вещи. Грань, которая не освещена, будет освещена, неверная сторона полигона будет освещена, и т.д. Нормаль должна указывать вовне полигона.

 
Посмотрев на переднюю грань, Вы заметите, что нормаль имеет положительное направление по оси Z. Это означает, что нормаль указывает на наблюдателя. Это точно, то направление, которое мы хотим указать. На обратной грани, нормаль указывает от наблюдателя вглубь экрана. Снова это точно то, что мы хотим. Если куб повернут на 180 градусов или по оси X или по оси Y, передняя грань ящика будет лицом вглубь экрана, и задняя грань ящика будет лицом к наблюдателю. Независимо от того, какую грань видит наблюдатель, нормаль этой грани будет также направлена на наблюдателя. Поскольку свет - близко к наблюдателю всегда нормаль, указывающая на наблюдателя, также указывает на свет. Если это сделать, то грань будет освещена. Чем больше нормалей указывает на свет, тем более яркая будет грань. Если Вы переместились в центр куба, Вы заметите, что там темно. Нормали – указывают вовне, а не во внутрь, поэтому нет освещения внутри куба.

 

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

  glNormal3f( 0.0f, 0.0f, 1.0f);     // Нормаль указывает на наблюдателя

  glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f); // Точка 1 (Перед)

  glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f); // Точка 2 (Перед)

  glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f); // Точка 3 (Перед)

  glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f); // Точка 4 (Перед)

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

  glNormal3f( 0.0f, 0.0f,-1.0f);     // Нормаль указывает от наблюдателя

  glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Точка 1 (Зад)

  glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f); // Точка 2 (Зад)

  glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f); // Точка 3 (Зад)

  glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Точка 4 (Зад)

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

  glNormal3f( 0.0f, 1.0f, 0.0f);     // Нормаль указывает вверх

  glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f); // Точка 1 (Верх)

  glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f); // Точка 2 (Верх)

  glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f); // Точка 3 (Верх)

  glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f); // Точка 4 (Верх)

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

  glNormal3f( 0.0f,-1.0f, 0.0f);     // Нормаль указывает вниз

  glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Точка 1 (Низ)

  glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Точка 2 (Низ)

  glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f); // Точка 3 (Низ)

  glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f); // Точка 4 (Низ)

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

  glNormal3f( 1.0f, 0.0f, 0.0f);     // Нормаль указывает вправо

  glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Точка 1 (Право)

  glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f); // Точка 2 (Право)

  glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f); // Точка 3 (Право)

  glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f); // Точка 4 (Право)

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

  glNormal3f(-1.0f, 0.0f, 0.0f);     // Нормаль указывает влево

  glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Точка 1 (Лево)

  glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f); // Точка 2 (Лево)

  glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f); // Точка 3 (Лево)

  glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f); // Точка 4 (Лево)

 glEnd();        // Кончили рисовать четырехугольник

 

В следующих двух строках увеличиваются значения xrot и yrot на значения, сохраненные в xspeed, и yspeed. Если значение в xspeed или yspeed большое, xrot и yrot увеличивается быстро. Чем быстрее увеличение xrot, или yrot, тем быстрее куб вращается по соответствующей оси.

 

 xrot+=xspeed;        // Добавить в xspeed значение xrot

 yrot+=yspeed;        // Добавить в yspeed значение yrot

 return TRUE;         // Выйти

}

 

Теперь мы спускаемся до WinMain (). Здесь добавим код для включения или выключения освещения, вращения корзины, смены фильтра и перемещения ящика ближе или дальше от экрана. Ближе к концу WinMain () Вы увидите вызов SwapBuffers (hDC). Сразу после этой строки, добавьте следующий код.


Этот код отслеживает нажатие клавиши 'L'. В первой строке проверяется, нажата ли 'L'. Если 'L' нажата, но lp - не ложь, что значит клавиша 'L' уже была нажата, или она удерживается нажатой, то тогда ничего не происходит.

 

    SwapBuffers(hDC);     // Переключение буферов (Двойная буферизация)

    if (keys['L'] && !lp) // Клавиша 'L' нажата и не удерживается?

    {

 

Если lp – ложь то, это означает, что клавиша 'L' не нажата, иначе, если она уже отпущена, lp - истина. Это гарантирует, что клавишу 'L' отпустят прежде, чем этот код выполнится снова. Если мы не будет проверять удержание клавиши, освещение будет мерцать, постоянно включаясь и выключаясь много раз, поскольку программа думала бы, что Вы нажимает клавишу 'L' снова и снова, при этом вызывая этот раздел кода.


Переменная lp будучи равной истине, сообщает, что 'L' отпущена, мы включаем или выключаем освещение. Переменная light может только быть истина или ложь. Поэтому, если мы говорим light=!light, то мы фактически говорим, что свет, не равен свету. По-русски это будет значит, что, если light равена истине, то light не будет истина (ложь), и если light равена ложь, то light не будет ложь (истина). Поэтому, если переменная light была истина, она станет ложь, и если light была ложь, она станет истина.

 

     lp=TRUE;      // lp присвоили TRUE

     light=!light; // Переключение света TRUE/FALSE

 

Теперь мы проверим, какое значение light получилось в конце. Первая строка, переведенная на русский означает: если light равняется ложь. Поэтому, если Вы совместите все строки вместе, то они делают следующее: если light равняется ложь, то надо запретить освещение. Это выключает все освещение. Команда 'else' означает: если light не ложь. Поэтому, если light не была ложь то, она истинна, поэтому мы включаем освещение.

 

     if (!light)               // Если не свет

     {

      glDisable(GL_LIGHTING);  // Запрет освещения

     }

     else                      // В противном случае

     {

      glEnable(GL_LIGHTING);   // Разрешить освещение

     }

    }

 

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

 

    if (!keys['L']) // Клавиша 'L' Отжата?

    {

     lp=FALSE;      // Если так, то lp равно FALSE

    }

 

Теперь мы сделаем что-то подобное с клавишей 'F'. Если клавиша нажата, и она не удерживается, или она не была нажата до этого, тогда присвоим значение переменной fp равное истине, что значит клавиша 'F' нажата и удерживается. При этом увеличится значение переменной filter. Если filter больше чем 2 (т.е. texture[3], а такой текстуры не существует), мы сбрасываем значение переменной texture назад в ноль.

 

    if (keys['F'] && !fp) // Клавиша 'F' нажата?

    {

     fp=TRUE;             // fp равно TRUE

     filter+=1;           // значение filter увеличивается на один

     if (filter>2)        // Значение больше чем 2?

     {

      filter=0;           // Если так, то установим filter в 0

     }

    }

    if (!keys['F'])       // Клавиша 'F' отжата?

    {

     fp=FALSE;            // Если так, то fp равно FALSE

    }

 

В следующих четырех строках проверяем, нажали ли мы клавишу 'Page up'. Если так, то уменьшим значение переменной z. Если эта переменная уменьшается, куб будет двигаться вглубь экрана, поскольку мы используем glTranslatef (0.0f, 0.0f, z) в процедуре DrawGLScene.

 

    if (keys[VK_PRIOR])   // Клавиша 'Page Up' нажата?

    {

     z-=0.02f;            // Если так, то сдвинем вглубь экрана

    }

 

В этих четырех строках проверяется, нажали ли мы клавишу 'Page down'. Если так, то увеличим значение переменной z и сместим куб к наблюдателю, поскольку мы используем glTranslatef (0.0f, 0.0f, z) в процедуре DrawGLScene.

 

    if (keys[VK_NEXT])    // Клавиша 'Page Down' нажата?

    {

     z+=0.02f;            // Если так, то придвинем к наблюдателю

    }

 

Теперь все, что мы должны проверить - клавиши курсора. Нажимая влево или вправо, xspeed увеличивается или уменьшается. Нажимая вверх или вниз, yspeed увеличивается или уменьшается. Помните выше в этом уроке, я говорил, что, если значения xspeed или yspeed большие, куб вращается быстрее. Чем дольше Вы удерживаете одну из клавиш курсора, тем, быстрее куб будет вращаться в соответствующем направлении.

 

    if (keys[VK_UP])     // Клавиша стрелка вверх нажата?

    {

     xspeed-=0.01f;      // Если так, то уменьшим xspeed

    }

    if (keys[VK_DOWN])   // Клавиша стрелка вниз нажата?

    {

     xspeed+=0.01f;      // Если так, то увеличим xspeed

    }

    if (keys[VK_RIGHT])  // Клавиша стрелка вправо нажата?

    {

     yspeed+=0.01f;      // Если так, то увеличим yspeed

    }

    if (keys[VK_LEFT])   // Клавиша стрелка влево нажата?

    {

     yspeed-=0.01f;      // Если так, то уменьшим yspeed

    }

 

Как и во всех предыдущих уроках, удостоверитесь, что заголовок наверху окна правильный.

 

    if (keys[VK_F1])          // Клавиша 'F1' нажата?

    {

     keys[VK_F1]=FALSE;       // Если так, то сделаем Key FALSE

     KillGLWindow();          // Уничтожим наше текущее окно

     fullscreen=!fullscreen;  // Переключение между режимами Полноэкранный/Оконный

                              // Повторное создание нашего окна OpenGL

     if (!CreateGLWindow("Урок NeHe Текстуры, Свет & Обработка Клавиатуры",640,480,16,fullscreen))

     {

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

     }

    }

   }

  }

 }

 

 // Сброс

 KillGLWindow();              // Уничтожение окна

 return (msg.wParam);         // Выход из программы

}

 

Здесь появляется новая функция KillGLWindow, которая занимается тем, что корректно уничтожает окно и все что с ним связано. Раньше тоже самое мы делали при обработке сообщений WM_DESTROY и WM_CLOSE. Вот код KillGLWindow, который нужно разместить сразу за функцией InitGL.

 

GLvoid KillGLWindow(GLvoid)               // Правильное уничтожение окна

{

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

    {

        ChangeDisplaySettings(NULL,0);    // Переход в режим разрешения рабочего стола

        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

    }

}

 

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

© Jeff Molofee (NeHe)

PMG  11 ноября 2004 (c)  Сергей Анисимов