OpenGL SuperBible (6th Edition) / OpenGL Супер Книга (6-ое издание): Математика для 3D-графики

← →
Английский оригинал Перевод на русский

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

Глава 4. Математика для 3D-графики
Из этой главы вы узнаете:
• Что такое вектор и чем он интересен
• Что такое матрица и чем она интересен вдвойне
• Как использовать матрицы и векторы для перемещения геометрических фигур
• Соглашения в OpenGL и координатные пространства

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

vk955 15.08.14 в 10:39

0

Насколько страшна эта глава?
В большинстве книг про программирование 3D-графики, да, подобная глава действительно была бы страшной. Но вы можете расслабиться: наш подход более умеренный.

Одна из основных математических операций, которая будет выполняться вашим шейдером - это преобразование координат, которое сводится к перемножению матриц с векторами и друг другом. Ключ к преобразованиям координат и объектов - два соглашения для матриц, используемых программистами OpenGL. Для вашего ознакомления с этими матрицами, эта глава поразит компромиссом между двумя крайностями в философии компьютерной графики. С одной стороны, мы могли бы предупредить вас, "Пожалуйста, ознакомьтесь с учебником по линейной алгебре, прежде чем читать эту главу." С другой стороны, мы могли бы обманом уверить вас, что вы можете "изучить 3D-графику без всех этих сложных математических формул". Но мы не согласимся ни с одной из сторон.

vk955 15.08.14 в 11:11

0

На самом деле, вы можете прекрасно справиться, не понимая тонкостей математики 3D-графики, точно так же, как вы можете вести свой автомобиль каждый день, не зная вообще ничего об автомобильной механике и двигателе внутреннего сгорания. Но вам лучше знать о вашем автомобиле достаточно, чтобы понимать: время от времени нужно менять масло, регулярно заполнять бензобак и необходимо менять покрышки, когда они стираются. Эти знания делают вас ответственным (и осторожным!) владельцем автомобиля. Если вы хотите быть ответственным и умелым OpenGL программистом, то это относятся и к вам. По крайней мере вам нужно понимать основы, чтобы знать что вы можетe сделать, и какие лучше использовать инструменты для выполнения работы. По мере практики вы ощутите, что математические матрицы и векторы постепенно обретают смысл, и будете развивать более интуитивные (и мощные) навыки, чтобы в полной мере использовать понятия, которые мы представим в этой главе.

vk955 15.08.14 в 14:32

0

Но даже если вы еще не умеете перемножать матрицы в уме, вам нужно знать, что это такое и какую роль они играют в OpenGL. Но прежде, чем вы побежите стряхивать пыль с учебника алгебры (у всех он есть?), мы напомним: в библиотеке sb6 есть компонент, называемый vmath, который содержит несколько полезных классов и функций для представления и манипулирования векторами и матрицами. Они могут быть использованы непосредственно с OpenGL и очень похожи по синтаксису и внешнему виду на GLSL - язык, на котором вы будете писать свои шейдеры. Но хоть вы и не должны совершать матричные и векторные преобразования самостоятельно, будет отлично, если вы будете знать, что это такое и как применять. Видите - вы можете усидеть сразу на двух стульях!

vk955 15.08.14 в 17:27

0

Интенсивный курс математики в 3D-графике
Существует немало книг по математике в 3D-графике, и самые лучшие из тех, что мы нашли, перечислены в Приложении А, "Дальнейшее чтение". Мы не заявляем, что осветим все то, что вам нужно знать. И даже не будем пытаться осветить все то, что вам следует знать. В этой главе мы изучим лишь то, без чего действительно не сможем обойтись. Если вы уже гений математики, сразу же переходите к следующему разделу о стандартных 3D преобразованиях. Теперь вы знаете не только, какой предмет мы сейчас охватим, но и что, к некоторому разочарованию большинства любителей математики, мы не уделили особого внимания их излюбленной теме пространств с однородными координатами. Представьте одно из тех реалити-шоу, где вы должны покинуть искусственное болото полное крокодилов. Сколько математики из области 3D вам нужно знать для выживания? Вот о чём будут два следующих раздела: математические навыки выживания. Крокодилов не волнует, знаете ли вы, что такое однородные координатные пространства или нет.

vk955 16.08.14 в 03:51

0

Вектор, или куда ведет этот путь?
Главная входящая информация в OpenGL - вершина, которая имеет несколько атрибутов, в том числе положение. В основном, это положение в координатном пространстве xyz, которое определяется одним и только одним уникальным XYZ триплетом. Однако, xyz-триплет, может быть представлен как вектор (на самом деле, положение - тоже вектор). Вектор является, пожалуй, самой фундаментальной и ключевой для понимания концепцией, когда дело доходит до операций над 3D-геометрией. Эти три значения (x, y и z) вместе выражают два важных понятия: направление и величину.

vk955 16.08.14 в 04:49

0
+1

Рисунок 4.1 показывает точку в пространстве (взятую произвольно) и стрелку, нарисованную от начала системы координат к этой точке. Точку можно рассматривать как вершину, когда вы составляете треугольник, а стрелку как вектор. В первую очередь, вектор - это просто направление от начала координат к точке в пространстве. Мы используем векторы в OpenGL всё время для представления направленных величин. Например, ось x - вектор (1, 0, 0). Это означает, отложить одну единицу в положительном направлении по оси x, и ноль в направлении по осям y и z. Вектор, также означает направление, куда мы движемся, например, в какую сторону указывает камера или в каком направлении бежать, чтобы уйти от крокодила! Вектор настолько фундаментальное понятие в OpenGL, что в GLSL для него предусмотрены типы разных размеров, которым даны имена, такие как vec3 и vec4 (представляющие соответственно 3- и 4-мерные векторы).

vk955 16.08.14 в 05:54

0

Рисунок 4.1: Точка в пространстве - это и вершина и вектор.

vk955 16.08.14 в 09:55

0

Вторая величина, которую представляет вектор, это модуль. Модуль вектора - это его длина. Для нашего вектора (1, 0, 0) длина вектора равна единице. Вектор с длиной равной одному называется единичным вектором. Если вектор не единичный, а мы хотим изменить его длину до единицы, то мы называем это нормализацией. Процесс нормализации вектора заключается в масштабировании его таким образом, что длина становится равной единице. Такой вектор называется нормализованным. Единичные векторы важны, когда мы хотим указать лишь направление, а не длину. Также, если длины векторов появятся в уравнениях, которые мы будем использовать, они станут намного проще при длинах равных 1! Модуль также может быть важной величиной; например, он может сказать, как далеко мы должны двигаться в данном направлении - как далеко мне нужно убежать от этого крокодила.

vk955 16.08.14 в 10:35

0

Векторы (и матрицы) настолько важные понятия в 3D-графике, что они объекты первого класса в GLSL — язык, в котором вы будете писать ваши шейдеры. Тем не менее, это не так в C++ подобных языках. Чтобы вы могли использовать их в ваших программах на C++, библиотека vmath, которая идёт с исходным кодом этой книгой, содержит классы, которые могут представить векторы и матрицы, которые называются соответственно с аналогами из GLSL: к примеру, vmath::vec3 может представить трёхкомпонентным вектором из чисел с плавающей запятой (x, y, z) и vmath::vec4 может представить вектор из четырёх компонентов чисел с плавающей запятой (x, y, z, w) и так далее. Координата w добавлена, чтобы сделать вектор однородным, но как правило, всегда имеет значение 1.0. Значения x, y, и z в дальнейшем могут быть разделены на w, когда он равен 1.0, по сути оставляет значения xyz прежними. Классы в vmath фактически являются шаблонными классами с определением типов для предоставления общих типов, таких как значения с плавающей запятой одинарной и двойной точностей, и знаковые и без знаковые целочисленные переменные. vmath::vec3 и vmath::vec4 определены просто как следующим образом:

typedef Tvec3<float> vec3;
typedef Tvec4<float> vec4;

vk955 16.08.14 в 12:10

0

Объявлять трёхкомпонентный вектор вот так просто:

sb6::vmath::vec3 vVector;

Если вы добавите “using namespace vmath” в ваш исходный код, вы можете писать

vec3 vVector;

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

vk955 16.08.14 в 13:20

0

vec3 vVertex1(0.0f, 0.0f, 1.0f);
vec4 vVertex2 = vec4(1.0f, 0.0f, 1.0f, 1.0f);
vec4 vVertex3(vVertex1, 1.0f);

vk955 16.08.14 в 13:36

0

Теперь, массив трёхкомпонентных вершин, как для треугольника может быть объявлен как:

vk955 16.08.14 в 13:37

0

vec3 vVerts[] = { vec3(-0.5f, 0.0f, 0.0f),
vec3( 0.5f, 0.0f, 0.0f),
vec3( 0.0f, 0.5f, 0.0f) } ;

vk955 16.08.14 в 13:39

0

Это должно быть знакомым, этот код был представлен в разделе "Рисуем наш первый треугольник" в главе 2. Библиотека vmath также включает много-много математических функций и перегрузки большинства операций её класса, позволяя складывать, вычитать, умножать, транспонировать и так далее векторы и матрицы

vk955 16.08.14 в 13:51

0

Нам нужно быть осторожными, чтобы не пропустить четвёртый компонент W. Большую часть времени, когда вы определяете положение вершин геометрической фигуры, трёхкомпонентные вершины это всё, что вам нужно хранить и отправлять в OpenGL. Для многих направленных векторов, таких как нормаль к поверхности (вектор указывающий перпендикулярно к поверхности, который используется для расчетов освещения) достаточно использовать трёхкомпонентный вектор. Однако, мы скоро погрузимся в мир матриц, и чтобы превратить 3D вершину, вы должны умножить её на матрицу преобразования размером 4 х 4. Правила такое: вы должны умножать четырёхкомпонентный вектор на матрицу 4 x 4, если вы попробуете умножить трёхкомпонентный вектор на матрицу 4 x 4... крокодилы съедят вас! Подробности на счёт этого будут скоро. По сути, если вы собираетесь делать свои собственные матричные операции над векторами, тогда возможно вы будете использовать четырёхкомпонентный вектор, во многих случаях.

vk955 16.08.14 в 14:12

0

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

vk955 16.08.14 в 15:43

0

vmath::vec3 a(1.0f, 2.0f, 3.0f);
vmath::vec3 b(4.0f, 5.0f, 6.0f);
vmath::vec3 c;
c = a + b;
c = a - b;
c += b;
c = -c;

vk955 16.08.14 в 15:47

0

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

vk955 16.08.14 в 17:12

0

Скалярное произведение
Векторы можно складывать, вычитать и масштабировать просто складывая, вычитая или масштабируя их XYZ компоненты. Интересная и полезная операция, впрочем это может быть применимо только для двух векторов, называется скалярное произведение. Скалярное произведение двух (трехкомпонентных) векторов возвращает скаляр (только одно значение), то есть косинус угла между двумя векторами и увеличенное на произведения их длин. Если два вектора имеют единичную длину, возвращаемое значение будет находится между -1.0 и 1.0 и равна косинусу угла между ними. Конечно, чтобы получить угол между векторами, вам надо взять значение обратного косинуса (или арккосинуса). Скалярное произведение широко используется в расчётах рассеивания света при вычислениях освещения и берется между нормалью поверхности и вектором указывающий на источник света. Мы будем углубляться в этот тип шейдерного кода в разделе "Модели освещения" в главе 12. Рисунок 4.2 показывает два вектора, v1 и v2, θ - угол между ними

vk955 16.08.14 в 17:40

0

Рисунок 4.2: Скалярное произведение — косинус угла между двумя векторами

vk955 16.08.14 в 18:17

0

Математически, скалярное произведение двух векторов v1 и v2 считается так:

v1 x v2 = v1.x x v2.x + v1.y x v2.y + v1.z x v2.z

Библиотека vmath имеет несколько полезных функций, которые используют скалярное произведение. Для начинающих, вы можете скалярно умножить векторы с помощью функции wmath::dot или методом векторных классов.

vmath::vec3 a(...);
vmath::vec3 b(...);

float c = a.dot(b);
float d = dot(a, b);

vk955 16.08.14 в 19:02

0

Как упоминалось ранее, скалярное произведение между парой единичных векторов имеет значение между -1.0 и +1.0, что дает косинус угла между ними. Немного выше по уровню функция vmath::angle, вообще-то, возвращает угол в радианах.

vk955 16.08.14 в 20:06

0

float angle(const vmath::vec3& u, const vmath::vec3& v);

vk955 16.08.14 в 20:07

0

Векторное произведение
Еще одна полезная математическая операция между двумя векторами - это векторное произведение. Векторное произведение между двумя векторами - это третий вектор, который перпендикулярен плоскости, в которой лежат первый и второй векторы. Векторное произведение двух векторов v1 и v2 определяются как:

vk955 16.08.14 в 20:10

0

где n это единичный вектор, который перпендикулярен и v1, и v2. Это значит, что если вы нормализуете результат векторного произведения, вы получите нормаль к плоскости. Если оба v1 и v2 имеют длину один, и известно, что они перпендикулярны друг к другу, то вам не нужно будет нормализовать результат, так как он и так будет единицей. Рисунок 4.3: показывает два вектора v1 и v2, v3 - результат их векторного произведения.

vk955 16.08.14 в 20:25

0

Рисунок 4.3: Векторное произведение возвращает перпендикуляр к его параметрам.

vk955 17.08.14 в 09:03

0

Векторное произведение двух трёхмерных v1 и v2 могут быть сосчитаны следующим образом:

vk955 17.08.14 в 17:09

0

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

vec3 a(...);
vec3 b(...);

vec3 c = a.cross(b);
vec3 d = cross(a, b);

vk955 17.08.14 в 17:16

0

В отличие от скалярного произведения, здесь порядок векторов важен. На рисунке 4.3, v3 является результатом векторного произведения v2 и v1. Если бы вы поменяли местами v1 и v2, то результирующий вектор v3 смотрел бы в противоположную сторону. Применение векторного произведения разнообразно, от нахождения нормалей к поверхностям треугольников до построения матриц преобразования.

vk955 17.08.14 в 17:19

0

Длина вектора
Как мы уже обсуждали, векторы имеют направление и модуль. Модуль вектора так же известен, как его длина. Модуль трёхмерного вектора можно найти по следующей формуле:

vk955 17.08.14 в 17:32

0

Квадратный корень суммы квадратов компонентов вектора. Это простая теорема Пифагора, только в двух измерениях - квадрат гипотенузы равен сумме квадратов катетов. Это распространяется на любое количество измерений, библиотека vmath включает функции для таких вычислений.
---------------------------------------
1. Сумма квадратов компонентов вектора - это скалярное произведение вектора с собой.

vk955 17.08.14 в 17:54

0

template <typename T, int len>
static inline T length(const vecN<T,len>& v) { ... }

vk955 17.08.14 в 17:54

0

Отражение и преломление
Распространённые операции в компьютерной графике - вычисление векторов отражения и преломления. Учитывая входящий вектор Rin и нормаль к поверхности N, мы узнаем направление, в котором Rin будет отражаться (Rreflect), и данный показатель преломления η, и в каком направлении Rin будет преломлено. Мы покажем это на рисунке 4.4, с преломленными векторами с Rrefract,η1 по Rrefract,η4 различных значений η.

vk955 17.08.14 в 17:56

0

Рисунок 4.4: Отражение и преломление

vk955 17.08.14 в 19:37

0

Нам интересно вычисление в трёхмерном пространстве (всё-таки эта книга о 3D-графике), хотя на рисунке 4.4 показана система только в двух измерениях. Формула для вычисления Rreflect

vk955 17.08.14 в 19:45

0

Rreflect = Rin – (2N · Rin)N

vk955 17.08.14 в 19:48

0

и формула для вычисления Rrefract с заданным η

vk955 17.08.14 в 19:50

0

Чтобы получить желаемый результат и R и N должны быть единичными векторами (то есть они должны быть нормализованы перед применением). Две vmath функции reflect() и refract() реализовывают эти уравнения.

vk955 17.08.14 в 19:53

0

Матрицы
Матрица это не только голливудская кинотрилогия, но еще и мощный математический инструмент, который значительно упрощает процесс решения уравнений с одной или несколькими переменными, которые имеют сложные взаимосвязи друг с другом. Один часто встречающийся пример, близкий к сердцу графических программистов, преобразование координат. Например, у вас есть точка в пространстве с координатами x, y и z, и вам нужно узнать, где эта точка будет, если если вы ее повернёте на несколько градусов относительно произвольной точки или направления, то вам нужно будет использовать матрицы. Почему? Потому что новая координата x зависит не только от старого x, а еще от других параметров вращения, и от прежних значений y и z. Этот вид зависимости между переменными и решение как раз та проблема, в которой преуспевают матрицы. Любители фильмов "Матрица", кто имеют математический уклон, согласятся, что термин матрица действительно имеет подходящее название.

vk955 17.08.14 в 20:05

0

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

vk955 18.08.14 в 07:33

0

В литературе программирования 3D-графики термины "матрица" и "вектор" важны. Когда вы будете иметь дело с этими величинами, вы также встретите термин "скаляр". Скаляр - это просто число, означающее величину либо количество !ПРОПУСТИЛ!. Матрицы можно перемножать и складывать не только между собой, но еще и с векторами и скалярами. Умножая точку (представленную вектором) на матрицу (преобразования) получаем новую преобразованную точку (другой вектор). Матричные преобразования на самом деле не так уж сложно понять, но могут показаться пугающими сначала. Вы должны познакомится с матричными преобразованиями, потому что эти понятия - основа многих задач в 3D. К счастью, с помощью лёгких для понимания вещей можно делать довольно-таки невероятные вещи с OpenGL. Со временем, и с небольшой практикой, обучаясь, вы освоите этот математический инструмент самостоятельно.

vk955 18.08.14 в 16:09

0

А пока, как ранее с векторами, вы найдете несколько полезных матричных функций и возможностей в библиотеке vmath. Исходный код этой библиотеке доступен в файле vmath.h в папке исходного кода книги. Эта библиотека 3D-математики значительно упрощает многие задачи в этой и грядущих главах. Одна полезная возможность этой библиотеки - недостаток невероятно умного и хорошо оптимизированного кода! Это делает библиотеку переносимой и легкой для понимания. Вы также увидите, что в ней GLSL подобный синтаксис.

vk955 18.08.14 в 19:17

0

В 3D-задач программирования с OpenGL, вы будете использовать матрицы трёх размеров: 2 x 2, 3 x 3, 4 x 4. Библиотека vmath имеет матричные типы данных, которые соответствуют тем, которые определены GLSL, такие как

vmath::mat2 m1;
vmath::mat3 m2;
vmath::mat4 m3;

vk955 18.08.14 в 19:36

0

Как GLSL, матричные классы в vmath определены общие операторы, такие как сложение, вычитание, унарный минус, умножение и деление вместе с конструкторами и операторами сравнения. Опять же, матричные классы в vmath построены с использованием шаблонов и включается в себя определение типов для чисел с плавающей точкой одинарной и двойной точностей и для знакового и без знакового целого матричного типа.

vk955 19.08.14 в 07:08

0

Матричные конструкция и операторы
В OpenGL матрица 4 x 4 представлена не как двумерный массив, а как одномерный с 16 числами с плавающей точкой. По-умолчанию, OpenGL использует основную колонку или первичную строку в расположении матриц. Это означает, что для матрицы 4 x 4 первые четыре элемента расположены в первом столбце матрицы, следующие элементы во втором и так далее. Этот подход отличается от многих математических библиотек, которые используют двумерный массив. Например, OpenGL предпочитает первый из следующего примера:

vk955 19.08.14 в 07:43

0

GLfloat matrix[16]; // Прекрасно дружит с матрицами в OpenGL
GLfloat matrix[4][4]; // Не так удобно для OpenGL программистов

vk955 19.08.14 в 09:00

0

OpenGL может использовать второй вариант, но первый эффективнее представить. Сейчас это станет яснее. Эти 16 элементов представлены в матрице 4 x 4, как показано ниже. Когда в массиве элементы проходят вниз по столбцам один за другим, мы называем это главным порядком по столбцу. В памяти, двумерный массив 4 x 4 (второй вариант в предыдущем коде) разложен по порядку, где строка главная. Такой математический термин, смены порядка, называется транспонирование.

vk955 19.08.14 в 09:08

0

Представление матрицы сверху с главным порядком по столбцу в памяти выглядит следующим образом:

vk955 19.08.14 в 09:52

0

static const float A[] =
{
A00, A01, A02, A03, A10, A11, A12, A13,
A20, A21, A22, A23, A30, A31, A32, A33
};

vk955 19.08.14 в 09:54

0
Минутку...

Следующая страница →