Цель лекции: Изучить команды видовых и перспективных преобразований библиотеки OpenGL и их реализацию в Object Pascal и в C# с использованием библиотеки OpenTK.
Пример № 5.1 Пример демонстрирует координатные преобразования.
Пример № 5.2 Пример демонстрирует формирование изображения цилиндра без освещения с использованием четырехугольников с общими гранями.
Задание координат примитивов в ).
(рис 5.1) Правосторонняя система координат (мировая) | (рис 5.2) Левосторонняя система координат (видовая) |
|---|
Единичный масштаб в системе координат может быть выбран произвольно. Единица масштаба может, например, соответствовать 1 м, 1 км или 1 нм. Это позволяет использовать OpenGL с одинаковой легкостью для изображения как макрообъектов, так и объектов микромира.
Для построения проекции используется левосторонняя система координат (видовая), в которой ось ). Преобразование одной системы координат в другую библиотека выполняет с помощью матрицы проекции.
Для задания координат объектов в OpenGL используются однородные координаты. Это позволяет представлять различные преобразования в виде квадратных матриц размером 4*4, а сложные преобразования представлять в виде произведения матриц, реализующих более простые преобразования.
В OpenGL для представления координат вершин используется вектор-столбец. Все матрицы библиотеки, используемые в библиотеке OpenGL, являются вертикальными:
Для построения проекции трехмерного изображения библиотека выполняет ряд преобразований, последовательность которых показана на рис 5.3.
(рис 5.3) Последовательность координатных преобразований OpenGL
Для выполнения координатных преобразований в библиотеке используется три матрицы: видовая матрица, матрица проекций и матрица текстур.
Значения матриц хранятся внутри библиотеки. OpenGL предоставляет набор команд для операций над ними. Ниже более
подробно описан каждый из этапов координатных преобразований.
Входными данными для этого этапа являются координаты примитивов, задаваемые с помощью команды Vertex и её аналогов
(например, команды Rect). Координаты вершин примитивов умножаются на текущую видовую матрицу. Результатом умножения
являются видовые координаты вершин:
Обычно видовая матрица используется для таких преобразований как перенос, масштабирование и поворот. Для выполнения этих операций
библиотека реализует команды Translate, Scale, Rotate. Каждая из этих команд формирует матрицу, осуществляющую
соответствующее преобразование, и умножает её на текущую матрицу. Если $$M$$ – текущая видовая матрица, $$T$$ – матрица преобразования, то результирующая матрица $$M’$$, которая заменит текущую матрицу, будет получена в результате операции:
В результате новые преобразования как бы добавляются к тем, которые выполнялись с помощью текущей матрицы. Сложные координатные преобразования обычно представляют в виде комбинации более простых преобразований. При необходимости видовая матрица может быть полностью сформирована программно. После инициализации библиотеки видовая матрица является единичной.
На этом этапе координатных преобразований видовые координаты вершин (полученные на предыдущем этапе вычислений) умножаются на матрицу проекций $$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.
| Назначение параметра | Значение параметра | |
|---|---|---|
| Библиотека OpenTK, C# | Object Pascal | |
| Видовая матрица | MatrixMode.Modelview | GL_MODELVIEW |
| Матрица проекции | MatrixMode.Projection | GL_PROJECTION |
| Матрица текстуры | MatrixMode.Texture | GL_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$$).
Указатель на произвольную матрицу передается команде в качестве параметра. Матрица, передаваемая в качестве параметра, должна быть вертикальной.
В 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 формируется вертикальная матрица:
где $$x, y, z$$– координаты вектора параллельного переноса.
Полученная матрица ($$T$$) умножается на текущую матрицу библиотеки ($$M$$), и результирующая матрица ($$M’$$) заменяет текущую матрицу OpenGL:
В результате преобразование параллельного переноса добавляется к тем преобразованиям, которые выполняла текущая матрица на момент вызова команды 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’$$) заменяет текущую матрицу:
В результате к преобразованиям, выполняемым текущей матрицей перед вызовом команды 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’$$) затем становится текущей матрицей.
Преобразование поворота вокруг произвольного вектора в пространстве является сложным преобразованием. На практике часто используют комбинацию преобразований из поворотов вокруг осей координат. Ниже приведены матрицы, выполняющие повороты вокруг осей координат.
| $$\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,$$ которые описывают каждую из операций сдвига:
Суммарный перенос, вычисленный как произведение матриц, аддитивен:
$$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.Результатом умножения видовых координат вершин на матрицу проекций является получение усеченных координат вершин видимой области, лежащих в интервале от -1 до 1 для каждой из осей координат.
Для формирования матрицы параллельной проекции используется команда
Ortho(l, r, b, t, n, f: GLdouble)
Команда имеет шесть параметров. Каждая пара параметров определяет координаты границ области видимости для каждой из осей координат:
l, r – левая и правая границы области видимости вдоль оси OX;
b, t – нижняя и верхняя границы области видимости вдоль оси OY;
n, f – ближняя и дальняя границы области видимости вдоль оси OZ.
Фактически параметры команды определяют расположение граничных плоскостей, ограничивающих видимую область пространства.
Команда Ortho формирует матрицу:
Диагональные элементы матрицы $$\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’$$) заменяет текущую матрицу:
Для задания центральной перспективной проекции в 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$$):
При выполнении команды Frustum матрица перспективной проекции ($$P$$) умножается на текущую матрицу ($$M$$), и результирующая матрица ($$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.
Рассчитаем координаты проекции одной из вершин квадрата с координатами (-1,-1,0). После видового преобразования координаты точки будут иметь значения:
После умножения видовых координат вершин на матрицу проекций получаются усечённые координаты вершин, которые имеют значения:
$$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, y$$ – координаты точки на окружности, $$\alpha$$ - угол поворота
Построение точек на окружности будет выполняться в цикле.
Так как все грани цилиндра будут одного цвета, и он строится без использования освещения, то в программе будет предусмотрено переключение между режимами отображения многоугольников: отображаемых в виде закрашенных фрагментов и отображение только граней четырехугольников. Для этого необходимо объявить глобальную переменную, которая и будет определять режим отображения примитивов.
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) |
Цель лекции: Изучить команды видовых и перспективных преобразований библиотеки OpenGL и их реализацию в Object Pascal и в C# с использованием библиотеки OpenTK.
Пример № 5.1 Пример демонстрирует координатные преобразования.
Пример № 5.2 Пример демонстрирует формирование изображения цилиндра без освещения с использованием четырехугольников с общими гранями.
Задание координат примитивов в ).
(рис 5.1) Правосторонняя система координат (мировая) | (рис 5.2) Левосторонняя система координат (видовая) |
|---|
Единичный масштаб в системе координат может быть выбран произвольно. Единица масштаба может, например, соответствовать 1 м, 1 км или 1 нм. Это позволяет использовать OpenGL с одинаковой легкостью для изображения как макрообъектов, так и объектов микромира.
Для построения проекции используется левосторонняя система координат (видовая), в которой ось ). Преобразование одной системы координат в другую библиотека выполняет с помощью матрицы проекции.
Для задания координат объектов в OpenGL используются однородные координаты. Это позволяет представлять различные преобразования в виде квадратных матриц размером 4*4, а сложные преобразования представлять в виде произведения матриц, реализующих более простые преобразования.
В OpenGL для представления координат вершин используется вектор-столбец. Все матрицы библиотеки, используемые в библиотеке OpenGL, являются вертикальными:
Для построения проекции трехмерного изображения библиотека выполняет ряд преобразований, последовательность которых показана на рис 5.3.
(рис 5.3) Последовательность координатных преобразований OpenGL
Для выполнения координатных преобразований в библиотеке используется три матрицы: видовая матрица, матрица проекций и матрица текстур.
Значения матриц хранятся внутри библиотеки. OpenGL предоставляет набор команд для операций над ними. Ниже более
подробно описан каждый из этапов координатных преобразований.
Входными данными для этого этапа являются координаты примитивов, задаваемые с помощью команды Vertex и её аналогов
(например, команды Rect). Координаты вершин примитивов умножаются на текущую видовую матрицу. Результатом умножения
являются видовые координаты вершин:
Обычно видовая матрица используется для таких преобразований как перенос, масштабирование и поворот. Для выполнения этих операций
библиотека реализует команды Translate, Scale, Rotate. Каждая из этих команд формирует матрицу, осуществляющую
соответствующее преобразование, и умножает её на текущую матрицу. Если $$M$$ – текущая видовая матрица, $$T$$ – матрица преобразования, то результирующая матрица $$M’$$, которая заменит текущую матрицу, будет получена в результате операции:
В результате новые преобразования как бы добавляются к тем, которые выполнялись с помощью текущей матрицы. Сложные координатные преобразования обычно представляют в виде комбинации более простых преобразований. При необходимости видовая матрица может быть полностью сформирована программно. После инициализации библиотеки видовая матрица является единичной.
На этом этапе координатных преобразований видовые координаты вершин (полученные на предыдущем этапе вычислений) умножаются на матрицу проекций $$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.
| Назначение параметра | Значение параметра | |
|---|---|---|
| Библиотека OpenTK, C# | Object Pascal | |
| Видовая матрица | MatrixMode.Modelview | GL_MODELVIEW |
| Матрица проекции | MatrixMode.Projection | GL_PROJECTION |
| Матрица текстуры | MatrixMode.Texture | GL_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$$).
Указатель на произвольную матрицу передается команде в качестве параметра. Матрица, передаваемая в качестве параметра, должна быть вертикальной.
В 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 формируется вертикальная матрица:
где $$x, y, z$$– координаты вектора параллельного переноса.
Полученная матрица ($$T$$) умножается на текущую матрицу библиотеки ($$M$$), и результирующая матрица ($$M’$$) заменяет текущую матрицу OpenGL:
В результате преобразование параллельного переноса добавляется к тем преобразованиям, которые выполняла текущая матрица на момент вызова команды 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’$$) заменяет текущую матрицу:
В результате к преобразованиям, выполняемым текущей матрицей перед вызовом команды 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’$$) затем становится текущей матрицей.
Преобразование поворота вокруг произвольного вектора в пространстве является сложным преобразованием. На практике часто используют комбинацию преобразований из поворотов вокруг осей координат. Ниже приведены матрицы, выполняющие повороты вокруг осей координат.
| $$\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,$$ которые описывают каждую из операций сдвига:
Суммарный перенос, вычисленный как произведение матриц, аддитивен:
$$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.Результатом умножения видовых координат вершин на матрицу проекций является получение усеченных координат вершин видимой области, лежащих в интервале от -1 до 1 для каждой из осей координат.
Для формирования матрицы параллельной проекции используется команда
Ortho(l, r, b, t, n, f: GLdouble)
Команда имеет шесть параметров. Каждая пара параметров определяет координаты границ области видимости для каждой из осей координат:
l, r – левая и правая границы области видимости вдоль оси OX;
b, t – нижняя и верхняя границы области видимости вдоль оси OY;
n, f – ближняя и дальняя границы области видимости вдоль оси OZ.
Фактически параметры команды определяют расположение граничных плоскостей, ограничивающих видимую область пространства.
Команда Ortho формирует матрицу:
Диагональные элементы матрицы $$\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’$$) заменяет текущую матрицу:
Для задания центральной перспективной проекции в 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$$):
При выполнении команды Frustum матрица перспективной проекции ($$P$$) умножается на текущую матрицу ($$M$$), и результирующая матрица ($$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.
Рассчитаем координаты проекции одной из вершин квадрата с координатами (-1,-1,0). После видового преобразования координаты точки будут иметь значения:
После умножения видовых координат вершин на матрицу проекций получаются усечённые координаты вершин, которые имеют значения:
$$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, y$$ – координаты точки на окружности, $$\alpha$$ - угол поворота
Построение точек на окружности будет выполняться в цикле.
Так как все грани цилиндра будут одного цвета, и он строится без использования освещения, то в программе будет предусмотрено переключение между режимами отображения многоугольников: отображаемых в виде закрашенных фрагментов и отображение только граней четырехугольников. Для этого необходимо объявить глобальную переменную, которая и будет определять режим отображения примитивов.
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) |
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.