Основи програмування OpenGL в Borland С + + Builder і
Delphi. Найпростіші об'єкти h2>
Луковкін Сергій p>
Розглядаючи будь-який тривимірний об'єкт, ми завжди
визначаємо його розташування і розміри щодо деякої звичної, і зручною
зараз системи координат, пов'язаної з реальним світом. Така
вихідна система координат в комп'ютерній графіці є правобічної і
називається світовою системою координат .... p>
Для того, щоб можна було зобразити об'єкт на екрані, його
необхідно попередньо перевести (або перетворити) в іншу систему
координат, яка пов'язана з точкою спостереження і носить назву видовий системи
координат. Ця система координат є лівосторонньої. І, нарешті, будь-яке
тривимірне зображення ми завжди малюємо на двовимірному екрані, який має свою
екранну систему координат. (Цей абзац я списав у Ю. Тихомирова). P>
правостороння
система координат (світова) p>
лівостороння
система координат (видова) p>
p>
p>
За замовчуванням, площину xOy паралельна екрану, а вісь Z
спрямована у світових координатах до нас, у видових - від нас. p>
Перехід до нових координатах h2>
У OpenGL всі об'єкти малюються на початку координат, тобто в
точці (0,0,0). Для того, щоб зобразити об'єкт в точці (x1, y1, z1),
треба перемістити початок координат в цю точку, тобто перейти до нових
координатах. Для цього в OpenGL визначені дві процедури: p>
glTranslate [fd] (Dx, Dy, Dz) - зрушує початок координат на
(Dx, Dy, Dz) p>
glRotate [fd] (j, x, y, z) - повертає систему координат на
кут j (в градусах) проти годинникової стрілки навколо вектора (x, y, z) p>
ПРИМІТКА: [fd] - означає, що в кінці може бути або
буква "f", або "d". p>
Тепер варто сказати ще про дві процедури: p>
glPushMatrix p>
glPopMatrix p>
Перша призначена, для збереження, а другий - для
відновлення поточних координат. Дуже зручно за допомогою glPushMatrix зберегти
поточні координати, потім зрушуватися і звертатися як завгодно, а після, викликом
glPopMatrix, повернутися до вихідних координатах. Параметрів у цих процедур немає. P>
Частина 2. Найпростіші фігури h2>
Найпростіші об'ємні фігури h2>
У прикладі з минулої статті ми створили сферу. Для цього ми
використовували механізм з glu32.dll. Алгоритм був такий: p>
1. Створюємо об'єкт типу GLUquadricObj p>
2. Ініціалізіруем його функцією gluNewQuadric p>
3. Встановлюємо стиль фігури функцією
gluQuadricDrawStyle (quadObj, GLU_FILL). Стиль може бути GLU_FILL, GLU_LINE,
GLU_SILHOUETTE або GLU_POINT. Що кожен з них означає, перевірте самі. P>
4. Робимо з quadObj (об'єкта типу GLUquadricObj) сферу,
циліндр, конус, диск або частину диска. Для цього визначені такі функції: p>
· gluSphere (quadObj, radius, slices, loops). Три останніх
параметри - це радіус і кількість розбиття впоперек і вздовж осі Z
відповідно. p>
· gluCylinder (quadObj,
baseRadius, topRadius, height, slices, loops). Після
quadObj йдуть наступні параметри: радіус нижньої основи, радіус верхнього
підстави, висота і кількість розбиття впоперек і вздовж осі Z відповідно.
Очевидно, що ця функція задає як циліндр, так і конус. P>
· gluDisk (quadObj,
innerRadius, outerRadius, slices, loops). Тут
після quadObj вказуються внутрішній і зовнішній радіуси диска. p>
· gluPartialDisk
(quadObj, innerRadius, outerRadius, slices, loops, startAngle, sweepAngle). Тут додаються два параметри: кут (в градусах), з
якого почнеться малювання диска, і кут, яким малювання закінчиться. p>
5. Звільняємо пам'ять, зайняту під quadObj функцією
gluDeleteQuadric (quadObj). p>
Тепер ви можете малювати прості тривимірні фігури! p>
Примітиви h2>
Будь-яку тривимірну фігуру, яка б складна вона не була,
можна розбити на двомірні (плоскі) складові. Ці складові я і буду
називати примітивами, хоча деякі автори вважають, що примітивами слід
обізвати вищеперелічені тривимірні фігури. p>
Примітиви визначаються однією або декількома точками,
які в OpenGL задаються всередині командних дужок glBegin/glEnd: p>
С + + p>
void glBegin (mode); p>
void glEnd (); p>
Delphi p>
procedure glBegin (mode); p>
procedure glEnd; p>
Параметр mode показує, які примітиви будуть малюватися.
Доступні наступні значення: p>
GL_POINTS p>
Кожна вершина - окрема точка p>
GL_LINES p>
Кожна пара вершин - окрема лінія. Якщо
число вершин непарній, то остання ігнорується p>
GL_LINE_STRIP p>
Послідовність пов'язаних відрізків.
Перші дві вершини - перший відрізок. Третя вершина визначає другий відрізок
з початком в кінці першого і кінцем в цій вершині і т.д p>
GL_LINE_LOOP p>
Аналогічний GL_LINE_STRIP, тільки остання
вершина з'єднується відрізком з перших. p>
GL_TRIANGLES p>
Кожна трійка вершин - окремий трикутник p>
GL_TRIANGLE_STRIP p>
Група пов'язаних трикутників. Перші три
вершини - перший трикутник. Друга, третя і четверта вершини - другий
трикутник і т.д. p>
GL_TRIANGLE_FAN p>
Також група пов'язаних трикутників. Перші
три вершини - перший трикутник. Перша, друга і четверта вершини - другий
трикутник і т.д. p>
GL_QUADS p>
Кожні чотири вершини - окремий
чотирикутник. p>
GL_QUAD_STRIP p>
Група пов'язаних чотирикутників. Перші
чотири вершини - першого чотирикутник. Третя, четверта, п'ята і шоста
вершини - другий чотирикутник і т.д. p>
GL_POLYGON p>
Малює окремий опуклий багатокутник
(один). p>
Особливу увагу потрібно приділити GL_QUAD_STRIP. Тут не
зовсім зрозумілий, але дуже зручний порядок вказівки вершин: p>
p>
У кожного примітиву є мінімальне число вершин. Якщо
вказане число вершин менше мінімального для даного примітиву, то примітив
не малюється. p>
Залишилося тільки сказати, як задати вершину. Для цього
визначена наступна процедура: p>
glVertex [2 3 4] [s i f
d] [v] (coord) p>
Вершина визначається чотирма параметрами: координати x, y,
z і параметр w - коефіцієнт, на який ділиться кожна з координат, тобто w
визначає масштаб. За замовчуванням z = 0, w = 1, тобто коли ви викликаєте, наприклад,
glVertex2f (1,1) насправді викликається glVertex4f (1,1,0,1). p>
З кожною вершиною пов'язані деякі дані: p>
· Поточний колір - колір вершини (остаточний колір вираховується
з урахуванням світла). Колір задається процедурою glColor * p>
· Поточні координати текстури - координати текстури,
відповідні цієї вершини. Задаються процедурою glTexCoord * p>
· Поточна нормаль - вектор нормалі, що відповідає даній
вершині. Здається процедурою glNormal * p>
· Поточна позиція растру - використовується для визначення
положення растра при роботі з пікселями і бітовими масивами. задається
процедурою glRasterPos * p>
ПРИМІТКА: замість зірочки '*' ставляться відповідні
суфікси; таке скорочення прийнято в багатьох документація з OpenGL. p>
Точки h2>
Намалювати точку дуже просто. Наступний код зображує 10
точок різного розміру. p>
С + + p>
void TForm1:: Draw () p>
( p>
glClear (GL_DEPTH_BUFFER_BIT
or GL_COLOR_BUFFER_BIT); p>
glColor3f (1,1,1);
p>
Byte i; p>
for (i = 0; i <10; i + +)
p>
( p>
glPointSize ((i +1) * 4);
p>
glBegin (GL_POINTS);
p>
glVertex2f (i, i);
p>
glEnd (); p>
) p>
SwapBuffers (ghDC);
p>
) p>
Delphi p>
procedure TForm1.Draw; p>
var p>
i: byte; p>
begin p>
glClear (GL_DEPTH_BUFFER_BIT
or GL_COLOR_BUFFER_BIT); p>
glColor3f (1,1,1);
p>
for i: = 0
to 9 do p>
begin p>
glPointSize ((i +1) * 4);
p>
glBegin (GL_POINTS);
p>
glVertex2f (i, i);
p>
glEnd; p>
end; p>
SwapBuffers (ghDC); p>
end; p>
ПРИМІТКА: в FormResize я викликав glOrtho наступним чином
- GlOrtho (-1,12, -1,12, 2,12). Це - для того, щоб всі крапки помістилися в
вікні. p>
p>
Для зміни розміру точки використовується процедура
glPointSize (size). Параметр size задає діаметр точки. P>
У цьому прикладі всі крапки квадратні. У OpenGL дозволено
згладжування (smoothening) як точок, так і більш складних об'єктів. Як і все в
OpenGL, цей режим вмикається і вимикається процедурами glEnable/glDisable. Для
точок це робиться так: p>
glEnable (GL_POINT_SMOOTH); p>
Вставивши цей рядок де-небудь перед малюванням точок,
отримаємо: p>
p>
Відверто кажучи, у мене OpenGL робить це досить
плохоL, можливо ваша реалізація справляється з цим краще. p>
Лінії h2>
З лініями - не на багато складніше. Замість розміру у лінії
вказується ширина: p>
glLineWidth (width) p>
згладжування дозволяється наступним чином: p>
glEnable (GL_LINE_SMOOTH) p>
Але на цьому можливості ліній не закінчуються. Я вже
розповів, як можна намалювати два або навіть три лінії, вказавши лише три
вершини (викликаємо glBegin з параметром GL_LINE_STRIP або GL_LINE_LOOP), а й
це ще не все! У OpenGL можна вказати штрихування лінії! Робиться це процедурою
glLineStipple (factor, pattern). Тут pattern - 16-розрядна бітова маска.
Наприклад, щоб намалювати пунктирну лінію, маску треба задати рівний 255, що
в шістнадцятковій системі числення відповідає 00FF, а в двійковій --
0000000011111111. А ціле factor показує, скільки разів буде повторюватися
кожний біт маски. p>
Залишилося тільки вирішити Штрихована лінії:
glEnable (GL_LINE_STIPPLE). p>
Приклад. p>
С + +, Delphi p>
glEnable (GL_LINE_SMOOTH); p>
glLineStipple (1,255);
p>
glEnable (GL_LINE_STIPPLE);
p>
glBegin (GL_LINES);
p>
glVertex2f (0,2); p>
glVertex2f (10,6); p>
glEnd; p>
Ось, що вийде: p>
p>
Полігони h2>
Тепер перейдемо до плоских фігур: трикутника, чотирикутника
і довільним опуклим багатокутника. З ними можна робити все те ж, що і з
лініями (тільки згладжування вмикається і вимикається процедурами
glEnable/glDisable з параметром GL_POLYGON_SMOOTH), плюс ще одна процедура:
glPolygonMode (face, mode). Другий параметр - mode - вказує, як буде
малюватися полінгон (по русски - багатокутник). Він може приймати значення
GL_POINT (малюються тільки точки), GL_LINE (тільки лінії) або GL_FILL (заповнений
полігон). А перший параметр - face - показує, який стороні полігону
застосовується режим mode: GL_FRONT (до лицьової), GL_BACK (до тильної) або
GL_FRONT_AND_BACK (до обох). P>
Давайте намалюємо трикутник. Ось як буде виглядати
функція Draw: p>
С + + p>
void TForm1:: Draw () p>
( p>
glClear (GL_DEPTH_BUFFER_BIT
or GL_COLOR_BUFFER_BIT); p>
glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
p>
glBegin (GL_TRIANGLES);
p>
glColor3f (1,0,0);
p>
glVertex2f (0,2);
p>
glColor3f (0,1,0);
p>
glVertex2f (8,9);
p>
glColor3f (0,0,1);
p>
glVertex2f (10,4);
p>
glEnd (); p>
SwapBuffers (ghDC); p>
) p>
Delphi p>
procedure TForm1.Draw; p>
begin p>
glClear (GL_DEPTH_BUFFER_BIT
or GL_COLOR_BUFFER_BIT); p>
glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
p>
glBegin (GL_TRIANGLES);
p>
glColor3f (1,0,0);
p>
glVertex2f (0,2);
p>
glColor3f (0,1,0);
p>
glVertex2f (8,9);
p>
glColor3f (0,0,1);
p>
glVertex2f (10,4);
p>
glEnd; p>
SwapBuffers (ghDC); p>
end; p>
Я вже говорив, що кожна вершина може мати свій колір,
цим я тут і скористався. І ось що вийшло: p>
p>
Кожній вершині вказувати колір зовсім не обов'язково. Якщо
ви хочете намалювати трикутник одного кольору, то цей колір вказується один
раз - перед малюванням самого примітиву. p>
Забігаючи наперед, скажу, що плавного перетікання кольорів як
на малюнку може і не бути, якщо перед малюванням викликати процедуру
glShadeModel (GL_FLAT), за замовчуванням її параметр - GL_SMOOTH. Ця процедура
вказує, згладжувати чи ні кути між суміжними полігонами. Ось картинки для
ілюстрації її дії: p>
p>
Раз вже я сказав про штрихування ліній, то потрібно сказати і про
трафареті - штрихування для полігонів. Він включається командою
glEnable (GL_POLYGON_STIPPLE). Також як і з лініями, трафарет задається
масивом, який визначає бітову маску. Розмір трафарету - 32x32 біта, тобто
розмір масиву буде 128 байт. p>
Мені було ліньки прописувати кожен з 128 байт маски по
окремо, і я сформував її в циклі, і ось результат: p>
С + + p>
void TForm1:: Draw () p>
( p>
glClear (GL_DEPTH_BUFFER_BIT or
GL_COLOR_BUFFER_BIT); p>
//
формуємо маску p>
for (int
k = 0; k <16; k + +) p>
for (int i = 0; i <8; i + +) p>
stip [k] [i]: = k-i; p>
glEnable (GL_POLYGON_STIPPLE); p>
glPolygonStipple (@ stip); p>
glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
p>
glColor3f (1,0,0); p>
glBegin (GL_TRIANGLES); p>
glVertex2f (0,2); p>
glVertex2f (8,9); p>
glVertex2f (10,4); p>
glEnd (); p>
SwapBuffers (ghDC); p>
) p>
Delphi p>
procedure TForm1.Draw; p>
var p>
stip: array [1 .. 16,1 .. 8] of
GLubyte; p>
i, k: byte; p>
begin p>
glClear (GL_DEPTH_BUFFER_BIT or
GL_COLOR_BUFFER_BIT); p>
// формуємо маску p>
for k: = 1 to 16 do p>
for i: = 1 to 8 do p>
stip [k] [i]: = k-i; p>
glEnable (GL_POLYGON_STIPPLE); p>
glPolygonStipple (@ stip); p>
glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
p>
glColor3f (1,0,0); p>
glBegin (GL_TRIANGLES); p>
glVertex2f (0,2); p>
glVertex2f (8,9); p>
glVertex2f (10,4); p>
glEnd; p>
SwapBuffers (ghDC); p>
end; p>
Ось результат: p>
p>
Взагалі маска формується один раз, тому, якщо ви
перенесете код її формування в інше місце (наприклад в FormCreate), то
програма буде працювати швидше. p>
Ще хотілося б сказати про масивах OpenGL. Цей метод
дозволяє зберігати всі вершини об'єкта в масиві, причому в цьому масиві можна
зберігати не тільки координати вершин, але і їх атрибути (іноді це буває
корисно). Але з Borland'а товариші вирішили, що нам це не потрібно і не оголосили
відповідні процедури і константи. Без цього легко можна обійтися, але все-таки
обідноL. p>
Паралелепіпед h2>
Не знаю, чи помітили ви чи ні, але GLU не дозволяє створювати
паралелепіпеди. Давайте це виправимо: напишемо процедуру, рісующую
паралелепіпед. p>
С + + p>
void piped (GLfloat a,
GLfloat b, GLfloat c) p>
( p>
glShadeModel (GL_FLAT);
p>
glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
p>
glBegin (GL_QUAD_STRIP);
p>
glVertex3f (-a/2,-b/2,-c/2);
p>
glVertex3f (-a/2,-b/2,
c/2); p>
glVertex3f (-a/2,
b/2,-c/2); p>
glVertex3f (-a/2,
b/2, c/2); p>
glVertex3f (
a/2, b/2,-c/2); p>
glVertex3f (
a/2, b/2, c/2); p>
glVertex3f (
a/2,-b/2,-c/2); p>
glVertex3f (
a/2,-b/2, c/2); p>
glVertex3f (-a/2,-b/2,-c/2);
p>
glVertex3f (-a/2,-b/2,
c/2); p>
glEnd (); p>
glBegin (GL_QUADS);
p>
glVertex3f (-a/2,-b/2,
c/2); p>
glVertex3f (-a/2,
b/2, c/2); p>
glVertex3f (
a/2, b/2, c/2); p>
glVertex3f (
a/2,-b/2, c/2); p>
glVertex3f (-a/2,-b/2,-c/2);
p>
glVertex3f (-a/2,
b/2,-c/2); p>
glVertex3f (
a/2, b/2,-c/2); p>
glVertex3f (
a/2,-b/2,-c/2); p>
glEnd (); p>
) p>
Delphi p>
procedure
piped (a, b, c: GLfloat); p>
begin p>
glShadeModel (GL_FLAT);
p>
glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
p>
glBegin (GL_QUAD_STRIP);
p>
glVertex3f (-a/2,-b/2,-c/2);
p>
glVertex3f (-a/2,-b/2,
c/2); p>
glVertex3f (-a/2,
b/2,-c/2); p>
glVertex3f (-a/2,
b/2, c/2); p>
glVertex3f (
a/2, b/2,-c/2); p>
glVertex3f (
a/2, b/2, c/2); p>
glVertex3f (
a/2,-b/2,-c/2); p>
glVertex3f (
a/2,-b/2, c/2); p>
glVertex3f (-a/2,-b/2,-c/2);
p>
glVertex3f (-a/2,-b/2,
c/2); p>
glEnd; p>
glBegin (GL_QUADS);
p>
glVertex3f (-a/2,-b/2,
c/2); p>
glVertex3f (-a/2,
b/2, c/2); p>
glVertex3f (
a/2, b/2, c/2); p>
glVertex3f (
a/2,-b/2, c/2); p>
glVertex3f (-a/2,-b/2,-c/2);
p>
glVertex3f (-a/2,
b/2,-c/2); p>
glVertex3f (
a/2, b/2,-c/2); p>
glVertex3f (
a/2,-b/2,-c/2); p>
glEnd; p>
end; p>
Можна перевіряти! p>
glOrtho викличемо також, як і у прикладі зі сферою:
glOrtho (-5,5, -5,5, 1,12). p>
А в Draw напишемо наступне: p>
С + +, Delphi p>
glColor3f (0.6,0.7,0.9); p>
glPushMatrix;
p>
glRotatef (10,
0,0,1); p>
glRotatef (25,
0,1,0); p>
glRotatef (20,
1,0,0); p>
piped (5,1.2,3.5); p>
glPopMatrix; p>
І отримаємо картинку: p>
p>
Список літератури h2>
Для
підготовки даної роботи були використані матеріали з сайту http://bestcode.org/
p>