«Состояние» OpenGL
OpenGL отслеживает множество переменных состояния (state variables): текущий размер точки, текущий цвет рисования, текущий цвет фона и т. д. Значение любой переменной состояния остается неизменным до тех пор, пока не получено новое значение. Размер точки может быть установлен с помощью функции glPointSize(), принимающей один вещественный аргумент. Если этот аргумент равен 3.0, то точка обычно рисуется как квадратик со стороной в три пиксела. (Если вы хотите узнать дополнительные подробности об этой или о других функциях OpenGL, то вам следует обратиться к соответствующей документации по OpenGL, часть из которой доступна в он-лайновом режиме в Интернете; см. приложение А). Цвет для рисования (color of a drawing) может быть задан с помощью функции
glColor3f(red, green, blue);
где значения переменных red, green и blue изменяются от 0,0 до 1,0. Например, некоторые из цветов, перечисленных в табл. 1.1, можно задать с помощью такой группы командных строк:
glColor3f(1.0, 0.0, 0.0);
// set drawing color to red
// устанавливаем красный цвет рисования
glColor3f(0.0, 0.0, 0.0);
// set drawing color to black
// устанавливаем черный цвет рисования
glColor3f(1.0, 1.0, 1.0);
// set drawing color to white
// устанавливаем белый цвет рисования
glColor3f(1.0, 1.0, 0.0);
// set drawing color to yellow
// устанавливаем желтый цвет рисования
Цвет фона (background color) устанавливается с помощью функции glClearColor(red, green, blue, alpha), где переменная alpha определяет степень прозрачности и будет рассматриваться позднее. (Пока задавайте alpha=0.0.) Для очистки всего окна до цвета фона следует использовать функцию glClear(GL_ COLOR_ BUFFER_BIT). Аргумент GL_COLOR_BUFFER_BIT представляет собой еще одну встроенную в OpenGL константу.
Установка системы координат
Наш метод установки начальной системы координат может сейчас показаться неясным, однако он станет более понятным в следующей главе, где мы рассмотрим окна, окна проекции и отсечение (clipping). Пока что примем несколько команд на веру. Функция myInit() на рис. 2.9 представляется хорошим средством назначения системы координат. Как мы увидим позже, OpenGL регулярно производит большое число преобразований. Для этого используются матрицы, и команды в myInit() также оперируют определенными матрицами для достижения своей цели. Подпрограмма gluOrtho2D() производит нужное нам преобразование для экранного окна размером 640 пикселов в ширину и 480 пикселов в длину.
Листинг 2.4. Установка простой системы координат
void myInit(void)
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 640.0, 0, 480.0);
}
Соберем все это вместе. Законченная программа на OpenGL
В листинге 2.5 приведена законченная программа, рисующая три непритязательные точки из рис. 2.3. Как мы увидим, эту программу нетрудно изменить так, чтобы она рисовала более интересные объекты. Инициализация функции myInit() задает систему координат, размер точки, цвет фона и цвет рисования. Само рисование инкапсулировано в функции обратного вызова myDisplay(). Поскольку данная программа не является интерактивной, не используется никаких других функций обратного вызова. Функция glFlush() вызывается после того, как точки нарисованы, чтобы убедиться, что все данные были должным образом обработаны и отправлены на дисплей. Это важно для некоторых систем, работающих с сетью. Данные буферизуются на хост-машине (host-machine) и пересылаются на удаленный дисплей только при переполнении буфера или при выполнении команды glFlush().
Листинг 2.5. Законченная OpenGL-программа рисования трех точек
#include
// use as needed for your system
// используем, как нужно для вашей системы
#include
#include
//<<<<<<<<<<<< myInit >>>>>>>>>>
void myInit(void)
{
glClearColor(1.0,1.0,1.0,0.0);
// set white background color;
// устанавливаем белый цвет фона
glColor3f(0.0f, 0.0f, 0.0f);
// set the drawing color
// устанавливаем цвет рисования
glPointSize(4.0);
// a 'dot' is 4 by 4 pixels
// "точка" размером 4 на 4 пиксела
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, 640.0, 0.0, 480.0);
}
//<<<<<<<<<<<< myDisplay >>>>>>>>>
void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT);
// clear the screen
// очищаем экран
Листинг 2.5 (продолжение)
glBegin(GL_POINTS);
glVertex2i(100, 50);
// draw three points
// рисуем три точки
glVertex2i(100, 130);
glVertex2i(150б 130);
glEnd();
glFlush();
// send all output to display
//отправляем весь вывод на дисплей
}
//<<<<<<<<<<<< main >>>>>>>>>>>
void main(int argc, char** argv)
{
glutInit(&argc, argv);
// initialize the toolkit
// инициализируем инструментарий
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
// set display mode
// устанавливаем режим отображения
glutInitWindowSize(640.480);
// set window size
// устанавливаем размер окна
glutInitWindowPosition(100, 150);
// set window position on screen
// устанавливаем положение окна на экране
glutCreateWindow("my first attempt");
// open the screen window
// открываем экранное окно
glutDisplayFunc(myDisplay);
// register redraw function
// регистрируем функцию обновления
mylnit();
glutMainLoop();
// go into a perpetual loop
// входим в бесконечный цикл
}
2.2.1. Рисование созвездия точек
Созвездие точек — это некий узор, составленный из точек (dots or points). Мы рассмотрим несколько интересных примеров созвездий точек, которые легко получить при помощи основной программы, приведенной в листинге 2.5. В каждом случае соответствующая функция объявляется в glutDisplayFunc() как функция обратного вызова для события обновления. Мы настоятельно рекомендуем вам реализовать и протестировать каждый пример для приобретения некоторого опыта в графике.
Пример 2.2.1. Большая Медведица
На рис. 2.5 изображен узор из восьми точек, представляющих собой Большую Медведицу — привычное созвездие на ночном небе.
Названия и местоположения этих восьми звезд Большой Медведицы (при одном определенном виде ночного неба) — Дубхе, Мерак, Фекда, Мегрец, Алиот, Мицар, Алькор, Бенетнаш (Алькаид) — задают ся посредством следующих упорядоченных троек: {Dubhe, 289, 190}, {Merak, 320, 128}, {Phecda, 239, 67}, {Megrez, 194, 101}, {Alioth, 129, 83}, [Mizar, 75, 73}, {Alcor, 74, 74}, {Alkaid, 20,10}. Поскольку данных для точек очень мало, проще внести их в явный список или «вмонтировать» («hardwire») в код. (Если требуется нарисовать много точек, то удобнее записать их в файл и заставить программу читать их из файла и рисовать; позже в данной главе мы проделаем это.) Эти точки могут заменить собой те три точки, которые задавались в листинге 2.5. Полезно поэкспериментировать с этим созвездием точек, задавая различные размеры точек, цвет фона и линий.
Пример 2.2.2. Рисование ковра Серпинского (Sierpinski gasket)
На рис. 2.6 изображен ковер Серпинского1. Совокупность точек для него генерируется процедурно. Это означает, что каждая последующая точка определяется некоторым процедурным правилом. Хотя этот закон в данном случае весьма прост, результирующий орнамент является фракталом (fractal) (см. главу 9). Сначала выведем правило построения ковра Серпинского чисто интуитивным путем. В тематическом задании 2.2 мы увидим еще один образец системы итерируемых функций (iterated function system).
Рис. 2.6. Ковер Серпинского
Ковер Серпинского рисуется посредством многократного вызова функции drawDot() с координатами точек (x0, y0), (x1, y1), (x2, y2)ј, задаваемых с помощью простого алгоритма. Обозначим k-ю точку pk = (xk, yk). Каждая следующая точка зависит от предыдущей точки pk–1. Процедура вычисления точек заключается в следующем:
1. Выбираем три фиксированные точки T0, T1, T2, так чтобы они составили некоторый треугольник, как показано на рис. 2.7, а.
2. Выбираем в качестве начальной точки для рисования p0 одну из точек T0, T1, T2 случайным образом.
Теперь будем выполнять итерационный процесс до тех пор, пока поле рисунка не будет заполнено в достаточной степени.
1. Выберем случайным образом одну из точек T0, T1, T2 и обозначим ее T.
2. Вычислим следующую точку pk как середину1 отрезка между точкой T и найденной ранее точкой pk–1. Это означает, что pk является серединой отрезка, соединяющего точки pk–1 и T.
3. Рисуем точку pk с помощью функции drawDot().
Рис. 2.7. Построение ковра Серпинского
Рисунок 2.7, б отображает несколько итераций описанной процедуры. Пусть начальной точкой будет T0, а следующей выбрана точка T1. В этом случае точка p1 ляжет посередине между p1 и T1. Пусть далее выбрана точка T2, так что p2 ляжет посередине между р1 и Т2. Пусть далее опять выбрана точка Т1, так что р3 будет вычислена по приведенной схеме и т. д. Данный процесс продолжает создавать и рисовать точки (теоретически бесконечно), и на экране быстро появляется орнамент ковра Серпинского.
Представляется удобным определить простой класс GlintPoint, описывающий точку с целыми координатами2:
class GlintPoint;
public:
Glint x, y;
};
Теперь создадим и инициализируем массив из трех точек T[0], T[1] и T[2] для хранения в нем трех углов треугольника, используя
GlintPoint T[3]={(10,10),(300,30),(200,300)}
Нет необходимости запоминать каждую точку pk последовательности по мере ее возникновения, поскольку мы просто хотим нарисовать ее и затем идти дальше. Поэтому зададим переменную point для хранения этой переменной точки. На каждой итерации point заменяется новым значением.
Для случайного выбора одной из точек T[i] используем выражение i=random(3). Функция random(3) возвращает с одинаковой вероятностью одно из значений: 0, 1, 2. Она определяется следующим образом3:
int random (int m)
{
return rand()%m;
)
В листинге 2.6 приведены остальные детали алгоритма, который генерирует 1000 точек ковра Серпинского.
Листинг 2.6. Генерирование ковра Серпинского
void Sierpinski(void)
{
GLintPoint T[3]=[[10,10},(300,300,(200,300)};
int index = random(3);
// 0, 1 or 2 equally likely
// 0, 1 или 2 равновероятны
GLintPoint point = T[index];
// initial point
// начальная точка
drawDot(point.x, point.y);
// draw initial point
// рисуем начальную точку
for(int i == 0; i < 1000; i++)
// draw 1000 dots
// рисуем 1000 точек
{
index = random(3);
point.x = (point.x + T[index].x) / 2;
point.y = (point.y + T[index].y) / 2;
drawDot(point.x,point.y);
}
glFlush();
}
Пример 2.2.3. Простейшие «точечные рисунки» («Dot Plots»)
Предположим, что вы хотите изучить поведение некоторой математической функции f(x) от переменной x. Например, как изменяется
f(x) = e–xcos(2p x)
при изменении аргумента x от 0 до 4? Многое может прояснить быстрый график, построенный в осях f(x) и x, подобный приведенному на рис. 2.8.
Рис. 2.8. «Точечный график» зависимости функции f(x) = e–xcos(2px) от аргумента x
Для построения графика данной функции просто подсчитаем ее значения при равноудаленных значениях аргумента x и нарисуем точки для каждой координатной пары (xi, f(xi)). Выберем какой-нибудь подходящий шаг между двумя последовательными значениями аргумента x, например 0,005, и тогда процесс в общих чертах пойдет так:
glBegin(GL_POINTS);
for(GLdouble x = 0; x < 4.0 ; x += 0.005)
glVertex2d(x, f(x));
glEnd();
glFlush();
Имеется, однако, одна проблема. Получившийся график будет недопустимо маленьким, так как значения x от 0 до 4 преобразуются в четыре первых пиксела в левом нижнем углу экрана. Более того, отрицательные значения f(.) будут размещаться ниже окна и будут невидимы. Поэтому нам необходимо масштабировать и позиционировать величины, подлежащие рисованию, таким образом, чтобы они правильно располагались в экранном окне. Здесь мы делаем это волевым усилием, в сущности, выбирая несколько значений так, чтобы график адекватно вырисовывался на экране. Позже мы создадим общую процедуру, которая будет сама осуществлять эту подгонку; она называется процедурой преобразования координат из мировых (world coordinates) к оконным (window coordinates).
Масштабирование x. Допустим, мы хотим, чтобы диапазон от 0 до 4 был преобразован так, чтобы он размещался по всей ширине экранного окна, заданного в пикселах величиной screenWidth. В таком случае нам нужно всего лишь перемасштабировать все значения x в screenWidth/4 раза, используя:
sx = x * screenWidth /4.0;
Величина sx равна 0 при x = 0 и screenWidth при x = 4.0, к чему мы и стремились.
Масштабирование и смещение y. Значения функции f(x) лежат между –1,0 и 1,0, следовательно, мы должны их тоже перемасштабировать и сдвинуть. Пусть мы задали экранное окно высотой в screenHeight пикселов. Тогда, чтобы поместить график в центре окна, умножим все значения y на screenHeight/2 и сместим их вверх на screenHeight/2:
sy = (y + 1.0) * screenHeight / 2.0;
Как нам и нужно, это преобразование выдает 0 при y = –1,0 и screenHeight при y = 1,0.
Отметим, что преобразования от x к sx и от y к sy имеют следующий вид:
sx = Ax + B;
sy = Cy + D (2.1)
для должным образом выбранных значений констант A, B, C и D. A и C выполняют масштабирование, а B и D — смещение. Операции масштабирования и смещения являются, в сущности, вариантом «аффинного преобразования» («affine transformation»). Мы будем подробно изучать аффинные преобразования в главе 5; они предоставляют более состоятельный метод отображения любого заданного диапазона величин x и y в экранное окно.
Нам необходимо только правильно установить значения A, B, C и D и начертить точечный график с помощью следующего кода:
GLdouble A, B, C, D, x;
A = screenWidth / 4.0;
B = 0.0;
C = screenHeight / 2.0;
D = C;
glBegin(GL_POINTS);
for(x =0; x < 4.0; x += 0.005)
glVertex2d(A * x + B, C * f(x) + D);
glEnd();
glFlush();
В листинге 2.7 программа для рисования точечного графика приведена полностью для демонстрации того, как соединяются вместе ее различные блоки. Начальные установки похожи на те, которые были в программе рисования трех точек в листинге 2.5. Заметим, что ширина и высота экранного окна задаются как константы и используются внутри кода по мере необходимости.
Практическое упражнение
2.2.1. Точечные графики для произвольной функции f()
Рассмотрим рисование точечного графика функции f(.) аналогично тому, который приведен в примере 2.3, если известно, что при изменении x от xlow до xhigh f(x) принимает значения от ylow до yhigh. Найдите необходимые коэффициенты масштабирования и сдвига из уравнения 2.1, так чтобы точки всегда располагались в пределах экранного окна шириной W и высотой H пикселов.
2.3. Создание рисунков из линий
Гамлет: Видите вы вон то облако в форме верблюда?
Полоний: Ей-богу, вижу, и действительно, ни дать ни взять — верблюд.
Гамлет: По-моему, оно смахивает на хорька.
Уильям Шекспир, Гамлет (пер. Б. Пастернака, действие 3, картина 2)
Как уже говорилось в главе 1, рисование линий является основой компьютерной графики, и почти в каждой графической системе имеются «драйверные» подпрограммы для рисования прямых линий. OpenGL упрощает рисование линий: воспользуйтесь GL_LINES как аргументом для функции glBegin() и передайте в нее две концевые точки в качестве вершин. Тогда для рисования прямой линии между точками (40, 100) и (202, 96) можно использовать следующий код:
glBegin (GL_LINES);
// use constant GL_LZNES here
// используем здесь константу GL_LZNES
glVertex2i(40, 100);
glVertex2i(202, 96);
glEnd();
Листинг 2.7. Полный текст программы вычерчивания «точечного графика» функции
#include
// use proper includes for your system
// используйте допустимые для вашей системы
// включаемые файлы
#include
#include
#include
const int screenWidth = 640;
// width of screen window in pixels
// ширина экранного окна в пикселах
const int screenHeight = 480;
// height of screen window in pixels
// высота экранного окна в пикселах
GLdouble A, B, C, D;
// values used for scaling and shifting
// величины, используемые для масштабирования и сдвига
//<<<<<<<<<<<< myInit >>>>>>>>>>
void myInit(void)
{
glClearColor(1.0,1.0,1.0,0.0);
// background color is white
// цвет фона - белый
glColor3f(0.0f, 0.0f, 0.0f);
// drawing color is black
// цвет для рисования - черный
gIPointSize(2.0);
// a 'dot' is 2 by 2 pixels
// «точка» является квадратом 2 на 2 пиксела
glMatrixMode(GL_PROJECTION);
// set "camera shape"
// устанавливаем «форму камеры»
glLoadIdenity();
gluOrtho2D(0.0, (GLdouble)screenWidth, 0.0, (GLdouble) screenHeight);
A = screenWidth / 4.0;
// set values used for scaling and shifting
// задаем величины, используемые для масштабирования
// и сдвига
B = 0.0:
C = D = screenHeight / 2.0:
}
//<<<<<<<<<<<< myDisplay >>>>>>>>>
void myDisplay(void)
{
glClear(GL_COLOR_BUFFER_BIT):
// clear the screen
// очищаем экран
glBegin(GL_POINTS);
for(GLdouble x = 0; x < 4.0; x += 0.005)
{
GLdouble func = exp(-x) * cos(2 * 3.14159265 * x);
glVertex2d(A * x + B, C * func + D);
glEnd();
glFlush();
// send all output to display
// отправляем весь вывод для отображения
}
//<<<<<<<<<<<< main >>>>>>>>>>>
void main(int argc, char** argv)
{
glutInit(&argc, argv};
// initialize the toolkit
// инициализируем инструментарий
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
// set display mode
// устанавливаем режим дисплея
glutInitWindowSize(screenWidth, screenHeight);
// set window size
// задаем размер окна
glutInitWindowPosition(100, 150);
// set window position on screen
// устанавливаем положение окна на экране
glutCreateWindow("Dot Plot of a Function");
// open the screen window
// открываем экранное окно
glutDisplayFunc(myDisplay);
// register redraw function
// регистрируем функцию перерисовки (обновления)
myInit();
glutMainLoop();
// go into a perpetual loop
// входим в бесконечный цикл
}
Данный код можно было бы для удобства инкапсулировать в подпрограмму drawLineInt:
void drawLineInt(GLint x1, GLint y1, GLint x2, GLint y2)
{
glBegin(GL_LINES);
glVertex2i(x1, y1);
glVertex2i(x2, y2);
glEnd();
}
Альтернативную подпрограмму, drawLineFloat(), можно было бы реализовать аналогично. (Как?)
Рис. 2.9. Простое изображение, построенное из четырех линий: а) тонкие линии;
б) утолщенные линии; в) пунктирные линии
Если между командами glBegin(GL_LINES) и glEnd() задано больше двух вершин, то они принимаются парами и каждая пара соединяется отдельным отрезком прямой. Поле для игры в «крестики-нолики» (tic-tac-toe board), приведенное на рис. 2.9, а, можно было бы нарисовать с помощью следующих команд:
glBegin(GL_LINES);
glVertex2i(10, 20);
// first horizontal line
// первая горизонтальная линия
glVertex2i(40, 20)
glVertex2i(20, 10);
// first vertical line
// первая вертикальная линия
glVertex2i(20, 40);
four more calls to glVertex2i() here for the other two lines
// здесь еще четыре вызова glVertex2i() для двух
// оставшихся прямых
glEnd();
glFlush();
OpenGL предлагает средства для задания атрибутов линий. Цвет линии устанавливается точно так же, как и для точек, с использованием функции glColor3f(). На рис. 2.9, б показано применение утолщенных линий, они установлены с помощью команды glLineWidth(4.0). По умолчанию толщина равна 1,0. На рис. 2.9, в показаны пунктирные линии (из точек или из штрихов). Подробности штрихования приведены в тематическом задании 2.5 в конце данной главы.
2.3.1. Рисование ломаных линий и полигонов
Напомним (из главы 1), что ломаной линией (polyline) называется совокупность отрезков прямых, соединенных своими концами. Ломаная описывается упорядоченным списком точек, как в равенстве:
p0 = (x0, y0), p1 = (x1, y1),…,pn = (xn, yn). (2.2)
В OpenGL ломаная называется «полосой линий» («line strip») и рисуется посредством задания вершин в нужном порядке, между командами glBegin(GL_LINE_STRIP) и glEnd(). К примеру, код
glBegin(GL_LINE_STRIP);
// draw an open polyline
// рисуем открытую ломаную
glVertex2i(20, 10);
glVertex2i(50, 10);
glVertex2i(20, 80);
glVertex2i(50, 80);
glEnd();
glFlush();
генерирует ломаную, изображенную на рис. 2.10, а. Такие атрибуты, как цвет, толщина и штриховка, могут быть заданы для ломаных линий таким же способом, как это было для одиночных прямых линий. Если нужно соединить последнюю точку с первой, чтобы ломаная линия превратилась в полигон, достаточно просто заменить GL_LINE_STRIP на GL_LINE_LOOP. Полученный полигон изображен на рис. 2.10, б.
Рис. 2.10. Ломаная линия и полигон
Полигоны, нарисованные с помощью GL_LINE_LOOP, нельзя заполнять цветом или узором. Для рисования закрашенных полигонов следует использовать команду glBegin(GL_POLYGON), которую мы будем рассматривать позднее.
Пример 2.3.1. Рисование линейных графиков
В примере 2.2.3 мы рассматривали построение точечного графика функции в осях f(x) — x из последовательности точек с координатами (xi, f(xi)). Линейный график является непосредственным расширением этого подхода: эти же самые точки попросту соединены отрезками прямой, то есть получается ломаная линия. Рисунок 2.11 представляет собой пример такого построения для функции
f(x) = 300–100 cos(2p x/100) + 30 cos(4p x/100) + 6 cos(6p x/100),
где x изменяется в шагах от 3 до 100 шагов. При увеличении этого рисунка была бы видна последовательность соединенных между собой отрезков прямой; в изображении же нормального размера они сливаются вместе и выглядят как плавно изменяющаяся кривая.
Процесс вычерчивания графика функции с помощью отрезков прямой почти идентичен созданию точечного рисунка, так что можно использовать (с небольшими изменениями) программу из листинга 2.7. Нам следует произвести масштабирование и сдвиг начерченных прямых, чтобы правильно разместить их в окне. Это требует вычисления констант A, B, C и D аналогично тому, как это делалось нами раньше (см. уравнение 2.1). В листинге 2.8 показаны те изменения, которые следует внести во внутренний цикл рисования в функции myDisplay():
Листинг 2.8. Вычерчивание линейного графика функции
glBegin(GL_LINE_STRIP);
for(Gldouble x = 0; x < 4.0; x += 0.005)
{
define func // задаем функцию
glVertex2d(A * x + B, C * func + D);
}
glEnd();
glFlush;
Рис. 2.11. График математической формулы
Достарыңызбен бөлісу: |