Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2633

.pdf
Скачиваний:
6
Добавлен:
07.01.2021
Размер:
54.26 Mб
Скачать

должны быть положительными. Чем больше отношение zfar/znear, тем хуже в буфере глубины будут различаться расположенные рядом поверхности, так как по умолчанию в него будет записываться «сжатая» глубина в диапазоне от 0 до 1.

После применения матрицы проекций на вход следующего преобразования подаются так называемые усеченные (clip) координаты, для которых значения всех компонент (xc, yc, zc, wc)T находятся в отрезке [-1,1].

Область вывода представляет собой прямоугольник в оконной системе координат, размеры которого задаются командой:

void glViewPort(GLint x, GLint y, GLint width, GLint height).

Значения всех параметров задаются в пикселях и определяют ширину и высоту области вывода с координатами левого нижнего угла (x,y) в оконной системе координат. Размеры оконной системы координат определяются текущими размерами окна приложения, точка (0,0)находится в левом нижнем углу окна.

Используя параметры команды glViewPort(), вычисляются оконные координаты центра области вывода (ox, oy).

При этом целые положительные величины n и f задают минимальную и максимальную глубину точки в окне и по умолчанию равны 0 и 1 соответственно. Глубина каждой точки записывается в специальный буфер глубины (z-буфер), который используется для удаления невидимых линий и поверхностей. Установить значения n и f

можно вызовом функции: void glDepthRange(GLclampd n, GLclampdf)

Команда glViewPort() обычно используется в функции, зарегистрированной с помощью команды glutReshapeFunc(), которая вызывается, если пользователь изменяет размеры окна приложения, изменяя соответсвующим образом область вывода.

1.5. Текстуры

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

Текстура – некоторое изображение, которое надо определенным образом нанести на объект. Для этого следует выполнить следующие этапы:

­выбрать изображение и преобразовать его к нужному

формату;

­загрузить изображение в память;

23

­ определить, как текстура будет наноситься на объект и как она будет с ним взаимодействовать.

Далее рассмотрим каждый из этих этапов.

Подготовка текстуры. Принятый в OpenGL формат хранения изображений отличается от стандартного формата Windows DIB только тем, что компоненты (R,G,B) для каждой точки хранятся в прямом порядке, а не в обратном и выравнивание задается программистом. Считывание графических данных из файла и их преобразование можно проводить и вручную, однако удобней воспользоваться функцией, входящей в состав библиотеки GLAUX (для ее использования надо дополнительно подключить glaux.lib), которая сама проводит необходимые операции, где file – название файла с расширением *.bmp или *.dib. В качестве результата функция возвращает указатель на область памяти, где хранятся преобразованные данные:

AUX_RGBImageRec* auxDIBImageLoad(string file).

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

void gluScaleImage(GLenum format, GLint widthin, GL heightin, GLenum typein, const void *datain, GLint widthout, GLint heightout, GLenum typeout, void *dataout)

В качестве значения параметра format обычно используется значение GL_RGB или GL_RGBA, определяющее формат хранения информации. Параметры widthin, heightin, widhtout, heightout

определяют размеры входного и выходного изображений, а с помощью typein и typeout задается тип элементов массивов, расположенных по адресам datain и dataout. Как и обычно, то может быть тип:

GL_UNSIGNED_BYTE, GL_SHORT, GL_INT

Результат своей работы функция заносит в область памяти, на которую указывает параметр dataout.

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

24

для изображения размером 2mx2n можно построить max(m,n)+1 уменьшенных изображений, соответствующих различным уровням детализации.

Эти два этапа создания образа текстуры в памяти можно провести с помощью команды:

void gluBuild2DMipmaps(GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data)

Где параметр target должен быть равен GL_TEXTURE_2D, components определяет количество цветовых компонент текстуры, которые будут использоваться при ее наложении и может принимать значения от 1 до 4 (1-только красный,2-красный и alpha, 3-красный, синий, зеленый, 4-все компоненты).

Параметры width, height, data определяют размеры и расположение текстуры соответственно, а format и type имеют аналогичный смысл, что

ив команде gluScaleImage().

ВOpenGL допускается использование одномерных текстур, то есть размера 1xN, однако это всегда надо указывать, используя в качестве значения target константу GL_TEXTURE_1D. Существует одномерный аналог рассматриваемой командыgluBuild1DMipmaps(), который отличается от двумерного отсутствием параметра height.

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

void glGenTextures(GLsizei n, GLuint*textures)

Перед началом определения свойств очередной текстуры следует вызвать команду, где target может принимать значения

GL_TEXTURE_1D или GL_TEXTURE_2D, а параметр texture должен быть равен идентификатору той текстуры, к которой будут относиться последующие команды:

void glBindTexture(GLenum target, GLuint texture)

Для того, чтобы в процессе рисования сделать текущей текстуру с некоторым идентификатором, достаточно опять вызвать команду glBindTexture() c соответствующим значением target и texture. Таким образом, команда glBindTexture() включает режим создания текстуры с идентификатором texture, если такая текстура еще не создана, либо режим ее использования, то есть делает эту текстуру текущей.

Рассмотрим методы наложения текстуры. При наложении текстуры, как уже упоминалось, надо учитывать случай, когда размеры текстуры отличаются от размеров объекта, на который она

25

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

Для определения положения точки на текстуре используется параметрическая система координат (s,t), причем значения s и t находятся в отрезке [0,1].

Для изменения различных параметров текстуры применяются команды:

void glTexParameter[i f](GLenum target, GLenum pname, GLenum param);

void glTexParameter[i f]v(GLenum target, GLenum pname, GLenum *params).

При этом target имеет аналогичный смысл, что и раньше, pname определяет, какое свойство будем менять, с помощью param или params устанавливается новое значение.

Далее приведены возможные значения pname. GL_TEXTURE_MIN_FILTER параметр param определяет

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

­значение по умолчанию: GL_LINEAR. GL_TEXTURE_MAG_FILTER параметр param определяет функцию,

которая будет использоваться для увеличения (растяжения) текстуры. При значении GL_NEAREST будет использоваться один (ближайший),

апри значении GL_LINEAR четыре ближайших элемента текстуры;

­значение по умолчанию: GL_LINEAR. GL_TEXTURE_WRAP_S параметр param устанавливает значение координаты s, если оно не входит в отрезок [0,1]. При значении GL_REPEAT целая часть s отбрасывается, и в результате изображение размножается по поверхности. При значении GL_CLAMP используются краевые значения: 0 или 1, что удобно использовать, если на объект накладывается один образ;

­значение по умолчанию: GL_REPEAT.

GL_TEXTURE_WRAP_T аналогично предыдущему значению, только для координаты t.

Использование режима GL_NEAREST значительно повышает скорость наложения текстуры, однако при этом снижается качество, так как в отличие от GL_LINEAR интерполяция не производится.

Для того, чтобы определить, как текстура будет взаимодействовать с материалом, из которого сделан объект, используются команды:

void glTexEnv[i f](GLenum target, GLenum pname, GLtype param);

26

void glTexEnv[i f]v(GLenum target, GLenum pname, GLtype *params).

Параметр target должен быть равен GL_TEXTURE_ENV, а в качестве pname рассмотрим только одно значение GL_TEXTURE_ENV_MODE, которое наиболее часто применяется.

Параметр param может быть равен:

­GL_MODULATE конечный цвет находится как произведение цвета точки на поверхности и цвета соответствующей ей точки на текстуре;

­GL_REPLACE в качестве конечного цвета используется цвет точки на текстуре;

­GL_BLEND конечный цвет находится как сумма цвета точки на поверхности и цвета соответствующей ей точки на текстуре с учетом их яркости.

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

Первый метод реализуется с помощью команд: void glTexCoord[1 2 3 4][s i f d](type coord); void glTexCoord[1 2 3 4][s i f d]v(type *coord).

Чаще всего используется команды вида glTexCoord2..(type s, type t), задающие текущие координаты текстуры.

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

void gluQuadricTexture(GLUquadricObj*quadObject, GLboolean textureCoords).

Параметр textureCoords должен быть равен GL_TRUE, и тогда текущая текстура будет автоматически накладываться на примитив.

Второй метод реализуется с помощью команд:

void glTexGen[i f d](GLenum coord, GLenum pname, GLtype param); void glTexGen[i f d]v(GLenum coord, GLenum pname, const GLtype

*params).

Параметр coord определяет для какой координаты задается формула и может принимать значение GL_S, GL_T; pname определяет

27

тип формулы и может быть равен GL_TEXTURE_GEN_MODE, GL_OBJECT_PLANE, GL_EYE_PLANE.

С помощью params задаются необходимые параметры, а param

может быть равен GL_OBJECT_LINEAR, GL_EYE_LINEAR, GL_SPHERE_MAP. Рассмотрение всех возможных комбинаций значений аргументов этой команды заняло бы слишком много места, поэтому в качестве примера рассмотрим, как можно задать зеркальную текстуру. При таком наложении текстуры изображение будет как бы отражаться от поверхности объекта, вызывая интересный оптический эффект. Для этого сначала надо создать два целочисленных массива коэффициентов s_coeffs и t_coeffs со значениями (1,0,0,1) и (0,1,0,1)

соответственно, а затем вызвать команды: glEnable(GL_TEXTURE_GEN_S); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);

glTexGendv(GL_S, GL_EYE_PLANE, s_coeffs).

И такие же команды для координаты t с соответствующими изменениями.

1.6. Использование эффекта тумана

Для использования «тумана» следует включить его, передавая аргумент GL_FOG, команде glEnable(). Также выбрать цвет тумана и уравнение, управляющее его плотностью, с помощью команды glFog*(). Кроме того, можно установить качество расчета тумана с помощью аргумента GL_FOG_HINT команды glHint().

Клавиша ‘f’ выбирает уравнение плотности тумана. Задает параметры и функцию для вычисления тумана. Если pname имеет значение GL_FOG_MODE, то param может принимать значения GL_EXP (значение по умолчанию), GL_EXP2 или GL_LINEAR и задает метод вычисления фактора тумана. Если pname равен

GL_FOG_DENSITY, GL_FOG_START или GL_FOG_END, то param

содержит (или для векторной версии команды указывает на) величины density, start или end для использования в уравнениях (значения по умолчанию – 1, 0 и 1 соответственно). В RGBA режиме pname может также содержать значение GL_FOG_COLOR. В таком случае params указывает на четверку величин, задающую RGBA цвет тумана. Соответствующее значение pname для индексного режима – GL_FOG_INDEX, в этом случае param должен содержать единственную величину, задающую цветовой индекс тумана.

28

1.7. Список отображения

Список отображения (дисплейные списки) – это группа команд OpenGL, сохраненная для дальнейшего исполнения. Когда исполнтся список отображения, команды, включенные в него, исполняются в том порядке, в котором они были заданы. Большинство команд OpenGL могут быть, как сохранены в списке отображения, так и выполняться в непосредственном режиме, в котором они выполняются немедленно.

Если нужно несколько раз обращаться к одной и той же группе команд, эти команды можно объединить в так называемый список изображений (display list) и вызывать его при необходимости. Для того, чтобы создать новый список изображений надо поместить все команды, которые должны в него войти между командными скобками:

void glNewList(GLuint list, GLenum mode); void glEndList().

Для различения списков используются целые положительные числа, задаваемые при создании списка значением параметра list, а параметр mode определяет режим обработки команд, входящих в список:

­GL_COMPILE команды записываются в список без выполнения;

­GL_COMPILE_AND_EXECUTE команды сначала выполняются, а затем записываются в список

После того, как список создан, его можно вызвать командой: void glCallList(GLuint list).

Здесь же, в параметре list, указывается идентификатор нужного списка. Чтобы вызвать сразу несколько списков, можно воспользоваться командой вызывающей n списков с идентификаторами из массива lists, тип элементов которого указывается в параметре type:

void glCallLists(GLsizei n, GLenum type, const GLvoid *lists).

Это могут быть типы GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_INT, GL_UNSIGNED_INT >и некоторые другие. Для удаления списков используется команда:

void glDeleteLists(GLint list, GLsizei range).

Команда удаляет списки с идентификаторами ID из диапазона list <=ID<= list+range-1.

29

1.8. Шрифты

Для рисования символов наиболее часто используются битовые карты.

Битовая карта – это прямоугольный массив из нулей и единиц, который служит в качестве маски для прямоугольной части окна. Например, если прорисовывается символ красного цвета, везде, где в битовой карте встречается 1, соответствующий пиксель в буфере кадра замещается красным. Там, где в битовой карте стоит 0, фрагменты не генерируются и пиксели не изменяются.

OpenGL предоставляет только низкоуровневую поддержку для рисования строк символов и манипуляций со шрифтами. Команды glRasterPos*() и glBitmap() позиционируют и рисуют на экране одну битовую карту. Кроме того, с помощью механизма списков отображения можно использовать последовательности кодов символов для индексации соответствующих серий битовых карт, представляющих эти символы.

Шрифт обычно состоит из набора символов, где каждый символ имеет идентификационный номер (как правило, свой ASCII код) и метод начертания.

В стандартном наборе символов ASCII заглавная буква A (в латинице) имеет номер 65, B – 66 и так далее. Строка «DAB» может быть представлена тремя индексами 68, 65 и 66. В простейшем случае список отображения номер 65 рисует A, номер 66 – B и так далее. То есть для того, чтобы вывести строку из символов 68, 65, 66 просто вызовите к исполнению соответствующие списки отображения.

Первый аргумент, n, индицирует количество выводимых символов, type обычно равен GL_BYTE, а lists– это массив кодов символов.

Можно использовать команду glCallLists() следующим образом: void glCallLists(GLsizei n, GLenum type, const GLvoid *lists).

Поскольку многим приложениям требуется отображать символы разных размеров и из разных шрифтов, простейший случай не устраивает. Оптимальное решение заключается в том, чтобы добавлять некоторое смещение к каждому вхождению в строку до того, как производится выбор списка отображения. В этом случае символы A, B и C будут представлены номерами 1065, 1066 и 1067 в шрифте 1, а в шрифте 2 они могут быть представлены числами 2065, 2066 и 2067. Тогда, чтобы нарисовать символы шрифтом 1, нужно установить смещение равным 1000 и вызвать к исполнению списки отображения 65, 66 и 67. Чтобы нарисовать ту же строку шрифтом 2, нужно

30

установить смещение равным 2000 и вызвать к исполнению те же списки.

Для того, чтобы установить смещение, используется командуа glListBase(). Для предыдущих примеров она должна быть вызвана с 1000 и 2000 в качестве единственного аргумента. Теперь все, что нужно

– это непрерывный диапазон неиспользуемых индексов списков отображения, который может быть получен с помощью команды: glGenLists():GLuint glGenLists (GLsizei range).

II. ПРИМЕРЫ ПРОГРАММИРОВАНИЯ КОМПЬЮТЕРНОЙ ГРАФИКИ С ИСПОЛЬЗОВАНИЕМ OPENGL

2.1. Инициализация OpenGL окна в Windows

Первым этапом чвляется создание пустого OpenGL окна для дальнейшей работы с ним.

Сначала следует установить MSVisualC++2010, далее реализовать библиотеки OpenGL, которые входят в поставку Windows – это библиотеки opengl32.dll & glu32.dll. Для этого надо скопировать opengl.dll и glu.dll в windows\system и положить opengl.lib, glu.lib в

подкаталог Lib.

Вменю Project/setting, необходимо выбрать вкладку LINK. В

строке «Object/Library Modules» выбрать «OpenGL32.lib GLu32.lib GLaux.lib».

После создания нового Win32 приложения в среде Visual C++, необходимо добавить для сборки проекта библиотеки OpenGL.

ВOpenGL не включены команды для управления окнами и для организации пользовательского ввода. Вся такая работа ведется через операционную систему.

Первые четыре строки – заголовочные файлы, включаемые для последующего использования дополнительных библиотек:

#include <windows.h>

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

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

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

// Заголовочные файлы для библиотеки GLaux

31

Далее необходимо инициализировать все переменные, которые планируется использовать в программе. Эта программа будет создавать пустое OpenGL окно, нет необходимости инициализировать большое количество переменных.

Первые строки устанавливают Контекст Рендеринга (Rendering Context). Каждая OpenGL программа связывается с Контекстом Рендеринга, который в свою очередь вызывает Контекст Устройства (Device Context). Контекст Рендеринга OpenGL определен как hRC. Для того чтобы рисовать в окне, необходимо создать Контекст Устройства Windows, который определен как hDC. DC соединяет окно с GDI (Graphics Device Interface). RC соединяет OpenGL с DC.

В третьей строке инициализирована переменная hWnd, которая хранит дескриптор (уникальную ссылку-идентификатор) окна. В четвёртой строке объявляется дескриптор приложения (экземпляр)

программы:

 

 

 

 

 

HGLRC hRC=NULL;

// Постоянный контекст рендеринга

HDC hDC=NULL;

// Приватный контекст устройства GDI

HWND hWnd=NULL;

//

Здесь

будет

хранится

дескриптор

окна

 

 

 

 

 

HINSTANCE hInstance

//

Здесь

будет

хранится

дескриптор

приложения В первой строке, приведённой ниже, инициализируем массив,

который используется для отслеживания нажатия клавиш на клавиатуре. Следующая переменная необходима для того, чтобы приложение знало, будет ли окно минимизировано в панель задач или нет. Если окно минимизировано, то возможна заморозка выполнения программы и появляется возможность выйти из нее. Таким образом, программа не будет выполняться, если она минимизирована. Смысл переменной fullscreen заключается в следующем: если программа запущена в полноэкранном режиме, fullscreen имеет значение true, если же она запущенна в оконном режиме, то fullscreen будет false. Эти переменные должны быть глобальными, чтобы было определено, что приложение запущенно в полноэкранном режиме либо в оконном:

bool keys[256];

//Массив, используемый для операций с клавиатурой bool active=true;

//Флаг активности окна, установленный в true по умолчанию bool fullscreen=true;

//Флаг режима окна, установленный в полноэкранный по умолчанию

32

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]