Тривимірна графіка своїми руками

Що таке тривимірна графіка і де вона застосовується, знає зараз кожна дитина Що таке тривимірна графіка і де вона застосовується, знає зараз кожна дитина. Найпопулярніші комп'ютерні ігри, такі як DOOM, Quake, Counter Strike, наочно демонструють моделі тривимірних світів. Як їх створюють, якими вони володіють можливостями і чим обмежені - ось що лежить в тіні загальновідомого і представляє дійсний інтерес.
Існує кілька ідеологічно різних підходів до реалізації 3D-графіки. Одні з них кращі для задач, що вимагають високої швидкості обробки, інші надають зміну неймовірної краси і реалістичні результати, треті об'єднують гідності перших двох, але вимагають найсучаснішого обладнання. У цій статті ми уважно розглянемо один з підходів, а в подальшому розкриємо секрети ще двох.
Класичним засобом для вирішення завдань тривимірного моделювання служить графічна бібліотека OpenGL (Open Graphics Library). Ця бібліотека є інтерфейсом до функцій графічного процесора і тим самим забезпечує досить ефективну (швидку) обробку запитів. Питання часу завжди дуже гостро стоїть у всьому, що пов'язано з обробкою графіки, тим більше якщо мова йде про анімованих сюжетах, розробку яких ми і розглянемо в цій статті. Ось чому дуже великою як для створення, так і для перегляду цих проектів є наявність досить хорошою відеокарти. Це не означає, що вам обов'язково знадобиться графічний процесор GeForce останньої моделі, але потужності засобів, вбудованих за умовчанням в літній ноутбук, може не вистачити.
Один з кращих способів чогось навчитися - просто зробити це. Давайте створимо сцену, яка була пустелю, по якій котиться перекотиполе, а потім починається дощ.

Перш ніж почати
Бібліотека OpenGL недосконала. Переконатися в цьому зовсім нескладно, так як на шляху створення навіть найпростішого проекту вас чекають численні терни. Я хочу провести вас через них з мінімальними втратами. Проект буде створюватися в MS Visual Studio 2003 але все те ж саме можна повторити майже без змін в будь-який інший середовищі розробки. Виняток становлять тільки Unix-подібні операційні системи і платформа Mac, бібліотеки для яких з цілком зрозумілих причин виглядають інакше. Ми будемо використовувати мову розробки C ++, але так як класова структура вводитися не буде, можна вважати, що це практично чистий Сі.
Крім базової бібліотеки, поставляються разом з операційною системою, ми будемо використовувати дуже зручне доповнення - бібліотеку GLUT. Тому перш за все потрібно подбати про правильне розташування всіх необхідних файлів в системі (файли можна взяти на «Мир ПК-диску»).
Тепер створимо проект консольного застосування (VC ++ Console Application). У створений каталог, де лежать файли проекту, додамо файл glut.h і приєднаємо його до проекту: File • Add Existing Item • glut.h add.

Відомості про функціонування бібліотеки
Бібліотеку OpenGL можна сприймати як автомат, який в кожен момент часу знаходиться в деякому стані і відповідно до нього виконує одержувані запити. Іншими словами, у бібліотеки є набір внутрішніх змінних, значення яких використовуються при виклику її функцій. Значення цих змінних можна міняти, домагаючись тим самим потрібної дії відповідної функції. Наприклад, процедура

glColor3f (0.0,0.0,1.0);

змінить поточне значення кольору на синій. Після виконання цієї команди все створювані об'єкти будуть синього кольору.
Для забезпечення ефекту анімації, тобто руху об'єктів і зміни їх властивостей, виконується постійне оновлення картинки. Швидкість перемальовування сцени (кількість разів на секунду) залежить насамперед від продуктивності графічного процесора і, звичайно, від покладених на нього завдань. Відповідно змінюється швидкість відтворення. Щоб забезпечити рівномірне відтворення ролика, оновлення можна штучно сповільнити, прив'язавши його до системних годинах.
Механізм поновлення в циклі реалізується з використанням чотирьох функцій:
void init (void); - це дії, що виконуються попередньо, до запуску основного циклу, наприклад ініціалізація змінних, установка параметрів;
void reshape (int, int); - це дії, пов'язані з масштабуванням сцени, що здійснюються при зміні розмірів вікна;
void display (void); - основна частина, промальовування всіх об'єктів, що знаходяться на сцені;
void idle (void); - функція викликається на кожному витку циклу. Як правило, її завдання - це збільшення лічильника, що є еквівалентом часу, і виклик display.
Далі ми передаємо бібліотеці покажчики на ці функції і запускаємо основний цикл.
Файли проекту і сам код (файл OpenGLSample.cpp) ви можете знайти на доданому до журналу «Світ ПК-диску».

Висновок основних форм
Спочатку створимо небо і землю.
Для цього використовуємо метод створення графічних примітивів через набори вершин. Координати вершин задаються процедурою
glVertex3d (GLdouble x, GLdouble y, GLdouble z),
де GLdouble - тип, що перетворюється в double і назад.
Вершини об'єднуються в об'єкт за допомогою конструкції:
glBegin (GLenum mode);
glVertex3d (x1, y1, z1);
..
glVertex3d (xn, yn, zn);
glEnd ();

Тут параметр mode визначає спосіб трактування списку вершин. Ось деякі з його значень:
GL_POINTS - створюється набір точок;
GL_TRIANGLES - трійки вершин задають окремі трикутники;
GL_QUADS - четвірки вершин задають окремі чотирикутники.

Зауваження про координатах
Аби не заглиблюватися в теорію, можна сказати, що насправді нам байдужі абсолютні значення координат, а істотні лише співвідношення між ними. Проектувати сцену можна в будь-яких найбільш зручних числах. Скорегувати положення камери допоможе процедура glViewPort ().
Земля буде являти собою прямокутник, що лежить в площині y = 0, небо нам знадобиться у вигляді двох прямокутників - один в площині z = -length, інший горизонтально в площині z = height. Отже,

void DrawGround () {
glColor3f (1.0,1.0,1.0);
glBegin (GL_QUADS);
glVertex3d (width, -1.0, length);
glVertex3d (width, -1.0, -length);
glVertex3d (-width, -1.0, -length);
glVertex3d (-width, -1.0, length);

glVertex3d (width, -1.0, -length);
glColor3f (0.10,0.20,0.30);
glVertex3d (width, height, -length);
glVertex3d (-width, height, -length);
glColor3f (0.35,0.65,0.99);
glVertex3d (-width, -1.0, -length);

glColor3f (0.10,0.20,0.30);
glVertex3d (width, height, -length);
glVertex3d (-width, height, -length);
glVertex3d (-width, height, length);
glVertex3d (width, height, length);
glEnd ();
}
Тут колір задається в форматі RGB в дійсних числах з відрізка [0,1]. При використанні прозорості можливо завдання кольору в форматі RGBA за допомогою процедури glColor4d (r, g, b, alpha).
Власне, на цьому принципі - завдання вершин і осмислення груп вершин як деяких об'єктів - і грунтується графічне моделювання. Для створення складних об'єктів, таких як моделі людей, хмар, трави, доводиться ставити велику (іноді астрономічне) число вершин, причому обчислення координат робиться вручну. Але не впадайте у відчай, розробки по автоматизації цього процесу вже ведуться.

освітлення
Під освітленням розуміється зміна кольору зафарбовування об'єкта в залежності від його розташування по відношенню до джерела світла. За замовчуванням є одне джерело розсіяного світла, але можна додавати і нові, зі своїм становищем, кутом розсіювання, кольором та іншими параметрами. Освітлення включається процедурою glEnable (GL_LIGHTING) і вимикається за допомогою glDisable (GL_LIGHTING).
Освітленість об'єкта, вид відкидаються їм тіней і його власна затененность залежать від напрямку нормалей до вершин, а також від матеріалу об'єкта.
Нормалі задаються процедурою glNormal3d (x, y, z), що використовується перед glVertex3d ().
Для присвоєння об'єкту матеріалу потрібно перед промальовуванням вершин викликати такі процедури:

GLfloat emissive [4] = {0.0f, 0.0f, 0.0f, 0.0f};
GLfloat diffuse [4] = {0.98f, 0.836f, 0.35f, 1.0f};
GLfloat specular [4] = {0.0f, 0.0f, 0.0f, 0.0f};
GLfloat ambient [4] = {0.98f, 0.836f, 0.35f, 1.0f};
glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION, emissive);
glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, specular);
glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 10);
glEnable (GL_COLOR_MATERIAL);

Після промальовування об'єкта потрібно вимкнути параметр:

glDisable (GL_COLOR_MATERIAL);

При використанні бібліотеки OpenGL слід дотримуватися простого правила - повертати всі змінені характеристики в початковий стан відразу після використання. Це заощадить вам масу часу і сил, а оптимізацією і видаленням непотрібних команд можна зайнятися на завершальному етапі, коли вже точно відомо, що ніяких змін в проект вноситися не буде.

текстурування
Накладання текстури робить об'єкт реалістичним і барвистим, а також дозволяє домогтися деяких спеціальних ефектів. Як текстур можна використовувати зображення у форматі BMP. Ми будемо застосовувати функції завантаження текстур, реалізація яких наведено у файлі BmpLoad.cpp. Ось як проводиться робота з текстурою:

  1. Створюється масив textures [NTEXT] значень типу GLuint, куди за допомогою процедури glGenTextures (NTEXT, textures) записуються ідентифікатори. За ним згодом будуть викликатися «прив'язані» до них текстури.
  2. У байтовий масив зчитується вміст файлу, потім масив завантажується у внутрішній ресурс бібліотеки (див. Процедуру OpaqTexture), при цьому встановлюється зв'язок останнього з обраним значенням id ідентифікатора текстури.
  3. При виклику glBindTexture (GL_TEXTURE_2D, id); в бібліотеці поточним встановлюється саме цей текстурний об'єкт.

Перетворимо землю в пустелю.
У нас є BMP-файл із зображенням піску. Завантажуємо його за наведеним вище алгоритмом і «натягуємо» на прямокутник землі. Для цього використовуємо процедуру glTexCoord2d (i, j), де i і j - текстурні координати, що відображають текстуру на квадрат [0,1] x [0,1]. Далі необхідно викликати glVertex, і до цієї вершини буде «пришпилена» зазначена точка текстури. Тоді вона буде плавно «розмазана» за великим прямокутника. Але нам потрібно зробити так, щоб текстура багаторазово розмножилася по всій землі. Для цього треба малювати землю не суцільним прямокутником, а у вигляді матриці, що складається з маленьких прямокутників, на кожен з яких «натягнута» текстура. Будемо описувати смуги прямокутників, встановлюючи GL_QUAD_ STRIP в glBegin. Ця дія об'єднує пару точок в відрізок, приєднує їх до попередньої парі і трактує всю четвірку як чотирикутник, потім приєднує до нього наступну пару і т.д. У підсумку виходить смуга.
Опис землі виглядає наступним чином:

glNormal3d (0.0, 1.0, 0.0);
int k = (int) (width / length);
glBindTexture (GL_TEXTURE_2D, textures [0]);
for (int i = 0; i glBegin (GL_QUAD_STRIP);
for (int j = 0; j glTexCoord2d (j% 2,0);
glVertex3d (-width + 2 * width / ((double) GRsl * k) * j, -1.0, length - 2 * length / (double) GRsl * i);
glTexCoord2d ((j + 1)% 2,1);
glVertex3d (-width + 2 * width / ((double) GRsl * k) * j, -1.0, length - 2 * length / (double) GRsl * (i + 1));
}
glEnd ();
}

Тут формується GRsl смуг прямокутників.

Дощ - система частинок
Такі об'єкти, як вогонь, дощ, хмари, найчастіше моделюються за допомогою великого числа точкових об'єктів - системи частинок.
Нехай в нашому дощі буде N крапель. Насамперед довільно розкидати їх по деякому обсягу, що знаходиться над небом. Щоб дощ йшов поступово, а не одним шаром, краплі повинні мати різну початкову висоту. Обчислимо N довільних координат всередині заданого обсягу і покладемо їх у масив double3 [N], де double3 = (double, double, double) - вектор, описаний у файлі Vector.h. Нехай дощ починається повільно, а не як з відра, тобто спочатку впаде кілька перших крапель, далі більше. Будемо опускати не всі краплі відразу, а тільки кожну десяту в списку. Кількість тих, хто в процес крапель будемо поступово збільшувати, поки воно не досягне значення N.
Зробимо дощ ще більш естетично приємним. Для цього будемо малювати краплі не у вигляді точок, а прирівняємо їх маленьким напівпрозорим судинах. Будемо малювати їх як прямокутники, на які накладена текстура з каналом прозорості. Завантаження цієї текстури здійснюється процедурою AlphaTexture, принцип роботи якої майже нічим не відрізняється від уже відомої нам OpaqTexture. Різниця полягає лише в тому, що кольори в масиві зберігаються в форматі RGBA. Параметр A (прозорість) зчитується з монохромного маски, яка також представляє собою файл BMP.
Перед виведенням крапель потрібно включити обробку прозорості:

glBlendFunc (GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
glEnable (GL_BLEND);
DrawRain ();
glDisable (GL_BLEND);

Подбаймо ще про одну важливу деталь: так як краплі напівпрозорі, то, щоб домогтися правдоподібного результату, будемо виводити спочатку найдальші з них, а потім ті, що ближче. Тоді вони будуть правильно просвічувати один через одного. Нам потрібно всього лише впорядкувати масив координат по z, тобто по глибині. Можна скористатися стандартною процедурою сортування qsort, для якої напишемо просту функцію порівняння координат крапель. Зрозуміло, сортування потрібно робити не в процедурі display, а в init, так як ця операція займає відчутний час, а зробити її треба тільки один раз.

Перекотиполе і динаміка
В нашій сцені не вистачає істинно тривимірного об'єкту. Земля, небо, краплі - це всього лише прямокутники. Створимо котиться перекотиполе. Побудуємо куля і накладемо на нього напівпрозору текстуру сухих гілочок. Нам пощастило - можна не хапатися за підручник з лінійної алгебри в пошуках формули для обчислення координати точки на сфері, тому що сферу для нас вже реалізували. Це один зі стандартних об'єктів GLUquadricObj, описаний в бібліотеці glu, тому створити його і накласти на нього текстуру дуже просто:

ball = gluNewQuadric ();
glBindTexture (GL_TEXTURE_2D, textures [1]);
gluQuadricTexture (ball, GL_TRUE);
glEnable (GL_CULL_FACE);
glCullFace (GL_FRONT);
gluSphere (ball, BLrad, BLsl, BLsl);
glCullFace (GL_BACK);
gluSphere (ball, BLrad, BLsl, BLsl);
glDisable (GL_CULL_FACE);

BLsl - число секторів, з яких будується сфера, воно характеризує якість її округлості і, в зворотній залежності, швидкість промальовування.
Тут, як і в випадку з дощем, ілюструється дуже важливий момент - висновок на екран тривимірного об'єкту. У разі, якщо об'єкт напівпрозорий або неопуклих, ми повинні виводити спочатку його задні грані, а потім передні. Питання впорядкування граней на особові-нелицьові в загальному випадку досить складний, але, оскільки ми використовуємо стандартний об'єкт, для нього впорядкування виконується автоматично. Установка glCullFace (GL_ FRONT) включає висновок лицьових граней.
Задамося ще одним важливим питанням: де буде намальована ця сфера? Адже жодна з процедур не надала нам можливості вказати координати. Сфера з'явиться в точці (0,0,0). Щоб перемістити її в інше місце, потрібно дізнатися трохи більше про роботу OpenGL. Насправді все, що ми малюємо в процедурі display, потрапляє не прямо на екран, а в спеціальний накопичувальний буфер. Їх два, і в кожен момент часу один використовується як накопичувач, а вміст іншого виводиться на екран. Процедура glutSwapBuffers () змінює буфери ролями, тобто вміст накопичувача потрапляє на екран. Перш ніж точки виявляться в накопичувачі, все їх координати домножаются на матрицю GL_MODELVEW. Якщо вона одинична, як задано за замовчуванням, то з ними нічого не відбувається. Але якщо помножити цю матрицю на матрицю повороту або перенесення, тоді те ж саме станеться і з координатами точок. Саме так ми і реалізуємо рух і поворот перекотиполя навколо своєї осі.

Продовжимо?
Ми створили цілий світ - у нього свої закони, унікальна природа. У нього можна щось додати, населити його істотами, наповнити прекрасними явищами. І все це в ваших руках. Тривимірна графіка - одна з найбільш захоплюючих гілок програмування, вона, як ніщо інше, підходить для реалізації творчих ідей, ваших фантазій. Програмуйте з інтересом.
Для отримання більш повної інформації, реалізації власних проектів я рекомендую звернутися до книг Є.В. Шикина і А.В. Борескова «Комп'ютерна графіка. Полігональні моделі »і Ю.М. Баяковского, А.В. Ігнатенко, А.І. Фролова «Графічна бібліотека OpenGL», звідки і були почерпнуті всі викладені тут відомості. Документацію по бібліотеці OpenGL і доповнень до неї, а також приклади програм можна знайти на сайті opengl.org .

Розташування необхідних файлів в системі Файли Розташування gl.h
glut.h
glu.h [compiler] includegl
в нашому випадку це C: Program Files
Microsoft Visual Studio .NET 2003Vc7PlatformSDKIncludegl Opengl32.lib
glut32.lib
glu32.lib [compiler] lib
в нашому випадку це C: Program FilesMicrosoft Visual
Studio .NET 2003Vc7PlatformSDKLib Opengl32.dll
glut32.dll
glu32.dll C: WINDOWSsystem32

Задамося ще одним важливим питанням: де буде намальована ця сфера?
Продовжимо?

Дополнительная информация

rss
Карта