ПрограммированиеСтатьиГрафика

Работа с расширениями 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.

Страницы: 1 2 3 4 Следующая »

#расширения OpenGL, #NVIDIA, #nv_math, #OpenGL, #PBuffer, #SDK, #векторы, #математика

25 февраля 2002 (Обновление: 17 сен 2009)