Геометрические свойства нескольких изображений. Программирование с использованием DirectX9: Вращение объектов

Движок не перемещает корабль. Корабль остается на месте, а движок перемещает вселенную относительно его.

Это очень важная часть уроков, убедитесь что прочитали ее несколько раз и хорошо поняли.

Однородные координаты

До текущего момента мы оперировали 3х-мерными вершинами как (x, y, z) триплетами. Введем еще один параметр w и будем оперировать векторами вида (x, y, z, w).

Запомните навсегда, что:

  • Если w == 1, то вектор (x, y, z, 1) - это позиция в пространстве.
  • Если же w == 0, то вектор (x, y, z, 0) - это направление.

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

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

Матрицы трансформаций

Введение в матрицы

Проще всего представить матрицу, как массив чисел, со строго определенным количеством строк и столбцов. К примеру, матрица 2x3 выглядит так:

Однако в трехмерной графике мы будем использовать только матрицы 4x4, которые позволят нам трансформировать наши вершины (x, y, z, w). Трансформированная вершина является результатом умножения матрицы на саму вершину:

Матрица x Вершина (именно в этом порядке!!) = Трансформир. вершина

Довольно просто. Мы будем использовать это довольно часто, так что имеет смысл поручить это компьютеру:

В C++, используя GLM:

glm :: mat4 myMatrix ; glm :: vec4 myVector ; glm :: // Обратите внимание на порядок! Он важен!

В GLSL:

mat4 myMatrix ; vec4 myVector ; // Не забудьте тут заполнить матрицу и вектор необходимыми значениями vec4 transformedVector = myMatrix * myVector ; // Да, это очень похоже на GLM:)

Попробуйте поэкспериментировать с этими фрагментами.

Матрица переноса

Матрица переноса выглядит так:

где X, Y, Z - это значения, которые мы хотим добавить к нашему вектору.

Значит, если мы захотим перенести вектор (10, 10, 10, 1) на 10 юнитов в направлении X, то мы получим:

… получим (20, 10, 10, 1) однородный вектор! Не забывайте, что 1 в параметре w, означает позицию, а не направление и наша трансформация не изменила того, что мы работаем с позицией.

Теперь посмотрим, что случится, если вектор (0, 0, -1, 0) представляет собой направление:

… и получаем наш оригинальный вектор (0, 0, -1, 0). Как было сказано раньше, вектор с параметром w = 0 нельзя перенести.

И самое время перенести это в код.

В C++, с GLM:

#include // после glm :: mat4 myMatrix = glm :: translate (glm :: mat4 (), glm :: vec3 (10.0 f , 0.0 f , 0.0 f )); glm :: vec4 myVector (10.0 f , 10.0 f , 10.0 f , 0.0 f ); glm :: vec4 transformedVector = myMatrix * myVector ;

В GLSL:

vec4 transformedVector = myMatrix * myVector ;

По факту, вы никогда не будете делать это в шейдере, чаще всего вы будете выполнять glm::translate() в C++, чтобы вычислить матрицу, передать ее в GLSL, а уже в шейдере выполнить умножение

Единичная матрица

Это специальная матрица, которая не делает ничего, но мы затрагиваем ее, так как важно помнить, что A умноженное на 1.0 дает A:

В C++ :

glm :: mat4 myIdentityMatrix = glm :: mat4 (1.0 f );

Матрица масштабирования

Выглядит также просто:

Значит, если вы хотите применить масштабирование вектора (позицию или направление - это не важно) на 2.0 во всех направлениях, то вам необходимо:

Обратите внимание, что w не меняется, а также обратите внимание на то, что единичная матрица - это частный случай матрицы масштабирования с коэффициентом масштаба равным 1 по всем осям. Также единичная матрица - это частный случай матрицы переноса, где (X, Y, Z) = (0, 0, 0) соответственно.

В C++ :

// добавьте #include и #include glm :: mat4 myScalingMatrix = glm :: scale (2.0 f , 2.0 f , 2.0 f );

Матрица поворота

Сложнее чем рассмотренные ранее. Мы опустим здесь детали, так как вам не обязательно знать это точно для ежедневного использования. Для получения более подробной информации можете перейти по ссылке Matrices and Quaternions FAQ (довольно популярный ресурс и возможно там доступен ваш язык)

В C++ :

// добавьте #include и #include glm :: vec3 myRotationAxis ( ?? , ?? , ?? ); glm :: rotate ( angle_in_degrees , myRotationAxis );

Собираем трансформации вместе

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

TransformedVector = TranslationMatrix * RotationMatrix * ScaleMatrix * OriginalVector ;

ВНИМАНИЕ! Эта формула на самом деле показывает, что сначала выполняется масштабирование, потом поворот и только в самую последнюю очередь выполняется перенос. Именно так работает перемножение матриц.

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

  • Сделайте шаг вперед и повернитесь влево
  • Повернитесь влево и сделайте шаг вперед

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

На самом деле, приведенный выше порядок - это то, что вам обычно нужно для игровых персонажей и других предметов: сначала масштабируйте его, если это необходимо; затем установливаете его направление, а затем перемещаете его. Например, для модели судна (повороты удалены для упрощения):

  • Неправильный путь:
    • Вы переносите корабль на (10, 0, 0). Его центр теперь находится в 10 единицах от начала координат.
    • Вы масштабируете свой корабль в 2 раза. Каждая координата умножается на 2 “относительно исходной”, что далеко… Итак, вы попадаете в большой корабль, но его центр 2 * 10 = 20. Не то, что вы хотели.
  • Правильный путь:
    • Вы масштабируете свой корабль в 2 раза. Вы получаете большой корабль, с центром в начале координат.
    • Вы переносите свой корабль. Он по прежнему того же размера и на правильном расстоянии.

В C++, с GLM:

glm :: mat4 myModelMatrix = myTranslationMatrix * myRotationMatrix * myScaleMatrix ; glm :: vec4 myTransformedVector = myModelMatrix * myOriginalVector ;

В GLSL:

mat4 transform = mat2 * mat1 ; vec4 out_vec = transform * in_vec ;

Мировая, видовая и проекционная матрицы

До конца этого урока мы будем полагать, что знаем как отображать любимую 3D модель из Blender - обезьянку Suzanne.

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

Мировая матрица

Эта модель, также, как и наш красный треугольник задается множеством вершин, координаты которых заданы относительно центра объекта, т. е. вершина с координатами (0, 0, 0) будет находиться в центре объекта.

Далее мы бы хотели перемещать нашу модель, так как игрок управляет ей с помощью клавиатуры и мышки. Все, что мы делаем - это применяем масштабирование, потом поворот и перенос. Эти действия выполняются для каждой вершины, в каждом кадре (выполняются в GLSL, а не в C++!) и тем самым наша модель перемещается на экране.

Теперь наши вершины в мировом пространстве. Это показывает черная стрелка на рисунке. Мы перешли из пространства объекта (все вершины заданы относительно центра объекта) к мировому пространству (все вершины заданы относительно центра мира).

Схематично это показывается так:

Видовая матрица

Еще раз процитируем Футураму:

Движок не перемещает корабль. Корабль остается на том же месте, а движок перемещает вселенную вокруг него.

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

Итак, изначально ваша камера находится в центре мировой системы координат. Чтобы переместить мир вам необходимо ввести еще одну матрицу. Допустим, что вы хотите переместить камеру на 3 юнита ВПРАВО (+X), что будет эквивалентом перемещения всего мира на 3 юнита ВЛЕВО (-X). В коде это выглядит так:

// Добавьте #include и #include glm :: mat4 ViewMatrix = glm :: translate (glm :: mat4 (), glm :: vec3 (- 3.0 f , 0.0 f , 0.0 f ));

Опять же, изображение ниже полностью показывает это. Мы перешли из мировой системы координат (все вершины заданы относительно центра мировой системы) к системе координат камеры (все вершины заданы относительно камеры):

И пока ваш мозг переваривает это, мы посмотрим на функцию, которую предоставляет нам GLM, а точнее на glm::LookAt:

glm :: mat4 CameraMatrix = glm :: LookAt ( cameraPosition , // Позиция камеры в мировом пространстве cameraTarget , // Указывает куда вы смотрите в мировом пространстве upVector // Вектор, указывающий направление вверх. Обычно (0, 1, 0) );

А вот диаграмма, которая показывает то, что мы делаем:

Однако это еще не конец.

Проекционная матрица

Итак, теперь мы находимся в пространстве камеры. Это означает, что вершина, которая получит координаты x == 0 и y == 0 будет отображаться по центру экрана. Однако, при отображении объекта огромную роль играет также дистанция до камеры (z). Для двух вершин, с одинаковыми x и y, вершина имеющая большее значение по z будет отображаться ближе, чем другая.

Это называется перспективной проекцией:

И к счастью для нас, матрица 4х4 может выполнить эту проекцию :

// Создает действительно трудночитаемую матрицу, но, тем не менее это стандартная матрица 4x4 glm :: mat4 projectionMatrix = glm :: perspective ( glm :: radians (FoV ), // Вертикальное поле зрения в радианах. Обычно между 90° (очень широкое) и 30° (узкое) 4.0 f / 3.0 f , // Отношение сторон. Зависит от размеров вашего окна. Заметьте, что 4/3 == 800/600 == 1280/960 0.1 f , // Ближняя плоскость отсечения. Должна быть больше 0. 100.0 f // Дальняя плоскость отсечения. );

Мы перешли из Пространства Камеры (все вершины заданы относительно камеры) в Однородное пространство (все вершины находятся в небольшом кубе. Все, что находится внутри куба - выводится на экран).

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

Применение Проекционной матрицы дает следующий эффект:

На этом изображении обзор камеры представляет собой куб и все объекты деформируются. Объекты, которые находятся ближе к камере отображаются большими, а те, которые дальше - маленькими. Прямо как в реальности!

Вот так это будет выглядеть:

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

И это изображение является тем, что на самом деле будет выведено.

Объединяем трансформации: матрица ModelViewProjection

… Просто стандартные матричные преобразования, которые вы уже полюбили!

// C++ : вычисление матрицы glm :: mat4 MVPmatrix = projection * view * model ; // Запомните! В обратном порядке!

// GLSL: применение матрицы transformed_vertex = MVP * in_vertex ;

Совмещаем все вместе

  • Первый шаг - создание нашей MVP матрицы. Это должно быть сделано для каждой модели, которую вы отображаете.

// Проекционная матрица: 45° поле обзора, 4:3 соотношение сторон, диапазон: 0.1 юнит <-> 100 юнитов glm :: mat4 Projection = glm :: perspective (glm :: radians (45.0 f ), 4.0 f / 3.0 f , 0.1 f , 100.0 f ); // Или, для ортокамеры glm :: mat4 View = glm :: lookAt ( glm :: vec3 (4 , 3 , 3 ), // Камера находится в мировых координатах (4,3,3) glm :: vec3 (0 , 0 , 0 ), // И направлена в начало координат glm :: vec3 (0 , 1 , 0 ) // "Голова" находится сверху ); // Матрица модели: единичная матрица (Модель находится в начале координат) glm :: mat4 Model = glm :: mat4 (1.0 f ); // Индивидуально для каждой модели // Итоговая матрица ModelViewProjection, которая является результатом перемножения наших трех матриц glm :: mat4 MVP = Projection * View * Model ; // Помните, что умножение матрицы производиться в обратном порядке

  • Второй шаг - передать это в GLSL:

// Получить хэндл переменной в шейдере // Только один раз во время инициализации. GLuint MatrixID = glGetUniformLocation (programID , "MVP" ); // Передать наши трансформации в текущий шейдер // Это делается в основном цикле, поскольку каждая модель будет иметь другую MVP-матрицу (как минимум часть M) glUniformMatrix4fv (MatrixID , 1 , GL_FALSE , & MVP [ 0 ][ 0 ]);

  • Третий шаг - используем полученные данные в GLSL, чтобы трансформировать наши вершины.

// Входные данные вершин, разные для всех исполнений этого шейдера. layout (location = 0 ) in vec3 vertexPosition_modelspace ; // Значения, которые остаются постоянными для всей сетки. uniform mat4 MVP ; void main (){ // Выходная позиция нашей вершины: MVP * position gl_Position = MVP * vec4 (vertexPosition_modelspace , 1 ); }

  • Готово! Теперь у нас есть такой же треугольник как и в Уроке 2, все так же находящийся в начале координат (0, 0, 0), но теперь мы его видим в перспективе из точки (4, 3, 3).

В Уроке 6 вы научитесь изменять эти значения динамически, используя клавиатуру и мышь, чтобы создать камеру, которую вы привыкли видеть в играх. Но для начала мы узнаем как придать нашем моделям цвета (Урок 4) и текстуры (Урок 5).

Задания

  • Попробуйте поменять значения glm::perspective
  • Вместо использования перспективной проекции попробуйте использовать ортогональную (glm:ortho)
  • Измените ModelMatrix для перемещения, поворота и масштабирования треугольника
  • Используйте предыдущее задание, но с разным порядком операций. Обратите внимание на результат.

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

Табл.3.3.Матрицы проектирующих преобразований и проецирования

Ортографическая проекция на XOY

Ортографическая проекция на YOZ

Ортографическая проекция на XOZ

Ортографическая проекция на плоскость x=p

Матрица триметрического преобразования на плоскость XOY

Матрица изометрического преобразования на плоскость XOY

Матрица изометрического проецирования на плоскость XOY

Матрица косоугольной проекции на XOY

Матрица свободной проекции на XOY

Матрица кабинетной проекции на XOY

Матрица перспективного преобразования с одной точкой схода (картинная плоскость перпендикулярна оси абсцисс)

Матрица перспективного преобразования с одной точкой схода (картинная плоскость перпендикулярна оси ординат)

Матрица перспективного преобразования с одной точкой схода (картинная плоскость перпендикулярна оси аппликат)

Матрица перспективного преобразования с двумя точками схода (картинная плоскость параллельна оси ординат)

Матрица перспективного преобразования с тремя точками схода (картинная плоскость произвольного положения)

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

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

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

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

Перспективные проекции (табл.3.3) также представлены перспективными преобразованиями и перспективными проекциями на плоскость XOY. V X , V Y и V Z являются центрами проецирования - точками на соответствующих осях. –V X , -V Y , -V Z будут точками, в которых сходятся пучки прямых, параллельных соответствующим осям.

Система координат наблюдателя представляет собой левую систему координат (рис.3.3), в которой ось z e направлена из точки зрения вперед, ось x e направлена вправо, а ось y e – вверх. Такое правило принято для совпадения осей x e и y e с осями x s и y s на экране. Определение значений координат экрана x s и y s для точки Р приводит к необходимости деления на координату z e . Для построения точного перспективного образа необходимо выполнять деление на координату глубины каждой точки.

В табл.3.4 приведены значения дескриптора вершин S(X,Y,Z) модели (рис.2.1), подвергнутой преобразованиям поворотов и изометрическому преобразованию.

Табл.3.4.Дескрипторы вершин модели

Исходная модель

M(R(z,90))xM(R(y,90))

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

1. Матрицы, общие понятия

Что такое матрицы? Вспоминаем высшую математику: матрица ¬- это набор чисел с заранее известной размерностью строк и столбцов.

Матрицы можно складывать, умножать на число, перемножать друг с другом и много еще чего интересного, но этот момент мы пропустим, т.к. он достаточно подробно изложен в любом учебнике по высшей математике (учебники можно поискать на google.com). Мы будем пользоваться матрицами как программисты, мы их заполняем и говорим, что с ними делать, все расчеты произведет математическая библиотека Direct3D, поэтому нужно включить в проект заголовочный модуль d3dx9.h (и библиотеку d3dx9.lib).

Наша задача - создать объект, т.е. заполнить матрицу координатами вершин объекта. Каждая вершина - это вектор (X, Y, Z) в трехмерном пространстве. Теперь, чтобы произвести какое-то действие, нужно взять наш объект (то есть матрицу) и умножить на матрицу преобразования, результат этой операции - новый объект, заданный в виде матрицы.

В Direct3D определены и используются три основные матрицы: мировая матрица, матрица вида и матрица проекции. Рассмотрим их подробнее.

Мировая матрица (World Matrix) - позволяет производить вращение, трансформацию и масштабирование объекта, а также наделяет каждый из объектов своей локальной системой координат.

Функции для работы с мировой матрицей:

  • D3DXMatrixRotationX(), D3DXMatrixRotationY(), D3DXMatrixRotationZ() - вращение точки относительно одной из осей;
  • D3DXMatrixTranslation() - перемещение точки в другое положение;
  • D3DXMatrixScale() - масштабирование.

    Матрица вида (View Matrix) - определяет местоположение камеры просмотра сцены и может состоять из любых комбинаций трансляции и вращения.
    D3DXMatrixLookAtLH()и D3DXMatrixLookAtRH() определяет положение камеры и угла просмотра для левостороней и правостороней систем координат соответственно.

    Матрица проекции (Projection Matrix) - создает проекцию 3D сцены на экран монитора. С ее помощью объект трансформируется, начало координат переносится в переднюю часть, а также определяется передняя и задняя плоскости отсечения.

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

    2. Создание объекта

    Создаем новый проект, аналогично первому. Прежде чем продолжить усложнять наш код, разобьем его на части для лучшей читаемости кода. Наш проект логично разделить на три составляющие:
    1. Окно Windows (инициализация окна, сообщения, …)
    2. Инициализация 3D (загрузка координат объектов, удаление ресурсов, …)
    3. Рендер сцены (матрицы, рисование примитивов, …)
    В результате у нас будет 3 файла - window.cpp, init3d.h, render.h с таким содержанием: init3d.h - переносим глобальный переменные и структуры, объявление функций, функции InitDirectX(), InitBufferVertex(), Destroy3D() render.h - переносим функцию RenderScene() все, что осталось, касается главного окна, это будет файл - window.cpp .

    Добавляем заголовочный файл и библиотеку для использования матричных функций

    #include // или C:\DXSDK\Include\d3dx9.h #pragma comment(lib, "d3dx9.lib") //или C:\\DXSDK\\Lib\\d3dx9.lib

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

    #include

    Изменим формат представления вершин:

    #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ/D3DFVF_DIFFUSE) struct CUSTOMVERTEX { FLOAT x, y, z; DWORD color; };

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

    PDirectDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

    На данный момент мы не пользуемся освещением, а закрашиваем вершины в определенный цвет, поэтому отключаем освещение:

    PDirectDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

    Упростим наше сердце, представив его в виде трех треугольников. Будем использовать локальную систему координат.


    CUSTOMVERTEX stVertex= { { -1.0f, 0.5f, 0.0f, 0x00ff0000 }, { -0.5f, 1.0f, 0.0f, 0x00ff0000 }, { 0.0f, 0.5f, 0.0f, 0x00ff0000 }, { 0.0f, 0.5f, 0.0f, 0x000000ff }, { 0.5f, 1.0f, 0.0f, 0x000000ff }, { 1.0f, 0.5f, 0.0f, 0x000000ff }, { -1.0f, 0.5f, 0.0f, 0x0000ff00 }, { 1.0f, 0.5f, 0.0f, 0x0000ff00 }, { 0.0f, -1.0f, 0.0f, 0x0000ff00 }, };

    3. Создание матриц преобразования

    Напишем в файле render.h функцию SetupMatrix() в которой будут происходить все действия над матрицами.

    Создадим матрицы:

  • D3DXMATRIX MatrixWorld; - мировая матрица
  • D3DXMATRIX MatrixView; - матрица вида
  • D3DXMATRIX MatrixProjection; - матрица проекции
    Установка мировой матрицы

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

    UINT iTime=timeGetTime()%5000; FLOAT fAngle=iTime*(2.0f*D3DX_PI)/5000.0f; D3DXMatrixRotationX(&MatrixWorld, fAngle); pDirectDevice->SetTransform(D3DTS_WORLD, &MatrixWorld); Установка матрицы вида

    Устанавливаем камеру в нужном месте и направляем ее на объект

  • D3DXMatrixLookAtLH(&MatrixView, - результат выполнения функции
  • &D3DXVECTOR3(0.0f, 0.0f, -8.0f), - точка, в которой находится камера
  • &D3DXVECTOR3(0.0f, 0.0f, 0.0f), - точка, в которую мы смотрим
  • &D3DXVECTOR3(0.0f, 1.0f, 0.0f)); - верх объекта

    После расчета необходимо применить полученные значения.

  • Графический вывод обычно осущестсвляется в некоторую прямоугольную область экрана или окна. В OpenGL-визуализации эта область называется порт вывода. Именно в этой прямоугольной области будет размещено библиотекой сформированное изображение. Его размеры определяются относительно левого верхнего угла окна и измеряются в пикселах. Для определения порта вывода приложение должно отследить событие изменения размеров окна и определить порт вывода с использованием функции:

    void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);

    Аргументы (x, y) определяют положение верхнего левого угла порта вывола, а width и height -- его размеры. По умолчанию библиотека растягивает порт вывода на всё OpenGL-окно.

    Координатная система

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

    Во-первых, вершина преобразуется в матрицу 1X4, в которой первые три элемента представляют собой координаты x, y, z. Четвёртое число - масштабный коэффициент w, который обычно равен 1.0. Вершина домножается на видовую матрицу, которая описывает преобразования видовой системы координат. Получаем вершину в координатах вида. Она в свою очередь домножается на матрицу проекций и получаем вершину в координатах проекции. На этом этапе некоторве вершины отбрасываются (из-за непопадания в объём визуализации). Затем вершины нормализуются для передачи перспективы (если координата w не равна 1.0). Окончательное проецирование вершины на двумерную поверхность экрана выполняется библиотекой OpenGL самостоятельно и вмешаться в этот процесс нельзя.

    Матрица проекций

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

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

    Матрица преспективной проекции определяется с использованием функции:

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

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

    Для задания матрицы перспективной проекции также можно использовать функцию gluPerspective(), которая имеет другие аргументы

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

    Аргумент fovy (field of view) определяет поле зрения, а aspect -- отношение ширины отсекающей рамки к высоте. near и far имеют всегда положительные значения и определяют расстояние от точки зрения до ближней и дальней отсекающих рамок.

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

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

    Для установки и последующего изменения матрицы проекций следует выполнить функцию glMatrixMode(GL_PROJECTION). Для начала также следует отменить все предыдущие установки и преобразования, сделав матрицу проекций единичной с помошью функции glLoadIdentity(). Матрица ортографической проекции устанавливается с использованием функции, которая имеет следующий прототип:

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

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

    //Функция изменения размеров и установки координат
    void Reshape(int width, int height)
    {
    //Установка порта вывода
    glViewport(0, 0, width, height);

    //Режим матрицы проекций
    glMatrixMode(GL_PROJECTION);
    //Единичная матрица
    glLoadIdentity();

    //Установка двумерной ортографической системы координат
    glOrtho(-50., 50., -50., 50., -1., 1.);

    //Режим видовой матрицы
    glMatrixMode(GL_MODELVIEW);
    }

    Специально для двумерной визуализации можно использовать функцию, которая имеет следующий прототип:

    void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top);

    Эта функция аналогична glOrtho(), при вызове которой аргумент near=-1.0, а far=1.0. В процессе двумерной визуализации z-координата у вершин имеет значение 0, то есть объекты находятся на средней плоскости.

    При необходимости сохранения пропорций установку системы координат необходимо осуществлять с учетом отношения ширины и высоты окна.

    //Установка ортографической системы координат
    double aspect=width/double(height);
    if(width>=height)
    {
    gluOrtho2D(-50.*aspect, 50.*aspect, -50., 50.);
    }
    else
    {
    gluOrtho2D(-50., 50., -50./aspect, 50./aspect);
    }

    Видовая матрица

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

    OpenGL предоставляет три функции для выполнения преобразования системы координат: glTranslated(), glRotated() и glScaled(). Эти команды генерируют матрицы переноса, поворота и масштабирования, которые домножаются на видовую матрицу с помощью функции glMultMatrix(). Как видите OpenGL берёт на себя матричные операции и выполняет их по специальным, быстрым алгоритмам с максимальной эффективностью. Например:

    Перенос

    Если аргумент больше 0, то объект при отображении будет смещён в сторону положительных значений вдоль оси. Если аргумент меньше 0, то объект при отображении будет смещён в сторону отрицательных значений вдоль оси.

    Масштабированиe

    Осуществляется с использованием функции, которая имеет следующий прототип:

    Если аргумент больше 1, то объект при отображении будет увеличенным. Если аргумент меньше 1, то объект при отображении будет уменьшенным. Если аргумент отрицательный, то объект при отображении будет ещё и отраженным. Нулевое значение аргумента допускается, но будет приводить ктому, что размеры объекта будут нулевыми.

    Поворот

    Осуществляется с использованием функции, которая имеет следующий прототип:

    void glRotated(double angle, double x, double y, double z); glRotated(30.,0.,0.,1.); //Поворот вокруг z
    glBegin(GL_LINE_LOOP);
    //Установка вершин
    glVertex2d(-2., 2.);
    glVertex2d(2., 2.);
    glVertex2d(2., -2.);
    glVertex2d(-2., -2.);
    glEnd();

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

    Стек матриц

    Полезным механизмом при построении сложных изображений является стек матриц. С использованием функций glPushMatrix() и glPopMatrix() можно запомнить текущую матрицу в стеке и восстановить ее после каких либо изменений в системе координат.

    Дисплейные списки

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

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

    GLuint glGenLists(GLsizei range);

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

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

    void glNewList (GLuint list, GLenum mode);

    Первый аргумент задает идентификатор формируемого списка, а второй определяет будет ли список только сформирован (GL_COMPILE) или сразу же и отображен (GL_COMPILE_AND_EXECUTE). Далее могут следовать команды OpenGL которые требуется сохранить в списке. Не все команды могут быть в него включены.

    Формирование списка заканчивается функцией:

    void glEndList (void);

    После формирования дисплейные списки сохраняются во внутренней структуре данных OpenGL-окна и будут удалены, когда окно будет закрыто или разрушено.

    Для выполнения дисплейного списка используется команда:

    void glCallList (GLuint list);

    которая в качестве аргумента принимает идентификатор списка.

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

    Рассмотрим пример:

    void Draw(void)
    {
    //Очистка цветового буфера

    glColor3d(1.0, 1.0, 0.0);

    glBegin(GL_LINES);
    glVertex2d(-50., .0);
    glVertex2d(50., .0);

    For(int i=-50; i<50; i++)
    {
    glVertex2d(i, .0);
    if(i % 5)
    {
    glVertex2d(i, -1.);
    }
    else if(i % 10)
    {
    glVertex2d(i, -2.);
    }
    else
    {
    glVertex2d(i, -3.);
    }
    }
    glEnd();

    glBegin(GL_LINES);
    glVertex2d(.0, -50.);
    glVertex2d(.0, 50.);
    for(int j=-50; j<50; j++)
    {
    glVertex2d(.0, j);
    if(j % 5)
    {
    glVertex2d(-1., j);
    }
    else if(j % 10)
    {
    glVertex2d(-2., j);
    }
    else
    {
    glVertex2d(-3., j);
    }
    }
    glEnd();
    //Завершить выполнение команд
    glFlush();
    }

    void Draw(void)
    {
    //Очистка цветового буфера
    glClear(GL_COLOR_BUFFER_BIT);
    //Установка цвета отображения
    glColor3d(1.0, 1.0, 0.0);

    //Формирование оси
    int axis = glGenLists(1);
    if (axis != 0)
    {
    glNewList(axis, GL_COMPILE);
    glBegin(GL_LINES);
    glVertex2d(0., .0);
    glVertex2d(100., .0);

    For(int i=0.; i<97; i++)
    {
    glVertex2d(i, .0);
    if(i % 5)
    {
    glVertex2d(i, 1.);
    }
    else if(i % 10)
    {
    glVertex2d(i, 2.);
    }
    else
    {
    glVertex2d(i, 3.);
    }
    }
    glEnd();
    //Формирование стрелки можно добавить позже
    glBegin(GL_LINE_STRIP);
    glVertex2d(97., 1.);
    glVertex2d(100.,.0);
    glVertex2d(97., -1.);
    glEnd();
    glEndList();
    }
    //Рисование горизонтальной оси
    glPushMatrix();
    glTranslated(-50.,0.,0.);
    glRotated(180.,1.,0.,0.);
    glCallList(axis);
    glPopMatrix();

    //Рисование вертикальной оси
    glPushMatrix();
    glTranslated(0.,-50.,0.);
    glRotated(90.,0.,0.,1.);
    glCallList(axis);
    glPopMatrix();

    //Завершить выполнение команд
    glFlush();
    }

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

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

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

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

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

    Матрица общего перспективного преобразования

    В этой матрице элементы a , d , е отвечают за масштабирование, m , n , L — за смещение, p , q , r — за проецирование, s — за комплексное масштабирование, х — за вращение.