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

RedBook

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

Обратите внимание на то, что использование (0.0, 0.0, 0.0) в качестве аргумента glTranslate*() это единичная операция, то есть она не влияет на объект или на его координатную систему.

3.2.2.2Поворот

void glRotate{fd} (TYPE angle, TYPE x, TYPE y, TYPE z);

Умножает текущую матрицу на матрицу, которая поворачивает объект (или локальную координатную систему) в направлении против часовой стрелки вокруг луча из начала координат, проходящего через точку (x, y, z). Параметр angleзадает угол поворота в градусах.

Результат выполнения glRotatef(45.0, 0.0,0.0,1.0), то есть поворот на 45 градусов вокруг оси z, показан на рисунке 3-6.

Рисунок 3-6. Поворот объекта

Заметьте, что чем дальше объект от оси вращения, тем больше орбита его поворота и тем заметнее сам поворот. Также обратите внимание на то, что вызов glRotate*() с параметром angle равным 0 не имеет никакого эффекта.

3.2.2.3Масштабирование

void glScale{fd} (TYPE x, TYPE y, TYPE z);

Умножает текущую матрицу на матрицу, которая растягивает, сжимает или отражает объект вдоль координатный осей. Каждая x -, y- и z- координата каждой точки объекта будет умножена на соответствующий аргумент x, yили zкоманды glScale*(). При рассмотрении преобразования с точки зрения локальной координатной системы, оси этой системы растягиваются, сжимаются или отражаются с учетом факторов x, y и z, и ассоциированный с этот системой объект меняется вместе с ней.

На рисунке 3-7 показан эффект команды glScalef(-2.0,0.5,1.0);

Рисунок 3-7. Масштабирование и отражение объекта

glScale*() это единственная из трех команд модельных преобразований, изменяющая размер объекта: масштабирование с величинами более 1.0 растягивает объект, использование величин меньше 1.0 сжимает его. Масштабирование с величиной -1.0 отражает объект относительно оси или осей. Единичными аргументами (то есть аргументами, не имеющими эффекта) являются (1.0, 1.0, 1.0). Вообще следует ограничивать использование glScale*() теми случаями, когда это действительно необходимо. Использование glScale*() снижает быстродействие расчетов освещенности, так как вектора нормалей должны быть нормализованы заново после преобразования.

Замечание: Величина масштабирования равная 0 приводит к коллапсу всех координат объекта по оси или осям до 0. Обычно это не является хорошей идеей, так как такая операция не может быть обращена. Говоря математически, матрица не может быть обращена, а обратные матрицы необходимы для многих расчетов, связанных с освещением. Иногда коллапс координат все же имеет смысл: расчет теней на плоской поверхности – это типичный пример применения коллапса. В общем, если координатная система должна быть подвергнута коллапсу, следует использовать для этого проекционную матрицу, а не видовую.

3.2.2.4Пример кода с модельными трансформациями

Пример 3-2это фрагмент программы, которая рисует треугольник 4 раза:

Сплошной треугольник в виде контура без модельных преобразований.

Тот же треугольник, нарисованный штриховой линией и перенесенный по оси x.

Треугольник, нарисованный линией с длинным штрихом, растянутый в 1.5 раза по оси x и сжатый в 1.5 раза по оси y.

Повернутый треугольник, нарисованный пунктирной линией.

Пример 3-2. Использование модельных преобразований

glLoadIdentity();

glColor3f(1.0,1.0,1.0); draw_triangle();

glEnable(GL_LINE_STIPPLE); glLineStipple(1,0xF0F0); glLoadIdentity(); glTranslatef(-20.0,0.0,0.0); draw_triangle();

glLineStipple(1,0xF00F);

glLoadIdentity();

glScalef(1.5,0.5,1.0); draw_triangle();

glLineStipple(1,0x8888);

glLoadIdentity();

glRotatef(90.0,0.0,0.0,1.0); draw_triangle(); glDisable(GL_LINE_STIPPLE);

Обратите внимание на использование glLoadIdentity() для изоляции эффектов модельных преобразований; инициализация матрицы единичными значениями предотвращает кумулятивный эффект последующих преобразований. Несмотря на то, что glLoadIdentity() позволяет добиться нужного результата, ее множественные вызовы могут быть неэффективны, так как после каждого из них вам необходимо заново задавать видовые и модельные преобразования. Для получения информации о

более эффективном способе изоляции преобразований смотрите раздел «Манипулирование матричными стеками» далее в этой главе.

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

3.2.3 Видовые трансформации

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

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

Используйте одну или несколько команд модельных преобразований (glTranslate*() или glRotate*()). Вы можете думать об эффекте этих

преобразований как перемещении камеры, или как о перемещении всех объектов сцены относительно стационарной камеры.

Используйте команду библиотеки утилит gluLookAt() для определения точки и направления обзора. Эта команда инкапсулирует в себе серию поворотов и переносов.

Создайте свою собственную (пользовательскую) функцию, инкапсулирующую повороты и переносы. В некоторых приложениях такая функция может понадобиться для того, чтобы можно было задавать видовую трансформацию каким-либо специфическим путем. Например, вам может понадобиться задавать преобразование в терминах полярных координат для камеры, вращающейся вокруг объекта или в терминах углов наклона самолета в полете.

3.2.3.1Использование glTranslate*() и glRotate*()

Когда вы используете команды модельных преобразований для эмуляции видовых, вы пытаете передвинуть точку наблюдения в желаемом направлении, сохранив объекты неподвижными. Поскольку точка наблюдения изначально находится в начала координат, и поскольку объекты обычно конструируются там же (рисунок 3-8), вам, в общем, необходимо выполнить некоторые преобразования, дабы объекты были видны. Заметьте, что изначально камера указывает в отрицательном направлении оси z.

Рисунок 3-8. Объект и точка обзора в начале координат

В простейшем случае вы можете передвинуть точку наблюдения назад, от объектов; эффект будет такой же, как если бы вы передвинули объекты вперед от точки наблюдения. Помните, что по умолчанию «вперед» – это значит в отрицательном направлении оси z; если вы повернете точку наблюдения, «вперед» будет иметь другой смысл. Таким образом, чтобы поместить 5 единиц дистанции между точкой наблюдения и объектами, переместив точку наблюдения (как показано на рисунке 3-9), используйте следующую команду:

glTranslatef(0.0,0.0,-5.0);

Эта команда передвигает объекты сцены на -5 единиц вдоль оси z. Она также эквивалентна передвижению камеры на +5 единиц вдоль оси z.

Рисунок 3-9. Разделение точки наблюдения и объекта

Теперь предположим, что вы хотите наблюдать объекты со стороны. Должны ли вы в этом случае выполнить команду поворота до или после команды переноса? Если вы мыслите в терминах фиксированной системы координат, для начала представьте объекты и камеру в начале координат. Сначала вы должны повернуть объекты, а затем отодвинуть их от камеры, чтобы выбранная сторона была видна. Поскольку вы знаете,

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

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

последовательность команд преобразований для получения желаемого результата должна быть следующей:

glTranslatef(0.0,0.0,-5.0); glRotatef(90.0,0.0,1.0,0.0);

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

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

3.2.3.2Использование команды библиотеки утилит gluLookAt()

Часто программисты конструируют сцену в районе начала координат или в некотором другом месте, а затем хотят посмотреть на нее с определенной точки обзора для получения лучшего вида. Как и говорит ее имя, команда из библиотеки утилит gluLookAt() разработана как раз для подобных целей. Она принимает три набора аргументов, которые задают точку наблюдения, прицельную точку (точку, на которую направлена камера) и направление, которое следует считать верхним. Выберите точку обзора, чтобы получить желаемый вид сцены. Прицельная точка, как правило, находится где-то в середине сцены. (Если вы строите сцену в начале координат, то, вероятно, оно и будет прицельной точкой.) Несколько сложнее, видимо, задать верный вектор верхнего направления. Если вы строите сцену в или около начала координат и считаете, что положительное направление оси yуказывает вверх, то это и есть ваш вектор верхнего направления для gluLookAt(). Однако если вы разрабатываете

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

Команда gluLookAt() может быть полезна, например, если вы хотите скользить по ландшафту. С объемом видимости симметричным по xи y точка (eyex, eyey, eyez) может всегда задаваться как лежащая в центре изображения, и вы можете исполнять серию команд для незначительного изменения этой точки, таким образом, скользя по сцене.

void gluLookAt (GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz,

GLdouble upx, GLdouble upy, GLdouble upz);

Задает видовую матрицу и умножает на нее текущую матрицу. Выбранная точка обзора задается аргументами eyex, eyey и eyez. Аргументы centerx, centeryи centerzзадают любую точку на линии обзора, но обычно они задают точку где-то в середине обозреваемой сцены. Аргументы upz, upyи upz определяют, какое направление считается верхним (то есть направление от дна до вершины объема видимости).

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

gluLookAt(0.0,0.0,0.0,0.0,0.0,-100.0,0.0,1.0,0.0);

Величина z-координаты прицельной точки здесь равна -100.0, но на самом деле она может быть любой отрицательной величиной, поскольку в этом случае направление обзора останется неизменным. Для описанного случая нет необходимости вызывать gluLookAt(), поскольку это установка по умолчанию (рисунок 3-10). (Линии из точки наблюдения представляют собой объем видимости, задающий видимое пространство.)

Рисунок 3-10. Позиция точки наблюдения по умолчанию

Заметьте, что gluLookAt() является частью библиотеки утилит, а не базовой командой OpenGL. Это произошло не потому, что gluLookAt() бесполезна, а потому, что она инкапсулирует несколько базовых команд OpenGL, а именно glTranslate*() и glRotate*(). Чтобы понять это представьте, что камера находится в выбранной точке обзора и направлена в соответствии с желаемым направлением обзора, как задано gluLookAt() и сцена находится в начале координат. Чтобы отменить действия gluLookAt() вам требуется поместить камеру в начале координат и установить направление обзора совпадающим с отрицательным направлением оси z (то есть

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

Замечание: Вы можете иметь только одно активное видовое преобразование. Вы не можете комбинировать эффекты двух видовых преобразований (фотокамера не может быть установлена на двух треногах одновременно). Если вы хотите изменить позицию камеры, убедитесь, что вы вызвали glLoadIdentity() для стирания эффекта от текущего видового преобразования.

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

нужного угла между векторами эквивалентен скалярному произведению между нормализованными векторами. Угол поворота вокруг оси, полученный как векторное произведение всегда находится между 0 и 180 градусами.

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

3.2.3.3Создание пользовательской функции

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

Предположим, что вы разрабатываете симулятор полетов, и вам требуется изображать мир с точки зрения пилота в кабине самолета. Самолет находится в точке с координатами (x, y, z). Предположим также, что самолет характеризуется тремя углами своего наклона относительно центра своей гравитации roll, pitch и heading. Для изображения мира глазами пилота может быть использована следующая функция:

void pilotView(GLdouble planex, GLdouble planey, GLdouble planez, GLdouble roll, GLdouble pitch, GLdouble heading)

{

glRotated(roll,0.0,0.0,1.0);

glRotated(pitch,0.0,1.0,0.0);

glRotated(heading,1.0,0.0,0.0); glTranslated(-planex,-planey,-planez);

}

Теперь предположим, что вашему приложению требуется вращать камеру вокруг объекта, находящегося в начале координат. В этом случае вы, вероятно, захотите задавать видовое преобразование в терминах полярной системы координат. Допустим, что переменная distance задает радиус орбиты, то есть расстояние от камеры до начала координат. (Вначале камера отодвигается на distance единиц вдоль положительного направления оси z.) Переменная azimuth задает угол вращения камеры вокруг объекта в плоскости xy, отмеряемый от положительного направления оси y. Похожим образом, elevation это угол вращения камеры в плоскости yz, отмеряемый от положительного направления оси z. Наконец, twist представляет собой угол вращения объема видимости вокруг линии обзора. В этом случае подойдет следующая функция.

void polarView(GLdouble distance, GLdouble twist, GLdouble elevation, GLdouble azimuth)

{

glTranslated(0.0,0.0,-distance); glRotated(-twist,0.0,0.0,1.0); glRotated(-elevation,1.0,0.0,0.0); glRotated(azimuth,0.0,0.0,1.0);

}

3.3 Проекционные трансформации

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

glMatrixMode(GL_PROJECTION); glLoadIdentity();

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

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

3.3.1 Перспективная проекция

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

Команда определения объема видимости в форме усеченной пирамиды glFrustum() вычисляет матрицу, выполняющую перспективное проецирование, и умножает на нее текущую матрицу проекции (обычно единичную). Вспомните, что объем видимости используется для отсечения объектов лежащих вне него; четыре стороны пирамиды, ее основание и вершина (точнее, верхняя сторона) соответствуют шести отсекающим плоскостям объема видимости, как показано на рисунке 3-11. Объекты или части объектов вне этих плоскостей отсекаются и не выводятся в финальном изображении. Заметьте, что glFrustum() не требует от вас указания симметричного объема видимости.

Рисунок 3-11. Объем видимости перспективной проекции, заданный командой glFrustum()

void glFrustum (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);

Создает матрицу перспективного проецирования и умножает на нее текущую матрицу. Объем видимости задается параметрами (left, bottom,-near) и (right, top,-near) определяющими координаты (x, y, z) левого нижнего и правого верхнего углов ближней отсекающей плоскости; nearи far задают дистанцию от точки наблюдения до ближней и дальней отсекающих плоскостей (они всегда должны быть положительными).

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

Дополнительно: Пирамида не обязана быть симметричной и ее центральная ось не обязательно должна совпадать с осью z. Например, вы можете использовать glFrustum() для создания такого изображения, как если бы вы смотрели через прямоугольное окно, причем это окно находится выше и правее вас. Фотографы используют этот прием для создания ложной перспективы. Вы можете использовать его, чтобы аппаратно визуализировать изображения с разрешением значительно выше обычного (например, для вывода на принтер). Предположим, вам требуется изображение с разрешением вдвое большим разрешения вашего экрана. Нарисуйте изображение 4 раза, каждый раз используя пирамидальный объем видимости для покрытия всего экрана одной четвертью изображения. После того, как каждая четверть будет выведена на экран, вы можете считать пиксели, собрав, таким образом, данные для изображения высокого разрешения.

Рисунок 3-12. Объем видимости перспективной проекции, заданный командой gluPerspective

Хотя glFrustum() концептуально ясна, ее использование не является интуитивно понятным. Вместо нее вы можете попробовать использовать функцию gluPerspective() из библиотеки утилит. Эта функция создает объем видимости той же формы, что и glFrustum(), но вы задаете его параметры иным путем. Вместо указания углов

ближней отсекающей плоскости, вы задаете угол визуального охвата ( или тета) в вертикальном направлении y и отношение ширины к высоте (x/y). (Для квадратной части экрана отношение ширины к высоте равно 1.0.) Этих двух параметров достаточно для определения неусеченной пирамиды вдоль направления обзора (рисунок 3-12). Вы

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

Заметьте, что gluPerspective() ограничена созданием только пирамид симметричных вдоль линии обзора по x- и y-осям, но обычно это именно то, что и требуется.

void gluPerspective (GLdouble fovy, GLdouble aspect, GLdouble near, GLdouble far);

Создает матрицу для пирамиды симметричного перспективного вида и умножает на нее текущую матрицу. Параметр fovy задает угол визуального охвата в плоскости yz, его значение должно лежать в диапазоне [0.0, 180.0]. Параметр aspect это отношение ширины пирамиды к ее высоте. Параметры near и far представляют дистанции от точки

наблюдения до ближней и дальней плоскостей отсечения вдоль отрицательного направления оси z.

При использовании gluPerspective() вам необходимо выбрать подходящее значение для угла визуального охвата, иначе изображение будет выглядеть непропорциональным. Для получения наилучшего результата, заметьте, на каком расстоянии ваш глаз находится от монитора обычно и насколько велико окно, затем вычислите угол, на который распространяется окно в вашем поле зрения при данных дистанции и размере. Скорее всего, эта величина будет меньше, чем вы ожидали. Другой способ задуматься об этом помнить, что 94-ех градусный охват с 35- миллиметровой камерой требует 20-миллиметровых линз, которые считаются весьма широкоугольными.

В данном разделе встречались упоминания о миллиметрах и дюймах имеют ли они значение в OpenGL. Ответ в одно слово нет. Проекционные и иные трансформации не имеют единиц измерения. Если вы предпочитаете думать, что ближняя и дальняя плоскости отсечения находятся на расстояниях 1.0 и 20.0 километров, метров, дюймов, миль и так далее это ваше право. Единственное правило используйте одинаковые

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