Создание графических моделей с помощью Open Graphics Library

Координатные преобразования в OpenGL

Разбить на страницы
Показывать лекцию целиком

Цель лекции: Изучить команды видовых и перспективных преобразований библиотеки OpenGL и их реализацию в Object Pascal и в C# с использованием библиотеки OpenTK.

Примеры на C# (WinForms) и Object Pascal (Delphi)

Пример № 5.1 Пример демонстрирует координатные преобразования.

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

Системы координат OpenGL

Задание координат примитивов в ).

(рис 5.1) Правосторонняя система координат (мировая)(рис 5.2) Левосторонняя система координат (видовая)

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

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

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

Для задания координат объектов в OpenGL используются однородные координаты. Это позволяет представлять различные преобразования в виде квадратных матриц размером 4*4, а сложные преобразования представлять в виде произведения матриц, реализующих более простые преобразования.

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

$$ \left( \begin{array}{cccc} a_1a_5 a_9 a_{13} \\ a_2 a_6 a_{10} a_{14}\\ a_3 a_7 a_{11} a_{15}\\ a_4 a_8 a_{12} a_{16}\\\end{array} \right)$$

Схема координатных преобразований

Для построения проекции трехмерного изображения библиотека выполняет ряд преобразований, последовательность которых показана на рис 5.3.

(рис 5.3) Последовательность координатных преобразований OpenGL

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

Значения матриц хранятся внутри библиотеки. OpenGL предоставляет набор команд для операций над ними. Ниже более подробно описан каждый из этапов координатных преобразований.

Видовые преобразования

Входными данными для этого этапа являются координаты примитивов, задаваемые с помощью команды Vertex и её аналогов (например, команды Rect). Координаты вершин примитивов умножаются на текущую видовую матрицу. Результатом умножения являются видовые координаты вершин:

$$\begin{pmatrix}x_e\\y_e\\z_e\\w_e\\\end{pmatrix}=M\begin{pmatrix}x_0\\y_0\\z_0\\w_0\\\end{pmatrix}$$

Обычно видовая матрица используется для таких преобразований как перенос, масштабирование и поворот. Для выполнения этих операций библиотека реализует команды Translate, Scale, Rotate. Каждая из этих команд формирует матрицу, осуществляющую соответствующее преобразование, и умножает её на текущую матрицу. Если $$M$$ – текущая видовая матрица, $$T$$ – матрица преобразования, то результирующая матрица $$M’$$, которая заменит текущую матрицу, будет получена в результате операции:

$$M’ = M * T$$

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

Перспективные преобразования

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

$$\begin{pmatrix}x_c\\y_c\\z_c\\w_c\\\end{pmatrix}=P\begin{pmatrix}x_e\\y_e\\z_e\\w_e\\\end{pmatrix}$$

В библиотеке реализованы две команды для формирования наиболее часто используемых проекций: команда Ortho - для формирования матрицы параллельной проекции и команда Frustum – для получения матрицы центральной перспективной проекции.

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

Деление перспективы

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

$$\begin{pmatrix}x_d\\y_d\\z_d\end{pmatrix}=\begin{pmatrix}x_c/w_c\\y_c/w_c\\z_c/w_c\end{pmatrix}$$

В терминах OpenGL это преобразование носит название "деление перспективы". Результатом выполнения этого преобразования является получение декартовых координат вершин.

Преобразования области вывода

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

Большинство устройств вывода, например, такие как монитор и принтер, используют целочисленные координаты для отображения графической информации. Поэтому кроме преобразования масштабирования координат к координатам устройства вывода, на этом этапе обычно выполняется округление полученных координат до целочисленных значений. Кроме того, в ряде случаев (например, для отображения на мониторе) выполняется изменение направления осей координат. Например, в библиотеке OpenGL проецирование выполняется на плоскость, задаваемую осями OX и ОY, где ось OY направлена вверх. В экранной же системе координат ось OY направлена вниз.

Универсальные команды работы с матрицами

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

Выбор текущей матрицы

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

В C# команда реализована как статический метод класса GL:

void MatrixMode(MatrixMode mode);

В Object Pascal команда, выполняющая данную операцию, реализована в виде процедуры:

procedure glMatrixMode (mode: GLenum);

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

Возможные значения параметра mode команды MatrixMode
Назначение параметраЗначение параметра
Библиотека OpenTK, C#Object Pascal
Видовая матрицаMatrixMode.ModelviewGL_MODELVIEW
Матрица проекцииMatrixMode.ProjectionGL_PROJECTION
Матрица текстурыMatrixMode.TextureGL_TEXTURE

После инициализации библиотеки текущей является видовая матрица.

Изменение текущей матрицы на единичную матрицу

Для замены текущей матрицы (которая выбрана с помощью команды MatrixMode) на единичную матрицу используется команда LoadIdentity. Команда не имеет параметров.

На C# команда реализована как статический метод класса GL библиотеки OpenTK:

void LoadIdentity();

В Object Pascal команда, выполняющая данную операцию, реализована в виде процедуры:

procedure glLoadIdentity;

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

Замена текущей матрицы на произвольную

Для замены текущей матрицы OpenGL (которая выбрана с помощью команды MatrixMode) на произвольную матрицу используется команда LoadMatrix[fd]. Команда существует в двух вариантах: для матрицы вещественных значений одинарной точности и двойной точности. Команде передается один параметр – указатель на новую матрицу размером 4*4. Матрица должна быть вертикальной.

В библиотеке OpenTK команду реализуют ряд статических методов класса GL (C#):

void LoadMatrix(double* m);
void LoadMatrix(double[] m);
void LoadMatrix(float* m);
void LoadMatrix(float[] m);
void LoadMatrix(ref double m);
void LoadMatrix(ref float m);
void LoadMatrix(ref Matrix4 mat);
void LoadMatrix(ref Matrix4d mat);

В Object Pascal в файле OpenGL.pas объявлено так же несколько процедур, которые реализуют данную команду:

procedure glLoadMatrixd (m: PGLdouble);
procedure glLoadMatrixf (m: PGLfloat);
procedure glLoadMatrix(m: PGLdouble); overload;
procedure glLoadMatrix(m: PGLfloat); overload;

Умножение текущей матрицы на произвольную матрицу

Команда умножения матриц MultMatrix[f d] выполняет умножение текущей матрицы библиотеки ($$M$$) на произвольную матрицу ($$N$$).

$$M’ = N*M$$

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

В C# команда реализована как ряд статических методов класса GL библиотеки OpenTK:

void MultMatrix(double* m);
void MultMatrix(double[] m);
void MultMatrix(float* m);
void MultMatrix(float[] m);
void MultMatrix(ref double m);
void MultMatrix(ref float m);
void MultMatrix(ref Matrix4 mat);
void MultMatrix(ref Matrix4d mat);

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

procedure glMultMatrixd (m: PGLdouble);
procedure glMultMatrixf (m: PGLfloat);
procedure glMultMatrix(m: PGLdouble);overload;
procedure glMultMatrix(m: PGLfloat); overload;

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

Видовые преобразования

К видовым преобразованиям в OpenGL относятся преобразования, выполняемые с помощью видовой матрицы.

Для изменения видовой матрицы она должна быть предварительно выбрана с помощью команды MatrixMode.

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

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

Параллельный перенос

Для выполнения параллельного переноса библиотека реализует команду Translate[f d]. Команда имеет три параметра, определяющие координаты вектора параллельного переноса. Команда Translate формирует матрицу параллельного переноса и умножает её на текущую матрицу OpenGL.

При выполнении команды Translate формируется вертикальная матрица:

$$T=\begin{pmatrix}1 0 0 x\\0 1 0 y\\0 0 1 z\\ 0 0 0 1\end{pmatrix}$$

где $$x, y, z$$– координаты вектора параллельного переноса.

Полученная матрица ($$T$$) умножается на текущую матрицу библиотеки ($$M$$), и результирующая матрица ($$M’$$) заменяет текущую матрицу OpenGL:

$$M’ = T * M$$

В результате преобразование параллельного переноса добавляется к тем преобразованиям, которые выполняла текущая матрица на момент вызова команды Translate.

В библиотеке OpenTK команда реализована как ряд статических методов класса GL (C#):

void Translate(Vector3 trans);
void Translate(Vector3d trans);
void Translate(double x, double y, double z);
void Translate(float x, float y, float z);

В Object Pascal в файле OpenGL.pas объявлено несколько процедур, которые реализуют эту команду, и различаются только типом параметров:

procedure glTranslated (x,y,z: GLdouble); 
procedure glTranslatef (x,y,z: GLfloat); 
procedure glTranslate(x,y,z: GLdouble); overload;
procedure glTranslate(x,y,z: GLfloat); overload;

Масштабирование относительно начала координат

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

$$S=\begin{pmatrix}x 0 0 0\\0 y 0 0\\0 0 z 0\\ 0 0 0 1\end{pmatrix}$$

В результате выполнения команды Scale матрица масштабирования ($$S$$) умножается на текущую матрицу ($$M$$), и результирующая матрица ($$M’$$) заменяет текущую матрицу:

$$M’ = S * M$$

В результате к преобразованиям, выполняемым текущей матрицей перед вызовом команды Scale, добавляется еще преобразование масштабирования.

В библиотеке OpenTK команда реализована как ряд статических методов класса GL (C#):

void Scale(Vector3 scale);
void Scale(Vector3d scale);
void Scale(double x, double y, double z);
void Scale(float x, float y, float z);

В Object Pascal команда реализуется с помощью процедур, которые различаются типом используемых параметров:

procedure glScaled (x,y,z: GLdouble);
procedure glScalef (x,y,z: GLfloat);
procedure glScale(x,y,z: GLdouble); overload;
procedure glScale(x,y,z: GLfloat); overload;

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

Преобразование поворота вокруг произвольной оси в пространстве может быть выполнено с помощью команды Rotate[f d].

В OpenTK команда реализована как ряд статических методов класса GL (C#):

void Rotate(double angle, Vector3d axis);
void Rotate(float angle, Vector3 axis);
void Rotate(double angle, double x, double y, double z);
void Rotate(float angle, float x, float y, float z);

В Object Pascal команда реализуется с помощью процедур, которые различаются типом параметров:

procedure glRotated (angle, x,y,z: GLdouble); 
procedure glRotatef (angle, x,y,z: GLfloat); 
procedure glRotate(angle, x,y,z: GLdouble); overload;
procedure glRotate(angle, x,y,z: GLfloat); overload;

Параметры x, y, z задают координаты вектора, вокруг которого выполняется поворот. Первый параметр angle определяет угол поворота в градусах вокруг вектора против часовой стрелки, если смотреть вдоль отрицательного направления вектора поворота. Необходимо обратить внимание на то, что угол поворота задается в градусах, в отличие от параметров различных тригонометрических функций в Object Pascal.

Как и при выполнении команд Translate и Scale, при выполнении команды Rotate библиотека формирует матрицу преобразования ($$R$$), которая умножается на текущую матрицу ($$M$$), результирующая матрица ($$M’$$) затем становится текущей матрицей.

$$M’ = R * M$$

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

$$\left( \begin{array}{cccc} 1 0 0 0 \\ 0 cos(a) -sin(a) 0 \\ 0 sin(a) cos(a) 0 \\ 0 0 0 1 \\ \end{array} \right)$$$$\left( \begin{array}{cccc} cos(a) 0 sin(a) 0 \\ 0 1 0 0 \\ -sin(a) 0 cos(a) 0 \\ 0 0 0 1 \\ \end{array} \right)$$$$\left( \begin{array}{cccc} cos(a) -sin(a) 0 0 \\ sin(a) cos(a) 0 0 \\ 0 0 1 0 \\ 0 0 0 1 \\ \end{array} \right)$$
glRotated(a, 1,0,0)
GL.Rotate(a, 1,0,0)
glRotated(a, 0,1,0)
GL.Rotate(a, 0,1,0)
glRotated(a, 0,0,1)
GL.Rotate(a, 0,0,1)

Композиция преобразований

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

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

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

Две последовательные операции параллельного переноса являются аддитивными. Результат выполнения этих операций не зависит от порядка их выполнения. Например, результат последовательного сдвига точки P0 на расстояние ($$Tx_1,Ty_1,Tz_1$$) в точку P1, а затем сдвига точки P1 на расстояние ($$Tx_2,Ty_2,Tz_2$$) в точку P2 не зависит от порядка умножения матриц $$T1$$ и $$T2,$$ которые описывают каждую из операций сдвига:

$$\text{P1 = P0 $\times$ T1;}\\\text{P2 = P1 $\times$ T2 = (P0 $\times$ T1) $\times$ T2 = P0 $\times$ (T1 $\times$ T2) = P0 $\times$ T}$$

Суммарный перенос, вычисленный как произведение матриц, аддитивен:

$$T=\begin{pmatrix}1 0 0 Tx_1\\0 1 0 Ty_1\\0 0 1 Tz_1\\ 0 0 0 1\end{pmatrix}\times\begin{pmatrix}1 0 0 Tx_2\\0 1 0 Ty_2\\0 0 1 Tz_2\\ 0 0 0 1\end{pmatrix}=\begin{pmatrix}1 0 0 Tx_1+Tx_2\\0 1 0 Ty_1+Ty_2\\0 0 1 Tz_1+Tz_2\\ 0 0 0 1\end{pmatrix}$$

Последовательное выполнение операций масштабирования является мультипликативной операцией. Например, результат последовательного выполнения двух операции масштабирования с коэффициентами ($$Sx_1,Sy_1,Sz_1$$) и ($$Sx_2,Sy_2,Sz_2$$) не зависит от порядка их выполнения. Если обозначить через $$S1$$ и $$S2$$ матрицы масштабирования, получим:

$$\text{P1 = P0 $\times$ S1,}\\\text{P2 = P1 $\times$ S2 = (P0 $\times$ S1) $\times$ S2 = P0 $\times$ (S1 $\times$ S2) = P0 $\times$ S}$$ $$S=\begin{pmatrix}Sx_1 0 0 0\\0 Sy_1 0 0\\0 0 Sz_1 0\\ 0 0 0 1\end{pmatrix}\times\begin{pmatrix} Sx_2 0 0 0\\0 Sy_2 0 0\\0 0 Sz_2 0\\ 0 0 0 1\end{pmatrix}=\begin{pmatrix} Sx_1 \times Sx_2 0 0 0\\0 Sy_1 \times Sy_2 0 0\\0 0 Sz_1\times Sz_2 0\\0 0 0 1\end{pmatrix}$$

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

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

Проекционные преобразования

Проекционные преобразования определяются матрицей проекции и выполняются после видовых преобразований (рис 5.3).

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

  • Ограничивает область видимости объемной сцены. Для этого в командах Ortho и Frustum в качестве параметров передаются три пары координат для трех осей координат. С ограничением области вывода неразрывно связана операция усечения координат. Результатом этого преобразования является преобразование координат вершин видимой области сцены для каждой из осей координат, таким образом, чтобы они находились в интервале от –1 до 1.
  • Определяет перспективные преобразования.
  • Выполняет преобразование мировой (правосторонней) системы координат (рис 5.1) в видовую (левостороннюю) систему координат (рис 5.2). Преобразование заключается в изменении направления оси OZ на противоположное.
  • Результатом умножения видовых координат вершин на матрицу проекций является получение усеченных координат вершин видимой области, лежащих в интервале от -1 до 1 для каждой из осей координат.

    Параллельная проекция

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

    Ortho(l, r, b, t, n, f: GLdouble)
    

    Команда имеет шесть параметров. Каждая пара параметров определяет координаты границ области видимости для каждой из осей координат:

    l, r – левая и правая границы области видимости вдоль оси OX;

    b, t – нижняя и верхняя границы области видимости вдоль оси OY;

    n, f – ближняя и дальняя границы области видимости вдоль оси OZ.

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

    Команда Ortho формирует матрицу:

    $$P=\begin{pmatrix}\frac{2}{r-l} 0 0 -\frac{r+l}{r-l}\\0 \frac{2}{t-b} 0 -\frac{t+b}{t-b}\\0 0 -\frac{2}{f-n} -\frac{f+n}{f-n}\\ 0 0 0 1\end{pmatrix}$$

    Диагональные элементы матрицы $$\text{2/(r-l), 2/(t-b) и –2/(f-n)}$$ выполняют усечение каждой из осей координат в соответствии с границами области видимости, определяемой командой Ortho. В результате координаты видимой области находятся в интервале [-1;1] для каждой из осей. Поэтому диагональные элементы имеют вид дроби с числом 2 в качестве числителя (длина области видимости в усеченных координатах) и знаменателя, который равен длине области видимости в мировых координатах.

    Элемент $$–2/(f-n)$$ в третьем столбце матрицы имеет отрицательный знак для изменения направления оси OZ на противоположное, и преобразует мировую систему координат в видовую.

    Элементы матрицы четвертого столбца переносят изображение в центр системы координат с учётом усечения координат.

    Центр сцены для параллельной проекции имеет координаты:

    $$x = (r-l)/2\\y = (t-b)/2\\z =–(f-n)/2$$

    Формируемая командой Ortho матрица ($$P$$) умножается на текущую матрицу (M$$),$$ результирующая матрица ($$M’$$) заменяет текущую матрицу:

    $$M’ = P*M$$

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

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

    Frustum (l, r, b, t, n, f: GLdouble);
    

    Команда Frustum имеет шесть параметров, которые определяют граничные координаты области видимости:

  • l, r – левая и правая границы области видимости вдоль оси OX;
  • b, t – нижняя и верхняя границы области видимости вдоль оси OY;
  • n, f – ближняя и дальняя границы области видимости вдоль оси OZ.
  • Ближняя граница оси OZ (параметр n) так же определяет положение плоскости проекции, которая располагается перпендикулярно оси OZ.

    Точка наблюдения находится в начале координат. Видимый объем представляет собой усеченную пирамиду, проекция видимой области формируется на плоскости усечения, перпендикулярной оси OZ. Значения l, r, b, t задаются для ближней к наблюдателю плоскости отсечения. Значения n и f должны быть положительными.

    Команда Frustum формирует матрицу ($$P$$):

    $$P=\begin{pmatrix}\frac{2n}{r-l} 0 \frac{r+l}{r-l} 0\\0 \frac{2n}{t-b} \frac{t+b}{t-b} 0\\0 0 -\frac{f+n}{f-n}-\frac{2fn}{f-n} \\ 0 0 -1 0\end{pmatrix}$$

    При выполнении команды Frustum матрица перспективной проекции ($$P$$) умножается на текущую матрицу ($$M$$), и результирующая матрица ($$M’$$) замещает текущую матрицу:

    $$M’ = P*M$$

    Центр сцены имеет координаты:

    $$x = f*(r+l)/(f+n)\\ y = f*(t+b)/ (f+n)\\ z =–(2*f*n)/ (f+n)\\$$

    После умножения на матрицу проекции координаты вершин будут в интервале [-1,1] для всех осей координат.

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

    Определение области вывода

    На последнем этапе координатных преобразований вычисляются координаты области вывода. Координаты области вывода определяются с помощью команды:

    Viewport (x,y: GLint; w, h: GLsizei);
    

    Команда имеет четыре параметра:

  • x, y – координаты верхнего левого угла области вывода;
  • w, h – ширина и высота области вывода.
  • Оконные координаты проекции вычисляются как

    $$P=\begin{pmatrix}x_w\\z_w\\z_w\\\end{pmatrix}=\begin{pmatrix}(w/2)x_d+o_x\\(h/2)y_d+o_y\\((f-n)/2)z_d+o\end{pmatrix}$$

    где $$o_x,o_y$$ – координаты центра области вывода изображения:

    $$ ox=x+w/2;\\ oy=y+h/2;\\ x_d,y_d,z_d – декартовые\ координаты\ вершины.$$

    Пример координатных преобразований

    Рассмотрим преобразования, которые выполняет OpenGL при построении изображения квадрата, который формируются в листинге 5.1 на языке C# с использованием библиотеки OpenTK и в листинге 5.2 на языке Object Pascal.

    private void glControl1_Paint(object sender, PaintEventArgs e)
    {
      GL.ClearColor(0.5f, 0.5f, 0.75f, 1.0f); // цвет фона
      // очистка буферов цвета и глубины
      GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
      // поворот изображения
      GL.LoadIdentity();
      GL.Rotate(AngleX, 1.0, 0.0, 0.0);
      GL.Rotate(AngleY, 0.0, 1.0, 0.0);
      GL.Rotate(AngleZ, 0.0, 0.0, 1.0);
      // формирование осей координат
      GL.Begin(BeginMode.Lines);
      GL.Color3(1f, 0f, 0f); GL.Vertex3(-10, 0, 0); GL.Vertex3(10, 0, 0);
      GL.Color3(0f, 1f, 0f); GL.Vertex3(0, -10, 0); GL.Vertex3(0, 10, 0);
      GL.Color3(0f, 0f, 1f); GL.Vertex3(0, 0, -10); GL.Vertex3(0, 0, 10);
      GL.End();
      // видовые преобразования
      GL.Rotate(45, 0, 0, 1);
      GL.Translate(5, 0, 0);
      // формирования квадрата
      GL.Color3(0f, 0f, 1f); // цвет фона
      GL.Rect(-1f, -1f, 1f, 1f);
      // завершение формирования изображения
      GL.Flush();
      GL.Finish();
      glControl1.SwapBuffers();
    }
    private void glControl1_Resize(object sender, EventArgs e)
    {
      GL.Viewport(0, 0, glControl1.Width, glControl1.Height);
      GL.MatrixMode(MatrixMode.Projection);
      GL.LoadIdentity();
      GL.Ortho(-10, 10, -10, 10, -10, 10);
      GL.MatrixMode(MatrixMode.Modelview);
      glControl1.Invalidate();
    }
    
    procedure TfrmGL.FormPaint(Sender: TObject);
    begin
      glClearColor (1, 1, 1, 1); // цвет фона
      glClear (GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
      // установка видовой матрицы в качестве текущей
      glMatrixMode(GL_MODELVIEW);
      // установка видовой матрицы единичной
      glLoadIdentity;
      // формирование осей координат
      glBegin (GL_LINES);
        glColor3f(1, 0, 0); glVertex3f (-10, 0, 0); glVertex3f (10, 0, 0);
        glColor3f(0, 1, 0); glVertex3f (0, -10, 0); glVertex3f (0, 10, 0);
        glColor3f(0, 0, 1); glVertex3f (0, 0, -10); glVertex3f (0, 0, 10);
      glEnd;
      // видовые преобразования
      glRotatef(45, 0, 0, 1);
      glTranslate(5, 0, 0);
      // формирования квадрата
      glColor (0, 0, 0, 1); // цвет фона
      glRectd(-1, -1, 1, 1);
      // завершение формирования изображения
      glFlush;
      glFinish;
      SwapBuffers(DC);
    end;
    procedure TfrmGL.FormResize(Sender: TObject);
    begin
      // определение области вывода
      glViewport(0, 0, ClientWidth, ClientHeight);
      // задание матрицы проекции
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity;
      glOrtho(-10, 10, -10, 10, -10, 10);
      // перерисовка изображения
      InvalidateRect(Handle, nil, False);
    end;
    

    В программе используются преобразования параллельного переноса и вращения вокруг оси Дополнительно на рисунке изображены направления осей координат OX и OY.

    (рис 5.4) Пример координатных преобразований

    Рассмотрим формирование видовой матрицы, которое выполняется при формировании изображения. Видовая матрица является текущей после выполнения команды MatrixMode:

    C#:
      GL.MatrixMode(MatrixMode.Modelview);
    Object Pascal:
      glMatrixMode(GL_MODELVIEW);
    

    Перед формированием изображения видовой матрице задается единичное значение с помощью команды LoadIdentity. Вызов команды Rotate формирует матрицу поворота ($$R$$) вокруг оси OZ:

    C#:
      GL.Rotate(45, 0, 0, 1);
    Object Pascal:
      glRotatef(45, 0, 0, 1);
    

    Эта матрица умножается на видовую матрицу ($$M$$), полученная матрица становится текущей ($$M’$$):

    $$R= \begin{pmatrix}0.707-0.70700\\0.7070.70700 \\0010 \\ 0001 \\\end{pmatrix}; M= \begin{pmatrix}1000\\0100\\0010\\0001\\\end{pmatrix}; M’= R\times M=\begin{pmatrix}0.707-0.70700\\0.7070.70700 \\0010 \\ 0001 \\\end{pmatrix}$$

    Команда Translate формирует матрицу параллельного переноса ($$T$$) и умножает её на видовую матрицу ($$M’$$), результирующая матрица ($$M’’$$) становится текущей:

    C#:
    GL.Translate(5, 0, 0);
    Object Pascal:
    glTranslate(5, 0, 0);
    
    $$T= \begin{pmatrix}1005\\0100\\0010\\0001\\\end{pmatrix}; M’’= T\times M’=\begin{pmatrix}0.707-0.70703.54\\0.7070.70703.54\\0010 \\ 0001 \\\end{pmatrix}$$

    Матрица проекций формируется в методе glControl1_Paint в программе на C# и в процедуре FormResize в программе на Object Pascal. В программе используется параллельная проекция, определяемая с помощью команды Ortho. После выбора матрицы проекции в качестве текущей с помощью команды MatrixMode её значение устанавливается единичным с помощью команды LoadIdentity:

    C#:
    GL.MatrixMode(MatrixMode.Projection);
    GL.LoadIdentity();
    Object Pascal:
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity;
    

    Команда Ortho формирует матрицу параллельной проекции (P), которая умножается на текущую матрицу проекции. Результирующая матрица становится новой матрицей проекции. Так как на момент вызова команды Ortho матрица проекции является единичной, то значение результирующей матрицы совпадает со значением матрицы, формируемой командой Ortho.

    $$P=\begin{pmatrix}\frac{2n}{r-l} 0 \frac{r+l}{r-l} 0\\0 \frac{2n}{t-b} \frac{t+b}{t-b} 0\\0 0 -\frac{f+n}{f-n}-\frac{2fn}{f-n} \\ 0 0 -1 0\end{pmatrix}= \begin{pmatrix}0.1000\\00.100\\00-0.10\\0001\end{pmatrix}$$

    Рассчитаем координаты проекции одной из вершин квадрата с координатами (-1,-1,0). После видового преобразования координаты точки будут иметь значения:

    $$V_e=\begin{pmatrix}-1\\_1\\0\\1\end{pmatrix}\times\begin{pmatrix}0.707-0.70703.54\\0.7070.70703.54\\0010 \\ 0001 \\\end{pmatrix}=\begin{pmatrix}3.54\\2.126\\0\\1\end{pmatrix}$$

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

    $$V_e=\begin{pmatrix}3.54\\2.126\\0\\1\end{pmatrix}\times\begin{pmatrix}0.1000\\00.100\\00-0.10\\0001\end{pmatrix}=\begin{pmatrix}0.354\\0.213\\0\\1\end{pmatrix}$$

    Полученные значения преобразуются в координаты области вывода, которые определяются командой Viewport:

    C#:
      GL.Viewport(0, 0, glControl1.Width, glControl1.Height);
    Object Pascal:
      glViewport(0, 0, ClientWidth, ClientHeight);
    

    В рассматриваемом примере ширина и высота клиентской области формы одинаковы и равны 300 пикселов.

    После преобразования в координаты области вывода, координаты проекции вершины принимают значения:

    $$ X = 150 + 150*0.354 = 203\\ Y = 150 + 150*0.213 = 182$$

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

    (рис 5.5) Исходное положение объекта(рис 5.6) Положение объекта после преобразования поворота
    (рис 5.7) Положение объекта после преобразования сдвига

    Во втором варианте система координат остается неподвижной, и все преобразования выполняются в исходной системе координат. Для того чтобы получить результат выполнения преобразований необходимо представить выполнение видовых преобразований в порядке обратном вызову соответствующих команд в программе. Например, для выше приведенного примера порядок преобразований необходимо рассматривать в следующем порядке: сначала перенос, а затем поворот вокруг оси , рис 5.9, рис 5.10).

    (рис 5.8) Исходное положение объекта (рис 5.9) Положение объекта после преобразования сдвига
    (рис 5.10) Положение объекта после преобразования поворота

    Стек матриц

    При формировании изображений часто возникает необходимость сохранения содержания текущей матрицы и затем восстановления её значения. Для этого OpenGL реализует стек матриц, который функционирует по принципу "последним пришел – первым ушел". Для видовой и проекционной матриц OpenGL реализует отдельные стеки. Размер этих стеков различается. Для видовой матрицы размер стека составляет не менее 32 матриц, для проекционной – не менее 2 матриц. Некоторые реализации библиотеки могут иметь больший размер этих стеков. Небольшой размер стека проекционных матриц объясняется тем, что проекционные матрицы редко используются для реализации композиции преобразований. Текущий стек матриц изменяется при изменении типа текущей матрицы с помощью команды MatrixMode, поэтому текущая видовая матрица может быть скопирована только в стек видовых матриц, текущая проекционная матрица соответственно может быть скопирована только в стек проекционных матриц.

    Для копирования текущей матрицы в соответствующий её типу стек матриц используется команда PushMatrix.

    C#:
      void PushMatrix();
    Object Pascal:
      procedure glPushMatrix;
    

    Если на момент вызова команды стек уже заполнен, то копия текущей матрицы в стек добавлена не будет и библиотека сгенерирует ошибку переполнения стека:

    C#:
      ErrorCode.StackOverflow
    Object Pascal:
      GL_STACK_OVERFLOW
    

    Которую можно проанализировать с помощью команды GetError:

    C#:
      ErrorCode GetError();
    Object Pascal:
      function  glGetError: GLenum;
    

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

    C#:
      void PopMatrix();
    Object Pascal:
      procedure glPopMatrix;
    

    Если стек пуст, то текущая матрица не будет изменена, а библиотека сгенерирует ошибку:

    C#:
      ErrorCode.StackUnderflow
    Object Pascal:
      GL_STACK_UNDERFLOW
    

    Которую можно проанализировать с помощью команды GetError.

    Краткие итоги

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

    Упражнения. Построение поверхностей вращения

    Пример выполнения задания

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

    Выполнение задания

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

    $$x=Rcos\alpha\\y=Rsin\alpha$$

    Где $$x, y$$ – координаты точки на окружности, $$\alpha$$ - угол поворота радиус-вектора вокруг центра окружности в плоскости её построения, $$R$$ – радиус окружности.

    Построение точек на окружности будет выполняться в цикле.

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

    C#:
        PolygonMode mode = PolygonMode.Fill;
    Object Pascal:
      var
        mode: GLenum = GL_FILL;
    

    Для изменения режима отображения будет использоваться команда PolygonMode.

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

    C#:
    
    private void glControl1_Paint(object sender, PaintEventArgs e)
    {
    …
      GL.PolygonMode(MaterialFace.FrontAndBack, mode);
      GL.Begin(BeginMode.QuadStrip);
      double r = 0.5;
      int n = 20;
      for (int i = 0; i <= n; ++i)
      {
        double a = 2 * Math.PI/n * i;
        double x = r * Math.Cos(a);
        double y = r * Math.Sin(a);
        GL.Vertex3(x, y, -0.5);
        GL.Vertex3(x, y, 0.5);
      }
      GL.End();
    …
    }
    
    Object Pascal:
    
    procedure TfrmMain.FormPaint(Sender: TObject);
      var
      i, n: integer;
      x, y, r, a: double;
    begin
    …
      glPolygonMode(GL_FRONT_AND_BACK, mode);
      glBegin (GL_QUAD_STRIP);
      r := 0.5;
      n := 20;
      for i := 0 to n do
      begin
        a := 2*pi/n * i;
        x := r * Cos(a);
        y := r * Sin(a);
        glVertex3d(x, y, -0.5);
        glVertex3d(x, y, 0.5);
      end;
      glEnd; 
    …
    end;
    

    Для изменения режима отображения примитивов будут использоваться клавиши F1 и F2.

    C#:
    
    private void glControl1_KeyDown(object sender, KeyEventArgs e)
    {
      switch(e.KeyCode)
      {
        case Keys.Insert:   AngleX += AngleDl; break;
        case Keys.Delete:   AngleX -= AngleDl; break;
        case Keys.Home:     AngleY += AngleDl; break;
        case Keys.End:      AngleY -= AngleDl; break;
        case Keys.Prior:    AngleZ += AngleDl; break;
        case Keys.Next:     AngleZ -= AngleDl; break;
        case Keys.F1: mode = PolygonMode.Fill; break;
        case Keys.F2: mode = PolygonMode.Line; break;
      }
      glControl1.Invalidate(); 
    }
    
    Object Pascal:
    
    procedure TfrmMain.FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    begin
     if Key = VK_ESCAPE then Close;
     case Key of
       VK_INSERT: inc(AngleX, AngleDl);
       VK_DELETE: dec(AngleX, AngleDl);
       VK_HOME:   inc(AngleY, AngleDl);
       VK_END:    dec(AngleY, AngleDl);
       VK_PRIOR:  inc(AngleZ, AngleDl);
       VK_NEXT:   dec(AngleZ, AngleDl);
       VK_F1: mode := GL_FILL;
       VK_F2: mode := GL_LINE;
     end;
     InvalidateRect(Handle, nil, False);
    end;
    

    На рис 5.11 и рис 5.12 показан вид цилиндра при режимах отображения граней примитивов PolygonMode.Fill/GL_FILL и PolygonMode.Line/GL_LINE.

    (рис 5.11) Вид цилиндра при режиме отображения граней примитивов PolygonMode.Fill(C#)/GL_FILL (Object Pascal)(рис 5.12) Вид цилиндра при режиме отображения граней примитивов PolygonMode.Line(C#)/GL_LINE(Object Pascal)

    Задания

  • Постройте изображение тора, используя в качестве основных примитивов треугольники с общими гранями.
  • Постройте изображение тора, используя в качестве основных примитивов четырехугольники с общими гранями.
  • Постройте поверхность вращения на основе графика функции лемниската Бернули, уравнение которой в полярной системе координат: $$\rho^2=2a^2cos(2\phi)$$
  • Постройте изображение конуса, используя в качестве основных примитивов треугольники с общими гранями.
  • Постройте изображение сферы, используя в качестве основных примитивов четырехугольники с общими гранями.
  • Постройте поверхность вращения на основе графика функции розы, уравнение которой в полярных координатах $$\rho=a\sin(r\phi)$$.
  • Постройте изображение цилиндра, используя в качестве основных примитивов треугольники с общими гранями.
  • Постройте поверхность вращения на основе графика функции, уравнение которой $$F(x,y)=x^6-x^4+y^2=0$$.
  • Постройте поверхность вращения на основе графика функции эллипса $$\frac{x^2}{a^2}+\frac{y^2}{b^2}=1$$.
  • Страницы:

    Цель лекции: Изучить команды видовых и перспективных преобразований библиотеки OpenGL и их реализацию в Object Pascal и в C# с использованием библиотеки OpenTK.

    Примеры на C# (WinForms) и Object Pascal (Delphi)

    Пример № 5.1 Пример демонстрирует координатные преобразования.

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

    Системы координат OpenGL

    Задание координат примитивов в ).

    (рис 5.1) Правосторонняя система координат (мировая)(рис 5.2) Левосторонняя система координат (видовая)

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

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

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

    Для задания координат объектов в OpenGL используются однородные координаты. Это позволяет представлять различные преобразования в виде квадратных матриц размером 4*4, а сложные преобразования представлять в виде произведения матриц, реализующих более простые преобразования.

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

    $$ \left( \begin{array}{cccc} a_1a_5 a_9 a_{13} \\ a_2 a_6 a_{10} a_{14}\\ a_3 a_7 a_{11} a_{15}\\ a_4 a_8 a_{12} a_{16}\\\end{array} \right)$$

    Схема координатных преобразований

    Для построения проекции трехмерного изображения библиотека выполняет ряд преобразований, последовательность которых показана на рис 5.3.

    (рис 5.3) Последовательность координатных преобразований OpenGL

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

    Значения матриц хранятся внутри библиотеки. OpenGL предоставляет набор команд для операций над ними. Ниже более подробно описан каждый из этапов координатных преобразований.

    Видовые преобразования

    Входными данными для этого этапа являются координаты примитивов, задаваемые с помощью команды Vertex и её аналогов (например, команды Rect). Координаты вершин примитивов умножаются на текущую видовую матрицу. Результатом умножения являются видовые координаты вершин:

    $$\begin{pmatrix}x_e\\y_e\\z_e\\w_e\\\end{pmatrix}=M\begin{pmatrix}x_0\\y_0\\z_0\\w_0\\\end{pmatrix}$$

    Обычно видовая матрица используется для таких преобразований как перенос, масштабирование и поворот. Для выполнения этих операций библиотека реализует команды Translate, Scale, Rotate. Каждая из этих команд формирует матрицу, осуществляющую соответствующее преобразование, и умножает её на текущую матрицу. Если $$M$$ – текущая видовая матрица, $$T$$ – матрица преобразования, то результирующая матрица $$M’$$, которая заменит текущую матрицу, будет получена в результате операции:

    $$M’ = M * T$$

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

    Перспективные преобразования

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

    $$\begin{pmatrix}x_c\\y_c\\z_c\\w_c\\\end{pmatrix}=P\begin{pmatrix}x_e\\y_e\\z_e\\w_e\\\end{pmatrix}$$

    В библиотеке реализованы две команды для формирования наиболее часто используемых проекций: команда Ortho - для формирования матрицы параллельной проекции и команда Frustum – для получения матрицы центральной перспективной проекции.

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

    Деление перспективы

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

    $$\begin{pmatrix}x_d\\y_d\\z_d\end{pmatrix}=\begin{pmatrix}x_c/w_c\\y_c/w_c\\z_c/w_c\end{pmatrix}$$

    В терминах OpenGL это преобразование носит название "деление перспективы". Результатом выполнения этого преобразования является получение декартовых координат вершин.

    Преобразования области вывода

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

    Большинство устройств вывода, например, такие как монитор и принтер, используют целочисленные координаты для отображения графической информации. Поэтому кроме преобразования масштабирования координат к координатам устройства вывода, на этом этапе обычно выполняется округление полученных координат до целочисленных значений. Кроме того, в ряде случаев (например, для отображения на мониторе) выполняется изменение направления осей координат. Например, в библиотеке OpenGL проецирование выполняется на плоскость, задаваемую осями OX и ОY, где ось OY направлена вверх. В экранной же системе координат ось OY направлена вниз.

    Универсальные команды работы с матрицами

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

    Выбор текущей матрицы

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

    В C# команда реализована как статический метод класса GL:

    void MatrixMode(MatrixMode mode);
    

    В Object Pascal команда, выполняющая данную операцию, реализована в виде процедуры:

    procedure glMatrixMode (mode: GLenum);
    

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

    Возможные значения параметра mode команды MatrixMode
    Назначение параметраЗначение параметра
    Библиотека OpenTK, C#Object Pascal
    Видовая матрицаMatrixMode.ModelviewGL_MODELVIEW
    Матрица проекцииMatrixMode.ProjectionGL_PROJECTION
    Матрица текстурыMatrixMode.TextureGL_TEXTURE

    После инициализации библиотеки текущей является видовая матрица.

    Изменение текущей матрицы на единичную матрицу

    Для замены текущей матрицы (которая выбрана с помощью команды MatrixMode) на единичную матрицу используется команда LoadIdentity. Команда не имеет параметров.

    На C# команда реализована как статический метод класса GL библиотеки OpenTK:

    void LoadIdentity();
    

    В Object Pascal команда, выполняющая данную операцию, реализована в виде процедуры:

    procedure glLoadIdentity;
    

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

    Замена текущей матрицы на произвольную

    Для замены текущей матрицы OpenGL (которая выбрана с помощью команды MatrixMode) на произвольную матрицу используется команда LoadMatrix[fd]. Команда существует в двух вариантах: для матрицы вещественных значений одинарной точности и двойной точности. Команде передается один параметр – указатель на новую матрицу размером 4*4. Матрица должна быть вертикальной.

    В библиотеке OpenTK команду реализуют ряд статических методов класса GL (C#):

    void LoadMatrix(double* m);
    void LoadMatrix(double[] m);
    void LoadMatrix(float* m);
    void LoadMatrix(float[] m);
    void LoadMatrix(ref double m);
    void LoadMatrix(ref float m);
    void LoadMatrix(ref Matrix4 mat);
    void LoadMatrix(ref Matrix4d mat);
    

    В Object Pascal в файле OpenGL.pas объявлено так же несколько процедур, которые реализуют данную команду:

    procedure glLoadMatrixd (m: PGLdouble);
    procedure glLoadMatrixf (m: PGLfloat);
    procedure glLoadMatrix(m: PGLdouble); overload;
    procedure glLoadMatrix(m: PGLfloat); overload;
    

    Умножение текущей матрицы на произвольную матрицу

    Команда умножения матриц MultMatrix[f d] выполняет умножение текущей матрицы библиотеки ($$M$$) на произвольную матрицу ($$N$$).

    $$M’ = N*M$$

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

    В C# команда реализована как ряд статических методов класса GL библиотеки OpenTK:

    void MultMatrix(double* m);
    void MultMatrix(double[] m);
    void MultMatrix(float* m);
    void MultMatrix(float[] m);
    void MultMatrix(ref double m);
    void MultMatrix(ref float m);
    void MultMatrix(ref Matrix4 mat);
    void MultMatrix(ref Matrix4d mat);
    

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

    procedure glMultMatrixd (m: PGLdouble);
    procedure glMultMatrixf (m: PGLfloat);
    procedure glMultMatrix(m: PGLdouble);overload;
    procedure glMultMatrix(m: PGLfloat); overload;
    

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

    Видовые преобразования

    К видовым преобразованиям в OpenGL относятся преобразования, выполняемые с помощью видовой матрицы.

    Для изменения видовой матрицы она должна быть предварительно выбрана с помощью команды MatrixMode.

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

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

    Параллельный перенос

    Для выполнения параллельного переноса библиотека реализует команду Translate[f d]. Команда имеет три параметра, определяющие координаты вектора параллельного переноса. Команда Translate формирует матрицу параллельного переноса и умножает её на текущую матрицу OpenGL.

    При выполнении команды Translate формируется вертикальная матрица:

    $$T=\begin{pmatrix}1 0 0 x\\0 1 0 y\\0 0 1 z\\ 0 0 0 1\end{pmatrix}$$

    где $$x, y, z$$– координаты вектора параллельного переноса.

    Полученная матрица ($$T$$) умножается на текущую матрицу библиотеки ($$M$$), и результирующая матрица ($$M’$$) заменяет текущую матрицу OpenGL:

    $$M’ = T * M$$

    В результате преобразование параллельного переноса добавляется к тем преобразованиям, которые выполняла текущая матрица на момент вызова команды Translate.

    В библиотеке OpenTK команда реализована как ряд статических методов класса GL (C#):

    void Translate(Vector3 trans);
    void Translate(Vector3d trans);
    void Translate(double x, double y, double z);
    void Translate(float x, float y, float z);
    

    В Object Pascal в файле OpenGL.pas объявлено несколько процедур, которые реализуют эту команду, и различаются только типом параметров:

    procedure glTranslated (x,y,z: GLdouble); 
    procedure glTranslatef (x,y,z: GLfloat); 
    procedure glTranslate(x,y,z: GLdouble); overload;
    procedure glTranslate(x,y,z: GLfloat); overload;
    

    Масштабирование относительно начала координат

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

    $$S=\begin{pmatrix}x 0 0 0\\0 y 0 0\\0 0 z 0\\ 0 0 0 1\end{pmatrix}$$

    В результате выполнения команды Scale матрица масштабирования ($$S$$) умножается на текущую матрицу ($$M$$), и результирующая матрица ($$M’$$) заменяет текущую матрицу:

    $$M’ = S * M$$

    В результате к преобразованиям, выполняемым текущей матрицей перед вызовом команды Scale, добавляется еще преобразование масштабирования.

    В библиотеке OpenTK команда реализована как ряд статических методов класса GL (C#):

    void Scale(Vector3 scale);
    void Scale(Vector3d scale);
    void Scale(double x, double y, double z);
    void Scale(float x, float y, float z);
    

    В Object Pascal команда реализуется с помощью процедур, которые различаются типом используемых параметров:

    procedure glScaled (x,y,z: GLdouble);
    procedure glScalef (x,y,z: GLfloat);
    procedure glScale(x,y,z: GLdouble); overload;
    procedure glScale(x,y,z: GLfloat); overload;
    

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

    Преобразование поворота вокруг произвольной оси в пространстве может быть выполнено с помощью команды Rotate[f d].

    В OpenTK команда реализована как ряд статических методов класса GL (C#):

    void Rotate(double angle, Vector3d axis);
    void Rotate(float angle, Vector3 axis);
    void Rotate(double angle, double x, double y, double z);
    void Rotate(float angle, float x, float y, float z);
    

    В Object Pascal команда реализуется с помощью процедур, которые различаются типом параметров:

    procedure glRotated (angle, x,y,z: GLdouble); 
    procedure glRotatef (angle, x,y,z: GLfloat); 
    procedure glRotate(angle, x,y,z: GLdouble); overload;
    procedure glRotate(angle, x,y,z: GLfloat); overload;
    

    Параметры x, y, z задают координаты вектора, вокруг которого выполняется поворот. Первый параметр angle определяет угол поворота в градусах вокруг вектора против часовой стрелки, если смотреть вдоль отрицательного направления вектора поворота. Необходимо обратить внимание на то, что угол поворота задается в градусах, в отличие от параметров различных тригонометрических функций в Object Pascal.

    Как и при выполнении команд Translate и Scale, при выполнении команды Rotate библиотека формирует матрицу преобразования ($$R$$), которая умножается на текущую матрицу ($$M$$), результирующая матрица ($$M’$$) затем становится текущей матрицей.

    $$M’ = R * M$$

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

    $$\left( \begin{array}{cccc} 1 0 0 0 \\ 0 cos(a) -sin(a) 0 \\ 0 sin(a) cos(a) 0 \\ 0 0 0 1 \\ \end{array} \right)$$$$\left( \begin{array}{cccc} cos(a) 0 sin(a) 0 \\ 0 1 0 0 \\ -sin(a) 0 cos(a) 0 \\ 0 0 0 1 \\ \end{array} \right)$$$$\left( \begin{array}{cccc} cos(a) -sin(a) 0 0 \\ sin(a) cos(a) 0 0 \\ 0 0 1 0 \\ 0 0 0 1 \\ \end{array} \right)$$
    glRotated(a, 1,0,0)
    GL.Rotate(a, 1,0,0)
    
    glRotated(a, 0,1,0)
    GL.Rotate(a, 0,1,0)
    
    glRotated(a, 0,0,1)
    GL.Rotate(a, 0,0,1)
    

    Композиция преобразований

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

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

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

    Две последовательные операции параллельного переноса являются аддитивными. Результат выполнения этих операций не зависит от порядка их выполнения. Например, результат последовательного сдвига точки P0 на расстояние ($$Tx_1,Ty_1,Tz_1$$) в точку P1, а затем сдвига точки P1 на расстояние ($$Tx_2,Ty_2,Tz_2$$) в точку P2 не зависит от порядка умножения матриц $$T1$$ и $$T2,$$ которые описывают каждую из операций сдвига:

    $$\text{P1 = P0 $\times$ T1;}\\\text{P2 = P1 $\times$ T2 = (P0 $\times$ T1) $\times$ T2 = P0 $\times$ (T1 $\times$ T2) = P0 $\times$ T}$$

    Суммарный перенос, вычисленный как произведение матриц, аддитивен:

    $$T=\begin{pmatrix}1 0 0 Tx_1\\0 1 0 Ty_1\\0 0 1 Tz_1\\ 0 0 0 1\end{pmatrix}\times\begin{pmatrix}1 0 0 Tx_2\\0 1 0 Ty_2\\0 0 1 Tz_2\\ 0 0 0 1\end{pmatrix}=\begin{pmatrix}1 0 0 Tx_1+Tx_2\\0 1 0 Ty_1+Ty_2\\0 0 1 Tz_1+Tz_2\\ 0 0 0 1\end{pmatrix}$$

    Последовательное выполнение операций масштабирования является мультипликативной операцией. Например, результат последовательного выполнения двух операции масштабирования с коэффициентами ($$Sx_1,Sy_1,Sz_1$$) и ($$Sx_2,Sy_2,Sz_2$$) не зависит от порядка их выполнения. Если обозначить через $$S1$$ и $$S2$$ матрицы масштабирования, получим:

    $$\text{P1 = P0 $\times$ S1,}\\\text{P2 = P1 $\times$ S2 = (P0 $\times$ S1) $\times$ S2 = P0 $\times$ (S1 $\times$ S2) = P0 $\times$ S}$$ $$S=\begin{pmatrix}Sx_1 0 0 0\\0 Sy_1 0 0\\0 0 Sz_1 0\\ 0 0 0 1\end{pmatrix}\times\begin{pmatrix} Sx_2 0 0 0\\0 Sy_2 0 0\\0 0 Sz_2 0\\ 0 0 0 1\end{pmatrix}=\begin{pmatrix} Sx_1 \times Sx_2 0 0 0\\0 Sy_1 \times Sy_2 0 0\\0 0 Sz_1\times Sz_2 0\\0 0 0 1\end{pmatrix}$$

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

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

    Проекционные преобразования

    Проекционные преобразования определяются матрицей проекции и выполняются после видовых преобразований (рис 5.3).

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

  • Ограничивает область видимости объемной сцены. Для этого в командах Ortho и Frustum в качестве параметров передаются три пары координат для трех осей координат. С ограничением области вывода неразрывно связана операция усечения координат. Результатом этого преобразования является преобразование координат вершин видимой области сцены для каждой из осей координат, таким образом, чтобы они находились в интервале от –1 до 1.
  • Определяет перспективные преобразования.
  • Выполняет преобразование мировой (правосторонней) системы координат (рис 5.1) в видовую (левостороннюю) систему координат (рис 5.2). Преобразование заключается в изменении направления оси OZ на противоположное.
  • Результатом умножения видовых координат вершин на матрицу проекций является получение усеченных координат вершин видимой области, лежащих в интервале от -1 до 1 для каждой из осей координат.

    Параллельная проекция

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

    Ortho(l, r, b, t, n, f: GLdouble)
    

    Команда имеет шесть параметров. Каждая пара параметров определяет координаты границ области видимости для каждой из осей координат:

    l, r – левая и правая границы области видимости вдоль оси OX;

    b, t – нижняя и верхняя границы области видимости вдоль оси OY;

    n, f – ближняя и дальняя границы области видимости вдоль оси OZ.

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

    Команда Ortho формирует матрицу:

    $$P=\begin{pmatrix}\frac{2}{r-l} 0 0 -\frac{r+l}{r-l}\\0 \frac{2}{t-b} 0 -\frac{t+b}{t-b}\\0 0 -\frac{2}{f-n} -\frac{f+n}{f-n}\\ 0 0 0 1\end{pmatrix}$$

    Диагональные элементы матрицы $$\text{2/(r-l), 2/(t-b) и –2/(f-n)}$$ выполняют усечение каждой из осей координат в соответствии с границами области видимости, определяемой командой Ortho. В результате координаты видимой области находятся в интервале [-1;1] для каждой из осей. Поэтому диагональные элементы имеют вид дроби с числом 2 в качестве числителя (длина области видимости в усеченных координатах) и знаменателя, который равен длине области видимости в мировых координатах.

    Элемент $$–2/(f-n)$$ в третьем столбце матрицы имеет отрицательный знак для изменения направления оси OZ на противоположное, и преобразует мировую систему координат в видовую.

    Элементы матрицы четвертого столбца переносят изображение в центр системы координат с учётом усечения координат.

    Центр сцены для параллельной проекции имеет координаты:

    $$x = (r-l)/2\\y = (t-b)/2\\z =–(f-n)/2$$

    Формируемая командой Ortho матрица ($$P$$) умножается на текущую матрицу (M$$),$$ результирующая матрица ($$M’$$) заменяет текущую матрицу:

    $$M’ = P*M$$

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

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

    Frustum (l, r, b, t, n, f: GLdouble);
    

    Команда Frustum имеет шесть параметров, которые определяют граничные координаты области видимости:

  • l, r – левая и правая границы области видимости вдоль оси OX;
  • b, t – нижняя и верхняя границы области видимости вдоль оси OY;
  • n, f – ближняя и дальняя границы области видимости вдоль оси OZ.
  • Ближняя граница оси OZ (параметр n) так же определяет положение плоскости проекции, которая располагается перпендикулярно оси OZ.

    Точка наблюдения находится в начале координат. Видимый объем представляет собой усеченную пирамиду, проекция видимой области формируется на плоскости усечения, перпендикулярной оси OZ. Значения l, r, b, t задаются для ближней к наблюдателю плоскости отсечения. Значения n и f должны быть положительными.

    Команда Frustum формирует матрицу ($$P$$):

    $$P=\begin{pmatrix}\frac{2n}{r-l} 0 \frac{r+l}{r-l} 0\\0 \frac{2n}{t-b} \frac{t+b}{t-b} 0\\0 0 -\frac{f+n}{f-n}-\frac{2fn}{f-n} \\ 0 0 -1 0\end{pmatrix}$$

    При выполнении команды Frustum матрица перспективной проекции ($$P$$) умножается на текущую матрицу ($$M$$), и результирующая матрица ($$M’$$) замещает текущую матрицу:

    $$M’ = P*M$$

    Центр сцены имеет координаты:

    $$x = f*(r+l)/(f+n)\\ y = f*(t+b)/ (f+n)\\ z =–(2*f*n)/ (f+n)\\$$

    После умножения на матрицу проекции координаты вершин будут в интервале [-1,1] для всех осей координат.

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

    Определение области вывода

    На последнем этапе координатных преобразований вычисляются координаты области вывода. Координаты области вывода определяются с помощью команды:

    Viewport (x,y: GLint; w, h: GLsizei);
    

    Команда имеет четыре параметра:

  • x, y – координаты верхнего левого угла области вывода;
  • w, h – ширина и высота области вывода.
  • Оконные координаты проекции вычисляются как

    $$P=\begin{pmatrix}x_w\\z_w\\z_w\\\end{pmatrix}=\begin{pmatrix}(w/2)x_d+o_x\\(h/2)y_d+o_y\\((f-n)/2)z_d+o\end{pmatrix}$$

    где $$o_x,o_y$$ – координаты центра области вывода изображения:

    $$ ox=x+w/2;\\ oy=y+h/2;\\ x_d,y_d,z_d – декартовые\ координаты\ вершины.$$

    Пример координатных преобразований

    Рассмотрим преобразования, которые выполняет OpenGL при построении изображения квадрата, который формируются в листинге 5.1 на языке C# с использованием библиотеки OpenTK и в листинге 5.2 на языке Object Pascal.

    private void glControl1_Paint(object sender, PaintEventArgs e)
    {
      GL.ClearColor(0.5f, 0.5f, 0.75f, 1.0f); // цвет фона
      // очистка буферов цвета и глубины
      GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
      // поворот изображения
      GL.LoadIdentity();
      GL.Rotate(AngleX, 1.0, 0.0, 0.0);
      GL.Rotate(AngleY, 0.0, 1.0, 0.0);
      GL.Rotate(AngleZ, 0.0, 0.0, 1.0);
      // формирование осей координат
      GL.Begin(BeginMode.Lines);
      GL.Color3(1f, 0f, 0f); GL.Vertex3(-10, 0, 0); GL.Vertex3(10, 0, 0);
      GL.Color3(0f, 1f, 0f); GL.Vertex3(0, -10, 0); GL.Vertex3(0, 10, 0);
      GL.Color3(0f, 0f, 1f); GL.Vertex3(0, 0, -10); GL.Vertex3(0, 0, 10);
      GL.End();
      // видовые преобразования
      GL.Rotate(45, 0, 0, 1);
      GL.Translate(5, 0, 0);
      // формирования квадрата
      GL.Color3(0f, 0f, 1f); // цвет фона
      GL.Rect(-1f, -1f, 1f, 1f);
      // завершение формирования изображения
      GL.Flush();
      GL.Finish();
      glControl1.SwapBuffers();
    }
    private void glControl1_Resize(object sender, EventArgs e)
    {
      GL.Viewport(0, 0, glControl1.Width, glControl1.Height);
      GL.MatrixMode(MatrixMode.Projection);
      GL.LoadIdentity();
      GL.Ortho(-10, 10, -10, 10, -10, 10);
      GL.MatrixMode(MatrixMode.Modelview);
      glControl1.Invalidate();
    }
    
    procedure TfrmGL.FormPaint(Sender: TObject);
    begin
      glClearColor (1, 1, 1, 1); // цвет фона
      glClear (GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
      // установка видовой матрицы в качестве текущей
      glMatrixMode(GL_MODELVIEW);
      // установка видовой матрицы единичной
      glLoadIdentity;
      // формирование осей координат
      glBegin (GL_LINES);
        glColor3f(1, 0, 0); glVertex3f (-10, 0, 0); glVertex3f (10, 0, 0);
        glColor3f(0, 1, 0); glVertex3f (0, -10, 0); glVertex3f (0, 10, 0);
        glColor3f(0, 0, 1); glVertex3f (0, 0, -10); glVertex3f (0, 0, 10);
      glEnd;
      // видовые преобразования
      glRotatef(45, 0, 0, 1);
      glTranslate(5, 0, 0);
      // формирования квадрата
      glColor (0, 0, 0, 1); // цвет фона
      glRectd(-1, -1, 1, 1);
      // завершение формирования изображения
      glFlush;
      glFinish;
      SwapBuffers(DC);
    end;
    procedure TfrmGL.FormResize(Sender: TObject);
    begin
      // определение области вывода
      glViewport(0, 0, ClientWidth, ClientHeight);
      // задание матрицы проекции
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity;
      glOrtho(-10, 10, -10, 10, -10, 10);
      // перерисовка изображения
      InvalidateRect(Handle, nil, False);
    end;
    

    В программе используются преобразования параллельного переноса и вращения вокруг оси Дополнительно на рисунке изображены направления осей координат OX и OY.

    (рис 5.4) Пример координатных преобразований

    Рассмотрим формирование видовой матрицы, которое выполняется при формировании изображения. Видовая матрица является текущей после выполнения команды MatrixMode:

    C#:
      GL.MatrixMode(MatrixMode.Modelview);
    Object Pascal:
      glMatrixMode(GL_MODELVIEW);
    

    Перед формированием изображения видовой матрице задается единичное значение с помощью команды LoadIdentity. Вызов команды Rotate формирует матрицу поворота ($$R$$) вокруг оси OZ:

    C#:
      GL.Rotate(45, 0, 0, 1);
    Object Pascal:
      glRotatef(45, 0, 0, 1);
    

    Эта матрица умножается на видовую матрицу ($$M$$), полученная матрица становится текущей ($$M’$$):

    $$R= \begin{pmatrix}0.707-0.70700\\0.7070.70700 \\0010 \\ 0001 \\\end{pmatrix}; M= \begin{pmatrix}1000\\0100\\0010\\0001\\\end{pmatrix}; M’= R\times M=\begin{pmatrix}0.707-0.70700\\0.7070.70700 \\0010 \\ 0001 \\\end{pmatrix}$$

    Команда Translate формирует матрицу параллельного переноса ($$T$$) и умножает её на видовую матрицу ($$M’$$), результирующая матрица ($$M’’$$) становится текущей:

    C#:
    GL.Translate(5, 0, 0);
    Object Pascal:
    glTranslate(5, 0, 0);
    
    $$T= \begin{pmatrix}1005\\0100\\0010\\0001\\\end{pmatrix}; M’’= T\times M’=\begin{pmatrix}0.707-0.70703.54\\0.7070.70703.54\\0010 \\ 0001 \\\end{pmatrix}$$

    Матрица проекций формируется в методе glControl1_Paint в программе на C# и в процедуре FormResize в программе на Object Pascal. В программе используется параллельная проекция, определяемая с помощью команды Ortho. После выбора матрицы проекции в качестве текущей с помощью команды MatrixMode её значение устанавливается единичным с помощью команды LoadIdentity:

    C#:
    GL.MatrixMode(MatrixMode.Projection);
    GL.LoadIdentity();
    Object Pascal:
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity;
    

    Команда Ortho формирует матрицу параллельной проекции (P), которая умножается на текущую матрицу проекции. Результирующая матрица становится новой матрицей проекции. Так как на момент вызова команды Ortho матрица проекции является единичной, то значение результирующей матрицы совпадает со значением матрицы, формируемой командой Ortho.

    $$P=\begin{pmatrix}\frac{2n}{r-l} 0 \frac{r+l}{r-l} 0\\0 \frac{2n}{t-b} \frac{t+b}{t-b} 0\\0 0 -\frac{f+n}{f-n}-\frac{2fn}{f-n} \\ 0 0 -1 0\end{pmatrix}= \begin{pmatrix}0.1000\\00.100\\00-0.10\\0001\end{pmatrix}$$

    Рассчитаем координаты проекции одной из вершин квадрата с координатами (-1,-1,0). После видового преобразования координаты точки будут иметь значения:

    $$V_e=\begin{pmatrix}-1\\_1\\0\\1\end{pmatrix}\times\begin{pmatrix}0.707-0.70703.54\\0.7070.70703.54\\0010 \\ 0001 \\\end{pmatrix}=\begin{pmatrix}3.54\\2.126\\0\\1\end{pmatrix}$$

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

    $$V_e=\begin{pmatrix}3.54\\2.126\\0\\1\end{pmatrix}\times\begin{pmatrix}0.1000\\00.100\\00-0.10\\0001\end{pmatrix}=\begin{pmatrix}0.354\\0.213\\0\\1\end{pmatrix}$$

    Полученные значения преобразуются в координаты области вывода, которые определяются командой Viewport:

    C#:
      GL.Viewport(0, 0, glControl1.Width, glControl1.Height);
    Object Pascal:
      glViewport(0, 0, ClientWidth, ClientHeight);
    

    В рассматриваемом примере ширина и высота клиентской области формы одинаковы и равны 300 пикселов.

    После преобразования в координаты области вывода, координаты проекции вершины принимают значения:

    $$ X = 150 + 150*0.354 = 203\\ Y = 150 + 150*0.213 = 182$$

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

    (рис 5.5) Исходное положение объекта(рис 5.6) Положение объекта после преобразования поворота
    (рис 5.7) Положение объекта после преобразования сдвига

    Во втором варианте система координат остается неподвижной, и все преобразования выполняются в исходной системе координат. Для того чтобы получить результат выполнения преобразований необходимо представить выполнение видовых преобразований в порядке обратном вызову соответствующих команд в программе. Например, для выше приведенного примера порядок преобразований необходимо рассматривать в следующем порядке: сначала перенос, а затем поворот вокруг оси , рис 5.9, рис 5.10).

    (рис 5.8) Исходное положение объекта (рис 5.9) Положение объекта после преобразования сдвига
    (рис 5.10) Положение объекта после преобразования поворота

    Стек матриц

    При формировании изображений часто возникает необходимость сохранения содержания текущей матрицы и затем восстановления её значения. Для этого OpenGL реализует стек матриц, который функционирует по принципу "последним пришел – первым ушел". Для видовой и проекционной матриц OpenGL реализует отдельные стеки. Размер этих стеков различается. Для видовой матрицы размер стека составляет не менее 32 матриц, для проекционной – не менее 2 матриц. Некоторые реализации библиотеки могут иметь больший размер этих стеков. Небольшой размер стека проекционных матриц объясняется тем, что проекционные матрицы редко используются для реализации композиции преобразований. Текущий стек матриц изменяется при изменении типа текущей матрицы с помощью команды MatrixMode, поэтому текущая видовая матрица может быть скопирована только в стек видовых матриц, текущая проекционная матрица соответственно может быть скопирована только в стек проекционных матриц.

    Для копирования текущей матрицы в соответствующий её типу стек матриц используется команда PushMatrix.

    C#:
      void PushMatrix();
    Object Pascal:
      procedure glPushMatrix;
    

    Если на момент вызова команды стек уже заполнен, то копия текущей матрицы в стек добавлена не будет и библиотека сгенерирует ошибку переполнения стека:

    C#:
      ErrorCode.StackOverflow
    Object Pascal:
      GL_STACK_OVERFLOW
    

    Которую можно проанализировать с помощью команды GetError:

    C#:
      ErrorCode GetError();
    Object Pascal:
      function  glGetError: GLenum;
    

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

    C#:
      void PopMatrix();
    Object Pascal:
      procedure glPopMatrix;
    

    Если стек пуст, то текущая матрица не будет изменена, а библиотека сгенерирует ошибку:

    C#:
      ErrorCode.StackUnderflow
    Object Pascal:
      GL_STACK_UNDERFLOW
    

    Которую можно проанализировать с помощью команды GetError.

    Краткие итоги

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

    Упражнения. Построение поверхностей вращения

    Пример выполнения задания

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

    Выполнение задания

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

    $$x=Rcos\alpha\\y=Rsin\alpha$$

    Где $$x, y$$ – координаты точки на окружности, $$\alpha$$ - угол поворота радиус-вектора вокруг центра окружности в плоскости её построения, $$R$$ – радиус окружности.

    Построение точек на окружности будет выполняться в цикле.

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

    C#:
        PolygonMode mode = PolygonMode.Fill;
    Object Pascal:
      var
        mode: GLenum = GL_FILL;
    

    Для изменения режима отображения будет использоваться команда PolygonMode.

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

    C#:
    
    private void glControl1_Paint(object sender, PaintEventArgs e)
    {
    …
      GL.PolygonMode(MaterialFace.FrontAndBack, mode);
      GL.Begin(BeginMode.QuadStrip);
      double r = 0.5;
      int n = 20;
      for (int i = 0; i <= n; ++i)
      {
        double a = 2 * Math.PI/n * i;
        double x = r * Math.Cos(a);
        double y = r * Math.Sin(a);
        GL.Vertex3(x, y, -0.5);
        GL.Vertex3(x, y, 0.5);
      }
      GL.End();
    …
    }
    
    Object Pascal:
    
    procedure TfrmMain.FormPaint(Sender: TObject);
      var
      i, n: integer;
      x, y, r, a: double;
    begin
    …
      glPolygonMode(GL_FRONT_AND_BACK, mode);
      glBegin (GL_QUAD_STRIP);
      r := 0.5;
      n := 20;
      for i := 0 to n do
      begin
        a := 2*pi/n * i;
        x := r * Cos(a);
        y := r * Sin(a);
        glVertex3d(x, y, -0.5);
        glVertex3d(x, y, 0.5);
      end;
      glEnd; 
    …
    end;
    

    Для изменения режима отображения примитивов будут использоваться клавиши F1 и F2.

    C#:
    
    private void glControl1_KeyDown(object sender, KeyEventArgs e)
    {
      switch(e.KeyCode)
      {
        case Keys.Insert:   AngleX += AngleDl; break;
        case Keys.Delete:   AngleX -= AngleDl; break;
        case Keys.Home:     AngleY += AngleDl; break;
        case Keys.End:      AngleY -= AngleDl; break;
        case Keys.Prior:    AngleZ += AngleDl; break;
        case Keys.Next:     AngleZ -= AngleDl; break;
        case Keys.F1: mode = PolygonMode.Fill; break;
        case Keys.F2: mode = PolygonMode.Line; break;
      }
      glControl1.Invalidate(); 
    }
    
    Object Pascal:
    
    procedure TfrmMain.FormKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    begin
     if Key = VK_ESCAPE then Close;
     case Key of
       VK_INSERT: inc(AngleX, AngleDl);
       VK_DELETE: dec(AngleX, AngleDl);
       VK_HOME:   inc(AngleY, AngleDl);
       VK_END:    dec(AngleY, AngleDl);
       VK_PRIOR:  inc(AngleZ, AngleDl);
       VK_NEXT:   dec(AngleZ, AngleDl);
       VK_F1: mode := GL_FILL;
       VK_F2: mode := GL_LINE;
     end;
     InvalidateRect(Handle, nil, False);
    end;
    

    На рис 5.11 и рис 5.12 показан вид цилиндра при режимах отображения граней примитивов PolygonMode.Fill/GL_FILL и PolygonMode.Line/GL_LINE.

    (рис 5.11) Вид цилиндра при режиме отображения граней примитивов PolygonMode.Fill(C#)/GL_FILL (Object Pascal)(рис 5.12) Вид цилиндра при режиме отображения граней примитивов PolygonMode.Line(C#)/GL_LINE(Object Pascal)

    Задания

  • Постройте изображение тора, используя в качестве основных примитивов треугольники с общими гранями.
  • Постройте изображение тора, используя в качестве основных примитивов четырехугольники с общими гранями.
  • Постройте поверхность вращения на основе графика функции лемниската Бернули, уравнение которой в полярной системе координат: $$\rho^2=2a^2cos(2\phi)$$
  • Постройте изображение конуса, используя в качестве основных примитивов треугольники с общими гранями.
  • Постройте изображение сферы, используя в качестве основных примитивов четырехугольники с общими гранями.
  • Постройте поверхность вращения на основе графика функции розы, уравнение которой в полярных координатах $$\rho=a\sin(r\phi)$$.
  • Постройте изображение цилиндра, используя в качестве основных примитивов треугольники с общими гранями.
  • Постройте поверхность вращения на основе графика функции, уравнение которой $$F(x,y)=x^6-x^4+y^2=0$$.
  • Постройте поверхность вращения на основе графика функции эллипса $$\frac{x^2}{a^2}+\frac{y^2}{b^2}=1$$.
  • Вернуться к учебному плану