Основы программирования OpenGL в Borland С+Builder и Delphi

         

Линии


С линиями – не на много сложнее. Вместо размера у линии указывается ширина:

glLineWidth(width)

сглаживание разрешается следующим образом:

glEnable(GL_LINE_SMOOTH)

Но на этом возможности линий не заканчиваются. Я уже рассказал, как можно нарисовать две или даже три линии, указав всего три вершины (вызываем glBegin с параметром GL_LINE_STRIP или GL_LINE_LOOP), но и это ещё не всё! В OpenGL можно указать штриховку линии! Делается это процедурой glLineStipple(factor, pattern). Здесь pattern – 16-разрядная битовая маска. Например, чтобы нарисовать пунктирную линию, маску надо задать равной 255, что в шестнадцатеричной системе счисления соответствует 00FF, а в двоичной – 0000000011111111. А целое factor показывает, сколько раз будет повторяться каждый бит маски.

Осталось только разрешить штриховать линии: glEnable(GL_LINE_STIPPLE).

Пример.

С++, Delphi

 glEnable(GL_LINE_SMOOTH);

glLineStipple(1,255);

glEnable(GL_LINE_STIPPLE);

glBegin(GL_LINES);

glVertex2f(0,2);

glVertex2f(10,6);

glEnd;



Вот, что получится:



Основы программирования OpenGL в Borland С++Builder и Delphi. Простейшие объекты


Луковкин Сергей

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

Для того, чтобы можно было изобразить объект на экране, его необходимо предварительно перевести (или преобразовать) в другую систему координат, которая связана с точкой наблюдения и носит название видовой системы координат. Эта система координат является левосторонней. И, наконец, любое трёхмерное изображение мы всегда рисуем на двумерном экране, который имеет свою экранную систему координат. (Этот абзац я списал у Ю.Тихомирова).

Правосторонняя система координат (мировая)

Левосторонняя система координат (видовая)

По умолчанию, плоскость xOy параллельна экрану, а ось Z направлена в мировых координатах к нам, в видовых - от нас.



Параллелепипед


Не знаю, заметили вы или нет, но GLU не позволяет создавать параллелепипеды. Давайте это исправим: напишем процедуру, рисующую параллелепипед.

С++

 void piped(GLfloat a, GLfloat b, GLfloat c)

{

glShadeModel(GL_FLAT);

glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);

glBegin(GL_QUAD_STRIP);

glVertex3f(-a/2,-b/2,-c/2);

glVertex3f(-a/2,-b/2, c/2);

glVertex3f(-a/2, b/2,-c/2);

glVertex3f(-a/2, b/2, c/2);

glVertex3f( a/2, b/2,-c/2);

glVertex3f( a/2, b/2, c/2);

glVertex3f( a/2,-b/2,-c/2);

glVertex3f( a/2,-b/2, c/2);

glVertex3f(-a/2,-b/2,-c/2);

glVertex3f(-a/2,-b/2, c/2);

glEnd();

glBegin(GL_QUADS);

glVertex3f(-a/2,-b/2, c/2);

glVertex3f(-a/2, b/2, c/2);

glVertex3f( a/2, b/2, c/2);

glVertex3f( a/2,-b/2, c/2);

glVertex3f(-a/2,-b/2,-c/2);

glVertex3f(-a/2, b/2,-c/2);

glVertex3f( a/2, b/2,-c/2);

glVertex3f( a/2,-b/2,-c/2);

glEnd(); 

}

Delphi

 procedure piped(a,b,c:GLfloat);

begin

glShadeModel(GL_FLAT);

glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);

glBegin(GL_QUAD_STRIP);

glVertex3f(-a/2,-b/2,-c/2);

glVertex3f(-a/2,-b/2, c/2);

glVertex3f(-a/2, b/2,-c/2);

glVertex3f(-a/2, b/2, c/2);

glVertex3f( a/2, b/2,-c/2);

glVertex3f( a/2, b/2, c/2);

glVertex3f( a/2,-b/2,-c/2);

glVertex3f( a/2,-b/2, c/2);

glVertex3f(-a/2,-b/2,-c/2);

glVertex3f(-a/2,-b/2, c/2);

glEnd;

glBegin(GL_QUADS);

glVertex3f(-a/2,-b/2, c/2);

glVertex3f(-a/2, b/2, c/2);

glVertex3f( a/2, b/2, c/2);

glVertex3f( a/2,-b/2, c/2);

glVertex3f(-a/2,-b/2,-c/2);

glVertex3f(-a/2, b/2,-c/2);

glVertex3f( a/2, b/2,-c/2);

glVertex3f( a/2,-b/2,-c/2);

glEnd;

end;

Можно проверять!

glOrtho вызовем также, как и в примере со сферой: glOrtho(-5,5, -5,5, 1,12).

А в Draw напишем следующее:

С++, Delphi

 glColor3f(0.6,0.7,0.9);

glPushMatrix;

glRotatef(10, 0,0,1);

glRotatef(25, 0,1,0);

glRotatef(20, 1,0,0);

piped(5,1.2,3.5);

glPopMatrix;

И получим картинку:



Переход к новым координатам


В OpenGL все объекты рисуются в начале координат, т.е. в точке (0,0,0). Для того, чтобы изобразить объект в точке (x1,y1,z1), надо переместить начало координат в эту точку, т.е. перейти к новым координатам. Для этого в OpenGL определены две процедуры:

glTranslate[f d](Dx, Dy, Dz) – сдвигает начало координат на (Dx, Dy, Dz)

glRotate[f d](j, x,y,z) – поворачивает систему координат на угол j (в градусах) против часовой стрелки вокруг вектора (x,y,z)

ПРИМЕЧАНИЕ: [f d] – означает, что в конце может быть либо буква “f”, либо “d”.

Теперь стоит сказать ещё о двух процедурах:

glPushMatrix

glPopMatrix

Первая предназначена, для сохранения, а вторая – для восстановления текущих координат. Очень удобно с помощью glPushMatrix сохранить текущие координаты, потом сдвигаться и вертеться как угодно, а после, вызовом glPopMatrix, вернуться к исходным координатам. Параметров у этих процедур нет.



Полигоны


Теперь перейдём к плоским фигурам: треугольникам, четырёхугольникам и произвольным выпуклым многоугольникам. С ними можно делать всё то же, что и с линиями (только сглаживание включается и выключается процедурами 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(к обеим).

Давайте нарисуем треугольник. Вот как будет выглядеть функция Draw:

С++

 void TForm1::Draw()

{

glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);

glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);

glBegin(GL_TRIANGLES);

glColor3f(1,0,0);

glVertex2f(0,2);

glColor3f(0,1,0);

glVertex2f(8,9);

glColor3f(0,0,1);

glVertex2f(10,4);

glEnd();

SwapBuffers(ghDC);

}

Delphi

 procedure TForm1.Draw;

begin

glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);

glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);

glBegin(GL_TRIANGLES);

glColor3f(1,0,0);

glVertex2f(0,2);

glColor3f(0,1,0);

glVertex2f(8,9);

glColor3f(0,0,1);

glVertex2f(10,4);

glEnd;

SwapBuffers(ghDC);

end;

Я уже говорил, что каждая вершина может иметь свой цвет, этим я здесь и воспользовался. И вот что получилось:

Каждой вершине указывать цвет совсем не обязательно. Если вы хотите нарисовать треугольник одного цвета, то этот цвет указывается один раз – перед рисованием самого примитива.

Забегая вперёд, скажу, что плавного перетекания цветов как на рисунке может и не быть, если перед рисованием вызвать процедуру glShadeModel(GL_FLAT), по умолчанию её параметр – GL_SMOOTH. Эта процедура указывает, сглаживать или нет углы между смежными полигонами. Вот картинки для иллюстрации её действия:

 

Раз уж я сказал о штриховке линий, то нужно сказать и о трафарете – штриховке для полигонов. Он включается командой glEnable(GL_POLYGON_STIPPLE). Также как и с линиями, трафарет задаётся массивом, который определяет битовую маску. Размер трафарета - 32x32 бита, т.е. размер массива будет 128 байт.

Мне было лень прописывать каждый из 128 байт маски по отдельности, и я сформировал её в цикле, и вот результат:

С++

 void TForm1::Draw()

{

glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);

// формируем маску

for(int k=0;k<16;k++)

for(int i=0;i<8;i++)

stip[k][i]:=k-i;

glEnable(GL_POLYGON_STIPPLE);

glPolygonStipple(@stip);

glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);

glColor3f(1,0,0);

glBegin(GL_TRIANGLES);

glVertex2f(0,2);

glVertex2f(8,9);

glVertex2f(10,4);

glEnd();

SwapBuffers(ghDC);

}

Delphi

 procedure TForm1.Draw;

var

stip:array [1..16,1..8] of GLubyte;

i,k:byte;

begin

glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);

// формируем маску

for k:=1 to 16 do

for i:=1 to 8 do

stip[k][i]:=k-i;

glEnable(GL_POLYGON_STIPPLE);

glPolygonStipple(@stip);

glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);

glColor3f(1,0,0);

glBegin(GL_TRIANGLES);

glVertex2f(0,2);

glVertex2f(8,9);

glVertex2f(10,4);

glEnd;

SwapBuffers(ghDC);

end;

Вот результат:

Вообще маска формируется один раз, поэтому, если вы перенесёте код её формирования в другое место (например в FormCreate), то программа будет работать быстрее.

Ещё хотелось бы сказать о массивах OpenGL. Этот метод позволяет хранить все вершины объекта в массиве, причем в этом массиве можно хранить не только координаты вершин, но и их атрибуты (иногда это бывает полезно). Но товарищи из Borland’а решили, что нам это не нужно и не объявили соответствующие процедуры и константы. Без этого легко можно обойтись, но всё-таки обидноL.



Примитивы


Любую трёхмерную фигуру, какая бы сложная она не была, можно разбить на двухмерные (плоские) составляющие. Эти составляющие я и буду называть примитивами, хотя некоторые авторы считают, что примитивами следует обозвать вышеперечисленные трёхмерные фигуры.

Примитивы определяются одной или несколькими точками, которые в OpenGL задаются внутри командных скобок glBegin/glEnd:

С++

void glBegin(mode);

void glEnd();

Delphi

procedure glBegin(mode);

procedure glEnd;

Параметр mode показывает, какие примитивы будут рисоваться. Доступны следующие значения:

GL_POINTS

Каждая вершина – отдельная точка

GL_LINES

Каждая пара вершин – отдельная линия. Если число вершин нечётно, то последняя игнорируется

GL_LINE_STRIP

Последовательность связанных отрезков. Первые две вершины – первый отрезок. Третья вершина определяет второй отрезок с началом в конце первого и концом в этой вершине и т.д

GL_LINE_LOOP

Аналогичен GL_LINE_STRIP, только последняя вершина соединяется отрезком с первой.

GL_TRIANGLES

Каждая тройка вершин – отдельный треугольник

GL_TRIANGLE_STRIP

Группа связанных треугольников. Первые три вершины – первый треугольник. Вторая, третья и четвёртая вершины - второй треугольник и т.д.

GL_TRIANGLE_FAN

Также группа связанных треугольников. Первые три вершины – первый треугольник. Первая, вторая и четвёртая вершины - второй треугольник и т.д.

GL_QUADS

Каждые четыре вершины – отдельный четырёхугольник.

GL_QUAD_STRIP

Группа связанных четырёхугольников. Первые четыре вершины – первый четырёхугольник. Третья, четвёртая, пятая и шестая вершины – второй четырёхугольник и т.д.

GL_POLYGON

Рисует отдельный выпуклый многоугольник (один).

Особое внимание нужно уделить GL_QUAD_STRIP. Здесь не совсем понятный, но очень удобный порядок указания вершин:

У каждого примитива есть минимальное число вершин. Если указанное число вершин меньше минимального для данного примитива, то примитив не рисуется.

Осталось только сказать, как задать вершину. Для этого определена следующая процедура:

glVertex[2 3 4][s i f d][v](coord)

Вершина определяется четырьмя параметрами: координаты x, y, z и параметр w – коэффициент, на который делится каждая из координат, т.е. w определяет масштаб. По умолчанию z=0, w=1, т.е когда вы вызываете, например, glVertex2f(1,1) на самом деле вызывается glVertex4f(1,1,0,1).

С каждой вершиной связаны некоторые данные:

· Текущий цвет – цвет вершины (окончательный цвет высчитывается с учётом света). Цвет задаётся процедурой glColor*

· Текущие координаты текстуры – координаты текстуры, соответствующие этой вершине. Задаются процедурой glTexCoord*

· Текущая нормаль – вектор нормали, соответствующий данной вершине. Задаётся процедурой glNormal*

· Текущая позиция растра – используется для определения положения растра при работе с пикселями и битовыми массивами. задаётся процедурой glRasterPos*

ПРИМЕЧАНИЕ: вместо звёздочки ‘*’ ставятся соответствующие суффиксы; такое сокращение принято во многих документациях по OpenGL.



Простейшие объёмные фигуры


В примере из прошлой статьи мы создали сферу. Для этого мы использовали механизм из glu32.dll. Алгоритм был такой:

1. Создаём объект типа GLUquadricObj

2. Инициализируем его функцией gluNewQuadric

3. Устанавливаем стиль фигуры функцией gluQuadricDrawStyle(quadObj, GLU_FILL). Стиль может быть GLU_FILL, GLU_LINE, GLU_SILHOUETTE или GLU_POINT. Что каждый из них значит, проверьте сами.

4. Делаем из quadObj (объекта типа GLUquadricObj) сферу, цилиндр, конус, диск или часть диска. Для этого определены следующие функции:

· gluSphere (quadObj, radius, slices, loops). Три последних параметра – это радиус и количество разбиений поперёк и вдоль оси Z соответственно.

· gluCylinder (quadObj, baseRadius, topRadius, height, slices, loops). После quadObj идут следующие параметры: радиус нижнего основания, радиус верхнего основания, высота и количество разбиений поперёк и вдоль оси Z соответственно. Очевидно, что эта функция задаёт как цилиндр, так и конус.

· gluDisk (quadObj, innerRadius, outerRadius, slices, loops). Здесь после quadObj указываются внутренний и внешний радиусы диска.

· gluPartialDisk (quadObj, innerRadius, outerRadius, slices, loops, startAngle, sweepAngle). Здесь добавляются два параметра: угол (в градусах), с которого начнётся рисование диска, и угол, которым рисование закончится.

5. Освобождаем память, занимаемую под quadObj функцией gluDeleteQuadric(quadObj).

Теперь вы можете рисовать простые трёхмерные фигуры!



Список литературы


Для подготовки данной работы были использованы материалы с сайта http://bestcode.org/



Точки


Нарисовать точку очень просто. Следующий код изображает 10 точек разного размера.

С++

 void TForm1::Draw()

{

glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);

glColor3f(1,1,1);

Byte i;

for(i=0;i<10;i++)

{

glPointSize((i+1)*4);

glBegin(GL_POINTS);

glVertex2f(i,i);

glEnd();

}

SwapBuffers(ghDC);

}

Delphi

 procedure TForm1.Draw;

var

i:byte;

begin

glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);

glColor3f(1,1,1);

for i:=0 to 9 do

begin

glPointSize((i+1)*4);

glBegin(GL_POINTS);

glVertex2f(i,i);

glEnd;

end;

SwapBuffers(ghDC);

end;

ПРИМЕЧАНИЕ: в FormResize я вызвал glOrtho следующим образом - glOrtho(-1,12, -1,12, 2,12). Это – для того, чтобы все точки поместились в окне.

Для изменения размера точки используется процедура glPointSize(size). Параметр size задаёт диаметр точки.

В этом примере все точки квадратные. В OpenGL разрешено сглаживание (smoothening) как точек, так и более сложных объектов. Как и всё в OpenGL, этот режим включается и выключается процедурами glEnable/glDisable. Для точек это делается так:

glEnable(GL_POINT_SMOOTH);

Вставив эту строчку где-нибудь перед рисованием точек, получим:

Откровенно говоря, у меня OpenGL делает это довольно плохоL, возможно ваша реализация справляется с этим лучше.