КОМПЬЮТЕРНАЯ ГРАФИКА
лабораторный практикум
ЛАБОРАТОРНАЯ РАБОТА №1
OpenGL: инициализация, построение двумерных примитивов в отдельном окне
Цель работы
Изучить алгоритм инициализации OpenGL для Windows, получить практические навыки создания оконного приложения для вывода графических изображений, созданных с помощью OpenGL.
Теоретическая информация
Использование библиотеки OpenGL требует навыков программирования на C++, а также знания основ событийного программирования на уровне операционной системы Windows. Однако, авторы не ставят своей целью ограничивать выбор программных и аппаратных средств для выполнения работ по компьютерной графике, поэтому в приложении приведены дополнительно варианты инициализации OpenGL для различных ситуаций: с использованием библиотеки glut, языков С# и Delphi, для ОС Linux.
Для выполнения работы так же необходимо иметь представление о современных стандартах и интерфейсах программирования компьютерной графики, об основных принципах формирования изображения на экране компьютера, о составе графической библиотеки OpenGL.
Основные понятия, используемые в данной лабораторной работе: контекст устройства, контекст воспроизведения, формат пиксела, синтаксис команд OpenGL, примитивы OpenGL.
Контекст графического устройства (Device Context) указывает плоскость отображения, на которую осуществляется графический вывод: окно программы на экране дисплея, страница принтера или другое место, куда может быть направлен графический вывод. Если программа вызывает различные графические функции, такие как рисование точек, линий, фигур и др., необходимо указывать идентификатор контекста (hdc – handle of device context) и координаты. Смысл использования контекста устройства заключается в том, что вывод на различные устройства осуществляется одними и теми же функциями, изменяется лишь значение hDC. «Контекст устройства является структурой, которая определяет комплект графических объектов и связанных с ними атрибутов и графические режимы, влияющие на вывод». При составлении программы необходимо получить это числовое значение перед рисованием, а после рисования – освободить контекст.
В OpenGL существует понятие контекст воспроизведения (контекст рендеринга), аналогичное понятию контекст устройства. Графическая система OpenGL также нуждается в ссылке на устройство, на которое будет осуществляться вывод. Это специальная ссылка на контекст воспроизведения – величина типа HGLRC (handle openGL rendering context, ссылка на контекст воспроизведения OpenGL) - hRC.
Основная задача инициализации – синхронизировать 2 контекста: hRC (контекст OpenGL) и hDC (контекст окна), чтобы всё, что будет нарисовано OpenGL, отображалось в окне.
Графически алгоритм инициализации OpenGL можно представить следующим образом (рис. 1).
Рассмотрим его подробнее.
1. Подключение заголовочных файлов.
В зависимости от системы программирования набор подключаемых библиотек может быть различным.
Если вы используете Delphi, то всё необходимое для работы с OpenGL находится в модуле OpenGL.dcu.
Рисунок 1. Обобщенный алгоритм инициализации OpenGL
Код на языке Delphi
uses OpenGL;
Если вы используете С++ Builder, то подключать придётся несколько файлов:
· gl.h и glu.h содержат прототипы основных функций OpenGL определённых в opengl32.dll и glu32.dll;
· glaux.h содержит вспомогательные (auxiliary) функции (glaux.dll).
Код на языке C++
#include <GL/gl.h>;
#include <GL/glu.h>;
#include <GL/glaux.h>;
Для инициализации в Microsoft Visual Studio необходимо подключить следующие библиотеки:
#include <windows.h> // Заголовочный файл для Windows
#include <gl\gl.h> //Заголовочный файл для библиотеки OpenGL32
#include <gl\glu.h> // Заголовочный файл для библиотеки GLu32
#include <gl\glaux.h>// Заголовочный файл для библиотеки GLaux
2. Объявить глобальные переменные для контекста устройства и контекста воспроизведения
Код на Delphi
var
hRC : HGLRC;
hDC : HDC;
Код на языке C++
static HGLRC hRC; // Постоянный контекст рендеринга
static HDC hDC; // Приватный контекст устройства GDI
3. Создание контекста устройства и установка подходящего формата пикселей.
Прежде чем получить контекст воспроизведения, сервер OpenGL должен получить детальные характеристики используемого оборудования. Эти характеристики хранятся в специальной структуре – описание формата пикселя. Формат пикселя определяет конфигурацию буфера цвета и вспомогательных буферов, а также то, как OpenGL будет выводить изображение в окно. Эти характеристики хранятся в специальной структуре, тип которой PixelFormatDescriptor (описание формата пикселей). Рассмотрим данную операцию более подробно. Выполняется она в два приема:
Ø подбирается один из доступных в системе форматов пикселей, наиболее подходящий нашим нуждам,
Ø затем этот формат и назначается устройству воспроизведения.
Задание всех необходимых требований выполняется в структуре PixelFormatDescriptor, которая содержит более 30 полей. Все мы их рассматривать не будем, остановимся на самых важных:
· nSize
Как и многие структуры в Windows это поле должно содержать размер самой структуры, проще всего его инициализировать оператором sizeof().
· nVersion
Для текущей реализации должно быть установлено в 1.
· dwFlags
Битовые флаги, определяющие устройство и интерфейс, с которым совместим буфер пикселей данного формата. Рассмотрим основные из них:
Ø PFD_DRAW_TO_WINDOW – разрешено рисование в окне или поверхности устройства.
Ø PFD_DRAW_TO_BITMAP – р \азрешено рисование в битовый образ в памяти.
Ø PFD_SUPPORT_GDI – буфер поддерживает рисование с использованием GDI. Этот флаг нельзя использовать с флагом PFD_DOUBLEBUFFER.
Ø PFD_SUPPORT_OPENGL – буфер поддерживает OpenGL рисование.
Ø PFD_DOUBLEBUFFER – поддерживается режим двойной буферизации. Нельзя использовать совместно с флагом PFD_SUPPORT_GDI.
Ø PFD_SWAP_COPY – поддерживается ли копирование из внеэкранного буфера на первичную (видимую поверхность). Обмен не влияет на внеэкранную поверхность, а копирует изображение из нее на первичную.
В стандартном запросе на формат рисования в dwFlags устанавливаются следующие флаги: для вывода графических объектов в окно необходимо выставить флаг PFD_DRAW_TO_WINDOW; буфер кадра поддерживает вывод через OpenGL - PFD_SUPPORT_OPENGL; и, так как мы используем два буфера (Back и Front), то устанавливаем флаг PFD_DOUBLEBUFFER. Рассмотрим двойную буферизацию подробнее. Обычно в трёхмерных сценах каждый видимый кадр строится заново. То есть, в каждом кадре полностью перерисовывается вся видимая графика. Построение кадра может происходить следующим образом. Сначала весь кадр очищается, а потом последовательно выводятся трёхмерные графические объекты. Такое действие происходит несколько раз в секунду. Количество этих перерисовок в секунду называют FPS (frames per second). Если мы будем стирать и рисовать в видимой части экрана, то мы будем видеть мигание и мерцание наших рисующихся и стирающихся объектов.
Для исключения этого неприятного эффекта, пользуются следующим способом. Весь кадр полностью строят в невидимой части видеопамяти адаптера. Эту часть называют Back Buffer. После того, как все графические объекты выведены, вся эта невидимая часть копируется в видимую область видеопамяти, называемую Front Buffer. Т.е. происходит так называемая смена буферов, что и составляет эффект двойной буферизации и позволяет выполнять анимацию изображений без ненужных эффектов.
Если ваше приложение выводит графику на весь экран, а не в окно, то можно избежать копирования буфера, переключая указатель на видимую часть в видео памяти. Такой эффект называют Flip. Понятно, что он работает быстрее, чем копирование буфера, но нам данный способ не подходит.
· iPixelType
Определяет режим, используемый для отображения цветов: значение PFD_TYPE_RGBA - цвет каждого пикселя определяется 4 значениями – красным, зеленым, синим и альфа; PFD_TYPE_COLORINDEX - цвет задается индексом в таблице цветов (палитре).
· cColorBits
Количество бит (битовых плоскостей) цвета. Фактически глубина цвета. Здесь указывается желаемая глубина цвета (bpp – bits per pixel, 8, 15, 16, 24, 32; список поддерживаемых значений зависит от драйверов и варьируется в зависимости от видеоплаты).
· cRedBits cRedShift cGreenBits cGreenShift cBlueBits cBlueShift cAlphaBits cAlphaShift
Биты R,G,B,A составляющих и их смещения от начала битовых плоскостей, т.е. порядок следования цветовых компонент: RGBA или ABGR. Например, если глубина цвета равна 32, cRedBits = 8 и cRedShift = 16, то это означает, что под красную составляющую будет выделен один байт (8 бит) и в двойном слове глубины (32 бита) она будет занимать биты с 16 по 23 (отсчет бит идет с нуля).
· cDepthBits
Размер (глубина) буфера глубины (Z-буфер). Обычные значения 16, 24, 32 бит. Z-буфер используется в трехмерной графике, и его разрядность влияет на точность вычислений операций с глубиной сцены. Недостаточная глубина Z-буфера будет приводить к ошибкам визуализации сцены, например, более дальний объект будет вылезать сквозь ближний. Глубина сцены задается как отношение задней плоскости отсечения к передней плоскости. Подробнее о механизмах построения трехмерных сцен, а так же алгоритмах работы с Z-буфером мы поговорим позднее.
· cStencilBits
Размер (глубина) буфера трафарета. Буфер трафарета используется во многих продвинутых техниках рисования, например, для расчета стенсильных теней и пр. Однако на современных видеоплатах его разрядность, как правило, ограничена 8 битами. К тому же в большинстве случаев аппаратно акселерируется буфер трафарета только в режиме 32bpp, поэтому, например, в режиме 16bpp любое обращение к буферу трафарета будет сильно тормозить рендеринг всей сцены.
Шаблон инициализации полей объекта типа структура PixelFormatDescriptor приведен ниже:
{
RECT Screen; // используется позднее для размеров окна
GLuint PixelFormat;
static PIXELFORMATDESCRIPTOR pfd=
{
sizeof(PIXELFORMATDESCRIPTOR), // Размер этой структуры
1, // Номер версии
PFD_DRAW_TO_WINDOW | // Формат для Окна
PFD_SUPPORT_OPENGL | // Формат для OpenGL
PFD_DOUBLEBUFFER, // Формат для двойного буфера
PFD_TYPE_RGBA, // Требуется RGBA формат
16, // Выбор 16 бит глубины цвета
0, 0, 0, 0, 0, 0, // Игнорирование цветовых битов
0, // нет буфера прозрачности
0, // Сдвиговый бит игнорируется
0, // Нет буфера аккумуляции
0, 0, 0, 0, // Биты аккумуляции игнорируются
16, // 16 битный Z-буфер (буфер глубины)
0, // Нет буфера трафарета
0, // Нет вспомогательных буферов
PFD_MAIN_PLANE, // Главный слой рисования
0, // Резерв
0, 0, 0 // Маски слоя игнорируются
};
Код на языке Delphi
function SetDCPixelFormat (DC: HDC): integer;
var
pfnum: integer;
pfd: PIXELFORMATDESCRIPTOR;
begin
fillchar(pfd, sizeof(pfd), 0);
pfd.nVersion := 1;
pfd.nSize := sizeof(pfd);
pfd.dwFlags := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;
pfd.iPixelType := PFD_TYPE_RGBA
{выбираем формат пикселей}
pfnum := ChoosePixelFormat(DC, @pfd);
{если вернули 0, значит ошибка}
if pfnum = 0 then exit;
{устанавливаем формат пикселей}
SetPixelFormat(DC, pfnum, @pfd);
Result := pfnum;
End;
Следующая секция предназначена для обработки системных сообщений: выход из программы, нажатие клавиш, перемещение окна и т.д., каждая секция "case" обрабатывает свой тип сообщения.
switch (message) // Тип сообщения
{ case WM_CREATE:
hDC = GetDC(hWnd); // Получить контекст устройства для окна
PixelFormat = ChoosePixelFormat(hDC, &pfd);
// Найти ближайшее совпадение для формата пикселей
WM_CREATE указывает программе, что сообщение должно быть создано. Сначала следует запросить DC (контекст устройства) для окна – без него рисование в окне невозможно. Затем запрашивается формат пикселя.
Функция ChoosePixelFormat (<контекст устройства>, <указатель на структуру >) возвращает номер одного из доступных в системе форматов пикселей. Компьютер будет выбирать формат, который полностью совпадает или наиболее близок к запрашиваемому формату[1].
Если подходящий формат пикселя не найден, будет выведено сообщение об ошибке.
if (!PixelFormat)
{
MessageBox(0,"Не найден подходящий формат пиксела.",
"Ошибка",MB_OK|MB_ICONERROR);[2]
PostQuitMessage(0);
// Это сообщение говорит, что программа должна завершится
break; // Предотвращение повтора кода
}
Если подходящий формат найден, необходимо установить его для устройства рисования с помощью функции SetPixelFormat. Если формат пикселя не может быть установлен по какой-то причине, появится сообщение об ошибке, что формат пикселя не установлен.
if(!SetPixelFormat(hDC,PixelFormat,&pfd))
{
MessageBox(0,"Формат пиксела не установлен.",
"Ошибка",MB_OK|MB_ICONERROR);
PostQuitMessage(0);
break;
}
Если код записан, как показано выше, будет создан контекст устройства (hDC), и установлен подходящий формат пикселя. Рекомендуется все эти действия выполнять в отдельной функции, дав ей значимое имя, например, SetWindowPixelFormat или SetDCPixelFormat.
4. Создание контекста воспроизведения
Если удалось установить пиксельный формат, то можно перейти к созданию просчитывающего контекста (rendering context) OpenGL. Для этого выполняется вызов функции
wglCreateContext(<контекст воспроизведения>),
которая определяет контекст воспроизведения OpenGL, подходящий для рисования на устройстве, определённом дескриптором hRC. При успешном завершении функция возвращает дескриптор созданного контекста воспроизведения OpenGL, и NULL – в случае неудачи.
hRC = wglCreateContext(hDC);
if(!hRC)
{
MessageBox(0,"Контекст воспроизведения не создан.",
"Ошибка",MB_OK|MB_ICONERROR);
PostQuitMessage(0);
break;
}
Текущий контекст воспроизведения потока должен быть единственным. Следующая функция позволяет определить контекст воспроизведения для контекста устройства.
wglMakeCurrent(hDC, hRC)
В результате у нас получилась функция создания GL контекста рисования:
Код на языке Delphi
function CreateContext(DC: HDC): boolean;
var
hrc: HGLRC;
begin
Result := false;
//Проверим входные параметры
if DC = 0 then exit;
//создаем GL if SetWindowPixelFormat(hDC, 32, 0, 0) = 0 then exit;
{теперь можно создать контекст GL}
rc := wglCreateContext(hDC);
if rc = 0 then exit;
{назначаем GL контекст текущим}
wglMakeCurrent(hDC, hrc);
OneTimeSceneInit;
Result := true;
end;
Код на языке C++
void __fastcall TForm1::FormCreate(TObject *Sender)
{
hDC = GetDC(Handle);
if (!SetupPixelFormat(hDC)) Close();
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hRC);
}
5. Отрисовка.
Теперь можно перейти непосредственно к отрисовке объектов сцены с помощью OpenGL. В этой части программы мы будем использовать функции OpenGL для отрисовки конкретных графических сцен. Более подробно об этом будет сказано ниже. Пока запишем в этой секции только команду для очистки экрана цветом, который мы определили выше. Команды рисования будут следовать за ней.
Область вывода в OpenGL задается командой
glViewPort (0, 0, ClientWidth, ClientHeight),
где первые два параметра задают положение левого верхнего угла окна вывода, параметры ClientWidth, ClientHeight определяют размер окна в экранных координатах. Центр полученной области вывода имеет координаты (0, 0). Координаты изменяются в диапазоне [-1; 1] по каждой оси.
По умолчанию центр системы координат находится в центре окна вывода, а максимальное значение координат по осям определено в диапазоне [-1; 1].
Если содержимое буфера кадра изменяется в процессе регенерации изображения, то зритель может увидеть совершенно нежелательные эффекты, например, дерганье изменяющейся картинки.
Эту проблему решают использованием стандартной технологии организации компьютерной анимации – двойной буферизации. Такой подход подразумевает использование двух буферов кадра: рабочего (переднего) и фонового (заднего). Рабочий буфер – это тот, из которого выполняется регенерация изображения на экране, а в фоновом буфере изображение формируется программой. С помощью специальных функций из прикладной программы функции буферов можно переключать, что позволяет наблюдать плавную анимационную сцену.
Если при установке формата пикселя был установлен режим двойной буферизации (флаг PFD_DOUBLEBUFFER), то изображение готовится в фоновом буфере, и необходимо обеспечить перезапись содержимого фонового буфера в рабочий. Для этого можно использовать функцию
BOOL glSwapBuffers (HDC hdc)
Благодаря тому, что OpenGL реализован по модели клиент-сервер, помимо рассмотренной функции для вывода созданного изображения можно воспользоваться командой
VOID glFlush (void)
Функция glFlush, определенная в библиотеке gl.h, инициирует выполнение всех ожидающих команд OpenGL, которые обычно выстраиваются в очередь и выполняются пакетами с целью оптимизации производительности. Этот принцип может варьироваться для различного аппаратного обеспечения, драйверов и реализаций OpenGL, однако все действия должны завершаться “в конечное время”, что и позволяет реализовать команда glFlush .
В общем виде эту секцию кода можно записать так.
GLvoid DrawGLScene(GLvoid)
{
glViewPort (0, 0, ClientWidth, ClientHeight),
glClear(GL_COLOR_BUFFER_BIT); // очистка экрана
glSwapBuffers (hDC);
}
Позднее мы добавим сюда функции OpenGL для отрисовки примитивов.
6. Освобождение контекстов.
При завершении работы необходимо, чтобы контекст никем не использовался. Для этого достаточно выполнить вызов функции:
wglMakeCurrent(0,0); //освободить контекст
Завершая работу с OpenGL необходимо удалить контекст воспроизведения. Для этой цели используется функция:
wglDeleteContext(hRC);
После того как удалён контекст воспроизведения, следует удалить и ассоциированный с ним контекст устройства.
Код на Delphi:
procedure TForm1.FormDestroy(Sender: TObject);
begin
wglMakeCurrent(0,0); //освободить контекст
wglDeleteContext(hrc)//удалить контекст воспроизведения;
ReleaseDC(Handle,dc);// удалить контекст устройства
end;
Код на С++
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
wglMakeCurrent(0,0);
wglDeleteContext(hGLRC);
ReleaseDC(Handle, hDC);
Form1->Close();
}
Итак, если собрать воедино все полученные факты и написать свою первую программу с использованием графической библиотеки OpenGL, то у вас получится некая программа-шаблон, которую потом можно будет усложнять путем добавления различных функций (см. раздел Дополнительный материал). Т.е. достаточно один раз правильно написать шаблон и инициализировать OpenGL под вашу ОС и язык программирования, и больше к этой теме можно будет не возвращаться.
Однако, если мы остановимся на этом, то результат программы нас не удовлетворит. Потому как мы пишем программу для отрисовки графики! А где графика в этом шаблоне?
Поэтому мы добавим немножко информации уже непосредственно про OpenGL, чтобы ваша первая программа могла что-то нарисовать. А вы могли это увидеть, обрадоваться (все не зря!), и вдохновиться на новые свершения.
Итак, приступим.
В OpenGL полное имя команды имеет вид:
type glName[1 2 3 4][b s i f d ub us ui][v] (type1 arg1,…,typeN argN)
где:
gl |
это имя библиотеки, в которой описана эта функция: для базовых функций OpenGL, функций из библиотек GLU, GLUT, GLAUX это gl, glu, glut, glaux соответственно |
Name |
имя команды |
[1 2 3 4] |
число аргументов команды |
[b s i f d ub us ui] |
тип аргумента (см. таблица 1) |
[v] |
наличие этого символа показывает, что в качестве параметров функции используется указатель на массив значений |
Символы в квадратных скобках в некоторых названиях не используются. Например, команда glVertex2i() описана как базовая в библиотеке OpenGL и использует в качестве параметров два целых числа, а команда glColor3fv() использует в качестве параметра указатель на массив из трех вещественных чисел.
Если имя команды заканчивается на v (векторная форма), то аргументом её служит указатель на массив значений. Например: Если последние три символа в имени команды 3fv, то её аргумент – адрес массива трёх вещественных чисел.
Использования нескольких вариантов каждой команды можно частично избежать, применяя перегрузку функций языка C++. Но интерфейс OpenGL не рассчитан на конкретный язык программирования, и, следовательно, должен быть максимально универсален.
Таблица 1 Возможные типы аргументов
Символ |
Обозначение типа в OpenGL |
Расшифровка |
b |
GLbyte |
Байтовый |
s |
GLshort |
Короткий целый |
i |
GLint |
Целый |
d |
GLdouble |
Вещественный двойной точности |
f |
GLfloat |
вещественный |
ub |
GLubyte |
Байтовый, |
us |
GLushort |
Короткий целый |
ui |
GLuint |
Целый
|
Почти всегда предпочтительно использовать команду в вещественной форме, поскольку OpenGL хранит данные в вещественном формате.
Все изображения строятся из отдельных примитивов, которые описываются с помощью набора вершин (Vertex). Примитивами OpenGl являются точки (одиночные вершины), линии (пары вершин), треугольники (три вершины), четырехугольники (четыре вершины) и полигоны (3 и более вершин).
Для указания на отрисовку одного или нескольких примитивов необходимо использовать так называемые командные скобки библиотеки OpenGl, которые представляют собой специальные функции (не путать с операторными скобками языков программирования!). Ошибка при использовании командных скобок не распознается компилятором, но может привести к непредсказуемым результатам работы программы.
Командные скобки в общем виде записываются следующим образом:
glBegin (mode)
… // вершины и их атрибуты
glEnd;
где mode определяет правило соединения перечисленных вершин в графический примитив (см. таблица 2).
Вершины задаются своими координатами (количество координат зависит от пространства изображения) с помощью команд
glVertex{2,3,4}{s,i,f,d}(koord1, koord2, …)
Внутри командных скобок могут находиться любые операторы языка программирования и многие функции OpenGL. Подробнее о процессе и функциях рисования можно будет прочитать в Лабораторной работе №2 настоящего пособия.
Пример:
Отрисовка трех точек в двумерной системе координат.
glBegin(GL_POINTS);
glColor3d(1,0,0);
glVertex2d(-4.5,4); // первая точка
glColor2d(0,1);
glVertex2d(-4,4); // вторая точка
glColor3d(0,0,1);
glVertex2d(-3.5); // третья точка
glEnd();
Таблица 2 Возможные значения параметра mode
mode |
Описание |
GL_POINTS |
Рисует N точек. Каждый вызов glVertex задает отдельную точку. |
GL_LINES |
Рисует N/2 линий. Каждая пара вершин задает отрезок. |
GL_LINE_STRIP |
Рисуется ломаная из N - 1 отрезков. Вершина N и N+1 определяют отрезок N ломаной. |
GL_LINE_LOOP |
Рисуется ломаная, причем ее последняя точка соединяется с первой. Механизм отрисовки тот же, что и в GL_LINE_STRIP. |
GL_TRIANGLES |
Рисуется N/3 треугольников. Каждые три вершины N, N+1, N+2 определяют треугольник N. |
GL_TRIANGLE_STRIP |
Рисуется N-1 треугольников. Рисуются треугольники с общей стороной. Для нечетного N, элементы N, N+1, N+2 определяют треугольник N. |
GL_TRIANGLE_FAN |
Рисуется N - 2 треугольников. Рисует группу соединенных в одной вершине треугольников. Один треугольник определяется для каждого элемента после двух предыдущих. Два последних элемента соединяются с первым. |
GL_QUADS |
Рисуется N/4 четырехугольника. Каждые четыре вершины задают четырехугольник. |
GL_QUAD_STRIP |
Рисуются четырехугольники с общей стороной, соединяя чётные элементы с чётными, а нечётные с нечётными. |
GL_POLYGON |
Рисуется полигон. Вершины полигона определяются элементами от 1 до N-го. |
Как правило, разные типы примитивов имеют различную скорость визуализации на разных платформах. Для увеличения производительности предпочтительнее использовать примитивы, требующие меньшее количество информации для передачи на сервер, такие как GL_TRIANGLE_STRIP, GL_QUAD_STRIP, GL_TRIAGLE_FAN.
Рисунок 2. Примитивы OpenGL
Методика выполнения лабораторной работы
В течение занятия необходимо:
1) изучить теоретическую информацию, предлагаемую в данном пособии;
2) сформировать собственную блок-схему инициализации OpenGL, в зависимости от используемого программного обеспечения (примеры вариантов инициализации см. в разделе «Дополнительный материал» к данной лабораторной работе);
3) выполнить инициализацию OpenGL;
4) выполнить отрисовку типовых графических примитивов согласно варианту задания.
Варианты заданий
1. Выполнить инициализацию OpenGL и отрисовать 4 точки, два треугольника и полигон.
2. Выполнить инициализацию OpenGL и отрисовать 3 точки, треугольник, ленту треугольников и два четырехугольника.
3. Выполнить инициализацию OpenGL и отрисовать две линии, ломаную, 2 треугольника и квадрат.
4. Выполнить инициализацию OpenGL и отрисовать 5 точек, 2 ломаные, ленту треугольников и полигон.
5. Выполнить инициализацию OpenGL и отрисовать 2 полигона из 6 вершин каждый, две замкнутые линии из 5 вершин и 3 точки.
6. Выполнить инициализацию OpenGL и отрисовать 2 четырехугольника, 2 треугольника и 5 точек.
7. Выполнить инициализацию OpenGL и отрисовать 2 отрезка, 3 треугольника и полигон.
8. Выполнить инициализацию OpenGL и отрисовать 3 треугольника, ленту треугольников, ленту четырехугольников и 1 точку.
9. Выполнить инициализацию OpenGL и отрисовать все примитивы по одному разу.
10. Выполнить инициализацию OpenGL и отрисовать 2 полигона из 5 вершин каждый, две замкнутые линии из 7 вершин и 2 точки.
11. Выполнить инициализацию OpenGL и отрисовать 1 четырехугольник, 2 треугольника, 4 точки и 1 отрезок.
12. Выполнить инициализацию OpenGL и отрисовать 3 точки, 2 треугольника, ленту треугольников и четырехугольник.
13. Выполнить инициализацию OpenGL и отрисовать 6 точек, треугольник, ленту треугольников и ленту четырехугольников.
14. Выполнить инициализацию OpenGL и отрисовать 3 полигона, треугольник, ленту четырехугольников и 2 точки.
15. Выполнить инициализацию OpenGL и отрисовать 2 полигона из 5 вершин каждый, две ломаные из 7 вершин и 2 треугольника.
Содержание отчета
Результатом выполнения лабораторной работы должен стать отчет (в печатном и электронном вариантах), состоящий из следующих пунктов:
1) постановка задачи;
2) блок-схема инициализации;
3) программный код предлагаемого решения;
4) скриншоты результатов работы программы.
Отчет лабораторной работы должен быть произведен студентом преподавателю в срок до начала следующего лабораторного занятия. Во время занятия преподавателю необходимо предоставить:
· отчет о лабораторной работе (в печатном виде);
· программную реализацию решения.
Оценка (в баллах) выставляется за устный отчет студента по предоставленным преподавателю материалам.
Пример выполнения лабораторной работы
Постановка задачи:
1. Выполнить инициализацию OpenGL.
2. Изобразить точки, линии, треугольник, многоугольники (минимум 5 различных примитивов).
Листинг программы:
//------------------------------------------------------------
#include <vcl.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <Gl/glaux.h>
#pragma hdrstop
#include "Unit1.h"
//------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
HGLRC hGLRC; //контекст воспроизведения (связь с WinExplorer);
HDC hDC; //контекст окна (устройства) вывода;
//------------------------------------------------------------
//функция, устанавливающая параметры контекста воспроизведения OpenGL;
void SetDCPixelFormat(HDC dc)
{
int pfnum;
PIXELFORMATDESCRIPTOR pfd;
pfd.nSize=sizeof(pfd); //размер структуры;
pfd.nVersion=1;
pfd.dwFlags=
//битовые флаги:
PFD_DRAW_TO_WINDOW || //рисование в окне
PFD_SUPPORT_OPENGL || //поддержка буфером OpenGL
PFD_DOUBLEBUFFER; //двойная буферизация;
pfd.iPixelType=PFD_TYPE_RGBA; //режим отображения цветов
pfnum=ChoosePixelFormat(hDC, &pfd); //предлагаемый формат;
SetPixelFormat(hDC, pfnum, &pfd); //устанавливаем формат
};
//------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
hDC=GetDC(Handle);
SetDCPixelFormat(hDC); //устанавливаем параметры OpenGL;
hGLRC=wglCreateContext(hDC);//создаем контекст воспроизведения;
wglMakeCurrent(hDC,hGLRC); //определяем контекст воспроизведения;
}
//------------------------------------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
glClearColor(0.1,0.1,0.1,0); //очищение экрана и заполнение цветом;
glViewport(0,0,ClientWidth,ClientHeight); //область вывода;
glColor3f(1, 1, 1);
glBegin(GL_POINTS);
glVertex2f(-0.6, -0.55); ;
glVertex2f(-0.55, -1);
glVertex2f(-0.5, -0.55);
glVertex2f(0.6, -0.55);
glVertex2f(0.55, -1);
glVertex2f(0.5, -0.55);
glEnd();
glColor3d(1,0,0);
glBegin(GL_TRIANGLES); // рисуем треугольник
glVertex3d(-4,2,0);
glVertex3d(-3,2.9,0);
glVertex3d(-2,2,0);
glEnd();
glBegin(GL_TRIANGLE_STRIP); // рисуем ленту треугольников
glColor3d(0,1,0);
glVertex3d(1,2,0);
glVertex3d(0,2.9,0);
glVertex3d(-1,2,0);
glVertex3d(0,1.1,0);
glEnd();
SwapBuffers(hDC);
}
//------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
wglMakeCurrent(0,0);
wglDeleteContext(hGLRC);
ReleaseDC(Handle, hDC);
Form1->Close();
}
Дополнительный материал
// initOpenGL.cpp: определяет точку входа для приложения.
#include "stdafx.h"
#include "initOpenGL.h"
#include <math.h>
/* Добавим свои переменные ******************/
/*ссылка на поверхность рисования на которой будет все рисование *********/
HDC hDC;
/*ссылка на OpenGL через которую будем передавать параметр **/
HGLRC hGLRC;
/*ссылка на окно в котором будет происходить рисование ******/
HWND hWnd;
/************************************************************/
#define MAX_LOADSTRING 100
// Глобальные переменные:
HINSTANCE hInst; // текущий экземпляр
TCHAR szTitle[MAX_LOADSTRING]; // Текст строки заголовка
TCHAR szWindowClass[MAX_LOADSTRING];// имя класса главного окна
// Отправить объявления функций, включенных в этот модуль кода:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: разместите код здесь.
MSG msg;
HACCEL hAccelTable;
// Инициализация глобальных строк
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_INITOPENGL, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Выполнить инициализацию приложения:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_INITOPENGL));
// Цикл основного сообщения:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// ФУНКЦИЯ: MyRegisterClass()
// НАЗНАЧЕНИЕ: регистрирует класс окна.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_INITOPENGL));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_INITOPENGL);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// ФУНКЦИЯ: InitInstance(HINSTANCE, int)
// НАЗНАЧЕНИЕ: сохраняет обработку экземпляра и создает главное окно.
// КОММЕНТАРИИ:
// В данной функции дескриптор экземпляра сохраняется в глобальной переменной,
// а также создается и выводится на экран главное окно программы.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Сохранить дескриптор экземпляра в глобальной переменной
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
/*Весь код инициализации будет находиться здесь **********/
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_SUPPORT_OPENGL |
PFD_DRAW_TO_WINDOW |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
32,
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
16,
0,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0,
};
/************************************************************/
/*получаем ссылку на поверхность где будем рисовать */
hDC = GetDC(hWnd);
/*Задаем параметры точек*/
int pixelFormat = ChoosePixelFormat(hDC, &pfd);
/*задаем параметры точки*/
SetPixelFormat(hDC, pixelFormat, &pfd);
/*сообщаем OpenGL где будем рисовать*/
hGLRC = wglCreateContext(hDC);
/*создаем контекст рисования*/
wglMakeCurrent(hDC, hGLRC);
/*****************************************************/
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// ФУНКЦИЯ: WndProc(HWND, UINT, WPARAM, LPARAM)
// НАЗНАЧЕНИЕ: обрабатывает сообщения в главном окне.
// WM_COMMAND - обработка меню приложения
// WM_PAINT - закрасить главное окно
// WM_DESTROY - ввести сообщение о выходе и вернуться.
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Разобрать выбор в меню:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// выполняем отрисовку примитивов
/************************************************************/
glClear(GL_COLOR_BUFFER_BIT);
glColor3ub(145, 30, 66);
glBegin(GL_TRIANGLES);
glVertex2f(0.0f, 0.8f); //верхняя вершина
glVertex2f(-0.1f, 0.4f); //левая вершина
glVertex2f(0.1f, 0.4f); //правая вершина
glEnd();
//рисуем прямоугольник
glColor3ub(220, 380, 52);
glRectf(-0.1f, -0.4f, 0.1f, 0.4f);
glBegin(GL_POLYGON);
glColor3f(1.0, 0.0, 0.0);
glVertex2f(0.1, -0.4);
glVertex2f(0.2, -0.7);
glVertex2f(0.05, -0.5);
glVertex2f(-0.05, -0.5);
glEnd();
glBegin(GL_POLYGON);
glVertex2f(-0.05, -0.5);
glVertex2f(-0.2, -0.7);
glVertex2f(-0.1, -0.4);
glVertex2f(0.1, -0.4);
glEnd();
glFlush();
SwapBuffers(hDC);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Обработчик сообщений для окна "О программе".
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
Существует специальная мультиплатформенная библиотека, позволяющая сократить алгоритм инициализации OpenGL. И называется эта библиотека – GLUT. Эта библиотека обеспечивает единый интерфейс для работы с окнами вне зависимости от платформы, поэтому структура приложения, приведенная ниже, остается неизменной для операционных систем Windows, Linux и многих других.
Минимальная программа, которая создает окно и что-нибудь рисует там, состоит из следующих шагов:
1. Инициализация GLUT
2. Установка параметров окна.
3. Создание окна.
4. Установка функций, отвечающих за рисование в окне и изменение формы окна.
5. Вход в главный цикл GLUT.
Рассмотрим все 5 пунктов подробнее.
Сначала необходимо скачать библиотеку glut.h с официального сайта OpenGL и подключить ее:
#include <GL/glut.h>
1. Инициализация GLUT производится командой:
void glutInit(int *argcp, char **argv);
где *argcp – указатель на количество аргументов в командной строке, а **argv – указатель на массив аргументов. Обычно эти значения берутся из главной функции программы: int main(int argc, char *argv[]).
2. Установка параметров окна.
Прежде всего, необходимо указать размеры окна:
void glutInitWindowSize(int width, int height);
где width – ширина окна в пикселях, height – высота окна в пикселях. По умолчанию размеры окна 300x300.
Далее задать положение создаваемого окна относительно верхнего левого угла экрана с помощью функции:
void glutInitWindowPosition(int x, int y);
Необходимо также установить для окна режим отображения информации, т. е. установить для окна такие параметры, как: используемая цветовая модель, количество различных буферов, и т.д. Для этого в используется функция:
void glutInitDisplayMode (unsigned int mode);
В качестве аргумента в эту функцию необходимо передать одну из следующих констант (см. таблица 3) или их комбинацию (с помощью побитового ИЛИ).
Таблица 3
Константа |
Значение |
GLUT_RGB |
Для отображения графической информации используются 3 компоненты цвета RGB |
GLUT_RGBA |
То же что и RGB, но используется также 4 компонента ALPHA (прозрачность) |
GLUT_INDEX |
Цвет задается не с помощью RGB компонентов, а с помощью палитры. Используется для старых дисплеев, где количество цветов, например, 256 |
GLUT_SINGLE |
Вывод в окно осуществляется с использованием 1 буфера. Обычно используется для статического вывода информации |
GLUT_DOUBLE |
Вывод в окно осуществляется с использованием 2 буферов. Применяется для анимации, чтобы исключить эффект мерцания |
GLUT_ACCUM |
Использовать также буфер накопления (Accumulation Buffer). Этот буфер применяется для создания специальных эффектов, например отражения и тени |
GLUT_ALPHA |
Использовать буфер ALPHA. Этот буфер, как уже говорилось, используется для задания 4-го компонента цвета – ALPHA. Обычно применяется для таких эффектов, как прозрачность объектов и антиалиасинг |
GLUT_DEPTH |
Создать буфер глубины. Этот буфер используется для отсечения невидимых линий в 3D пространстве при выводе на плоский экран монитора |
GLUT_STENCIL |
Буфер трафарета используется для таких эффектов, как вырезание части фигуры, делая этот кусок прозрачным. Например, наложив прямоугольный трафарет на стену дома, Вы получите окно, через которое можно увидеть, что находится внутри дома |
GLUT_STEREO |
Этот флаг используется для создания стереоизображений. Используется редко, так как для просмотра такого изображения нужна специальная аппаратура |
Механизм двойной буферизации устанавливается в процессе инициализации аргументом функции glutInitDisplayMode(). Вместо константы GLUT_SINGLE нужно задать константу GLUT_DOUBLE. Переключение буферов выполняется функцией glutSwapBuffers(). Все операторы формирования изображения включатся в функцию display(), но при использовании двойной буферизации в этой функции сначала нужно очистить рабочий буфер, вызвав команду glClear(), а последним оператором вызвать функцию переключения буферов glutSwapBuffers().
Например:
void glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
3. Создание окна.
После того как окно установлено, необходимо его создать:
int glutCreateWindow(const char *title);
Эта функция создаёт окно с заголовком, который передается ей в качестве параметра, и возвращает HANDLER окна в виде числа int, который затем используется для последующих операций над этим окном, таких как изменение параметров окна и закрытие окна.
4. Установка функций, отвечающих за рисование в окне и изменение формы окна.
Функция для рисования, которая всегда будет вызываться операционной системой, чтобы нарисовать (перерисовать) содержимое окна:
void glutDisplayFunc(void (*func)(void));
аргумент этой функции – указатель на функцию, которая будет отвечать за рисование в окне. Например, чтобы функция void Draw(void), определенная в вашей программе, отвечала за рисование в окне, надо присоединить ее к GLUT следующим образом:
glutDisplayFunc(Draw);
Функция, отслеживающая изменения окна:
void glutReshapeFunc(void (*func)(int width, int height));
аргумент которой – это указатель на функцию, отвечающую за изменение размеров окна, которая должна принимать два параметра width и height, соответственно ширина и высота нового (измененного) окна.
5. Вход в главный цикл GLUT.
Этот цикл запускает на выполнение и обеспечивает взаимосвязь между операционной системой и теми функциями, которые отвечают за окно, получают информацию от устройств ввода/вывода. Для того, чтобы перейти в главный цикл GLUT, надо указать:
void glutMainLoop(void);
Команда glFlush() гарантирует, что команда рисования будет выполнена немедленно, а не сохранена в буфере.
Функции библиотеки GLUT реализуют так называемый событийно-управляемый механизм. Это означает, что есть некоторый внутренний цикл, который запускается после соответствующей инициализации и обрабатывает одно за другим все события, объявленные во время инициализации. К событиям относятся: щелчок мыши, закрытие окна, изменение свойств окна, передвижение курсора, нажатие клавиши, и «пустое» (idle) событие, когда ничего не происходит. Для проведения периодической проверки совершения того или иного события надо зарегистрировать функцию, которая будет его обрабатывать.
void glutKeyboardFunc(void (*func)(unsigned int key, int x, int y);
Определяет функцию (func), которая вызывается, когда нажата клавиша на клавиатуре. Возвращаемые параметры:
key – сгенерированный клавиатурой ASCII код;
x,y – координаты положения мыши в координатах отображаемого окна, в момент, когда была нажата кнопка на клавиатуре.
void glutMouseFunc(void (*func)(int button, int state, int x, int y));
Определяет функцию (func), которая вызывается, когда кнопка мыши нажата или отпущена. Возвращаемый функцией параметр button может принимать значения GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, или GLUT_RIGHT_BUTTON. Значение параметра state есть GLUT_UP или GLUT_DOWN в зависимости от того, была ли кнопка мыши нажата или отпущена, x and y параметры указывают на координаты в текущем окне, где находилась мышь в момент нажатия или отпускания кнопки.
void glutMotionFunc(void (*func)(int x, int y));
Определяет функцию (func), которая вызывается, когда указатель мыши перемещается в пределах окна при нажатой одной или более кнопке, x and y параметры указывают на координаты в текущем окне, где находилась мышь в момент начала события void glutPostRedisplay(void);
Отмечает текущее окно как требующее перерисовки. На следующем шаге работы программы будет вызвана функция, зарегистрированная в glutDisplayFunc().
void glutIdleFunc (void (*func) (void));
glutIdleFunc() задает функцию, которая будет вызываться каждый раз, когда нет событий от пользователя.
Пример инициализации с Glut
Программа выполняет инициализацию Opengl и отрисовывает чайник с помощью специальной встроенной функции glutSolidTeapot(width). Так как в сцене будет отрисован трехмерный предмет, то, соответственно, необходимо добавить в алгоритм инициализации установки для определения трехмерной сцены. В тексте программы все соответствующие строки закомментированы для лучшего понимания процесса. На данном этапе этого достаточно. Сам механизм определения и расчета трехмерных сцен подробно будет рассмотрен позднее.
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>
#include <GL/glut.h>
void resize(int width,int height)
{
}
void display(void)
{
glColor3d(1,1,0); // установка цвета
glutSolidTeapot(0.8); //отрисовка чайника
glFlush();
}
void init(void)
{
/*данная часть кода имеет смысл при использовании трехмерной сцены и освещения; в случае двумерной отрисовки можно использовать установки по умолчанию, а эту функцию из программы исключить, потому что каждая строка кода должна быть понятна разработчику и ее использование должно быть аргументировано */
glEnable(GL_LIGHTING); //включаем режим расчета освещения
glEnable(GL_LIGHT0); //включаем источник света
glEnable(GL_DEPTH_TEST); //включаем режим проверки
//глубины сцены
glClearColor (0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//обращаемся к матрице проекций
glMatrixMode( GL_PROJECTION );
glLoadIdentity(); //приводим ее к единичному виду
//устанавливаем заданный параллелепипед видимости
glOrtho(-5.0,5.0,-5.0,5.0,2.0,12.0);
//определяем положение наблюдателя (камеры)
gluLookAt( 0,0,5, 0,1,0, 0,1,0 );
//обращаемся к модельно-видовой матрице
glMatrixMode( GL_MODELVIEW); }
int main(int argc,char ** argv)
{
glutInitDisplayMode(GLUT_SINGLE| GLUT_RGB | GLUT_DEPTH);
glutInitWindowPosition(50,10);
glutInitWindowSize(400,400);
glutCreateWindow(«Hello»);
glutReshapeFunc(resize);
init();
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
Контрольные вопросы
1. Что понимается под контекстом устройства?
2. Что представляет собой контекст воспроизведения?
3. Какие основные фрагменты должна содержать минимальная программа OpenGL.
4. Что входит в понятие формат пиксела?
5. Какие библиотечные файлы должны быть подключены к программе для работы с OpenGL?
6. Какая цветовая модель используется при определении цвета в данной программе?
7. Какие параметры следует указать, чтобы цвет фона был зеленым?
8. Для чего нужна функция масштабирования сцены?
9. В какой секции кода можно записывать команды рисования сцены?
10. Какое правило необходимо соблюдать, чтобы иметь возможность перехода в полноэкранный режим?
ЛАБОРАТОРНАЯ РАБОТА №2
OpenGL: Разработка приложения для визуализации связного набора двумерных примитивов
Цель работы
Изучить возможности библиотеки OpenGl для построения различных графических примитивов, ознакомиться с базовыми понятиями по отрисовке полигонов и использования режимов сглаживания и интерполяции цветов.
Теоретическая информация
Функции для рисования заключаются между командными скобками glBegin и glEnd. Командные скобки библиотеки OpenGl представляют собой специальные функции (не имеющие никакого отношения к операторным скобкам языков программирования). Ошибка при использовании командных скобок не распознается компилятором, но может привести к непредсказуемым результатам работы программы.
Внутри командных скобок могут находиться любые операторы языка и многие функции OpenGL. Главное назначение командных скобок – задание режима (примитива) для команд glVertex (вершина), определяющих координаты вершин для рисования примитивов OpenGL. Аргументами функции glBegin могут быть стандартные константы OpenGl, определяющие примитивы библиотеки: 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. В программе имена констант должны быть записаны именно так: в верхнем регистре.
Команды, устанавливающие размер точки, толщину и тип линии, включение и отключение режима сглаживания (англ. anti-aliasing) должны стоять вне командных скобок.
Для установки (включения) специализированных режимов обработки и вывода изображений в Opengl, необходимо использовать функцию glEnable() с соответствующей константой в качестве аргумента. Для отключения режима – соответственно, функцию glDisable() с той же константой.
Функции glEnable/glDisable включают/выключают множество опций, но следует учитывать, что некоторые опции влекут за собой большие вычисления и, следовательно, изрядно затормаживают приложение, поэтому без надобности не стоит их включать.
Рассмотрим рисование точек. Точки представляют собой одиночные вершины. Вершина является атомарным графическим примитивом OpenGL и определяет точку, конец отрезка, угол многоугольника и т.д. Все остальные примитивы формируются с помощью задания вершин, входящих в данный примитив. Например, отрезок определяется двумя вершинами, являющимися концами отрезка.
С каждой вершиной ассоциируются ее атрибуты. В число основных атрибутов входят:
· размер;
· положение вершины в пространстве;
· цвет вершины;
· вектор нормали;
· координаты текстуры
Рассмотрим каждый из атрибутов подробнее.
Атрибуты точек
· Размер
Когда вы отрисовываете точку, ее размер по умолчанию равен одному пикселю. Для изменения размера точки используется функция
void glPointSize(GLfloat size),
где size – натуральное число, определяющее размер точки в пикселях.
Поддерживаются не все размеры, поэтому следует проверять, доступен ли размер, который вы задаете для точки. Чтобы найти диапазон размеров и наименьший интервал между ними, применяется следующий код:
GLfloat sizes[2];// диапазон размеров поддерживаемых точек
GLfloat step; // поддерживаемый инкремент размеров точек
// Получаем диапазон размеров поддерживаемых точек
// и размер шага
glGetFloatv(GL_POINT_SIZE_RANGE,sizes);
glGetFloatv(GL_POINT_SIZE_GRANULARITY,&step);
Здесь массив размеров будет содержать два элемента – наименьшее и наибольшее возможное значение glPointsize. Кроме того, шаг переменной будет равен наименьшему шагу, возможному между размерами точек. Спецификация OpenGL требует поддержки только одного размера точек – 1,0. Программная реализация OpenGL от Microsoft, например, позволяет менять размер точек от 0,5 до 10,0 с минимальным размером шага 0,125. Задание размера, не входящего в диапазон, не интерпретируется как ошибка. Вместо этого используется наибольший или наименьший поддерживаемый размер, ближайший к заданному значению. Точки, в отличие от других геометрических объектов, не меняются при делении на коэффициент перспективы. Т.е. они не становятся меньше при удалении от точки наблюдения, и не становятся больше при приближении к наблюдателю. Точки всегда являются квадратными. Даже используя glPointsize для увеличения размера точек, вы просто получите большие квадраты! Чтобы увидеть круглые точки, нужно использовать механизм сглаживания, о котором будет сказано ниже.
· Положение вершины в пространстве
Для рисования вершины используется команда glVertex, с помощью которой и задаются координаты для отрисовки. В общем виде команда записывается следующим образом
void glVertex[2 3 4][s i f d] (type coords)
либо
void glVertex[2 3 4][s i f d]v (type *coords)
Если точка должна быть изображена на плоскости, то для определения ее положения необходимы две координаты. В этом случае используется функция с двумя аргументами, например
glVertex2f(0, 0)
Буква f в названии данной функции определяет тип ее аргументов, в данном случае это вещественные числа типа float.
Если для отрисовки используется трехмерная сцена, то точка в пространстве определяется либо тремя координатами, например:
glVertex3f(0.5, 0.3,-0.7),
либо четырьмя координатами:
glVertex4f(0.5, 0.3,-0.7, 0.5)
На самом деле, каждая из этих команд задает четыре координаты для одной вершины: x, y, z, w, так называемые однородные координаты. Такой подход необходим для управления объектом в трехмерном объеме, но мы об этом поговорим позже. А сейчас вам достаточно знать, что, в зависимости от суффикса, программист может самостоятельно задавать определенную часть из 4 возможных координат, а остальные устанавливаются компилятором по умолчанию: координата z устанавливается равной 0, координата w – равной 1.
Координатные оси расположены так, что точка (0,0) находится в центре экрана, ось x направлена влево, ось y – вверх, а ось z – из экрана. Это расположение осей мировой системы координат, в которой задаются координаты вершин объекта, другие системы координат будут рассмотрены позже.
Команды glVertex должны размещаться между командными скобками. При этом количество точек может быть любым, зависящим от режима отрисовки примитива (см. ниже).
Аргументом функции glBegin для рисования точек является константа GL_POINTS.
· Цвет
Цвет отдельных вершин или примитивов может устанавливаться как вне командных скобок, так и внутри них. Для установки цвета используется команда
void glColor[3 4][b s i f] (GLtype components)
void glColor[3 4][b s i f]v (GLtype components).
Цифра 3 или 4 в суффиксе команды означает число аргументов. В случае, если установлен при инициализации флаг RGB, то цвет формируется как сумма 3 components: красного, зеленого и синего цветов в указанной пропорции. Если же был установлен флаг RGBA, то первые три параметра задают R, G, B компоненты цвета, а последний параметр будет определять коэффициент непрозрачности (альфа-компонента), с помощью которого можно управлять интенсивностью цвета в изображении.
Если в названии команды указан тип ‘f’ (float), то значения всех параметров должны принадлежать отрезку [0,1], при этом по умолчанию значение альфа-компоненты устанавливается равным 1.0, что соответствует полной непрозрачности. Тип ‘ub’ (unsigned byte) подразумевает, что значения должны лежать в отрезке [0,255].
По умолчанию значения компонент задаются в виде вещественных чисел в интервале [0; 1], например
glColor3f(0.3f, 0.5f, 0.1f)
Компоненты цвета могут быть заданы и в целочисленной форме, предельным значением в этом случае будет являться максимальное 8-битное целое без знака, например, белый цвет будет записан следующим образом:
glColor3i(214748647, 214748647, 214748647).
Однако предпочтительно использовать команду в вещественной форме, т.к. OpenGL хранит данные именно в вещественном формате.
Таблица значений аргументов для задания основных цветов :
glColor3f(0.0, 0.0, 0.0); black
glColor3f(1.0, 0.0, 0.0); red
glColor3f(0.0, 1.0, 0.0); green
glColor3f(1.0, 1.0, 0.0); yellow
glColor3f(0.0, 0.0, 1.0); blue
glColor3f(1.0, 0.0, 1.0); magenta
glColor3f(0.0, 1.0, 1.0); cyan
glColor3f(1.0, 1.0, 1.0); white
Вершинам можно назначать различные цвета, и, если включен соответствующий режим, то будет проводиться линейная интерполяция цветов по поверхности примитива. Opengl позволяет управлять процессом смешивания цветов с помощью функции
void glShadeModel (GLenum mode)
где при mode = GL_SMOOTH интерполяция включается (установка по умолчанию), а при mode = GL_FLAT, – отключается.
Остальные атрибуты – вектор нормали и текстурные координаты – будут рассмотрены позже, в соответствующих темах.
Примеры рисования точек:
// рисуем точки
glPointSize(2);
glBegin(GL_POINTS);
glColor3d(1,0,0);
glVertex3d(-4.5,4,0); // первая точка
glColor3d(0,1,0);
glVertex3d(-4,4,0); // вторая точка
glColor3d(0,0,1); // третья
glVertex3d(-3.5,4,0);
glEnd();
// десять точек по диагонали
glBegin (GL_POINTS);
For i := 0 to 9 do
glVertex2f (i / 5 - 1, i / 5 - 1);
glEnd;
//сто точек со случайными координатами и цветами
glBegin (GL_POINTS);
For i := 1 to 100 do begin
glColor3f (random, random, random);
glVertex2f (random * 2 - 1, random * 2-1);
end;
glEnd;
Еще один интересный пример, для реализации которого необходимо в разделе private описать две переменные:
xpos: GLfloat; // координаты курсора в системе координат OpenGL
ypos:
GLfloat;
Затем создать обработчик события MouseMove формы
xpos: = 2 * X / ClientWidth - 1;
ypos: = 2 * (ClientHeight - Y) / ClientHeight - 1;
Refresh; // перерисовываем окно
В обработчике события Paint необходимо описать локальную целочисленную переменную i и содержательную часть кода привести к виду
For
i: = 1 to 30 do begin // тридцать точек
glColor3f (random, random, random); // случайного цвета
glBegin (GL_POINTS}; // со случайными координатами
glVertex2f
(xpos + 0. 2 * random * sin (random (360)),
ypos + 0. 2 * random * cos (random (3 60)));
glEnd;
end;
Данный код позволит наблюдать появление облака разноцветных точек при движении курсора по поверхности формы. Обратите внимание, что обработчик движения мыши заканчивается вызовом Refresh – принудительной перерисовкой окна при каждом движении курсора.
Для рисования линий существует три режима: одиночные линии, ломаная, замкнутая ломаная.
Одиночная линия определяется двумя вершинами. Если требуется нарисовать несколько одиночных линий, то между командными скобками должны быть описаны координаты пар вершин, т.е. количество команд glVertex между командными скобками должно быть четным. В случае, если количество вершин нечетно – последняя вершина игнорируется.
Аргументом функции glBegin для рисования одиночных линий является константа GL_LINES.
Если требуется нарисовать ломаную линию, то в командных скобках используют константу GL_LINE_STRIP. Вершины, перечисленные между командными скобками, интерпретируются следующим образом: конечная точка первой линии является начальной точкой следующего звена ломаной и т.д. Количество вершин может быть как четным, так и нечетным. Ширина и тип ломаной линии задаются так же, как и для одиночных линий.
Для рисования замкнутой ломаной аргументом функции glBegin должна быть установлена константа GL_LINE_LOOP. Последний отрезок замкнутой ломаной в качестве начала имеет последнюю вершину списка, а в качестве конца – первую вершину.
Для линий Вы также можете изменять ширину, цвет, размер, сглаживание. Если вы зададите разные цвета для начала и конца линии, то ее цвет будет переливающимся. OpenGL по умолчанию делает интерполяцию.
Для задания толщины линии используется команда glLineWidth(). Аргументом является натуральное число, определяющее толщину в пикселах. Так же, как и при установке размера точки, команду установки толщины линии записывают за пределами командных скобок.
Для изменения типа линии используется команда
void glLineStipple (GLint factor, GLushort pattern)
Первый аргумент factor – это масштабный множитель, а второй pattern представляет собой шестнадцатеричную константу[3], определяющую шаблон штриховки (побитовым способом). Например, если его значение равно 255(0x00FF), то, чтобы вычислить задаваемую маску, переведем число в двоичную систему счисления: 0000000011111111, т.е. всего 16 бит. Старшие восемь установлены в ноль, значит, тут линии не будет. Младшие установлены в единицу, тут будет рисоваться линия. Если первый параметр, определяющий, сколько раз повторяется каждый бит, установить равным 2, то накладываемая маска будет выглядеть так:
00000000000000001111111111111111
Важно: команда glLineStipple должна быть указана вне операторных скобок.
ПРИМЕР:
glColor3f(1.0, 0.0, 0.0); // Установили красный цвет
glEnable(GL_LINE_SMOOTH);// Включение режима сглаживания
glLineWidth(3); // Установили толщину линии 3 пиксела
glLineStipple(1, 0xF0F0); // Тип линии – пунктирная
glEnable(GL_LINE_STIPPLE);// Разрешить изменение типа линии
glBegin(GL_LINES); // Режим рисования – одиночные линии
glVertex2f(-0.5, -0.7); // Начало первой линии
glVertex2f(0.0, 0.0); // Конец первой линии
glVertex2f(0.1, 0.9); // Начало второй линии
glVertex2f(0.3, -0.5); // Конец второй линии
glEnd(); // Конец рисования
glDisable(GL_LINE_SMOOTH);// Отключение режима сглаживания
Для рисования отдельных треугольников константа командных скобок: GL_TRIANGLES. Количество вершин, перечисленных между командными скобками должно быть кратно трем. Каждые три вершины определяют треугольник (рис. 3), поэтому лишние вершины в списке игнорируются.
а2
а1 а3
Рисунок 3. Отрисовка треугольника
![]() |
Здесь сторона a2a3 является общей стороной для первого и второго треугольников, сторона a3a4 – общей стороной второго и третьего треугольников и т.д. Если такую фигуру описывать с помощью одиночных треугольников, то необходимо задавать координаты всех вершин всех треугольников: a1, a2, a3, a2, a3, a4, a3, a4, a5, a4, a5, a6 – всего 12 вершин. Использование ленты треугольников позволяет не дублировать вершины при описании их координат. Изображенная на рисунке фигура может быть представлена лентой треугольников, координаты вершин перечисляются в следующем порядке: a1, a2, a3, a4, a5, a6 – достаточно 6 вершин.
![]() |
Схематично порядок обхода вершин при отрисовке разного типа треугольников можно представить следующим образом (рис. 6):
Рисунок 6. Обход вершин треугольников
Константа командных скобок для рисования отдельных четырехугольников: GL_QUADS.
а
Рисунок 7. Отрисовка четырехугольника
Четырехугольник определяется группой из 4-х вершин, следовательно, количество вершин, записанных между командными скобками, должно быть кратно 4. Каждая четверка вершин определяет отдельный четырехугольник. Лишние вершины игнорируются.
Если изображение создается из связанных четырехугольников (каждая пара четырехугольников имеет общую сторону), то используется примитив «лента четырехугольников» - GL_QUAD_STRIP (рис.8).
Рисунок 8. Примеры применения ленты четырехугольников
Четырехугольник задается для каждой, кроме двух первых, пары вершин. Вершины 2n-1, 2n, 2n+2 и 2n+1 задают четырехугольник n. Всего отобразится (N-2)/2 четырехугольников. Заметим, что порядок сборки связных четырехугольников отличается от порядка, применяемого для независимых четырехугольников.
Порядок обхода вершин четырехугольников представлен на рисунке 8: а – независимые четырехугольники, б – лента четырехугольников.
Рисунок 9. Порядок обхода вершин четырехугольников
Для рисования многоугольников в командных скобках используется константа GL_POLIGON. При этом вершины, указанные между командными скобками, определяют выпуклый многоугольник.
Многоугольник строится из связанных треугольников с общей вершиной, в качестве общей вершины берется первая вершина списка (см. рисунок 5: Веер треугольников).
![]() |
Важное уточнение: При отрисовке многоугольников следует иметь в виду наличие лицевых (передних) и обратных (задних) граней у каждой поверхности.
Грань считается лицевой, если вершины перечисляются в направлении против часовой стрелки (в положительном направлении для левосторонней системы координат), и обратной, если обход вершин производится по часовой стрелке (в отрицательном направлении для левосторонней системы координат).
Opengl предоставляет возможность управлять тем, какой тип граней отображать на экране. Для этого сначала надо установить соответствующий режим вызовом команды glEnable (GL_CULL_FACE), а затем выбрать тип отображаемых граней с помощью команды
void glCullFace (GLenum mode),
при mode = GL_FRONT из изображения удаляются все лицевые грани,
при mode = GL_BACK – из изображения удаляются все обратные грани (установка по умолчанию).
Построение невыпуклых полигонов
Важно запомнить: базовые команды OpenGL предназначены для построения только выпуклых фигур, поэтому сложные фигуры чаще всего рисуются этапами, по частям.
При необходимости изобразить невыпуклый многоугольник, достаточно представить его как набор выпуклых многоугольников, каждый из которых описывается в своих командных скобках. Например, отрисовка фигуры (рис. 11), возможна только с помощью разбиения ее на набор выпуклых полигонов, показанных на рисунке с помощью цвета.
Рисунок 11. Отрисовка невыпуклого многоугольника
Следовательно, фигура может быть описана как 2 примитива типа GL_POLIGON. Оптимальным будет разбиение невыпуклой фигуры на треугольники, поскольку построение треугольников, как правило, реализовано на аппаратном уровне.
Режим вывода многоугольников
Многоугольники можно отрисовать в нескольких видах: в точечном, в контурном виде (без заливки), и с заливкой. Для задания способа рисования используется функция
void PolygonMode ( enum face, enum mode ),
где face устанавливает тип многоугольников и может принимать значения
· GL_FRONT – для лицевых граней;
· GL_BACK – для обратных граней;
· GL_FRONT_AND_BACK для всех граней.
mode указывает, как будет рисоваться многоугольник.
· GL_POINT – при таком режиме будут отображаться только вершины многоугольников.
· GL_LINE – при таком режиме многоугольник будет представляться набором отрезков.
· GL_FILL – при таком режиме многоугольники будут закрашиваться текущим цветом с учетом освещения, и этот режим установлен по умолчанию.
Описанная команда помещается перед командными скобками.
Пример:
glColor3f(0,1.0,0);
glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
glBegin(GL_TRIANGLES);
glVertex2dv(xy2[0]);
glVertex2dv(xy2[1]);
glVertex2dv(xy2[2]);
glEnd();
glPolygonMode(GL_BACK,GL_LINE);
glBegin(GL_TRIANGLE_FAN);
glVertex2dv(xy2[3]);
glVertex2dv(xy2[4]);
glVertex2dv(xy2[5]);
glVertex2dv(xy2[6]);
glEnd();
Массив xy2 описан как
double xy2[10][2];
и проинициализирован функцией coords2 (вызывается в main):
void coords2()
{
double ip2, *ipart2=&ip2;
for(int i=0;i<zx;i++)
for(int j=0;j<2;j++)
xy2[i][j]=modf(pow(3.14,random(7)),ipart2);
}
Для установки режима сглаживания, впрочем, как и для установки любого другого режима в Opengl, перед командными скобками необходимо вызвать функцию glEnable() с соответствующей константой в качестве аргумента, а после командных скобок – функцию glDisable() с той же константой.
Говоря непосредственно о режиме сглаживания, выбор константы определяется примитивом, сглаживание которого должно быть включено (или отключено):
· GL_POINT_SMOOTH (сглаживание для точек),
· GL_LINE_SMOOTH (сглаживание линий),
· GL_POLYGON_SMOOTH (сглаживание для полигонов).
Зачем нужен этот режим? Например, размер, заданный в glPointSize, не является точным размером точки в пикселях, а приблизительным диаметром окружности, содержащей все пиксели, используемые для рисования точки. Вы указываете OpenGL рисовать точки как улучшенные (т.е. маленькие, закрашенные окружности), разрешая их сглаживание. Эта технология вместе со сглаживанием линий относятся к категории защиты от наложения (antialiasing). Защита от наложения – это технология, позволяющая сглаживать зазубренные края и округлять углы (рис. 12).
а) б)
Рисунок 12. Отрисовка точек с:
а – отключенным режимом сглаживания,
б – включенным режимом
сглаживания
Режимы закрашивания для многоугольников
Если при отрисовке многоугольников для разных вершин примитивов были заданы различные цвета, то фигуры окрашиваются градиентно, т.е. с плавным переходом цветов от вершины к вершине (рис.13). Это происходит из-за того, что по умолчанию способ сглаживания (смешивания, тонирования) задан плавным.
Рисунок 13. Градиентное закрашивание
Чтобы изменить способ сглаживания, необходимо перед командными скобками вызвать функцию
glShadeModel (GL_FLAT)
В этом случае связанные фигуры окрашиваются по правилу старшинства цвета второго примитива.
Методика выполнения лабораторной работы
Данная лабораторная работа является логичным продолжением предыдущей лабораторной работы, на которой для заданной информационной системы были выделены варианты ее использования и построены usе case диаграммы. Поэтому для реализации используется задание, полученное на предыдущей лабораторной работе.
Для успешного выполнения лабораторной работы необходимо использовать результаты предыдущей работы.
Варианты заданий
1. Построить точки, расположенные в вершинах правильного n-угольника. Установить режим сглаживания для точек. Экспериментально определить максимальный размер точки, при котором возможно сглаживание.
2. Используя примитив для вывода линий нарисовать правильный n-угольник. Изменить тип и ширину линий.
3. Используя примитив для вывода ломаной линии нарисовать фигуру, изображенную на рис.1 (см. таблица 4).
4. Используя примитив для вывода замкнутой ломаной нарисовать фигуру, изображенную на рис.2 (см. таблица 4).
5. Построить фигуру, изображенную на рис.2 (см. таблица 4), разбив ее на треугольники (каждый треугольник окрашен случайным цветом). Выполните три варианта построений с использованием примитивов:
А) треугольник;
Б) лента треугольников;
В) веер треугольников.
6. Используя примитив для вывода многоугольников построить правильный n-угольник.
7. Построить невыпуклый многоугольник, изображенный на рис.3, представив его в виде совокупности отдельных многоугольников, назначив каждому многоугольнику свой цвет. Посмотреть результат работы программы для различных способов тонирования.
8. Изменить программу предыдущей задачи таким образом, чтобы
· лицевые грани изображались только вершинами;
· лицевые грани изображались закрашенными, а обратные – линиями;
Таблица 4
Вариант 1 |
N=5 |
|
|
|
|
Вариант 2 |
N=7 |
|
|
|
|
Вариант 3 |
N=6 |
|
|
|
|
Вариант 4 |
N=4 |
|
|
|
|
Вариант 5 |
N=8 |
|
|
|
|
Содержание отчета
Результатом выполнения лабораторной работы должен стать отчет (в печатном и электронном вариантах), состоящий из следующих пунктов:
1) постановка задачи;
2) перечень функций Opengl, использованных в предлагаемом решении;
3) блок-схема алгоритма отрисовки сцены;
4) программный код предлагаемого решения;
5) скриншоты результатов работы программы.
Отчет лабораторной работы должен быть произведен студентом преподавателю в срок до начала следующего лабораторного занятия. Во время занятия преподавателю необходимо предоставить:
· отчет о лабораторной работе (в печатном виде);
· программную реализацию решения.
Оценка (в баллах) выставляется за устный отчет студента по предоставленным преподавателю материалам.
Пример выполнения лабораторной работы
Программа отрисовывает двумерную картинку. Для экономии места здесь приводится только часть функции, отвечающая непосредственно за отрисовку объектов. Данный фрагмент кода встраивается в шаблон программы, который был приведен в предыдущей лабораторной работе.
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
glClearColor(0, 100, 130, 255);
//домик
glColor3ub(255, 0, 0);
glBegin(GL_TRIANGLES);//крыша
glVertex2f(-0.6f, 0.6f); //верхняя вершина
glVertex2f(-0.9f, 0.2f); //левая вершина
glVertex2f(-0.3f, 0.2f); //правая вершина
glEnd();
glColor3ub(0, 0, 255);
glBegin(GL_POLYGON);//стена
glVertex2f(-0.79f, 0.2f);
glVertex2f(-0.79f, -0.4f);
glVertex2f(-0.41f, -0.4f);
glVertex2f(-0.41f, 0.2f);
glEnd();
glColor3ub(100, 130, 255);
glBegin(GL_POLYGON);//окно
glVertex2f(-0.7f, 0.1f);
glVertex2f(-0.7f, -0.15f);
glVertex2f(-0.5f, -0.15f);
glVertex2f(-0.5f, 0.1f);
glEnd();
glColor3ub(0, 0, 255);
glLineWidth(5);
glBegin(GL_LINES);//рама окна
glVertex2f(-0.6f, 0.1f);
glVertex2f(-0.6f, -0.15f);
glVertex2f(-0.6f, 0.0f);
glVertex2f(-0.5f, 0.0f);
glEnd();
GLfloat the;//окно в крыше
GLfloat pi = acos(-1.0);
GLfloat radius = 2.0f; // радиус
GLfloat step = 0.5f; // чем больше шаг тем хуже диск
glColor3ub(100, 130, 255);
glBegin(GL_TRIANGLE_FAN);
for (GLfloat a = 0.0f; a < 360.0f; a += step) {
theta = 2.0f * pi * a / 180.0f;
glVertex2f(radius * cos(theta)/50-0.6f, radius * sin(theta)/25+0.4f);
}
glEnd();
//ёлки-иголки
float x = 0.0f, y=0.0f;
for (int i = 0; i < 4; i++)
{
glColor3ub(0, 150, 0);
glBegin(GL_TRIANGLES);
glVertex2f(0.0f, 0.5f-y);
glVertex2f(-0.1f-x, 0.3f-y);
glVertex2f(0.1f+x, 0.3f-y);
glEnd();
x += 0.05f; y += 0.15;
}
x = 0.0f, y = 0.0f;
for (int i = 0; i < 4; i++)
{
glColor3ub(0, 100, 0);
glBegin(GL_TRIANGLES);
glVertex2f(0.3f, 0.6f - y);
glVertex2f(0.2f - x, 0.4f - y);
glVertex2f(0.4f + x, 0.4f - y);
glEnd();
x += 0.03f; y += 0.12;
}
glColor3ub(150, 100, 0);
glLineWidth(50);
glBegin(GL_LINES);
glVertex2f(0.0f, -0.1f);
glVertex2f(0.0f, -0.35f);
glVertex2f(0.3f, 0.1f);
glVertex2f(0.3f, -0.1f);
glEnd();
glColor3ub(255, 237, 0);
glBegin(GL_TRIANGLE_FAN);
for (GLfloat a = 0.0f; a < 360.0f; a += step) {
the = 2.0f * pi * a / 180.0f;
glVertex2f(radius*cos(theta)/20+0.8f,radius*sin(the)/10+0.6f);
}
glEnd();
glLineWidth(50);
glBegin(GL_LINES);
glVertex2f(0.5f, 0.6f);
glVertex2f(0.68f, 0.6f);
glVertex2f(0.55f, 0.35f);
glVertex2f(0.7f, 0.5f);
glVertex2f(0.6f, 0.1f);
glVertex2f(0.72f, 0.4f);
glVertex2f(0.75f, 0.05f);
glVertex2f(0.8f, 0.35f);
glVertex2f(0.9f, 0.35f);
glVertex2f(0.98f, 0.15f);
glVertex2f(0.6f, 0.9f);
glVertex2f(0.7f, 0.72f);
glEnd();
SwapBuffers(hDC);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Скриншот работы программы:
Дополнительный материал
// отрисовка диска 1 вариант
void drawCircle(float x, float y, float r, int amountSegments)
{
glBegin(GL_LINE_LOOP);
for (int i = 0; i < amountSegments; i++)
{
float angle = 2.0 * 3.1415926 * float(i) / float(amountSegments);
float dx = r * cosf(angle);
float dy = r * sinf(angle);
glVertex2f(x + dx, y + dy);
}
glEnd();
}
/*********************************************************/
// отрисовка диска 2 вариант
GLfloat theta;
GLfloat pi = 3.14f;
GLfloat radius = 0.1f; // радиус
GLfloat step = 3.0f; // чем больше шаг, тем хуже диск
// рисуем диск по часовой стрелке GL_CW
glBegin(GL_TRIANGLE_FAN);
for (GLfloat a = 0.0f; a < 360.0f; a += step)
{
theta = 1.0f * pi * a / 180.0f;
glColor4f(a / 360.0f, 1.0f, 1.0f - a / 360.0f, 1.0f);
glVertex2f(radius * cos(theta)/2, radius * sin(theta));
}
glEnd();
/**********************************************************/
// десять точек по диагонали
glBegin
(GL_POINTS);
For i := 0 to 9 do
glVertex2f (i / 5 - 1, i / 5 - 1);
glEnd;
/*********************************************************/
//100 точек со случайными координатами и цветами:
glBegin (GL_POINTS);
For i := 1 to 100 do begin
glColor3f (random, random, random); glVertex2f (random * 2 - 1, random *
2-1);
end;
glEnd;
/*********************************************************/
// точки рядом с курсором при движении
xpos:
GLfloat; // координаты курсора в системе координат
OpenGL ypos: GLfloat;
//
создать обработчик события MouseMove формы
xpos: = 2 * X / ClientWidth - 1;
ypos: = 2 * (ClientHeight - Y) / ClientHeight - 1;
Refresh; // перерисовываем окно
// в обработчике Paint описать локальную целочисленную
//переменную i и
For
i: = 1 to 30 do
begin // тридцать точек
glColor3f (random, random, random); //
случайного цвета
glBegin (GL_POINTS};
// со
случайными координатами вокруг курсора
glVertex2f (xpos + 0. 2
* random * sin (random (360)),
ypos + 0. 2 * random * cos (random (3 60)));
glEnd;
end;
/*Если все сделано правильно, то при движении курсора по поверхности формы рядом с курсором должно появляться облачко разноцветных точек. Обратите внимание, что обработчик движения мыши заканчивается вызовом Refresh - принудительной перерисовкой окна при каждом движении курсора.*/
/**********************************************************/
Кроме рассмотренных стандартных примитивов в библиотеках GLU и GLUT описаны более сложные фигуры,
· такие, как сфера, цилиндр, диск (в GLU)
· и сфера, куб, конус, тор, тетраэдр, додекаэдр, икосаэдр, октаэдр и, внезапно, чайник (в GLUT).
Рисунок 14. Чайник из библиотеки giut.h
Автоматическое наложение текстуры предусмотрено только для фигур из библиотеки.
Например, чтобы нарисовать сферу или цилиндр, надо сначала создать объект специального типа GLUquadricObj с помощью команды
GLUquadricObj* gluNewQuadric (void);
а затем вызвать соответствующую команду:
void gluSphere (GLUquadricObj * qobj, GLdouble radius,
GLint slices, GLint stacks)
void gluCylinder (GLUquadricObj * qobj,
GLdouble baseRadius,
GLdouble topRadius,
GLdouble height, GLint slices,
GLint stacks),
где параметр slices задает число разбиений вокруг оси z, а stacks – вдоль оси z.
Важно отметить, что для корректного построения перечисленных примитивов необходимо удалять невидимые линии и поверхности, для чего надо включить соответствующий режим вызовом команды glEnable (GL_DEPTH_TEST).
Контрольные вопросы
1. Что такое командные скобки, каково их назначение?
2. Какие константы библиотеки OpenGL могут быть параметрами функции glBegin?
3. Что такое антиэлайзинг (anti-aliasing), для чего он служит?
4. Какие режимы существуют для рисования линий?
5. Какие режимы существуют для изображения треугольников, чем они различаются?
6. Что такое выпуклые и невыпуклые многоугольники?
7. Каким образом можно построить невыпуклый многоугольник?
8. Чем отличаются лицевые и обратные грани?
9. Какая команда изменяет способ тонирования?
10. Какие режимы вывода многоугольников вам известны? В каком месте программы должна быть записана команда, изменяющая режим вывода многоугольников?
Список литературы
1) Тарасов И. Opengl в России. – Режим доступа : http://www.helloworld.ru/texts/comp/games/opengl/opengl2/index.html; (скачать : http://www.read.in.ua/book120840/?razdel=11&p=47).
2) OpenGL. – Режим доступа : https://www.opengl.org.
3) Верма Р. Д. Введение в Opengl . – Москва : Горячая линия – Телеком, 2015. – 303 с.
4) Баяковский Ю.М., Игнатенко А.В., Фролов А.И. Графическая библиотека OpenGL.: Учебно-методическое пособие. – Москва : ВМиК МГУ, 2003. – 132 с.
5) Гайдуков С. Профессиональное программирование трехмерной графики на С++. – Санкт-Петербург : БХВ-Петербург, 2004. – 736 с.
6) Боресков А. Графика трехмерной компьютерной игры на основе OpenGL. – Москва : Диалог-МИФИ, 2005.
7) Херн Д., Бейкер М. Компьютерная графика и стандарт OpenGL. – Москва : ИД «Вильямс», 2005.
8) Программирование компьютерной графики средствами OpenG : Документация, статьи, советы. – Режим доступа : opengl.gamedev.ru.
Оглавление
ВВЕДЕНИЕ
OpenGL: инициализация, построение двумерных примитивов в отдельном окне
Теоретическая информация
Методика выполнения лабораторной работы
Варианты заданий
Содержание отчета
Пример выполнения лабораторной работы
Дополнительный материал
Контрольные вопросы
OpenGL: Разработка приложения для визуализации связного набора двумерных примитивов
Теоретическая информация
Методика выполнения лабораторной работы
Варианты заданий
Содержание отчета
Пример выполнения лабораторной работы
Дополнительный материал
Контрольные вопросы
Список литературы
Скачано с www.znanio.ru
[1] В программе на C++ указатель записан в виде &pfd, в программе на Delphi следует записать @pfd.
[2] В Delphi вместо символа «|» используется «or».
[3] В Delphi шаблон запишется в виде, например $F0F0.
Материалы на данной страницы взяты из открытых источников либо размещены пользователем в соответствии с договором-офертой сайта. Вы можете сообщить о нарушении.