Работа с расширениями OpenGL с использованием NVIDIA OpenGL SDK 5.1. (Часть 4) (3 стр)
Автор: Сергей Гайдуков
Аффинные преобразования
Как вы знаете, аффинные преобразования сводятся к перемножению матриц размером 4x4. В предыдущих двух разделах мы уже рассмотрели основные операции над матрицами библиотеки nv_math. Поэтому, при желании вы можете самостоятельно реализовать аффинные преобразования. Матрицы переноса, поворота и т.д. придется рассчитывать вручную, что скучно и неинтересно.
К счастью библиотека nv_math позволяет сильно упростить этот процесс. Дело в том, что составе этой библиотеки имеется набор функций для вычисления типовых матриц аффинных преобразований.
Класс mat4 имеет 2 метода, предназначенных для работы с аффинными преобразования. Синтаксис этих методов очень похож на синтаксис аналогичных команд OpenGL. Первый из них - set_translation - вычисляет матрицу переноса. В качестве параметра он принимает вектор смещения (класс vec3).
Для вычисления матрицы вращения используется метод set_rot. В качестве параметра он может принимать угол поворота в радианах и ось поворота (класс vec3). Возможны и другие варианты, например поворот можно задавать матрицей 3x3, но мы их не будем рассматривать.
Ниже приведён фрагмент изменённого примера Ex06 из первой части статьи "GLUT: расширение OpenGL", который использует функции nv_math вместо команд OpenGL glTranslatef/glRotatef (Ex04).
Для этого код
glTranslatef(tx,ty,tz);
glRotatef(rx,1,0,0);
glRotatef(ry,0,1,0);
был заменён следующим фрагментом:
mat4 Modelview; //Матрица модели mat4 rotx_mat(mat4_id); //Матрица вращения вокруг оси x mat4 roty_mat( mat4_id); //Матрица вращения вокруг оси y mat4 rotxy_mat; //Итоговая матрица вращения вокруг осей x и y mat4 translate( mat4_id); //Матрица переноса rotx_mat.set_rot( rx*nv_to_rad, vec3( 1, 0, 0)); //Поворот вокруг оси x roty_mat.set_rot( ry*nv_to_rad, vec3( 0, 1, 0)); //Поворот вокруг оси y translate.set_translation( vec3( tx, ty, tz)); //Перенос mult( rotxy_mat, roty_mat, rotx_mat); //Вычисление итоговой матрицы //вращения вокруг осей x и y mult( Modelview, rotxy_mat, translate); //Добавляем перенос glMultMatrixf( &Modelview.mat_array[0]); //Устанавливаем матрицу модели
Обратите внимание, что порядок сценарных преобразований был изменён на обратный. Это связано со следующей особенностью OpenGL - преобразование, которое должно быть в цепочке преобразований последним, передаётся первым вызовом соответствующей функции. (См: Эдвард Эйнджел: "Интерактивная компьютерная графика. Вводный курс на базе OpenGL") Так же можно отметить, что для перевода градусов в радианы используется константа nv_to_rad.
Внимание!!!
Новые версии библиотеки nv_math (например, из состава NVIDIA SDK 5.21) используют тот же самый порядок сценарных преобразований , что и OpenGL, поэтому если вы используете эти версии nv_math, то вам придётся переписать перемножение матриц предыдущего примера следующим образом:
mult(rotxy_mat, rotx_mat, roty_mat);
mult(Modelview, translate, rotxy_mat);
В принципе, можно попробовать изменить порядок сценарных преобразований, но это будет сложнее:
mat4 tr;
mult(tr, translate, rotx_mat);
mult(Modelview, tr, roty_mat);
В последующие примеры так же потребуется внести аналогичные изменения.
При желании, этот пример можно полностью переписать без использования матричных функций (Ex05) OpenGL (кроме финального умножения матрицы на вектор), заменив команду
gluPerspective(45,GLdouble(WinWidth)/WinHeight,1,100);на
mat4 Projection;
perspective(Projection, 45, GLdouble(WinWidth)/WinHeight, 1, 100);
glMultMatrixf(&Projection.mat_array[0]);
Функции projection и frustum вычисляют матрицу проекции и являются аналогами команд OpenGL glProjecton и glFrustum соответственно. Имеется даже функция look_at - аналог команды gluLookAt. Но эти команды используются довольно редко.
В принципе можно было бы пойти дальше и производить перемножение матрицы проекции, матрицы модели и координат вершины средствами nv_math, но это приведёт к очень сильному усложнению программы, т.к. в этом случае придется самостоятельно рассчитывать освещение.
После прочтения этого раздела у читателя может возникнуть вопрос "а зачем всё это нужно?". Действительно, в простых приложениях можно обойтись только средствами OpenGL. Но в реальных проектах возможен ряд ситуаций, когда приходится самостоятельно выполнять аффинные преобразования:
1. В приложение используются команды gluProject/gluUnProject или аналогичные, которые требуют передачи им матриц проекций и модели. Если эти матрицы получать командой glGet, то это может привести к резкому падению скорости на некоторых видеокартах из-за остановки конвейера (на видеокартах NVIDIA это вроде бы не происходит). Если же будем работать с этими матрицами только средствами nv_math, это не произойдёт.
2. В рамках одного блока glBegin() ... glEnd() необходимо использовать разные матрицы модели для разных вершин. Т.к. OpenGL не позволяет изменять матрицы модели, проекции и текстуры внутри блока glBegin() ... glEnd(), вам не остаётся никакого другого выбора, как пересчитать координаты вершин средствами nv_math.
Для демонстрации второй ситуации рассмотрим простую задачу, которую приходится решать при стыковке нескольких поверхностей. Пусть у нас имеется две одинаковых прямоугольника A и B. Прямоугольник B смещается относительно прямоугольника A командами:
// t и r - произвольные числа glTranslatef(1, 0, t); //смещаем плоскость на величину t вверх и 1 вправо glTranslatef( 1, 0, 0); //проводим поворот на r градусов вокруг левого края glRotatef( r, 0, 1, 0); glTranslatef( 1, 0, 0);
Требуется нарисовать прямоугольник C, соединяющий прямоугольники A и B между собой (рисунок 1).
Рисунок 1. Прямоугольники A, C, B (слева направо)
Начинающий программист, скорее всего, скажет: "чего же тут необычного", и быстро напишет программу наподобие следующей:
glBegin(GL_QUADS); glVertex2f( -1, -1); glVertex2f( -1, 1); glVertex2f( 1, 1); glVertex2f( 1, -1); glEnd( ); glPushMatrix( ); //смещаем плоскость на величину t вверх и 1 вправо glTranslatef( 1, 0, t); //проводим поворот на r градусов вокруг левого края glTranslatef( 1, 0, 0); glRotatef( r, 0, 1, 0); glTranslatef( 1, 0, 0); glBegin( GL_QUADS); glVertex2f( -1, -1); glVertex2f( -1, 1); glVertex2f( 1, 1); glVertex2f( 1, -1); glEnd( ); glPopMatrix( ); //Так нельзя!!! glBegin( GL_QUADS); glVertex2f( 1, 1); glVertex2f( 1, -1); glPushMatrix( ); glTranslatef( 1, 0, -t); glTranslatef( 1, 0, 0); glRotatef( r, 0, 1, 0); glTranslatef( 1, 0, 0); glVertex2f( -1, -1); glVertex2f( -1, 1); glPopMatrix( ); glEnd( );
Вроде бы всё просто, за исключением одной "маленькой" детали - этот фрагмент не будет работать. Дело в том, что в программе производится изменение текущей матрицы модели внутри блока glBegin() ... glEnd(), что делать нельзя. В результате команды glTranslatef и glRotatef внутри последнего блока glBegin() ... glEnd() будут проигнорированы.
Единственный выход из данной ситуации - рассчитывать координаты вершин вручную. К счастью, у нас есть библиотека nv_math, поэтому задача значительно упрощается. Ниже приведён фрагмент кода, который решает эту задачу (Ex06):
vec4 v1(-1, -1, 0, 1); //координаты правой стороны прямоугольника C vec4 v2( -1, 1, 0, 1); mat4 Translate1( mat4_id); mat4 Translate2( mat4_id); mat4 Rotate( mat4_id); //Расчитываем матрицу первого перноса. Обратите внимание - два оператора //glTranslate объединены в один Translate1.set_translation( vec3( 2, 0, t)); //Расчитываем матрицу второго переноса (после поворота) Translate2.set_translation( vec3( 1, 0, 0)); //Рассчитываем матрицу поворота Rotate.set_rot( r*nv_to_rad, vec3( 0, 1, 0)); mat4 m1; mat4 m2; //Перемножаем матрицы mult( m1, Translate2, Rotate); mult( m2, m1, Translate1); vec4 final_v1; vec4 final_v2; //Вычисляем новые координаты вершин mult( final_v1, m2, v1); mult( final_v2, m2, v2); glBegin( GL_QUADS); glVertex2f( 1, 1); glVertex2f( 1, -1); glVertex4fv( &final_v1.x); glVertex4fv( &final_v2.x); glEnd( );
Как видно, ничего сложного. Плоскость C рисуется в системе координат плоскости A, причём координаты правого края пересчитываются из системы координат плоскости B. Ещё раз обратите внимание на то, что перемножение матриц происходит в обратном порядке.
Давайте немного доработаем пример, добавив в него вычисление сглаженных нормалей (Ex07). Для упрощения программы вместо полноценного вычисления настоящих сглаженных нормалей сделаем так, что бы нормали левого края плоскости C совпадали с нормалями плоскости A, а правого - нормалями плоскости B. Т.к. плоскость C рисуется в системе координат плоскости A, необходимо пересчитывать только нормали левого края:
vec3 n(0, 0, 1); //------------------- vec3 final_n; mult_dir( final_n, m2, n); glBegin( GL_QUADS); glNormal3f( 0, 0, 1); glVertex2f( 1, 1); glVertex2f( 1, -1); glNormal3fv( &final_n.x); glVertex4fv( &final_v1.x); glVertex4fv( &final_v2.x); glEnd( );
Умножение нормали на матрицу осуществляется новой для нас командой mult_dir. Эта команда используется для повышения быстродействия в тех случаях, когда для нас важно только направление вектора, а его модуль безразличен (нормаль всё равно нормализуется, если включен режим glEnable(GL_NORMALIZE)). В составе nv_math ещё имеется функция mult_pos, которая является полной противоположностью mult_dir.
#расширения OpenGL, #NVIDIA, #nv_math, #OpenGL, #PBuffer, #SDK, #векторы, #математика
25 февраля 2002 (Обновление: 17 сен 2009)