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

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

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

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

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

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

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

В 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, вернуться к исходным координатам. Параметров у этих процедур нет.

Часть 2. Простейшие фигуры

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

В примере из прошлой статьи мы создали сферу. Для этого мы использовали механизм из 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).

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

Примитивы

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

Примитивы определяются одной или несколькими точками, которые в 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.

Точки

Нарисовать точку очень просто. Следующий код изображает 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, возможно ваша реализация справляется с этим лучше.

Линии

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

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;

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

Полигоны

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

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

Не знаю, заметили вы или нет, но 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 в Borland С++Builder и Delphi. Простейшие объекты