Міністерство освіти і науки України
Національний університет "Львівська політехніка"
Інститут комп’ютерних технологій , автоматики та метрології
Кафедра безпеки інформаційних технологій
ПРОГРАМУВАННЯ КОМП'ЮТЕРНОЇ ГРАФІКИ З ВИКОРИСТАННЯМ БІБЛІОТЕКИ OpenGL
Лабораторний практикум
Львів
2010
УДК 681.3.06 (075)
Л.В.
Програмування комп'ютерної графіки з використанням бібліотеки ОpenGL: Лабораторний практикум /, Л.В.
; НУЛП. – Львів., 2010.
Приведений опис комплексу лабораторних робіт по курсу «Програмування комп'ютерної графіки» для студентів спеціальності ******. У лабораторних роботах практикуму необхідно програмно генерувати зображення засобами багатоплатформової графічної бібліотеки openGL. Опис лабораторних робіт містить необхідний довідковий матеріал, зв'язаний з використанням бібліотеки.
Для студентів, що мають навики програмування на C++ або Delphi.
УДК 681.3.06 (075)
Рецензент: .
Затверджено
редакційно-видавничою
радою університету
© Автори, 2010
© НУЛП, 2010
Передмова
Практикум містить сім лабораторних робіт, що відображають основні можливості програмування графіки з використанням багатоплатформової бібліотеки OPENGL.
Даний посібник призначений для студентів, що вивчають програмування, для яких знання можливостей сучасних графічних бібліотек є необхідним компонентом професійних знань.
В даний час найбільш відомими і широко поширеними є графічні бібліотеки DIRECTX і OPENGL. Перша бібліотека є внутрішнім стандартом фірми Microsoft і сильно прив'язана до операційної системи Windows. Бібліотека OPENGL в даний час є відкритим міжнародним стандартом. Саме з цієї причини їй віддана перевага при складанні даного практикуму.
Теоретичні відомості, що відносяться до алгоритмічного аспекту графічного виводу, висвітлені в конспекті лекцій з дисципліни «Програмування комп'ютерної графіки» і в практикумі не розглядаються. У вказівках до лабораторних робіт є посилання на лекції, що містяться в конспекті, а також на додаткові джерела інформації по даній темі.
Для виконання лабораторних робіт практикуму студенти повинні мати навики програмування на мові C++, а також первинні знання про операційну систему Windows і особливості програмування під Windows. Хоча бібліотека OPENGL є багатоплатформовою, тобто не залежить від операційної системи, в роботах передбачається використовувати можливості саме операційної системи Windows, оскільки в даний момент вона є найбільш поширеною.
Всі завдання практикуму можуть бути виконані також на основі середовища візуального програмування Delphi. Деякі відмінності в синтаксисі окремих команд коментуються в тексті відповідними виносками. У Додатку 1 приведений код мінімальної програми з використанням бібліотеки OPENGL на мові C++; Додаток 2 містить варіант коду мінімальної програми на Delphi.
Кожна лабораторна робота містить набір обов'язкових і додаткових завдань. Обов'язкові завдання виконуються відповідно до варіанту. Додаткові завдання пропонує викладач в індивідуальному порядку.
При захисті лабораторної роботи студенти повинні надати працездатні програми за кожним завданням даної лабораторної роботи, звіт, що містить початкові коди розроблених програм і відповіді на контрольні питання.
Лабораторна робота № 1.
Підключення бібліотек; контекст пристрою, контекст відтворення; загальний вид програми.
Мета роботи.
Створення програми-заготовки для роботи з бібліотекою openGL.
Необхідні теоретичні відомості.
Використання бібліотеки OPENGL вимагає навиків програмування на C++, а також знання основ подієвого програмування на рівні операційної системи Windows.
Для виконання роботи необхідно мати уявлення про основні принципи формування зображення на екрані, про сучасні стандарти і інтерфейси програмування комп'ютерної графіки ([7] – лекція 17), знати особливості представлення кольору в колірній моделі RGB ([7] – лекція 4).
Основні поняття, використовувані в даній лабораторній роботі: контекст пристрою, контекст відтворення, формат піксела.
Контекст графічного пристрою (Device Context) указує площину відображення, на яку здійснюється графічний вивід: вікно програми на екрані дисплея, сторінка принтера або інше місце, куди може бути направлений графічний вивід. Якщо програма викликає різні графічні функції, такі як малювання крапок, ліній, фігур і ін., необхідно указувати ідентифікатор контексту (hdc – handle of device context) і координати. Сенс використання контексту пристрою полягає в тому, що вивід на різні пристрої здійснюється одними і тими ж функціями, змінюється лише значення hDC. «Контекст пристрою є структурою, яка визначає комплект графічних об'єктів і пов'язаних з ними атрибутів і графічні режими, що впливають на вивід». При складанні програми необхідно набути цього числового значення перед малюванням, а після малювання – звільнити контекст.
У OPENGL існує поняття контекст відтворення (контекст рендеринга), аналогічне поняттю контекст пристрою. Графічна система OPENGL також потребує посилання на пристрій, на який здійснюватиметься вивід. Це спеціальне посилання на контекст відтворення – величина типа HGLRC (handle openGL rendering context, посилання на контекст відтворення OPENGL) - hRC.
Перш ніж отримати контекст відтворення, сервер OPENGL повинен отримати детальні характеристики використовуваного устаткування. Ці характеристики зберігаються в спеціальній структурі – опис формату піксела. Формат піксела визначає конфігурацію буфера кольору і допоміжних буферів.
Порядок виконання роботи.
Програма, яка буде створена в результаті виконання даної лабораторної роботи, повинна здійснювати наступні дії: створювати порожнє вікно для графічного виводу засобами openGL; встановлювати всі необхідні параметри графічного виводу; реагувати на натиснення клавіші <ESC> для закриття вікна і завершення роботи. Для демонстрації графічного виводу в кінці лабораторної роботи у вікні буде побудовано деяке зображення. Створена в ході виконання лабораторної роботи програма буде основою для виконання всіх подальших робіт.
Створіть нове застосування в Visual C++.
Додайте для зборки проекту бібліотеки OPENGL. Для цього:
у меню Project/setting, виберіть закладку LINK;
у рядку "Object/Library Modules" додайте рядок "OpenGL32.lib GLu32.lib GLaux.lib"
для завершення клацніть лівою клавішею миші по кнопці OK.
Введіть в текст коду наступні рядки (вони повідомляють компілятору які бібліотечні файли слід використовувати):
#include <windows.h> // Заголовний файл для Windows
#include <gl\gl.h> // Заголовний файл для бібліотеки OpenGL32
#include <gl\glu.h> // Заголовний файл для бібліотеки GLu32
#include <gl\glaux.h> // Заголовний файл для бібліотеки GLaux
Ініціалізуйте всі змінні, які будуть використані в програмі.
Перші два рядки встановлюють контекст відтворення (рендеринга) і контекст пристрою. Контекст рендеринга OPENGL визначений як hRC і пов'язує виклики OPENGL з вікном Windows. Для того, щоб малювати у вікні, необхідно створити контекст пристрою Windows, який визначений як hDC. Контекст пристрою зв'язує вікно з GDI. Контекст відтворення пов'язує OPENGL з контекстом пристрою.
static HGLRC hRC; // Постійний контекст рендерингу
static HDC hDC; // Приватний контекст пристрою GDI
Оголосите масив для відстежування натиснення клавіш на клавіатурі (вказаний нижче спосіб дозволяє відстежувати натиснення декількох клавіш одночасно).
BOOL keys[256]; // Масив для процедури обробки клавіатури
У наступній секції коди будуть вироблені всі налаштування для OPENGL Ця процедура може бути викликана тільки після того, як буде створено вікно OPENGL. Встановимо колір, яким буде очищений екран. Всі значення можуть бути в діапазоні від 0.0f до 1.0f, при цьому 0.0 найтемніший, а 1.0 найсвітліший. Перші три параметри визначають колір в моделі RGB: перший - інтенсивність червоного, другого – зеленого, третього – синього. Найбільше значення – 1.0f, є найяскравішим значенням даного кольору. Останній параметр – альфа-значення (прозорість) – доки буде рівним 0.0f.
GLvoid INITGL(GLsizei Width, GLsizei Height) //Викликати після створення вікна GL
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Очищення екрану в чорний колір
}
Наступна секція коди – функція масштабування сцени, OPENGL, що викликається, кожний раз, коли змінюється розмір вікна. Навіть якщо розміри вікна не змінюються (наприклад, в повноекранному режимі), ця процедура все одно має бути викликана хоч би один раз (зазвичай під час запуску програми), оскільки сцена масштабується, грунтуючись на ширині і висоті вікна, що відображується.
GLvoid ReSizeGLScene(GLsizei Width, GLsizei Height)
{
if (Height==0) // Запобігання діленню на нуль
//якщо вікно дуже мале
Height=1;
glViewport(0, 0, Width, Height);
// Скидання поточної області виводу і перспективних перетворень
}
Наступна секція призначена для малювання сцени. Згодом код додаватиметься саме в цю секцію програми. Поки запишемо в цій секції тільки команду для очищення екрану кольором, який ми визначили вище. Команди малювання слідуватимуть за нею.
GLvoid DrawGLScene(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT); // очищення екрану
}
Одна з найважливіших секцій коди встановлює параметри вікна Windows, формат пікселя, обробляє повідомлення при зміні розмірів вікна, при натисненні на клавіші, і закритті програми.
Перші чотири рядки роблять наступне: змінна hWnd – є покажчиком на вікно. Змінна message – повідомлення, передавані програмі системою. Змінні wParam і lParam містять інформацію, яка посилається разом з повідомленням, наприклад таку як ширина і висота вікна.
LRESULT CALLBACK WndProc( HWND hWnd
UINT message
WPARAM wParam
LPARAM lParam)
Код між дужками встановлює формат пікселів. Формат пікселя визначає те, як OPENGL виводитиме у вікно. Велика частка коди ігнорується, але проте необхідна.
{
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 // Маски шару ігноруються
};
Наступна секція призначена для обробки системних повідомлень: вихід з програми, натиснення клавіш, переміщення вікна і так далі, кожна секція "case" обробляє свого типа повідомлення.
switch (message) // Тип повідомлення
{
WM_CREATE указує програмі, що повідомлення має бути створене. Спочатку слід запитати DC (контекст пристрою) для вікна – без нього малювання у вікні неможливе. Потім запрошується формат пікселя. Комп'ютер вибиратиме формат, який повністю збігається або найбільш близький до запрошуваного формату.
case WM_CREATE:
hDC = GETDC(hWnd); // Отримати контекст пристрою для вікна
PixelFormat = ChoosePixelFormat(hDC &pfd);
// Знайти найближчий збіг для формату пікселів
Якщо відповідний формат пікселя не знайдений, буде виведено повідомлення про помилку.
if (!PixelFormat)
{
MessageBox(0
"Не знайдений відповідний формат піксела.",
"Ошибка",mb_ok|mb_iconerror);
PostQuitMessage(0);
// Це повідомлення говорить, що програма винна завершиться
break; // Предтовращеніє повтору коди
}
Якщо відповідний формат знайдений, комп'ютер намагатиметься встановити формат пікселя для контексту пристрою. Якщо формат пікселя не може бути встановлений з якоїсь причини, з'явиться повідомлення про помилку, що формат пікселя не встановлений.
if(!SetPixelFormat(hDC,PixelFormat,&pfd))
{
MessageBox(0,"Формат пікселя не встановлений.",
"Помилка",mb_ok|mb_iconerror);
PostQuitMessage(0);
break;
}
Якщо код записаний, як показано вище, буде створений контекст пристрою (DC), і встановлений відповідний формат пікселя.
Далі слід створити Контекст Рендерінга (RC), для цього OPENGL використовує DC. Функція wglCreateContext захоплює Контекст Рендерінга і зберігає його в змінній hRC. Якщо з якоїсь причини Контекст Рендерінга недоступний, повинне з'явитися повідомлення про помилку.
hRC = wglCreateContext(hDC);
if(!hRC)
{
MessageBox(0,"Контекст відтворення не створений.",
"Помилка",mb_ok|mb_iconerror);
PostQuitMessage(0);
break;
}
Необхідно зробити активним Контекст Рендеринга, для того, щоб можна було малювати у вікні засобами OPENGL. Якщо з якої-небудь причини це неможливо, повинне з'явитися повідомлення про помилку.
if(!wglMakeCurrent(hDC, hRC))
{
MessageBox(0,"Неможливо активізувати GLRC.",
"Ошибка",mb_ok|mb_iconerror);
PostQuitMessage(0);
break;
}
Створимо область малювання OPENGL. За допомогою функції GetClientRect можна визначити ширину і висоту вікна. Після того, як ширина і висота вікна отримані, ініціалізували екран OPENGL. Це досягається за допомогою виклику функції INITGL з шириною і висотою вікна як параметри.
GetClientRect(hWnd &Screen);
INITGL(Screen.right, Screen.bottom);
break;
Наступний фрагмент коди необхідний для знищення вікна. Він використовує повідомлення WM_DESTROY і WM_CLOSE. Програма посилатиме це повідомлення при виході з програми по натисненню ALT-F4 або при помилці (PostQuitMessage(0)).
Функція ChangeDisplaySettings(NULL,0) відновлює режим робочого столу (роблячи його таким, яким воно було до переходу в повноекранний режим).
Функція RELEASEDC(hWnd,hDC) знищує контекст пристрою вікна.
Перераховані дії знищують вікно OPENGL.
case WM_DESTROY:
case WM_CLOSE:
ChangeDisplaySettings(NULL, 0);
wglMakeCurrent(hDC,NULL);
wglDeleteContext(hRC);
RELEASEDC(hWnd,hDC);
PostQuitMessage(0);
break;
Опишемо обробку повідомлень, що виникають при натисненні клавіш.
Повідомлення WM_KEYDOWN виникає всякий раз при натисненні клавіші. Клавіша, яка натискувала, зберігається в змінній wParam. При натисненні клавіші елемент масиву, відповідний коду клавіші, що натискує, набуває значення TRUE.
case WM_KEYDOWN:
keys[wParam]= TRUE;
break;
Повідомлення WM_KEYUP викликається всякий раз при відпуску клавіші. Клавіша, яка віджата, також зберігається в змінній wParam. При відпуску клавіші відповідний елемент масиву приймає значення FALSE.
case WM_KEYUP:
keys[wParam]= FALSE;
break;
У завершенні програми слід обробити зміну розмірів вікна. Навіть при запуску програми в повноекранному режимі цей код необхідний, без нього екран OPENGL не з'явиться.
Всякий раз повідомлення WM_SIZE посилається Windows з двома параметрами - нова ширина, і нова висота екрану. Ці параметри збережені в LOWORD(lParam) і HIWORD(lParam). Виклик функції ReSizeGLScene змінює розміри екрану, тобто передає висоту і ширину в цю секцію коди.
case WM_SIZE:
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));
break;
Наступний код необхідний для обробки Windows всіх повідомлень, що поступили, і завершення процедури.
default:
return (DefWindowProc(hWnd, message, wParam, lParam));
}
return (0);
}
Наступна процедура необхідна для створення і реєстрації вікна Windows.
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
LPSTR lpCmdLine,int nCmdShow)
{
MSG msg; // Структура повідомлення Windows
WNDCLASS wc; // Структура класу Windows для установки типа вікна
HWND hWnd; // Збереження дескриптора вікна
Прапори стилю CS_HREDRAW і CS_VREDRAW служать для перемальовування вікна при його переміщенні. CS_OWNDC створює прихований DC для вікна, тобто DC не може використовуватися спільно декільком додатками. WndProc - процедура, яка перехоплює повідомлення для програми. Властивість hIcon встановлена рівною нулю, тобто ікона вікна не потрібна; для миші використовується стандартний покажчик. Фоновий колір не має значення (ми встановимо його в GL). Якщо меню в цьому вікні не потрібне, то встановимо його значення в NULL. Ім'я класу – це будь-яке ім'я.
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "OPENGL WinClass";
Якщо при реєстрації класу сталася помилка, з'явиться відповідне повідомлення.
if(!RegisterClass(&wc))
{
MessageBox(0,"Помилка реєстрації класу вікна.",
"Ошибка",mb_ok|mb_iconerror);
return FALSE;
}
Створимо вікно. Проте OPENGL буде викликана тільки після того, як буде послано повідомлення WM_CREATE. Прапори WS_CLIPCHILDREN і WS_CLIPSIBLINGS потрібні для OPENGL і мають бути додані саме тут.
hWnd = CreateWindow(
"OPENGL WinClass"
"Це мінімальна програма OPENGL" // Заголовок зверху вікна
WS_POPUP |
WS_CLIPCHILDREN |
WS_CLIPSIBLINGS
0, 0, // Позиція вікна на екрані
640, 480, // Ширина і висота вікна
NULL
NULL
hInstance
NULL);
Далі слідує звичайна перевірка на помилки. Якщо вікно не було створене з якоїсь причини, повідомлення про помилку слід вивести на екран. При цьому генерується вікно з повідомленням про помилку і пропозицією завершити програму.
if(!hWnd)
{
MessageBox(0,"Помилка створення вікна.","Ошибка",MB_OK|MB_ICONERROR);
return FALSE;
}
Для того, щоб можна було перейти в повноекранний режим необхідно слідувати правилу: ширина і висота в повноекранному режимі повинні збігатися з шириною і висотою, які встановлені для вікна виводу.
DEVMODE dmScreenSettings; // Режим роботи
memset(&dmScreenSettings, 0, sizeof(DEVMODE));
// Очищення для зберігання установок
dmScreenSettings.dmSize = sizeof(DEVMODE);// Розмір структури Devmode
dmScreenSettings.dmPelsWidth = 640; // Ширіна екрану
dmScreenSettings.dmPelsHeight = 480; // Висота екрану
dmScreenSettings.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;// Режим Піксела
ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
// Перемикання в повний екран
Функція ShowWindow показує створене вікно.
Функція UpdateWindow оновлює вікно, SetFocus робить вікно активним, і викликає wglMakeCurrent(hDC,hRC) щоб переконатися, що контекст рендеринга не є вільним.
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
SetFocus(hWnd);
Для того, щоб програма не завершувала свою роботу відразу ж після промалювання зображення, створимо нескінченний цикл. Для виходу з циклу можна використовувати натиснення клавіші ESC. При цьому програмі буде відправлено повідомлення про вихід, і вона закінчить свою роботу.
while (1)
{
// Обробка всіх повідомлень
while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
{
if (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
return TRUE;
}
}
Функція DrawGLScene викликає ту підпрограму, яка фактично малює об'єкти OPENGL. Поки залишимо цю частку секції порожньою, все що буде зроблене - очищення екрану чорним кольором.
SwapBuffers(hDC) дуже важлива команда. Ми маємо вікно зі встановленою подвійною буферизацією. Це означає, що зображення малюється на прихованому вікні (званим буфером). Потім за допомогою команди перемикання буферів прихований буфер копіюється на екран. При цьому виходить плавна анімація без ривків, і користувач не помічає процесу малювання об'єктів.
DrawGLScene(); // Намалювати сцену
SwapBuffers(hDC); // Перемкнути буфер екрану
if (keys[VK_ESCAPE]) SendMessage(hWnd,WM_CLOSE,0,0);
// Якщо ESC - вийти
}
}
Потрібно відзначити, що цей код не буде скомпільований на Cі, він має бути збережений як .CPP файл.
Скомпілюйте і виконаєте проект. Отже, наша програма створює вікно, розміром 640х480, очищає його чорним кольором і чекає натиснення клавіші ESC (Alt+F4) для закриття вікна.
Спробуємо намалювати що-небудь в цьому вікні. Додайте в розділ DrawGLScene() наступний код:
{
glClear(GL_COLOR_BUFFER_BIT); // очищення екрану
glPointSize(2); //розмір крапки
glBegin(GL_POINTS);
glColor3d(1,0,0);
glVertex3d(-0.45,-0.4,0); // перша крапка
glColor3d(0,1,0);
glVertex3d(0.4,0.4,0); // друга крапка
glColor3d(0,0,1);
glVertex3d(-0.35,0.4,0); // третя крапка
glEnd();
}
Скомпілюйте і виконаєте проект. Збережіть результат вашої роботи в своїй папці. Цей шаблон використовуватиметься у вашій подальшій роботі.
Контрольні питання.
Що розуміється під контекстом пристрою?
Що є контекстом відтворення?
Які основні фрагменти повинна містити підпрограма OPENGL.
Що входить в поняття формат пікселя?
Які бібліотечні файли мають бути підключені до програми для роботи з OPENGL?
Яка колірна модель використовується при визначенні кольору в даній програмі?
Які параметри слід вказати, щоб колір фону був зеленим?
Для чого потрібна функція масштабування сцени?
У якій секції коди можна записувати команди малювання сцени?
Яке правило необхідно дотримувати, щоб мати можливість переходу в повноекранний режим?