вернуться в оглавление предыдущая глава предыдущий параграф следующий параграф следующая глава


Порт вывода и система координат


Установка порта вывода

Графический вывод обычно осущестсвляется в некоторую прямоугольную область экрана или окна. В OpenGL-визуализации эта область называется порт вывода. Именно в этой прямоугольной области будет размещено библиотекой сформированное изображение. Его размеры определяются относительно левого верхнего угла окна и измеряются в пикселах. Для определения порта вывода приложение должно отследить событие изменения размеров окна и определить порт вывода с использованием функции:

void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);

Аргументы (x, y) определяют положение верхнего левого угла порта вывола, а width и height -- его размеры. По умолчанию библиотека растягивает порт вывода на всё OpenGL-окно.

Координатная система

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

Во-первых, вершина преобразуется в матрицу 1X4, в которой первые три элемента представляют собой координаты x, y, z. Четвёртое число - масштабный коэффициент w, который обычно равен 1.0. Вершина домножается на видовую матрицу, которая описывает преобразования видовой системы координат. Получаем вершину в координатах вида. Она в свою очередь домножается на матрицу проекций и получаем вершину в координатах проекции. На этом этапе некоторве вершины отбрасываются (из-за непопадания в объём визуализации). Затем вершины нормализуются для передачи перспективы (если координата w не равна 1.0). Окончательное проецирование вершины на двумерную поверхность экрана выполняется библиотекой OpenGL самостоятельно и вмешаться в этот процесс нельзя.

Матрица проекций

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

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

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

void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);

(left, bottom, -near) и (right, top, -near) определяют координаты ближней отсекающей рамки; near и far имеют всегда положительные значения и определяют расстояние от точки зрения до ближней и дальней отсекающих рамок.

Для задания матрицы перспективной проекции также можно использовать функцию gluPerspective(), которая имеет другие аргументы

void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble near, GLdouble far);

Аргумент fovy (field of view) определяет поле зрения, а aspect -- отношение ширины отсекающей рамки к высоте. near и far имеют всегда положительные значения и определяют расстояние от точки зрения до ближней и дальней отсекающих рамок.

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

Особенностями ортографической проекции является то, что расстояние от камеры до объектов не влияет на итоговое изображение.

Для установки и последующего изменения матрицы проекций следует выполнить функцию glMatrixMode(GL_PROJECTION). Для начала также следует отменить все предыдущие установки и преобразования, сделав матрицу проекций единичной с помошью функции glLoadIdentity(). Матрица ортографической проекции устанавливается с использованием функции, которая имеет следующий прототип:

void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);

(left, bottom, -near) и (right, top, -near) -- точки, определяющие ближнюю отсекающую рамку. (left, bottom, -far) и (right, top, -far) -- точки, определяющие дальнюю отсекающую рамку. После применения этой команды направление проецирования параллельно оси z в сторону отрицательных значений

//Функция изменения размеров и установки координат
void Reshape(int width, int height)
{
//Установка порта вывода
    glViewport(0, 0, width, height);

//Режим матрицы проекций
    glMatrixMode(GL_PROJECTION);
//Единичная матрица
    glLoadIdentity();

//Установка двумерной ортографической системы координат
     glOrtho(-50., 50., -50., 50., -1., 1.);

//Режим видовой матрицы
    glMatrixMode(GL_MODELVIEW);
}

Специально для двумерной визуализации можно использовать функцию, которая имеет следующий прототип:

void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top);

Эта функция аналогична glOrtho(), при вызове которой аргумент near=-1.0, а far=1.0. В процессе двумерной визуализации z-координата у вершин имеет значение 0, то есть объекты находятся на средней плоскости.

При необходимости сохранения пропорций установку системы координат необходимо осуществлять с учетом отношения ширины и высоты окна.

//Установка ортографической системы координат
    double aspect=width/double(height);
    if(width>=height)
    {
        gluOrtho2D(-50.*aspect, 50.*aspect, -50., 50.);
    }
    else
    {
        gluOrtho2D(-50., 50., -50./aspect, 50./aspect);
   }

Видовая матрица

Видовая матрица отвечает за систему координат создаваемой трехмерной модели. В процессе создания модели видовая матрица может многократно изменяться для того, чтобы видоизменить изображение отдельных графических примитивов (превратить квадрат в прямоугольник, куб в параллелепипед, сферу в эллипсоид). Для установки и последующего изменения видовой матрицы следует выполнить функцию glMatrixMode(GL_MODELVIEW). Для начала также следует отменить все предыдущие установки и преобразования, сделав матрицу проекций единичной с помошью функции glLoadIdentity(). Если координаты вершин объекта задаются при его создании, то дополнительные преобразования системы координат не требуются. Однако часто это сложно или просто невозможно.

OpenGL предоставляет три функции для выполнения преобразования системы координат: glTranslated(), glRotated() и glScaled(). Эти команды генерируют матрицы переноса, поворота и масштабирования, которые домножаются на видовую матрицу с помощью функции glMultMatrix(). Как видите OpenGL берёт на себя матричные операции и выполняет их по специальным, быстрым алгоритмам с максимальной эффективностью. Например:

Перенос

Осуществляется с использованием функции, которая имеет следующий прототип:

void glTranslated(double x, double y, double z);
glTranslated(1.,1.,0.); //Перенос вдоль x и y
glBegin(GL_LINE_LOOP);
//Установка вершин
    glVertex2d(-2., 2.);
    glVertex2d(2., 2.);
    glVertex2d(2., -2.);
    glVertex2d(-2., -2.);
glEnd();

Если аргумент больше 0, то объект при отображении будет смещён в сторону положительных значений вдоль оси. Если аргумент меньше 0, то объект при отображении будет смещён в сторону отрицательных значений вдоль оси.

Масштабированиe

Осуществляется с использованием функции, которая имеет следующий прототип:

void glScaled(double x, double y, double z);
glScaled(0.5, 1.5, 1.); //Масштабирование вдоль x и y
glBegin(GL_LINE_LOOP);
//Установка вершин
    glVertex2d(-2., 2.);
    glVertex2d(2., 2.);
    glVertex2d(2., -2.);
    glVertex2d(-2., -2.);
glEnd();

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

Поворот

Осуществляется с использованием функции, которая имеет следующий прототип:

void glRotated(double angle, double x, double y, double z);
glRotated(30.,0.,0.,1.); //Поворот вокруг z
glBegin(GL_LINE_LOOP);
//Установка вершин
    glVertex2d(-2., 2.);
    glVertex2d(2., 2.);
    glVertex2d(2., -2.);
    glVertex2d(-2., -2.);
glEnd();

Первый аргумент определяет угол поворота, а остальные три -- координаты вектора, вокруг которого осуществляется вращение.

Стек матриц

Полезным механизмом при построении сложных изображений является стек матриц. С использованием функций glPushMatrix() и glPopMatrix() можно запомнить текущую матрицу в стеке и восстановить ее после каких либо изменений в системе координат.

Дисплейные списки

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

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

GLuint glGenLists(GLsizei range);

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

Для того, чтобы начать формировать список, необходимо вызвать функцию

void glNewList (GLuint list, GLenum mode);

Первый аргумент задает идентификатор формируемого списка, а второй определяет будет ли список только сформирован (GL_COMPILE) или сразу же и отображен (GL_COMPILE_AND_EXECUTE). Далее могут следовать команды OpenGL которые требуется сохранить в списке. Не все команды могут быть в него включены.

Формирование списка заканчивается функцией:

void glEndList (void);

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

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

void glCallList (GLuint list);

которая в качестве аргумента принимает идентификатор списка.

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

Рассмотрим пример:

void Draw(void)
{
//Очистка цветового буфера
    glClear(GL_COLOR_BUFFER_BIT);

//Установка цвета отображения
    glColor3d(1.0, 1.0, 0.0);
//Рисование горизонтальной оси
    glBegin(GL_LINES);
        glVertex2d(-50., .0);
        glVertex2d(50., .0);

        for(int i=-50; i<50; i++)
        {
            glVertex2d(i, .0);
            if(i % 5)
            {
                glVertex2d(i, -1.);
            }
            else if(i % 10)
            {
                glVertex2d(i, -2.);
            }
            else
            {
                glVertex2d(i, -3.);
            }
        }
    
glEnd();
//Рисование вертикальной оси
    glBegin(GL_LINES);
        glVertex2d(.0, -50.);
        glVertex2d(.0, 50.);
        
for(int j=-50; j<50; j++)
        {
            glVertex2d(.0, j);
            if(j % 5)
            {
                glVertex2d(-1., j);
            }
            else if(j % 10)
            {
                glVertex2d(-2., j);
            }
            else
            {
                glVertex2d(-3., j);
            }
        }
    glEnd();
//Завершить выполнение команд
    glFlush();
}
void Draw(void)
{
//Очистка цветового буфера
    glClear(GL_COLOR_BUFFER_BIT);
//Установка цвета отображения
    glColor3d(1.0, 1.0, 0.0);

//Формирование оси
    int axis = glGenLists(1);
    if (axis != 0)
    {
    glNewList(axis, GL_COMPILE);
    glBegin(GL_LINES);
        glVertex2d(0., .0);
        glVertex2d(100., .0);

        for(int i=0.; i<97; i++)
        {
            glVertex2d(i, .0);
            if(i % 5)
            {
                glVertex2d(i, 1.);
            }
            else if(i % 10)
            {
                glVertex2d(i, 2.);
            }
            else
            {
            glVertex2d(i, 3.);
            }
        }
        glEnd();
//Формирование стрелки можно добавить позже
        glBegin(GL_LINE_STRIP);
            glVertex2d(97., 1.);
            glVertex2d(100.,.0);
            glVertex2d(97., -1.);
        glEnd();
        glEndList();
    }
//Рисование горизонтальной оси
    glPushMatrix();
    glTranslated(-50.,0.,0.);
    glRotated(180.,1.,0.,0.);
    glCallList(axis);
    glPopMatrix();

//Рисование вертикальной оси
    glPushMatrix();
    glTranslated(0.,-50.,0.);
    glRotated(90.,0.,0.,1.);
    glCallList(axis);
    glPopMatrix();

//Завершить выполнение команд
glFlush();
}