Программирование с использованием DirectX9: Вращение объектов. Виртуальная камера. Перспективная проекция

В компьютерной графике определенны понятия различных матриц. Это мировая матрица (World Matrix), матрица вида (View Matrix) и матрица проекции (Projection Matrix). С помощью данных матриц в исходном коде программы производятся матричные преобразования над моделями. Матричные преобразования подразумевают под собой умножение каждой вершины объекта на одну из матриц, а точнее последовательное умножение всех вершин объекта на каждую из трех матриц. Такой подход позволяет корректно представить модель в трехмерном пространстве вашего двухмерного монитора. Техника прохода модели через три перечисленные матрицы представляет суть механизма работы с графическими данными в трехмерной плоскости монитора.

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

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

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

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


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

где cos - угол вращения в радианах.

Вращение вершины вокруг оси Y выглядит так:


Вращение вершины вокруг оси Y

А вращение вокруг оси Z происходит по следующей формуле:


вращение вокруг оси Z

Трансляция вершины позволяет переместить эту саму вершину с координатами x, y, z в новую точку с новыми координатами x1, y1, z1. В математической записи это выглядит так:

X1 = x + Tx y1 = y + Ty z1 = z + Tz

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


Трансляция вершины в матричной записи

где Tx, Ty и Tz - значения смещения по осям X, Y и Z.

Масштабировать вершину в пространстве (удалять или приближать) с координатами x, y, z в новую точку с новыми значениями x1, y1, z1, можно посредством следующей записи:

X1 = x * S y1 = y * S z1 = z * S

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


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

где Sx, Sy, Sz - значения коэффициентов растяжения или сжатия по осям X, Y, Z.

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

Матрица вида

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

Фактически эта матрица позволяет определять жанр игры. Например, игра DOOM от первого лица – это можно сказать первые ряды портера в театре, тогда как игра Warcraft – это галерка на балконе. Матрица вида предназначена для определения положения камеры в пространстве, и вы можете смещать позицию камеры влево, вправо, вверх, вниз, удалять, приближать ее и так далее.

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

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


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

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

На рисунке мы видим координатное пространство камеры. Направление ("взгляд") камеры всегда совпадает с положительным направлением оси z, а сама камера расположена в начале координат.

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

Обратите внимание на три плоскости. Первая расположена на расстоянии 1 по оси z. Это ближняя плоскость. То что находится до неё игрок никогда не увидит. В данном случае значение z равно единице, но вообще говоря, оно может быть любым. Именно с ближней плоскостью связан один дефект отображения графики. Этот дефект проявляется прежде всего в шутерах (из-за большой свободы камеры). Когда ты слишком близко подходишь к объекту, то можно оказаться "внутри". Из последних игр этот дефект особенно сильно проявлялся в Left 4 dead: когда на игрока наваливалась толпа зомби, то очень часто можно было заглянуть внутрь других персонажей.

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

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

Плоскость расположенная между ближней и дальней - проекционная. В дальнейшем, эту плоскость мы будем располагать в z=1, т.е. она будет совпадать с ближней. Здесь я отделил ближнюю и проекционную плоскости, чтобы показать, что это всё-таки не одно и то же. Проекционная плоскость предназначена для последнего преобразования координат: преобразование из трёхмерного пространства камеры - в двухмерное пространство.

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

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

Поле зрения/зона обзора (field of view)

На рисунке выше у проекционной плоскости (а значит и у изображения, которое увидит пользователь) ширина больше высоты. Ширина и высота проекционной плоскости задаются с помощью углов. Встречаются разные названия этих углов: поля зрения или зоны обзора. В английском - fields of view.

Зоны обзора задаются двумя углами. Назовём их: fovx - зона обзора по горизонтали, fovy - зона обзора по вертикали. Подробно о зонах обзора: ниже.

Z-буфер / w-буфер / буфер глубины (z-buffer / w-buffer / depth buffer)

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

Как вы возможно догадываетесь, изображение нужно рисовать начиная с самых дальных элементов и заканчивая самыми ближними. Очевидное решение: вычислить расстояние от начала координат (от камеры) до каждого объекта, а затем сравнить. В компьютерной графике используется немного более усовершенствованный механизм. У этого механизма несколько названий: z-буфер, w-буфер, буфер глубины. Размер z-буфера по количеству элементов совпадает с размером фонового и основного буферов. В z-буфер заносится z-компонента самого ближнего к камере объекта. В данном примере, там где синий треугольник перекрывает зелёный, в буфер глубины будут занесены z-координаты синего. Мы ещё поговорим о z-буферах более подробно в отдельном уроке.

Ортографическая / параллельная проекция (orthographic / parallel projection)

Операция при которой происходит уменьшение размерности пространства (было трёхмерное пространство, стало двухмерным) называется проекцией. Прежде всего нас интересует перспективная проекция, но сналача мы познакомимся с параллельной (parallel или orthographic projection).

Для вычисления параллельной проекции достаточно отбросить лишнюю координату. Если у нас есть точка в пространстве [ 3 3 3 ], то при параллельной проекции на плоскость z=1, она спроецируется в точку .

Перспективная проекция (perspective projection) на проекционную плоскость

В данном виде проекции все линии сходятся в одной точке. Именно так устроено наше зрение. И именно с помощью перспективной проекции моделируется "взгляд" во всех играх.


Сравните этот рисунок с рисунком показывающим однородные координаты из предыдущего урока. Чтобы из трёхмерного пространства перейти в двухмерное, нужно первые две компоненты векторов разделить на третью: [ x/z y/z z/z ] = [ x/z y/z 1 ].

Как я уже писал выше, проекционная плоскость может располагаться где угодно между ближней и дальней. Мы будем всегда размещать проекционную плоскость в z=1, но в этом уроке мы рассмотрим и другие варианты. Посмотрим на картинку:


Расстояние до проекционной плоскости от начала координат обозначим как d. Мы рассмотрим два случая: d=1 и d=5. Важный момент: третья компонента всех векторов после проекции должна быть равна d - все точки расположены в одной плоскости z=d. Этого можно добиться умножив все компоненты вектора на d: [ xd/z yd/z zd/z ]. При d=1, мы получим: [ x/z y/z 1 ], именно эта формула использовалась для преобразования однородных координат.

Теперь, если мы отодвинем проекционную плоскость в точку z=5 (соотвтественно d=5), мы получим: [ xd/z yd/z zd/z ] = [ 5x/z 5y/z 5 ]. Последняя формула проецирует все векторы пространства в одну плоскость, где d=5.
У нас здесь небольшая проблемка. Предыдущая формула работает с трёхмерными векторами. Но мы договорились использовать четырёхмерные векторы. Четвёртую компоненту в данном случае можно просто отбросить. Но мы не будем этого делать, так как её использование даёт некоторые специфические возможности, которые мы ещё обсудим.

Нужно найти общий делитель третьей и четвёртой компонент, при делении на который в третьей компоненте остаётся значение d, а в четвёртой единица. Делитель этот - d/z. Теперь из обычного вектора [ x y z 1 ] нам нужно получить вектор готовый к проекции (делению) [ x y z z/d ]. Делается это с помощью матрицы преобразования (проверьте результат умножив любой вектор на данную матрицу):


Последнее преобразование - это ещё не проекция. Здесь мы просто приводим все векторы к нужной нам форме. Напоминаю, что мы будем размещать проекционную плоскость в d=1, а значит векторы будут выглядеть вот так: [ x y z z ].

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

Мы рассмотрим матрицу перспективного преобразования использующуюся в DirectX:

Теперь мы знаем для чего предназначен элемент _34. Мы также знаем, что элементы _11 и _22 масштабируют изображение по горизонтали и вертикали. Давайте посмотрим, что конкретно скрывается за именами xScale и yScale.

Данные переменные зависят от зон обзора, о которых мы говорили выше. Увеличивая или уменьшая эти углы, можно масштавбировать (scale или zoom) изображение - менять размер и соотношение сторон проекционной плоскости. Механизм масштабирования отдалённо напомниает масштабирование в фотоаппаратах/камерах - принцип очень похожий. Рассмотрим рисунок:


Разделим угол fov на две части и рассмотрим только одну половинку. Что мы тут видим: увеличивая угол fov/2 (а соответсвенно и угол fov), мы увеличиваем sin угла и уменьшаем cos. Это приводит к увеличению проекционной плоскости и соответственно к уменьшеню спроецированных объектов. Идеальным для нас углом будет fov/2 = P/4. Напоминаю, что угол в P/4 радиан равен 45 градусам. При этом fov будет равен 90 градусам. Чем для нас хорош угол в 45 градусов? В данном случае не происходит масштабирования, а cos(P/4)/sin(P/4)=1.

Теперь мы можем легко масштабировать картинку по вертикали (горизонтали), используя синус и косинус половины зоны обзора (функция котангенса в C++ называется cot):

yScale = cos(fovY/2)/sin(fovY/2) = cot(fovY/2)
В DirectX используется только вертикальная зона обзора (fovY), а масштабирование по горизонатли зависит от вертикальной зоны обзора и соотношения сторон.

Напоминаю, что окно в наших программах размером в 500x500. Соотношение сторон: 1 к 1. Поэтому переменные будут равны: xScale=1, yScale=1.

Соотношение сторон стандартного монитора/телевизора: 4:3. Этому соотношению соответствуют разрешения экрана: 640x480, 800x600, 1600x1200. Мы пока не будем касаться полноэкранного режима, но можем изменить размер окна программы. Вы можете поменять размер окна (в present parameters), например, на 640X480. Но чтобы все предметы не растянулись (квадраты будут выглядеть как прямоугольники), не забудьте поменять соответствующие переменные в проекционной матрице.

Чуть не забыл, форумула для xScale в DirectX:

xScale = yScale / соотношение сторон
Соотношения сторон задаются просто: 1/1, 4/3, 16/9 - это из стандартных.

Осталось выяснить назначение элементов _33, _34 матрицы перспективного преобразования. zf - z-координата дальней плоскости (от far - далеко), а zn - z-координата ближней (от near - близко). Обратите внимание, что элемент _43 = _33 * -zn.

Легче всего понять, что именно делают эти формулы, можно на примерах. Умножим стандартный вектор [ x y z w ] на матрицу представленную выше. Рекомендую вам сделать это, взяв лист бумаги и карандаш (надеюсь вы помните как перемножать две матрицы). Компоненты вектора примут следующий вид.

1-ая = x*xScale
2-ая = y*yScale
3-я = z*(zf/(zf-zn)) + w*(-(zn*zf)/(zf-zn)) = (zf/(zf-zn))*(z - w*zn)
4-ая = (w*z)/d
Совершим проекционное преобразование (разделим все элементы на 4-ую компоненту, при этом допустим, что d=1 и w=1):

1-ая = (d*x*xScale)/(w*z) = (x*xScale)/z
2-ая = (d*y*yScale)/(w*z) = (y*xScale)/z
3-я = (zf/(zf-zn))*(z - w*zn)*(w*d/z) = (zf/(zf-zn))*(1 - zn/z)
4-ая = 1
В результате мы получили вектор вида:

[ x/(z*xScale) y/(z*yScale) (zf/(zf-zn))*(1-zn/z) 1 ]
Теперь, если вы зададите конкретные значения zf и zn, то обнаружите следующее (для положительных значений): если вектор расположен до ближней плоскости, то z-компонента после преобразования будет меньше нуля, если вектор расположен за дальней плоскостью, то z-компонента будет больше единицы.

Нет никакой разници где именно расположены ближняя и дальняя плоскости: zn=1, zf=10 или zn=10, а zf=100 (или любые другие значения) - после преобразования видимая область будет располагаться в отрезке от нуля до единицы, включительно.

Именно для этого и предназначены формулы в элементах _33, _34 проекционной матрицы - спроецировать расстояние от ближней до дальней плоскости в отрезок . Проверьте это, вычислив значения нескольких векторов для конкретных значений zn,zf (да-да, на листке бумаги!!!).

Графический вывод обычно осущестсвляется в некоторую прямоугольную область экрана или окна. В 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();
}

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

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)); - верх объекта

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

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

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

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

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

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

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

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