Урок 15. Текстурные шрифты

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

Небольшое примечание, этот код применим только в Windows. Здесь используются функции wgl Windows для построения шрифта. Очевидно, Apple имеет поддержку agl, которая должна делать тоже самое, и X имеет glx. К сожалению, я не могу гарантировать, что этот код переносим. Если кто-нибудь имеет платформо-незавизимый код для вывода шрифтов на экран, пришлите мне его, и я напишу другой урок по шрифтам.

Мы будем использовать код от урока 14 для нашей демонстрационной программы текстурных шрифтов. Если код изменился в каком-либо разделе программы, я перепишу весь раздел кода, чтобы было проще видеть изменения, которые я сделал.

Следующий раздел кода такой же как в уроке 14, но на этот раз мы не включим в него stdarg.h.

 

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

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

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

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

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

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

 

HDC hDC=NULL; // Приватный контекст устройства GDI

HGLRC hRC=NULL; // Постоянный контекст рендеринга

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

HINSTANCE hInstance; // Сохраняет экземпляр приложения

 

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

bool active=TRUE; // Флаг активации окна, по умолчанию = TRUE

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

  Мы собираемся добавить одну новую переменную целого типа, которая называется texture[]. Она будет использоваться для хранения нашей текстуры. Последние три строки такие же, как в уроке 14 и не изменились и здесь.

 

GLuint texture[1]; // Одна текстура ( НОВОЕ )

GLuint base; // База списка отображения для фонта

 

GLfloat rot; // Используется для вращения текста

 

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

  Следующий раздел кода претерпел незначительные изменения. В этом уроке я собираюсь использовать wingdings шрифт, для того чтобы отобразить объект в виде черепа и двух скрещенных костей под ним (эмблема смерти). Если Вы захотите вывести текст вместо этого, Вы можете оставить код таким же, как это было в уроке 14, или замените шрифт на ваш собственный.

Может быть кто-то уже задавался вопросом, как использовать wingdings шрифт. Это тоже является причиной, по которой я не использую стандартный шрифт. wingdings — СПЕЦИАЛЬНЫЙ шрифт, и требует некоторых модификаций, чтобы заставить программу работать с ним. При этом надо не просто сообщить Windows, чтобы Вы будете использовать wingdings шрифт. Если Вы изменяете, название шрифта на wingdings, Вы увидите, что шрифт не будет выбран. Вы должны сообщить Windows, что шрифт является специальным шрифтом, и не стандартным символьным шрифтом. Но об этом позже.

 

GLvoid BuildFont(GLvoid) // Построение шрифта

{

GLYPHMETRICSFLOAT gmf[256]; // Адрес буфера для хранения шрифта

HFONT font; // ID шрифта в Windows

 

base = glGenLists(256); // Храним 256 символов

font = CreateFont( -12, // Высота фонта

0, // Ширина фонта

0, // Угол отношения

0, // Угол наклона

FW_BOLD, // Ширина шрифта

FALSE, // Курсив

FALSE, // Подчеркивание

FALSE, // Перечеркивание

  Вот она волшебная строка! Вместо того чтобы использовать ANSI_CHARSET, как мы делали в уроке 14, мы используем SYMBOL_CHARSET. Это говорит Windows, что шрифт, который мы строим - не обычный шрифт, составленный из букв. Специальный шрифт обычно составлен из крошечных картинок (символов). Если Вы забудете изменить эту строку, wingdings, webdings и любой другой специальный шрифт, который Вы можете пробовать использовать, не будет работать.

 

SYMBOL_CHARSET, // Идентификатор набора символов ( Модифицировано )

  Следующие строки не изменились.

 

OUT_TT_PRECIS, // Точность вывода

CLIP_DEFAULT_PRECIS, // Точность отсечения

ANTIALIASED_QUALITY, // Качество вывода

FF_DONTCARE|DEFAULT_PITCH, // Семейство и шаг

  Теперь, когда мы выбрали идентификатор набора символов, мы можем выбирать wingdings шрифт!

 

"Wingdings"); // Имя шрифта ( Модифицировано )

 

SelectObject(hDC, font); // Выбрать шрифт, созданный нами

 

wglUseFontOutlines( hDC, // Выбрать текущий контекст устройства (DC)

0, // Стартовый символ

255, // Количество создаваемых списков отображения

base, // Стартовое значение списка отображения

  Я устанавливаю большой уровень отклонения. При этом GL не будет точно отслеживать контур шрифта. Если Вы зададите отклонение равным 0.0f, Вы заметите проблемы с текстурированием на очень изогнутых поверхностях. Если Вы допустите некоторое отклонение, большинство проблем исчезнет.

 

0.1f, // Отклонение от истинного контура

  Следующие три строки кода те же самые.

 

0.2f, // Толщина шрифта по оси Z

WGL_FONT_POLYGONS, // Использовать полигоны, а не линии

gmf); // Буфер адреса для данных списка отображения

}

  Перед ReSizeGLScene() мы собираемся добавить следующий раздел кода для загрузки нашей текстуры. Вы знаете этот код по прошлым урокам. Мы создаем память для хранения растрового изображения. Мы загружаем растровое изображение. Мы говорим OpenGL, сгенерировать 1 текстуру, и мы сохраняем эту текстуру в texture[0].

Я создаю мип-мап текстуру, так как она смотрится лучше. Имя текстуры - lights.bmp.

 

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

}

 

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

{

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

 

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

 

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

 

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

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

{

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

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

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

glBindTexture(GL_TEXTURE_2D, texture[0]);

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

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

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

}

  В следующих четырех строках кода автоматически генерируются текстурные координаты для любого объекта, который мы выводим на экран. Команда glTexGen чрезвычайно мощная и комплексная, и включает достаточно сложную математику. Но Вы должны только знать то, что GL_S и GL_T - координаты текстуры. По умолчанию они заданы так, чтобы брать текущее x положение на экране и текущее y положение на экране и из них вычислить вершину текстуру. Вы можете отметить, что объекты не текстурированны по z плоскости… только появляются полосы. Передние и задние грани текстурированны, однако, именно это и необходимо. X (GL_S) охватывает наложение текстуры слева направо, а Y (GL_T) охватывает наложение текстуры сверху и вниз.

GL_TEXTURE_GEN_MODE позволяет нам выбрать режим наложения текстуры, который мы хотим использовать по координатам текстуры S и T. Есть три возможности:

GL_EYE_LINEAR - текстура зафиксирована на экране. Она никогда не перемещается. Объект накладывается на любую часть текстуры, которую он захватывает.

 

GL_OBJECT_LINEAR — мы воспользуемся этим режимом. Текстура привязана к объекту, перемещающемуся по экрану.

GL_SPHERE_MAP — всегда в фаворе. Создает металлический отражающий тип объекта.

Важно обратить внимание на то, что я опускаю много кода. Мы также должны задать GL_OBJECT_PLANE, но значение по умолчанию то, которое мы хотим. Купите хорошую книгу, если Вы хотите изучить больше, или поищите в помощи MSDN на CD.

 

// Текстуризация контура закрепленного за объектом

glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);

// Текстуризация контура закрепленного за объектом

glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);

glEnable(GL_TEXTURE_GEN_S); // Автоматическая генерация

glEnable(GL_TEXTURE_GEN_T); // Автоматическая генерация

}

 

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

{

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

{

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

}

 

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

}

 

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

}

  Есть несколько новых строк кода в конце InitGL(). Вызов BuildFont() был помещен ниже кода, загружающего нашу текстуру. Строка с glEnable(GL_COLOR_MATERIAL) была удалена. Если Вы хотите задать текстуре цвет, используйте glColor3f(r, г, b) и добавьте строку glEnable(GL_COLOR_MATERIAL) в конце этой секции кода.

 

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

{

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

{

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

}

BuildFont(); // Построить шрифт

 

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

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

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

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

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

glEnable(GL_LIGHT0); // Быстрое простое освещение

// (устанавливает в качестве источника освещения Light0)

glEnable(GL_LIGHTING); // Включает освещение

// Действительно хорошие вычисления перспективы

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

  Разрешение наложения 2D текстуры, и выбор текстуры номер один. При этом будет отображена текстура номер один на любой 3D объект, который мы выводим на экран. Если Вы хотите большего контроля, Вы можете разрешать и запрещать наложение текстуры самостоятельно.

 

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

glBindTexture(GL_TEXTURE_2D, texture[0]); // Выбор текстуры

return TRUE; // Инициализация окончена успешно

}

  Код изменения размера не изменился, но код DrawGLScene изменился.

 

int DrawGLScene(GLvoid) // Здесь мы будем рисовать все

{

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

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

  Здесь наше первое изменение. Вместо того чтобы поместить объект в середину экрана, мы собираемся вращать его на экране, используя COS и SIN (это не сюрприз). Мы перемещаемся на 3 единицы в экран (-3.0f). По оси X, мы будем раскачиваться от -1.1 слева до +1.1 вправо. Мы будем использовать переменную rot для управления раскачиванием слева направо. Мы будем раскачивать от +0.8 верх до -0.8 вниз. Мы будем использовать переменную rot для этого раскачивания также (можно также задействовать и другие переменные).

 

// Позиция текста

glTranslatef(1.1f*float(cos(rot/16.0f)),0.8f*float(sin(rot/20.0f)),-3.0f);

  Теперь сделаем вращения. Символ будет вращаться по осям X, Y и Z.

 

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

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

glRotatef(rot*1.4f,0.0f,0.0f,1.0f); // Вращение по оси Z

  Мы смещаем символ по каждой оси немного налево, вниз, и вперед, чтобы центрировать его. Иначе, когда он вращается, он будет вращаться не вокруг собственного центра. (-0.35f, -0.35f, 0.1f) те числа, которые подходят. Я потратил некоторое время, прежде чем подобрал их, и они могут изменяться в зависимости от шрифта. Почему шрифты построены не вокруг центральной точки, я не знаю.

 

glTranslatef(-0.35f,-0.35f,0.1f); // Центр по осям X, Y, Z

  Наконец мы выводим наш эмблемы смерти, затем увеличиваем переменную rot, поэтому наш символ вращается и перемещается по экрану. Если Вы не можете понять, почему я получаю череп из символа 'N', сделайте так: запустите Microsoft Word или Wordpad. Вызовите ниспадающие меню шрифтов. Выберите wingdings шрифт. Наберите в верхнем регистре 'N'. Появиться эмблема смерти.

 

glPrint("N"); // Нарисуем символ эмблемы смерти

rot+=0.1f; // Увеличим переменную вращения

return TRUE; // Покидаем эту процедуру

}

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

 

if (!UnregisterClass("OpenGL",hInstance)) // Если класс не зарегистрирован

{

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

hInstance=NULL; // Установить копию приложения в ноль

}

 

KillFont(); // Уничтожить шрифт

}

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

© Jeff Molofee (NeHe)

 30 июля 2002 (c)  Сергей Анисимов