Домой / Группы / Графическая библиотека opengl. Программирование с использованием библиотеки opengl

Графическая библиотека opengl. Программирование с использованием библиотеки opengl

OpenGL является на данный момент одним из самых популярных программных интерфейсов (API) для разработки приложений в области двумерной и трехмерной графики. Стандарт OpenGL был разработан и утвержден в 1992 году ведущими фирмами в области разработки программного обеспечения, а его основой стала библиотека IRIS GL, разработанная Silicon Graphics.

На данный момент реализация OpenGL включает в себя несколько библиотек (описание базовых функций OpenGL, GLU,GLUT,GLAUX и другие), назначение которых будет описано ниже.

Характерными особенностями OpenGL, которые обеспечили распространение и развитие этого графического стандарта, являются:

Стабильность - дополнения и изменения в стандарте реализуются таким образом, чтобы сохранить совместимость с разработанным ранее программным обеспечением.

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

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

Основные возможности OpenGL

    Набор базовых примитивов: точки, линии, многоугольники и т.п.

    Видовые и координатные преобразования

    Удаление невидимых линий и поверхностей (z-буфер)

    Использование сплайнов для построения линий и поверхностей

    Наложение текстуры и применение освещения

    Добавление специальных эффектов: тумана, изменение прозрачности,сопряжение цветов (blending), устранение ступенчатости (anti-aliasing).

Как уже было сказано, существует реализация OpenGL для разных платформ, для чего было удобно разделить базовые функции графической системы и функции для отображения графической информации и взаимодействия с пользователем. Были созданы библиотеки для отображения информации с помощью оконной подсистемы для операционных систем Windows и Unix (WGL и GLX соответственно), а также библиотеки GLAUX и GLUT, которые используются для создания так называемых консольных приложений.

Библиотека GLAUX уступает по популярности написанной несколько позже библиотеке GLUT, хотя они предоставляют примерно одинаковые возможности. В состав библиотеки GLU вошла реализация более сложных функций, таких как набор популярных геометрических примитивов (куб, шар, цилиндр, диск), функции построения сплайнов, реализация дополнительных операций над матрицами и т.п. Все они реализованы через базовые функции OpenGL.

Архитектура и особенности синтаксиса

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

    Аппроксимация кривых и поверхностей

    Обработка вершин и сборка примитивов

    Растеризация и обработка фрагментов

    Операции над пикселями

    Подготовка текстуры

    Передача данных в буфер кадра

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

ИНИЦИАЛИЗАЦИЯ БИБЛИОТЕКИ OpenGL В C++

Первым делом нужно подключить заголовочные файлы:

#include

#include

#include

· gl.h и glu.h содержат прототипы основных функций OpenGL определённых в opengl32.dll и glu32.dll.

· glaux.h содержит вспомогательные (auxiliary) функции (glaux.dll).

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

BOOL bSetupPixelFormat(HDC hdc)

PIXELFORMATDESCRIPTOR pfd, *ppfd;

int pixelformat;

ppfd->nSize = sizeof(PIXELFORMATDESCRIPTOR);

ppfd->nVersion = 1;

ppfd->dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;

ppfd->dwLayerMask = PFD_MAIN_PLANE;

ppfd->iPixelType = PFD_TYPE_RGBA;

ppfd->cColorBits = 16;

ppfd->cDepthBits = 16;

ppfd->cAccumBits = 0;

ppfd->cStencilBits = 0;

if ((pixelformat = ChoosePixelFormat(hdc, ppfd)) == 0)

MessageBox(NULL, "ChoosePixelFormat failed", "Error", MB_OK);

if (SetPixelFormat(hdc, pixelformat, ppfd) == FALSE)

MessageBox(NULL, "SetPixelFormat failed", "Error", MB_OK);

Структура PIXELFORMATDESCRIPTOR сказать надо.

cColorBits - глубина цвета

cDepthBits - размер буфера глубины (Z-Buffer)

cStencilBits - размер буфера трафарета (мы его пока не используем)

iPixelType - формат указания цвета. Может принимать значения PFD_TYPE_RGBA (цвет указывается четырьмя параметрами RGBA - красный, зленный, синий и альфа) и PFD_TYPE_COLORINDEX (цвет указывается индексом в палитре).

Функция ChoosePixelFormat() подбирает формат пикселей и возвращает его дескриптор, а SetPixelFormat() устанавливает его в контексте устройства (dc).

После того как в контексте устройства установлен формат пикселей, нужно создать контекст воспроизведения (Rendering Context) для этого в OpenGL определены следующие функции:

HGLRC wglCreateContext(HDC hdc);

BOOL wglMakeCurrent(HDC hdc, HGLRC hglrc);

В объявлении класса формы в области private необходимо добавить следующее:

ghRC - указатель на контекст воспроизведения (Rendering Context)

ghDC - дескриптор устройства (для нас - просто указатель на окно)

Процедура Draw будет отвечать за рисование.

void __fastcall TForm1::FormCreate(TObject *Sender)

ghDC = GetDC(Handle);

if (!bSetupPixelFormat(ghDC))

ghRC = wglCreateContext(ghDC);

wglMakeCurrent(ghDC, ghRC);

glClearColor(0.0, 0.0, 0.0, 0.0);

FormResize(Sender);

glEnable(GL_COLOR_MATERIAL);

glEnable(GL_DEPTH_TEST);

glEnable(GL_LIGHTING);

glEnable(GL_LIGHT0);

float p={3,3,3,1},

glLightfv(GL_LIGHT0,GL_POSITION,p);

glLightfv(GL_LIGHT0,GL_SPOT_DIRECTION,d);

glViewport(0, 0, Width, Height);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

glOrtho(-5,5, -5,5, 2,12);

gluLookAt(0,0,5, 0,0,0, 0,1,0);

glMatrixMode(GL_MODELVIEW);

glClearColor() устанавливает цвет, которым будет заполняться экран при очищении. У этой процедуры - 4 параметра, что соответствует RGBA. Вместо нее можно написать glClearIndex(0.0) . Эта процедура устанавливает индекс цвета в палитре.

glViewport() устанавливает область вывода - область, в которую OpenGL будет выводить изображение.

glMatrixMode() устанавливает режим матрицы видового преобразования.

glLoadIdentity() заменяет текущую матрицу видового преобразования на единичную.

glOrtho() устанавливает режим ортогонального (прямоугольного) проецирования. Это значит, что изображение будет рисоваться как в изометрии. 6 параметров типа GLdouble (или просто double): left, right, bottom, top, near, far определяют координаты соответственно левой, правой, нижней, верхней, ближней и дальней плоскостей отсечения, т.е. всё, что окажется за этими пределами, рисоваться не будет. На самом деле эта процедура просто устанавливает масштабы координатных осей. Для того чтобы установить перспективное проецирование, используются процедуры glFrustum() и gluPerspective().

gluLookAt() устанавливает параметры камеры: первая тройка - её координаты, вторая - вектор направления, третья - направление оси Y.

В OpenGL всё включается и выключается (разрешается и запрещается) процедурами glEnable() и glDisable().

glLightfv() устанавливает свойства "лампочек": позицию и направление света.

После того, как завершена работа с OpenGL, нужно освободить занятые ресурсы: освободить контекст, вызвав wglMakeCurrent с параметром ноль для идентификатора контекста OpenGL и разрушить этот контекст функцией wglDeleteContext. Кроме того нужно удалить дескриптор ghDC. Так как обычно работу с OpenGL завершается при завершении работы приложения, то соответствующий код нужно поместить в FormClose:

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)

wglMakeCurrent(ghDC,0);

wglDeleteContext(ghRC);

ReleaseDC(Handle, ghDC);

ЗАКЛЮЧЕНИЕ

За время прохождения производственной практики с 5 июля по 31 июля 2011 г. в ЗАО «Транзас», Авиационное направление в отделе программирования, я ознакомился с работой отдела программирования. Ознакомился с устройством и функционированием комплексных авиа тренажеров, разрабатываемых в ЗАО «Транзас». Я узнал о такой системе визуализации ландшафтов и различных объектов, как «Аврора». Я получил первоначальные практические навыки и умения, необходимые для разработки приложений и программного обеспечения с помощью современного высокоуровневого языка программирования и графической библиотеки.

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

Например, тип GLint соответствует стандартному int:

typedef int GLint;

аналогично:

typedef unsigned int GLuint; typedef float GLfloat; typedef double GLdouble; typedef void GLvoid;

Эти типы объявлены в GL.h. Имена всех этих типов начинаются с GL. Рекомендуется с функциями OpenGL использовать эти типы.

Функции OpenGL

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

Чтобы не было путаницы в именах функций, ввели несколько договорённостей (правил), по которым строится имя функции OpenGL.

Во-первых, все имена функций OpenGL начинаются с приставки gl. Например,

GlBegin();
glEng();

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

GlОбщая_часть_имени_функции[n],

где n - количество параметров, type - тип параметров.

Например:

glVertex2d(1 .0 , 0 .5 ) ; // 2d означает: 2 параметра типа GLdouble glVertex3f(1 .0 f, 0 .5 f, 0 .0 f) ; // 3f означает: 3 параметра типа GLfloat glColor3ub(127 , 0 , 255 ) ; // 3ub означает: 3 параметра типа GLubyte

Ниже в таблице приведены значения сокращений для type:

i GLint
ui GLuint
f GLfloat
d GLdouble
s GLshort
us GLushort
b GLbyte
ub GLubyte
v массив

В различных документациях по OpenGL, чтобы не перечислять все функции одного семейства, принято записывать только имя общей части всех функций семейства и в конце ставить звёздочку "*". Например, функции, задающие координаты вершин записывают так:

GlVertex*

Дополнительные сведения об OpenGL

Команды OpenGL интерпретируются моделью client/server.

Код приложения (client) выдаёт команды, которые интерпретируются и обрабатываются.

OpenGL (server) может оперировать или не оперировать на компьютере как клиент. Сервер может содержать несколько контекстов OpenGL. Клиент может подключаться к любому из этих контекстов.

Оконная система выделяет буфер кадра (frame buffer). Она определяет, какая часть буфера кадра модели может быть доступна в данное время для OpenGL, и уведомляет OpenGL, как эти порции структурированы. Поэтому OpenGL не имеет команд, конфигурирующих буфер кадра или инициализирующие OpenGL.

Дополнительные библиотеки

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

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

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

Наиболее известные библиотеки:

OpenGL Utility Library (glu)

Утилитная библиотека glu предоставляет функции, работающие с матрицами, с координатными системами, с кривыми и поверхностями NURBS и т.п.

Эта библиотека поставляется почти со всеми реализациями OpenGL, в частности с MS Visual C++.

Для того чтобы её использовать, нужно в исходном файле включить заголовочный файл glu.h:

#include

и включить для линковки статическую библиотеку glu32.lib в ваш проект.

Имена функций в этой утилитной библиотеки начинаются на glu, например,

GluPerspective();

OpenGL Auxiliary Library (glaux)

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

Эта библиотека используется реже, и описание функций не включено в MSDN. Файлы для работы с библиотекой также поставляются с MS Visual C++.

Для того чтобы её использовать, нужно в исходном файле включить заголовочный файл glaux.h:

#include

и включить для линковки статическую библиотеку glaux.lib в ваш проект.

Имена функций в этой утилитной библиотеки начинаются на aux, например,

AuxSolidCube();

OpenGL Utility Toolkit (GLUT)

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

Для того чтобы её использовать, нужно в исходном файле включить заголовочный файл glut.h и включить для линковки статическую библиотеку glut32.lib в ваш проект.

Кроме того, у вас должна быть динамически подключаемая библиотека glut32.dll.

В этой главе рассмотрим рендеринг трехмерной графики с помощью библиотеки OpenGL, изучим библиотеки GLU и GLUT (вместо последней иод Linux используется библиотека FreeGLUT), разберем процесс загрузки текстур с помощью библиотек SOIL и DevIL.

Как уже отмечалось в гл. 9, программисты графики обычно не работают напрямую с GPU. Это связано как с тем, что существует много различных GPU, так и с тем, что низкоуровневая работа с GPU довольно сложна и обычно ею занимаются разработчики драйверов. Вместо этого используют различные API, предоставляющие некоторый интерфейс более высокого уровня для работы с GPU. Этот интерфейс абстрагируется от конкретного GPU (вся работа с которым идет через драйвер, обычно поставляемый производителем GPU), что позволяет писать переносимый код, который будет работать с различными GPU. Также подобный API скрывает от программиста ряд низкоуровневых деталей работы с GPU.

Основными API для программирования трехмерной графики на данный момент являются OpenGL и Dircct3D. Последний ориентирован только на платформу Microsoft Windows. В этой книге рассмотрены основы работы с OpenGL. Это кроссплатформен- ный API, поддерживающий все основные операционные системы (Windows, Linux, Mac OS X) и позволяющий работать с большим количеством различных GPU.

Существует версия API - OpenGL ES, предназначенная для работы на мобильных устройствах. С ее помощью можно делать трехмерную графику для платформ iOS и Android. Кроме того, существует WebGL - библиотека, позволяющая использовать OpenGL ES прямо в окне браузера, применяя для этого javascript. Также существуют привязки для OpenGL, позволяющие работать со всеми основными языками программирования, благодаря чему можно легко использовать OpenGL практически из любого языка программирования.

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

С точки зрения архитектуры OpenGL построен на модели клиент-сервер. При этом сама программа, использующая OpenGL, выступает в роли клиента, a GPU и его драйвер - в роли сервера. Обычно программа выполняется на том же компьютере, где установлен GPU, но это не обязательно.

На практике все выполняемые команды OpenGL буферизуются и уже потом поступают в очередь для передачи на GPU. Таким образом, выполнение CPU команды говорит только о том, что данная команда попала в буфер или была добавлена в очередь; вполне возможно, что GPU ее еще не начал выполнять. В то же время OpenGL можно рассматривать как конечный автомат - у него есть свое состояние. Единственный способ изменить это состояние - использовать команды OpenGL. Между командами состояние OpenGL не изменяется.

Важным понятием в OpenGL являются буферы (рис. 10.1). Для того чтобы осуществлять рендеринг, должны быть созданы необходимые буферы. Буфер цвета используется всегда и для каждого пиксела хранит его цвет как 24-битовое число в формате RGB (по 8 бит на каждый из базовых цветов - красный, зеленый и синий) или как 32-битовое в формате RGBA (к стандартным трем компонентам добавляется четвертая компонента - альфа, задающая непрозрачность).

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

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

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

Рис. 10.1.

Для современных GPU две части этого конвейера представлены с помощью программ, выполняющихся на GPU, - шейдеров. Далее будем рассматривать OpenGL версии 2, в которой эти программы необязательно задавать явно: существуют шейдеры, которые работают по умолчанию (г.е. в случае, когда программист явно не задал соответствующие шейдеры). Начиная с версии 3, OpenGL требует обязательного задания шейдеров и частично нарушает совместимость с предыдущими версиями, именно поэтому мы будем рассматривать версию OpenGL 2.

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

OpenGL использует матрицы 4x4 для преобразования вершин - модельно-видовую матрицу проектирования (рис. 10.2). Если вершинный шейдер не задан явно, то используется вершинный шейдер по умолчанию, который умножает координаты вершины (в виде вектора в однородных координатах) сначала на модельновидовую матрицу, а затем - на матрицу проектирования.

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


Рис. 10.2.

Если изначально координаты были заданы в своей системе координат, то умножение на модельно-видовую матрицу переводит их в систему координат камеры. Далее умножение на матрицу проектирования приводит координаты в пространство отсечения (clip space). После выполнения перспективного деления получаем нормализованные координаты устройства (normalized device coordinates).

Заключительный шаг - перевод нормализованных координат в координаты в окне, выражаемые в пикселах.

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

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

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

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

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

Если вы пишете программу, использующую OpenGL на С (или C++), то прежде всего необходимо включить следующий заголовочный файл:

Для обеспечения совместимости и переносимости кода OpenGL вводит ряд своих типов данных, имя каждого из этих типов начинается с префикса GL. GLint соответствует стандартному типу целых чисел, тип GLuint - стандартному типу беззнаковых целых чисел, a GLfloat - типу float. Также OpenGL использует несколько специальных типов, таких как GLsizei, обозначающий тип, используемый для задания размера, и GLclampf, используемый для задания значений с плавающей точкой, лежащих на отрезке .

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

В библиотеке OpenGL (а также в идущих с ней в комплекте библиотеках GLU и GLUT) принято использовать довольно простое соглашение об именовании констант и функций. Имена всех команд (функций) OpenGL начинаются с префикса gl (для функций из библиотек GLU и GLUT - с glu и glut соответственно).

Имена всех констант начинаются с GL_ (соответственно с GLU_ и GLUTJ.

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

glCommand{1 2 3 4}{b s i f d ub us ui}{v}

Необязательная цифра служит для задания количества передаваемых аргументов (в том случае, когда есть версии этой команды с различным числом аргументов). Далее идет необязательный суффикс из одной или двух букв, задающий тип передаваемых аргументов (в том случае, когда существуют версии этой команды, принимающие входные значения различных типов). Суффикс v сообщает о том, что ряд параметров (обычно набор последних параметров) передан в виде массива, - в действительности функция вместо этих параметров получает указатель на этот массив.

Так, в команде glVertex2i два целочисленных аргумента, в команде glColor3f - три аргумента типа float, а в команде glColor4ubv - четыре аргумента типа unsigned byte, переданных в виде массива (т.е. функция при вызове получает всего один аргумент - адрес массива).

Знакомство с OpenGL нужно начать с того, что OpenGL - это спецификация . Т.е. OpenGL лишь определяет набор обязательных возможностей. Реализация же зависит от конкретной платформы.
OpenGL является кроссплатформенным, независимым от языка программирования API для работы с графикой. OpenGL - низкоуровневый API, поэтому для работы с ним неплохо иметь некоторое представление о графике в целом и знать основы линейной алгебры.

Именования

Скажем пару слов об именовании функций в OpenGL. Во-первых имена всех функций, предоставляемых непосредственно OpenGL, начинаются с приставки gl . Во-вторых функции, задающие некоторый параметр, характеризующийся набором чисел (например координату или цвет), имеют суффикс вида [число параметров + тип параметров + представление параметров].
  • Число параметров - указывает число принимаемых параметров. Принимает следующие значения: 1 , 2 , 3 , 4
  • Тип параметров - указывает тип принимаемых параметров. Возможны следующие значения: b , s , i , f , d , ub , us , ui . Т.е. byte (char в C, 8-битное целое число), short (16-битное целое число), int (32-битное целое число), float (число с плавающей запятой), double (число с плавающей запятой двойной точности), unsigned byte, unsigned short, unsigned int (последние три - беззнаковые целые числа)
  • Представление параметров - указывает в каком виде передаются параметры, если каждое число по отдельности, то ничего не пишется, если же параметры передаются в виде массива, то к названию функции дописывается буква v
Пример: glVertex3iv задает координату вершины, состоящую из трех целых чисел, передаваемых в виде указателя на массив.

Графика

Все графические объекты в OpenGL представляют собой набор точек, линий и многоугольников. Существует 10 различных примитивов, при помощи которых строятся все объекты. Как двухмерные, так и трехмерные. Все примитивы в свою очередь задаются точками - вершинами.
  • GL_POINTS - каждая вершина задает точку
  • GL_LINES - каждая отдельная пара вершин задает линию
  • GL_LINE_STRIP - каждая пара вершин задает линию (т.е. конец предыдущей линии является началом следующей)
  • GL_LINE_LOOP - аналогично предыдущему за исключением того, что последняя вершина соединяется с первой и получается замкнутая фигура
  • GL_TRIANGLES - каждая отдельная тройка вершин задает треугольник
  • GL_TRIANGLE_STRIP - каждая следующая вершина задает треугольник вместе с двумя предыдущими (получается лента из треугольников)
  • GL_TRIANGLE_FAN - каждый треугольник задается первой вершиной и последующими парами (т.е. треугольники строятся вокруг первой вершины, образуя нечто похожее на диафрагму)
  • GL_QUADS - каждые четыре вершины образуют четырехугольник
  • GL_QUAD_STRIP - каждая следующая пара вершин образует четырехугольник вместе с парой предыдущих
  • GL_POLYGON - задает многоугольник с количеством углов равным количеству заданных вершин
Для задания примитива используется конструкция glBegin (тип_примитива)…glEnd () . Вершины задаются glVertex* . Вершины задаются против часовой стрелки. Координаты задаются от верхнего левого угла окна. Цвет вершины задается командой glColor* . Цвет задается в виде RGB или RGBA. Команда glColor* действует на все вершины, что идут после до тех пор, пока не встретится другая команда glColor* или же на все, если других команд glColor* нет.
Вот код рисующий квадрат с разноцветными вершинами:
  1. glBegin(GL_QUADS) ;
  2. glVertex2i(250 , 450 ) ;
  3. glVertex2i(250 , 150 ) ;
  4. glVertex2i(550 , 150 ) ;
  5. glVertex2i(550 , 450 ) ;
  6. glEnd() ;

Основы программы на OpenGL

Для платформонезависимой работы с окнами можно использовать библиотеку . GLUT упрощает работу с OpenGL.
Для инициализации GLUT в начале программы надо вызвать glutInit (&argc, argv) . Для задания режима дисплея вызывается glutInitDisplayMode (режим) , где режим может принимать следующие значения:
  • GLUT_RGBA - включает четырехкомпонентный цвет (используется по умолчанию)
  • GLUT_RGB - то же, что и GLUT_RGBA
  • GLUT_INDEX - включает индексированный цвет
  • GLUT_DOUBLE - включает двойной экранный буфер
  • GLUT_SINGLE - включает одиночный экранный буфер (по умолчанию)
  • GLUT_DEPTH - включает Z-буфер (буфер глубины)
  • GLUT_STENCIL - включает трафаретный буфер
  • GLUT_ACCUM - включает буфер накопления
  • GLUT_ALPHA - включает альфа-смешивание (прозрачность)
  • GLUT_MULTISAMPLE - включает мультисемплинг (сглаживание)
  • GLUT_STEREO - включает стерео-изображение
Для выбора нескольких режимов одновременно нужно использовать побитовое ИЛИ "|". Например: glutInitDisplayMode (GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH) включает двойную буферизацию, Z-буфер и четырехкомпонентный цвет. Размеры окна задаются glutInitWindowSize (ширина, высота) . Его позиция - glutInitWindowPosition (х, у) . Создается окно функцией glutCreateWindow (заголовок_окна) .
GLUT реализует событийно-управляемый механизм. Т.е. есть главный цикл, который запускается после инициализации, и в нем уже обрабатываются все объявленные события. Например нажатие клавиши на клавиатуре или движение курсора мыши и т.д. Зарегистрировать функции-обработчики событий можно при помощи следующих команд:
  • void glutDisplayFunc (void (*func) (void)) - задает функцию рисования изображения
  • void glutReshapeFunc (void (*func) (int width, int height)) - задает функцию обработки изменения размеров окна
  • void glutVisibilityFunc (void (*func)(int state)) - задает функцию обработки изменения состояния видимости окна
  • void glutKeyboardFunc (void (*func)(unsigned char key, int x, int y)) - задает функцию обработки нажатия клавиш клавиатуры (только тех, что генерируют ascii-символы)
  • void glutSpecialFunc (void (*func)(int key, int x, int y)) - задает функцию обработки нажатия клавиш клавиатуры (тех, что не генерируют ascii-символы)
  • void glutIdleFunc (void (*func) (void)) - задает функцию, вызываемую при отсутствии других событий
  • void glutMouseFunc (void (*func) (int button, int state, int x, int y)) - задает функцию, обрабатывающую команды мыши
  • void glutMotionFunc (void (*func)(int x, int y)) - задает функцию, обрабатывающую движение курсора мыши, когда зажата какая-либо кнопка мыши
  • void glutPassiveMotionFunc (void (*func)(int x, int y)) - задает функцию, обрабатывающую движение курсора мыши, когда не зажато ни одной кнопки мыши
  • void glutEntryFunc (void (*func)(int state)) - задает функцию, обрабатывающую движение курсора за пределы окна и его возвращение
  • void glutTimerFunc (unsigned int msecs, void (*func)(int value), value) - задает функцию, вызываемую по таймеру
Затем можно запускать главный цикл glutMainLoop () .

Первая программа

Теперь мы знаем основы работы с OpenGL. Можно написать простую программу для закрепления знаний.
Начнем с того, что нужно подключить заголовочный файл GLUT:

Теперь мы уже знаем, что писать в main. Зарегистрируем два обработчика: для рисования содержимого окна и обработки изменения его размеров. Эти два обработчика по сути используются в любой программе, использующей OpenGL и GLUT.
  1. int main (int argc, char * argv )
  2. glutInit(& argc, argv) ;
  3. glutInitDisplayMode(GLUT_DOUBLE| GLUT_RGBA) ; /*Включаем двойную буферизацию и четырехкомпонентный цвет*/
  4. glutInitWindowSize(800 , 600 ) ;
  5. glutCreateWindow(«OpenGL lesson 1» ) ;
  6. glutReshapeFunc(reshape) ;
  7. glutDisplayFunc(display) ;
  8. glutMainLoop() ;
  9. return 0 ;

Теперь надо написать функцию-обработчик изменений размеров окна. Зададим область вывода изображения размером со все окно при помощи команды glViewport (х, у, ширина, высота) . Затем загрузим матрицу проекции glMatrixMode (GL_PROJECTION) , заменим ее единичной glLoadIdentity () и установим ортогональную проекцию. И наконец загрузим модельно-видовую матрицу glMatrixMode (GL_MODELVIEW) и заменим ее единичной.
В итоге получим:
  1. void reshape(int w, int h)
  2. glViewport(0 , 0 , w, h) ;
  3. glMatrixMode(GL_PROJECTION) ;
  4. glLoadIdentity() ;
  5. gluOrtho2D(0 , w, 0 , h) ;
  6. glMatrixMode(GL_MODELVIEW) ;
  7. glLoadIdentity() ;

Осталось только написать функцию рисования содержимого окна. Рисовать будем тот квадрат, что я приводил выше в качестве примера. Добавить придется совсем немного кода. Во-первых перед рисованием надо очистить различные буфера при помощи glClear (режим) . Используется также как и glutInitDisplayMode. Возможные значения:
  • GL_COLOR_BUFFER_BIT - для очистки буфера цвета
  • GL_DEPTH_BUFFER_BIT - для очистки буфера глубины
  • GL_ACCUM_BUFFER_BIT - для очистки буфера накопления
  • GL_STENCIL_BUFFER_BIT - для очистки трафаретного буфера
В нашем случае нужно очистить только буфер цвета, т.к. другие мы не используем. Во-вторых после рисования нужно попросить OpenGL сменить экранные буфера при помощи glutSwapBuffers () , ведь у нас включена двойная буферизация. Все рисуется на скрытом от пользователя буфере и затем происходит смена буферов. Делается это для получения плавной анимации и для того, чтобы не было эффекта мерцания экрана.
Получаем:
  1. void display()
  2. glClear(GL_COLOR_BUFFER_BIT) ;
  3. glBegin(GL_QUADS) ;
  4. glColor3f(1.0 , 1.0 , 1.0 ) ;
  5. glVertex2i(250 , 450 ) ;
  6. glColor3f(0.0 , 0.0 , 1.0 ) ;
  7. glVertex2i(250 , 150 ) ;
  8. glColor3f(0.0 , 1.0 , 0.0 ) ;
  9. glVertex2i(550 , 150 ) ;
  10. glColor3f(1.0 , 0.0 , 0.0 ) ;
  11. glVertex2i(550 , 450 ) ;
  12. glEnd() ;
  13. glutSwapBuffers() ;

Итог

Все! Можно компилировать. Должно получиться что-то вроде этого:

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

Читатель, наверное, знает, что OpenGL это оптимизированная, высокопроизводительная графическая библиотека функций и типов данных для отображения двух-и трехмерной графики. Стандарт OpenGL был утвержден в 1992 г. Он основан на библиотеке IRIS GL, разработанной компанией Silicon Graphics (www.sgi.com). OpenGL поддерживают все платформы. Кроме того, OpenGL поддержана аппаратно. Существуют видеокарты с акселераторами и специализированные SD-карты, которые выполняют примитивы OpenGL на аппаратном уровне.

Материал первой части этого урока навеян очень хорошей книгой (доступной в online-варианте) издательства Addison-Wesley "OpenGL Programming Guide, The Official Guide to Learning OpenGL". Если читатель владеет английским языком, то мы рекомендуем ее прочесть.

Подключаемые библиотеки

Microsoft-реализация OpenGL включает полный набор команд OpenGL, то есть глобальных функций, входящих в ядро библиотеки OPENGL32.LIB и имеющих префикс gl (например, glLineWidth). Заметьте, что функции из ядра библиотеки имеют множество версий, что позволяет задать желаемый параметр или настройку любым удобным вам способом. Посмотрите справку по функциям из семейства glColor*. Оказывается, что задать текущий цвет можно 32 способами. Например, функция:

Void glColorSb(GLbyte red, GLbyte green, GLbyte blue);

Определяет цвет тремя компонентами типа GLbyte, а функция:

Void glColor4dv (const GLdouble *v);

Задает его с помощью адреса массива из четырех компонентов.

С учетом этих вариантов ядро библиотеки содержит более 300 команд. Кроме того, вы можете подключить библиотеку утилит GLU32.LIB, которые дополняют основное ядро. Здесь есть функции управления текстурами, преобразованием координат, генерацией сфер, цилиндров и дисков, сплайновых аппроксимаций кривых и поверхностей (NURBS - Non-Uniform Rational B-Spline ), а также обработки ошибок. Еще одна, дополнительная (auxiliary ) библиотека GLAUX.LIB позволяет простым способом создавать Windows-окна, изображать некоторые SD-объекты, обрабатывать события ввода и управлять фоновым процессом. К сожалению, эта библиотека не документирована. Компания Microsoft не рекомендует пользоваться ею для разработки коммерческих проектов, так как она содержит код цикла обработки сообщений, в который невозможно вставить обработку других произвольных сообщений.

Примечание
Тип GLbyte эквивалентен типу signed char, a GLdouble - типу double. Свои собственные типы используются в целях упрощения переносимости на другие платформы. Список типов OpenGL мы приведем ниже. Четвертый компонент цвета определяет прозрачность цвета, то есть способ смешивания цвета фона с цветом изображения. Некоторые команды OpenGL имеют в конце символ v, который указывает, что ее аргументом должен быть адрес массива (вектора). Вектор в математике - это последовательность чисел (координат), единственным образом задающих элемент векторного пространства. Многие команды имеют несколько версий, позволяя в конечном счете задать вектор разными способами
.

Около двадцати Windows GDI-функций создано специально для работы с OpenGL. Большая часть из них имеет префикс wgl (аббревиатура от Windows GL). Эти функции являются аналогами функций с префиксом glx, которые подключают OpenGL к платформе X window System. Наконец, существует несколько Win32-функций для управления форматом пикселов и двойной буферизацией. Они применимы только для специализированных окон OpenGL.