Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

RedBook

.pdf
Скачиваний:
18
Добавлен:
11.06.2015
Размер:
7.43 Mб
Скачать

Матричный стек полезен для построения иерархических моделей, в которых сложные объекты конструируются при помощи простых. Предположим, например, что вы рисуете автомобиль, у которого 4 колеса и каждое колесо прикреплено к автомобилю пятью болтами. У вас имеется функция, рисующая колесо и функция, рисующая болт, так как все колеса и болты выглядят одинаково. каждая из этих функций рисует колесо или болт в четко определенном месте, скажем в начале координат, и с определенной ориентацией, например, с центральной осью объекта, совпадающей с отрицательным направлением оси z. Когда вы рисуете машину, включая колеса и болты, вы захотите вызвать функцию, рисующую колесо 4 раза с применением различных трансформаций для правильного позиционирования каждого колеса. При рисовании каждого колеса вы захотите нарисовать болт пять раз, каждый раз перенося болт в нужное место относительно колеса.

На секунду предположим, что все, что вы хотите сделать это нарисовать корпус машины и колеса. В этом случае алгоритм процесса может быть описан следующим образом:

Нарисовать корпус машины.

Запомнить, где мы находимся, и выполнить перенос к переднему левому колесу.

Нарисовать колесо и отбросить последний перенос, чтобы вернуться в начало координат относительно корпуса машины.

Запомнить, где мы находимся, и выполнить перенос к левому заднему колесу...

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

Поскольку преобразования сохраняются в матрицах, матричный стек предоставляет идеальный механизм для подобного рода запоминаний, переносов и отбрасываний. Все ранее описанные матричные операции (glLoadMatrix(), glMultMatrix(), glLoadIdentity() и команды, создающие специфические матрицы) работают с текущей матрицей, то есть с верхней матрицей стека. С помощью команд управления стеком вы можете управлять тем, какая матрица находится на вершине стека: glPushMatrix() копирует текущую матрицу и добавляет копию на вершину матричного стека, glPopMatrix() уничтожает верхнюю матрицу в стеке (рисунок 3-19). (Помните, что текущей матрицей всегда является матрица на вершине). Говоря проще, glPushMatrix() означает «запомнить, где мы находимся», а glPopMatrix() – «вернуться туда, где мы были».

Рисунок 3-19. Помещение в матричный стек и извлечение из матричного стека

void glPushMatrix (void);

Опускает все имеющиеся в текущем стеке матрицы на один уровень. То, какой стек является текущим, задается с помощью вызова glMatrixMode(). Верхняя матрица при этом копируется, таким образом, ее содержимое продублировано в верхней и второй сверху матрице стека. Если добавлено слишком много матриц, будет сгенерирована ошибка.

void glPopMatrix (void);

Выкидывает верхнюю матрицу из стека, тем самым, уничтожая ее содержимое. Верхней (и, как следствие, текущей) становится матрица, которая занимала второе сверху место в стеке. Текущий стек задается командой glMatrixMode(). Если стек содержит только одну матрицу, вызов glPopMatrix() сгенерирует ошибку.

Пример 3-4 рисует автомобиль в предположении о наличие функций, рисующих корпус машины, колесо и болт.

Пример 3-4. Помещение и извлечение матриц

нарисовать_колесо_и_болты()

{

long i;

нарисовать_колесо(); for(i=0;i<5;i++)

{

glPushMatrix();

glRotatef(72.0*i,0.0,0.0,1.0);

glTranslatef(3.0,0.0,0.0);

нарисовать_болт(); glPopMatrix();

}

}

нарисовать_корпус_колеса_и_болты()

{

нарисовать_корпус_машины(); glPushMatrix();

//передвинуться к позиции первого колеса glTranslatef(40,0,30);

нарисовать_колесо_и_болты(); glPopMatrix();

glPushMatrix();

//передвинуться к позиции второго колеса glTranslatef(40,0,-30);

нарисовать_колесо_и_болты(); glPopMatrix();

//похожим образом нарисовать еще два колеса

...

}

В данном коде предполагается, что ось колеса и болта совпадает с осью z, что болты располагаются на колесе каждые 72 градуса и находятся на расстоянии 3 единицы от центра колеса. Также предполагается, что передние колеса находятся на 40 единиц впереди и на 30 влево и вправо от центра корпуса.

Стек более эффективен, чем индивидуальная матрица, особенно в том случае, если он реализован аппаратно. Когда вы проталкиваете матрицу в стек, вам не нужно предварительно извлекать из нее данные и запоминать их в главном процессе, кроме того, аппаратура может обладать способностью одновременно копировать несколько элементов матрицы. Иногда стоит держать на дне стека единичную матрицу во избежание многократных вызовов glLoadIdentity().

3.7.1 Стек видовых матриц

Как вы видели раньше, видовая матрица содержит кумулятивное произведение от перемножения матриц, представляющих отдельные видовые и модельные преобразования. Каждая видовая или модельная трансформация создает новую матрицу, на которую умножается текущая видовая матрица. Результат, который становится новой текущей видовой матрицей, представляет композитное преобразование. Стек видовых матриц может содержать как минимум 32 матрицы размерностью 4x4. В самом начале верхней (и единственной) матрицей является единичная. Некоторые реализации OpenGLмогут поддерживать больше чем 32 матрицы в видовом стеке. Чтобы выяснить максимально допустимое число матриц, используйте команду glGetIntegerv (GL_MAX_MODELVIEW_STACK_DEPTH, GLint *params).

3.7.2 Стек проекционных матриц

Матрица проекции содержит матрицу для проекционного преобразования, описывающую объем видимости. В общем случае вам не нужно объединять проекционные матрицы, поэтому вы вызываете glLoadIdentity() перед выполнением проекционного преобразования. По этой же причине стек проекционных матриц должен быть всего два уровня в глубину. Некоторые реализации OpenGLмогут позволять хранить больше двух матриц размерностью 4x4. Для выяснения максимальной глубины проекционного стека вызовите glGetIntegerv (GL_MAX_PROJECTION_STACK_DEPTH, GLint *params).

Одним из применений второго уровня стека может быть приложение, которому наряду со своим основным окном, содержащим трехмерную модель, требуется отображать окно помощи с текстом внутри него. Поскольку текст наиболее легко позиционируется с применением ортографической проекции, вы можете временно переключиться на ортографическую проекцию, отобразить помощь и затем вернуться к предыдущей проекции:

glMatrixMode(GL_PROJECTION); glPushMatrix();//сохранить текущую проекцию

glLoadIdentity();

glOrtho(...);//настроиться на отображение текста отобразить_помощь();

glPopMatrix();

Обратите внимание на то, что, вероятно, вам потребуется также изменить должным образом и видовую матрицу.

Дополнительно: Если вы достаточно понимаете в математике, вы можете создавать свои собственные проекционные матрицы, выполняющие более сложные проекционные преобразования. Например, OpenGL и ее библиотека утилит не содержит встроенных механизмов для выполнения двухточечной перспективной проекции. Тем не менее, если вы хотите эмулировать, скажем, рисунки в виде набросков, вам может понадобиться такая проекционная матрица.

3.8 Дополнительные плоскости отсечения

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

Каждая плоскость определяется коэффициентами своего уравнения: Ax+By+Cz+D=0.

Отсекающие плоскости автоматически трансформируются в соответствии с модельными и видовыми преобразованиями. Видимым усеченным объемом становится пересечение

объема видимости и всех полупространств заданных дополнительными плоскостями отсечения. Помните, что OpenGL автоматически реконструирует ребра отсеченных полигонов.

void glClipPlane (GLenum plane, const GLdouble *equation);

Задает плоскость отсечения. Аргумент equation указывает на 4 коэффициента уравнения плоскости, Ax+By+Cz+D=0. Все точки с видовыми координатами

, удовлетворяющими условию:

,

где M видовая матрица являющаяся текущей на момент вызова glClipPlane() лежат в полупространстве определенном плоскостью. Все точки вне этого полупространства отсекаются. Аргумент planeдолжен быть равен GL_CLIP_PLANEi, где iцелое, показывающее, какую из имеющихся плоскостей мы определяем. iдолжно лежать в диапазоне от 0 до числа на единицу меньшего, чем максимально допустимое количество дополнительных плоскостей отсечения.

Каждая из дополнительных плоскостей отсечения, кроме того, должна быть включена или выключена (соответственно) командами:

glEnable(GL_CLIP_PLANEi);

или

glDisable(GL_CLIP_PLANEi);

Все реализации OpenGL должны поддерживать как минимум 6 дополнительных плоскостей отсечения, хотя некоторые могут позволять и больше. Вы можете выяснить

максимально допустимое количество дополнительных плоскостей отсечения в вашей реализации OpenGL, вызвав glGetIntegerv() с аргументом GL_MAX_CLIP_PLANES.

Замечание: Отсечение, производимое как результат вызова glClipPlane(), совершается в видовых, а не в усеченных координатах. Эта разница заметна в том случае, если матрица проекции является вырожденной (то есть, реальной проекционной матрицей, отображающей трехмерные координаты на двумерные).

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

3.8.1 Пример кода с дополнительными плоскостями отсечения

Пример 3-5 визуализирует проволочную сферу с двумя отсекающими плоскостями, которые отрезают три четверти этой сферы (рисунок 3-20).

Рисунок 3-20. Отсеченная проволочная сфера

Пример 3-5. Проволочная сфера с двумя отсекающими плоскостями: файл clip.cpp

#include <glut.h>

//Инициализация void init(void)

{

glClearColor(0.0,0.0,0.0,0.0); glShadeModel(GL_FLAT);

}

//Изменение размеров окна void reshape(int w, int h)

{

glViewport(0,0,(GLsizei) w, (GLsizei) h); glMatrixMode(GL_PROJECTION); glLoadIdentity();

gluPerspective(60.0,(GLfloat) w/(GLfloat) h,1.0,20.0); glMatrixMode(GL_MODELVIEW);

}

//Отображение

void display(void)

{

GLdouble eqn[4]={0.0,1.0,0.0,0.0}; GLdouble eqn2[4]={1.0,0.0,0.0,0.0};

glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0,1.0,1.0); glPushMatrix(); glTranslatef(0.0,0.0,-5.0);

//Отсечь нижнюю половину (y<0) glClipPlane(GL_CLIP_PLANE0,eqn); glEnable(GL_CLIP_PLANE0);

//Отсечь левую половину (x<0) glClipPlane(GL_CLIP_PLANE1,eqn2); glEnable(GL_CLIP_PLANE1); glRotatef(90.0,1.0,0.0,0.0); glutWireSphere(2.0,20,16); glPopMatrix();

glFlush();

}

int main(int argc, char **argv)

{

glutInit(&argc,argv); glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB); glutInitWindowSize(500,500); glutInitWindowPosition(100,100);

glutCreateWindow("Wireframe Sphere with Two Clipping Planes"); init();

glutDisplayFunc(display);

glutReshapeFunc(reshape);

glutMainLoop(); return 0;

}

3.9 Примеры комбинирования нескольких преобразований

В этом разделе демонстрируется, как комбинировать несколько преобразований для достижения определенных результатов. Приводятся два примера: солнечная система, в которой объекты должны вращаться относительно своих осей, а также по орбитам вокруг друг-друга и рука робота, в которой присутствует несколько сочленений, изменяющих координатную систему и двигающихся относительно друг-друга.

3.9.1 Строим солнечную систему

Программа, описанная в данном разделе, рисует упрощенную модель солнечной системы, состоящую из солнца и одной планеты. Оба объекта рисуются в виде сфер. В

ходе написания этой программы потребуется команда glRotate*() для вращения планеты вокруг солнца и вокруг своей оси. Также потребуется glTranslate*() для перемещения планеты из начала координат на ее орбиту. Помните, что вы можете задавать нужные размеры сфер, передавая соответствующие аргументы функции glutWireSphere().

Чтобы нарисовать солнечную системы, сначала требуется настроить проекцию и видовое преобразование. Для данного примера были использованы gluPerspective() и gluLookAt().

Рисование солнца достаточно прямолинейно, поскольку оно должно находиться в начале координат фиксированной координатной системы, то есть там, где ее помещает рисующая ее функция. Таким образом, рисование сферы не требует никаких переносов. Вы можете использовать glRotate*(), чтобы заставить солнце вращаться вокруг своей оси. Чтобы нарисовать планету, вращающуюся вокруг солнца, как показано на рисунке 3-21, требуется выполнить несколько модельных преобразований. Планета должна вращаться вокруг своей оси раз в день, кроме того, раз в год планета должна совершать виток вокруг солнца.

Рисунок 3-21. Планета и солнце

Для определения порядка модельных преобразований представьте себе, что происходит с локальной координатной системой. Первый вызов glRotate*() поворачивает локальную координатную систему (изначально совпадающую с фиксированной). Затем glTranslate*() переносит локальную координатную систему в позицию на орбите планеты. Расстояние, на которое выполняется перенос, должно совпадать с радиусом орбиты планеты. Таким образом, начальный вызов glRotate*() на самом деле определяет, где именно на орбите находится планета (то есть определяет время года).

Второй вызов glRotate*() поворачивает локальную координатную системы вокруг одной из ее осей, таким образом, задавая для планеты время суток. После того, как выполнены все описанные преобразования, можно рисовать планету.

Обобщая вышесказанное, следующая последовательность команд OpenGL рисует солнце и планету, а полный текст программы приведен в примере 3-6.

glPushMatrix();

glutWireSphere(1.0,20,16); //Рисуем солнце glRotatef((GLfloat) year,0.0,1.0,0.0); glTranslatef(2.0,0.0,0.0); glRotatef((GLfloat) day,0.0,1.0,0.0); glutWireSphere(0.2,10,8); //Рисуем планету glPopMatrix();

Пример 3-6. Планетарная система: файл planet.cpp

#include <glut.h>

int year=0, day=0;

//Инициализация void init(void)

{

glClearColor(0.0,0.0,0.0,0.0); glShadeModel(GL_FLAT);

}

//Изменение размеров окна void reshape(int w, int h)

{

glViewport(0,0,(GLsizei) w, (GLsizei) h); glMatrixMode(GL_PROJECTION); glLoadIdentity();

gluPerspective(60.0,(GLfloat) w/ (GLfloat) h,1.0,20.0); glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

gluLookAt(0.0,0.0,5.0,0.0,0.0,0.0,0.0,1.0,0.0);

}

//Отображение

void display(void)

{

glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0,1.0,1.0); glPushMatrix();

//Рисуем солнце glutWireSphere(1.0,20,16); glRotatef((GLfloat)year,0.0,1.0,0.0); glTranslatef(2.0,0.0,0.0); glRotatef((GLfloat)day,0.0,1.0,0.0);

//Рисуем планету glutWireSphere(0.2,10,8); glPopMatrix(); glutSwapBuffers();

}

//Реакция на клавиатуру

void keyboard(unsigned char key,int x, int y)

{

switch(key)

{

case 'd': day=(day+10)%360; glutPostRedisplay(); break;

case 'D': day=(day-10)%360; glutPostRedisplay(); break;

case 'y': year=(year+5)%360; glutPostRedisplay(); break;

case 'Y': year=(year-5)%360; glutPostRedisplay(); break;

default:

break;

}

}

int main(int argc, char **argv)

{

glutInit(&argc,argv); glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB); glutInitWindowSize(500,500); glutInitWindowPosition(100,100); glutCreateWindow("Planetary System"); init();

glutDisplayFunc(display);

glutReshapeFunc(reshape);

glutKeyboardFunc(keyboard);

glutMainLoop(); return 0;

}

3.9.2 Строим руку робота

В этом разделе обсуждается программа, создающая искусственную руку робота с двумя или более сегментами. Эти фрагменты соединены в точках плеча, локтя и так далее. На рисунке 3-22 показана одна такая точка соединения.

Рисунок 3-22. Рука робота

В качестве сегментов руки вы можете использовать масштабированные кубы, но сначала следует применить нужные модельные преобразования, чтобы ориентировать каждый сегмент. Поскольку изначально начало локальной координатной системы совпадает с центром куба, требуется передвинуть ее к ребру куба, иначе он будет поворачиваться вокруг своего центра, а не вокруг точки соединения.

После вызова glTranslate*() для установки точки соединения и glRotate*() для поворота и присоединения куба, выполним перенос обратно к центру куба. До его рисования изменим его масштаб по осям. Вызовы glPushMatrix() и glPopMatrix() ограничат действие glScale*(). Для первого сегмента руки код будет выглядеть следующим образом (полный текст программы приводится в примере 3-7):

glTranslatef(-1.0,0.0,0.0); glRotatef((GLfloat) shoulder, 0.0,0.0,1.0); glTranslatef(1.0,0.0,0.0);

glPushMatrix();

glScalef(2.0,0.4,1.0);

glutWireCube(1.0);

glPopMatrix();

Чтобы построить второй сегмент, требуется передвинуть локальную координатную систему к следующей точке соединения. Поскольку координатная система была предварительно повернута, ее ось xуже ориентирована вдоль повернутой руки. Таким образом, перенос вдоль оси x передвигает локальную координатную систему к следующей точке соединения. После перемещения к ней, вы можете использовать уже приводившийся выше код для рисования второго сегмента руки, так же как вы использовали его для рисования первого. Этот процесс может быть повторен для бесконечного числа сегментов (плечо, локоть, запястье, пальцы).

glTranslatef(1.0,0.0,0.0); glRotatef((GLfloat) elbow,0.0,0.0,1.0); glTranslatef(1.0,0.0,0.0); glPushMatrix();

glScalef(2.0,0.4,1.0);

glutWireCube(1.0);

glPopMatrix();

Пример 3-7. Рука робота: файл robot.cpp

#include <glut.h>

static intshoulder=0,elbow=0;

//Инициализация void init(void)

{

glClearColor(0.0,0.0,0.0,0.0); glShadeModel(GL_FLAT);

}

//Изменение размеров окна void reshape(int w, int h)

{

glViewport(0,0,(GLsizei) w, (GLsizei) h); glMatrixMode(GL_PROJECTION); glLoadIdentity();

gluPerspective(65.0,(GLfloat) w/ (GLfloat) h,6.0,25.0); glMatrixMode(GL_MODELVIEW);

glLoadIdentity(); glTranslatef(0.0,0.0,-10.0);

}

//Отображение

void display(void)

{

glClear(GL_COLOR_BUFFER_BIT); glPushMatrix();

glTranslatef(-1.0,0.0,0.0); glRotatef((GLfloat)shoulder,0.0,0.0,1.0); glTranslatef(1.0,0.0,0.0); glPushMatrix();

glScalef(2.0,0.4,1.0);

glutWireCube(1.0);

glPopMatrix();

glTranslatef(1.0,0.0,0.0);

glRotatef((GLfloat)elbow,0.0,0.0,1.0);

glTranslatef(1.0,0.0,0.0);

glPushMatrix();

glScalef(2.0,0.4,1.0);

glutWireCube(1.0);

glPopMatrix();

glPopMatrix();

glutSwapBuffers();

}

//Реакция на клавиатуру

void keyboard(unsigned char key,int x, int y)

{

switch(key)

{

case 's': shoulder=(shoulder+5)%360; glutPostRedisplay(); break;

case 'S': shoulder=(shoulder-5)%360; glutPostRedisplay(); break;

case 'e': elbow=(elbow+5)%360; glutPostRedisplay(); break;

case 'E': elbow=(elbow-5)%360; glutPostRedisplay(); break;

default:

break;

}

}

int main(int argc, char **argv)

{

glutInit(&argc,argv); glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB); glutInitWindowSize(500,500);

Соседние файлы в предмете Компьютерная Графика