Непрограммируемый конвейер в OpenGL
Модельные преобразования
В мировой системе координат мы строим свою сцену из отдельных объектов, часть из которых должны быть подвижны и называются актерами. Как правило, вначале объект создается вершинами примитивов относительно начала мировых координат, а затем настраивается матрица преобразования модели для помещения объекта в требуемое место сцены. Затем относительно мировой системы координат мы определяем взгляд наблюдателя на построенную сцену с помощью камеры. Размещение трехмерных объектов на сцене, а также управление их дальнейшим положением и ориентацией осуществляется с помощью матриц преобразований модели, которые реализуют следующие три действия:
- Перемещение - плоско-параллельный перенос всего объекта в некоторую точку мирового пространства. Реализуется командой glTranslate{fd}(TYPE x, TYPE y, TYPE z). Окончания f или d - для команд с аргументами типа float или double. В аналитических выкладках принято обозначать как матрицу T.
- Вращение - поворот объекта вокруг некоторой линии, определенной в мировых координатах, на заданный угол. Реализуется командой glRotate{fd}(TYPE angle,TYPE x,TYPE y,TYPE z). Окончания f или d - для команд с аргументами типа float или double. В аналитических выкладках принято обозначать как матрицу R.
- Масштабирование - растяжение, сжатие или отражение объекта по соответствующим осям. Реализуется командой glScale{fd}(TYPE x, TYPE y, TYPE z). Окончания f или d - для команд с аргументами типа float или double. В аналитических выкладках принято обозначать как матрицу S. Масштабирование со значением больше +1.0 растягивает объект по соответствующей оси, со значением меньше +1.0 - уменьшает объект. Значение -1.0 приводит к зеркальному отображению объекта.
Все модельные преобразования применяются к объектам, заданным координатами вершин, последовательным домножением текущей матрицы справа на новую матрицу, определяющую нужное геометрическое преобразование. Последовательность применение тех или иных действий имеет значение. Например, при прочих равных условиях разные результаты дадут действия, выполненные в последовательности: переместить/повернуть и повернуть/переместить. Это и понятно, поскольку совершенно разными будут итоговые матрицы для таких действий:
M = M * T * R; M = M * R * T
Операции перемещения, вращения и масштабирования можно назвать элементарными преобразованиями, последовательно формирующими итоговую матрицу преобразования M. Если вспомнить, что итоговая матрица преобразования будет применена к каждой вершине-вектору v объекта, который будет умножаться справа, то порядок фактических матричных преобразований модели будет обратным порядку следования элементарных преобразований в коде программы.
Проекционные преобразования
В результате последовательности действий с текущей матрицей проецирования мы определяем отсекающий объем видимости, который настраивает OpenGL в следующих аспектах:
- Как сцена будет отображаться на экране монитора - посредством ортогонального или перспективного проецирования
- Какие объекты или части объектов войдут в окончательное изображение, а какие будут отсечены как не попавшие в объем видимости.
Для формирования матрицы проецирования нужно сделать ее текущей, выполнив команду glMatrixMode(GL_PROJECTION).
Ортогональный отсекающий объем видимости устанавливается командой
glOrtho(left, right, bottom, top);
с аргументами типа GLdouble.
Для плоских сцен, где отсекающий объем превращается в отсекающую рамку видимости с координатой z=0, можно применить упрощенный вариант команды из библиотеки GLU
gluOrtho2D(left, right, bottom, top, near, far);
с аргументами типа GLdouble.
Отсекающий объем видимости перспективной проекции представляет собой усеченную пирамиду, в отсутствующей вершине которой расположена камера (точка обзора). Объекты, попадающие в объем видимости, проецируются к вершине прирамиды и те из них, которые хоть и имеют одинаковые размеры, но расположены ближе к точке наблюдения, выглядят больше. Матрица перспективного проецирования устанавливается командой
glFrustum(left, right, bottom, top, near, far);
с аргументами типа GLdouble. Функция glFrustum() не требует, чтобы отсекающий объем был симметричным. Значения параметров near и far должны быть положительными.
Перспективный отсекающий объем можно задать и другой командой gluPerspective() из библиотеки GLU, которая делает то же самое, что и glFrustum(), но несколько иным способом.
gluPerspective(fovy, aspectratio, near, far);
с аргументами типа GLdouble fovy означает угол ракурса; aspectratio - характеристическое число, равное отношению ширины к высоте основания усеченной пирамиды.
Изначально созданные ортогональный или перспективный отсекающие объемы видимости определяют наблюдателя, находящегося в начале мировой системы координат и смотрящего вдоль отрицательного направления оси 0z. Чтобы поставить наблюдателя перед сценой или определить иной угол зрения, можно поступить двумя способами:
- модифицировать матрицу проекционного преобразования (отсекающего объема видимости), изменив положение камеры относительно неизменной сцены
- модифицировать в самом начале матрицу модельного преобразования, изменив всю сцену относительно неизменного положения камеры
Способы равноценные и какой из них применить, решает программист. Но конечно, более привычным и простым является первый способ. Считается, что объект установлен в фиксированной точке, а наблюдатель "бегает" вокруг него в поисках подходящего ракурса. Ведь в музеях не экспозиции перемещаются относительно посетителей, а наоборот.
Для ориентации отсекающего объема видимости относительно системы мировых координат, т.е. назначения требуемого ракурса наблюдения, применяют функцию
gluLookAt(x1, y1, z1, x2, y2, z2, x3, y3, z3);
имеющую девять аргументов типа GLdouble. Нужная точка обзора (положение глаза) описывается первой тройкой аргументов. Вторая тройка аргументов определяет любую точку на линии взгляда, но обычно координаты центра сцены. Третья тройка определяет вектор вверх ориентации (нормаль) видимого объема камеры.
По умолчанию камера располагается в начале системы мировых координат, смотрит вниз по оси z, а положительное направление оси y указывает вектор ориентации (нормаль). Это аналогично вызову функции
gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, -100.0, 0.0, 1.0, 0.0);
Значение z для линии взгляда равно -100.0, но может быть любым отрицательным значением z, так как линия взгляда останется такой же. Подобный вызов не имеет смысла, поскольку эти значения приняты по умолчанию.
Стеки матриц
Для хранения промежуточных значений текущих матриц преобразования в OpenGL предусмотрен специальный механизм памяти, работающий по схеме " первый пришел - первый ушел ". Применение стеков удобно при формировании сложных объектов. Пусть, например, на сцене нужно разместить автомобиль с четырьмя колесами. Вначале формируется матрица размещения самого автомобиля, которая сохраняется в стеке перед размещением колес. После размещения очередного колеса текущая матрица модели восстанавливается в позицию размещения автомобиля и продолжает модифицироваться для размещения нового колеса. Применение стеков освобождает от необходимости повторять все вычисления до точки разветвления процесса заново.
Глубина стека в реализации Microsoft равна:
- mode = GL_MODELVIEW: Модельные преобразования (модельно-видовая матрица) - 32 матрицы (стек изначально не пустой и хранит единичную матрицу 4x4 )
- mode = GL_PROJECTION: Видовые преобразования (проекционная матрица) - 2 матрицы (для хранения одной ортогональной и одной перспективной матриц)
- mode = GL_TEXTURE: Текстурные преобразования (текстурная матрица) - 2 матрицы
Глубину соответствующего стека, поддерживаемого платформой, можно узнать командами:
- glGet(GL_MAX_MODELVIEW_STACK_DEPTH);
- glGet(GL_MAX_PROJECTION_STACK_DEPTH);
- glGet(GL_MAX_TEXTURE_STACK_DEPTH);
Управление стеком выполняется командами:
- glPushMatrix(); - запомнить промежуточное значение текущей матрицы преобразования
- glPopMatrix(); - восстановить значение текущей матрицы преобразования
Если глубина стека превышена, то система генерирует сообщение об ошибке GL_STACK_OVERFLOW, а при попытке извлечь матрицу из пустого стека генерируется ошибка GL_STACK_UNDERFLOW.
В принципе, любую промежуточную матрицу можно скопировать в отдельную переменную, не используя стек. Но тогда такая операция будет реализована программно и будет выполняться центральным процессором. Стековые операции графической системой OpenGL по возможности реализуются аппаратно, а значит - с максимальным быстродействием.