Суббота, 04.05.2024, 06:16
Приветствую Вас Гость | RSS

VistaMEDIA

Главная | Регистрация | Вход

Главная » Статьи » Мои статьи

OpenGL часть 1
Основные принципы работы и что тако OpenGL вобще

  • Введение

    Введение

    OpenGL является на данный момент одним из самых популярных программных интерфейсов (API) для разработки приложений в области двумерной и трехмерной графики. Стандарт OpenGL был разработан и утвержден в 1992 году ведущими фирмами в области разработки программного обеспечения, а его основой стала библиотека IRIS GL, разработанная Silicon Graphics.

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

    Характерными особенностями OpenGL, которые обеспечили распространение и развитие этого графического стандарта, являются:

    Стабильность

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

    Надежность и переносимость

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

    Легкость применения

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

    Основные возможности OpenGL
    • Набор базовых примитивов: точки, линии, многоугольники и т.п.
    • Видовые и координатные преобразования
    • Удаление невидимых линий и поверхностей (z-буфер)
    • Использование сплайнов для построения линий и поверхностей
    • Наложение текстуры и применение освещения
    • Добавление специальных эффектов: тумана, изменение прозрачности,сопряжение цветов (blending), устранение ступенчатости (anti-aliasing).

    Как уже было сказано, существует реализация OpenGL для разных платформ, для чего было удобно разделить базовые функции графической системы и функции для отображения графической информации и взаимодействия с пользователем. Были созданы библиотеки для отображения информации с помощью оконной подсистемы для операционных систем Windows и Unix (WGL и GLX соответственно), а также библиотеки GLAUX и GLUT, которые используются для создания так называемых консольных приложений.

    Библиотека GLAUX уступает по популярности написанной несколько позже библиотеке GLUT, хотя они предоставляют примерно одинаковые возможности. В дальнейшем в данном пособии в качестве основной будет рассматриваться библиотека GLUT, предоставляющая широкий набор средств взаимодействия с пользователем.

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

    Архитектура и особенности синтаксиса

    С точки зрения архитектуры, графическая система OpenGL является конвейером, состоящим из нескольких этапов обработки данных:

    • Аппроксимация кривых и поверхностей
    • Обработка вершин и сборка примитивов
    • Растеризация и обработка фрагментов
    • Операции над пикселами
    • Подготовка текстуры
    • Передача данных в буфер кадра

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

    Для обеспечения интуитивно понятных названий в OpenGL полное имя команды имеет вид:

    type glCommand_name[1 2 3 4][b s i f d ub us ui][v](type1 arg1,…,typeN argN)

    Таким образом, имя состоит из нескольких частей:

    gl это имя библиотеки, в которой описана эта функция: для базовых функций OpenGL, функций из библиотек GLU, GLUT, GLAUX это gl, glu, glut, aux соответственно.

    Command_name имя команды [1 2 3 4]число аргументов команды [b s i f d ub us ui ]тип аргумента:

    символ b означает тип GLbyte (аналог char в С\С++), символ f тип GLfloat (аналог float), символ i– тип GLint(аналог int) и так далее.

    Полный список типов и их описание можно посмотреть в файле gl.h

    [v] наличие этого символа показывает, что в качестве параметров функции используется указатель на массив значений

    Символы в квадратных скобках в некоторых названиях не используются. Например, команда glVertex2i() описана как базовая в библиотеке OpenGL, и использует в качестве параметров два целых числа, а команда glColor3fv() использует в качестве параметра указатель на массив из трех вещественных чисел.

    Структура консольного приложения

    Будем рассматривать построение консольного приложения при помощи библиотеки GLUT или GL Utility Toolkit, получившей в последнее время широкое распространение. Эта библиотека обеспечивает единый интерфейс для работы с окнами вне зависимости от платформы, поэтому описываемая ниже структура приложения остается неизменной для операционных систем Windows, Linux и многих других.

    Функции GLUT могут быть классифицированы на несколько групп по своему назначению:

    • Инициализация
    • Начало обработки событий
    • Управление окнами
    • Управление меню
    • Регистрация вызываемых (callback) функций
    • Управление индексированной палитрой цветов
    • Отображение шрифтов
    • Отображение дополнительных геометрических фигур(тор, конус и др.)

    Инициализация проводится с помощью функции glutInit(int *argcp, char **argv)

    Переменная argcp есть указатель на стандартную переменную argc описываемую в функции main(), а argv– указатель на параметры, передаваемые программе при запуске, который описывается там же. Эта функция проводит необходимые начальные действия для построения окна приложения, и только несколько функций GLUT могут быть вызваны до нее. К ним относятся:

    glutInitWindowPosition (int x, int y) glutInitWindowSize (int width, int height) glutInitDisplayMode (unsigned int mode)

    Первые две функции задают соответственно положение и размер окна, а последняя функция определяет различные режимы отображения информации, которые могут совместно задаваться с использованием операции побитового “или”(|):

    GLUT_RGBA Режим RGBA. Используется по умолчанию, если не указаны явно режимы GLUT_RGBA или GLUT_INDEX. GLUT_RGB То же, что и GLUT_RGBA. GLUT_INDEX Режим индексированных цветов (использование палитры). Отменяет GLUT_RGBA. GLUT_SINGLE Окно с одиночным буфером. Используется по умолчанию. GLUT_DOUBLE Окно с двойным буфером. Отменяет GLUT_SINGLE. GLUT_DEPTH Окно с буфером глубины.

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

    Двойной буфер обычно используют для анимации, сначала рисуя что-нибудь в одном буфере, а затем меняя их местами, что позволяет избежать мерцания. Буфер глубины или z-буфер используется для удаления невидимых линий и поверхностей.

    Функции библиотеки GLUT реализуют так называемый событийно-управляемый механизм. Это означает, что есть некоторый внутренний цикл, который запускается после соответствующей инициализации и обрабатывает, одно за другим, все события, объявленные во время инициализации. К событиям относятся: щелчок мыши, закрытие окна, изменение свойств окна, передвижение курсора, нажатие клавиши, и "пустое" (idle) событие, когда ничего не происходит. Для проведения периодической проверки совершения того или иного события надо зарегистрировать функцию, которая будет его обрабатывать. Для этого используются функции вида:

    • void glutDisplayFunc (void (*func) (void))
    • void glutReshapeFunc (void (*func) (int width, int height))
    • void glutMouseFunc (void (*func) (int button, int state, int x, int y))
    • void glutIdleFunc (void (*func) (void))

    То есть параметром для них является имя соответствующей функции заданного типа. С помощью glutDisplayFunc() задается функция рисования для окна приложения, которая вызывается при необходимости создания или восстановления изображения. Для явного указания, что окно надо обновить, иногда удобно использовать функцию void glutPostRedisplay(void)

    Через glutReshapeFunc() устанавливается функция обработки изменения размеров окна пользователем, которой передаются новые размеры.

    glutMouseFunc() определяет обработчика команд от мыши, а glutIdleFunc() задает функцию, которая будет вызываться каждый раз, когда нет событий от пользователя.

    Контроль всех событий происходит внутри бесконечного цикла в функции void glutMainLoop(void) которая обычно вызывается в конце любой программы, использующей GLUT.

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

    #include <GL/glut.h>
    void MyIdle(void){
    //--Код, который меняет переменные--//
    //--определяющие следующий кадр--//
    ....
    };
    void MyDisplay(void){
    //--Код OpenGL, который отображает кадр --//
    ....
    //-- После рисования переставляем буфера --//
    glutSwapBuffers();
    };
    void main(int argcp, char **argv){
    //-- Инициализация GLUT --//
    glutInit(&argcp, argv);
    glutInitWindowSize(640, 480);
    glutInitWindowPosition(0, 0);
    //--Открытие окна--//
    glutCreateWindow("My OpenGL Application");
    //-- Выбор режима:Двойной буфер и RGBA цвета --//
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    //-- Регистрация вызываемых функций --//
    glutDisplayFunc(MyDisplay);
    glutIdleFunc(MyIdle);
    //-- Запуск механизма обработки событий --//
    glutMainLoop();
    };

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

    В случае, если приложение должно строить статичное изображение, можно заменить GLUT_DOUBLE на GLUT_SINGLE, так как одного буфера в этом случае будет достаточно, и убрать вызов функции glutIdleFunc().

  • Построение объектов в OpenGL

  • Вершины и примитивы Вершины и примитивы

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

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

    • void glVertex[2 3 4][s i f d](type coords)
    • void glVertex[2 3 4][s i f d]v(type *coords)

    Координаты точки задаются максимум четырьмя значениями: x, y, z, w, при этом можно указывать два (x,y) или три (x,y,z) значения, а для остальных переменных в этих случаях используются значения по умолчанию: z=0, w=1. Как уже было сказано выше, число в названии команды соответствует числу явно задаваемых значений, а последующий символ – их типу.

    Координатные оси расположены так, что точка (0,0) находится в левом нижнем углу экрана, ось x направлена влево, ось y- вверх, а ось z- из экрана. Это расположение осей мировой системы координат, в которой задаются координаты вершин объекта, другие системы координат будут рассмотрены ниже.

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

    • void glBegin(GLenum mode)
    • void glEnd(void)

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

    • GL_POINTS каждая вершина задает координаты некоторой точки.
    • GL_LINES каждая отдельная пара вершин определяет отрезок; если задано нечетное число вершин, то последняя вершина игнорируется.
    • GL_LINE_STRIP каждая следующая вершина задает отрезок вместе с предыдущей.
    • GL_LINE_LOOP отличие от предыдущего примитива только в том, что последний отрезок определяется последней и первой вершиной, образуя замкнутую ломаную.
    • GL_TRIANGLES каждая отдельная тройка вершин определяет треугольник; если задано не кратное трем число вершин, то последние вершины игнорируются.
    • GL_TRIANGLE_STRIP каждая следующая вершина задает треугольник вместе с двумя предыдущими.
    • GL_TRIANGLE_FAN треугольники задаются первой и каждой следующей парой вершин (пары не пересекаются).
    • GL_QUADS каждая отдельная четверка вершин определяет четырехугольник; если задано не кратное четырем число вершин, то последние вершины игнорируются.
    • GL_QUAD_STRIP четырехугольник с номером n определяется вершинами с номерами 2n-1, 2n, 2n+2, 2n+1.
    • GL_POLYGON последовательно задаются вершины выпуклого многоугольника.

    Для задания текущего цвета вершины используются команды

    • void glColor[3 4][b s i f](GLtype components)
    • void glColor[3 4][b s i f]v(GLtype components)

    Первые три параметра задают R, G, B компоненты цвета, а последний параметр определяет alpha-компоненту, которая задает уровень прозрачности объекта. Если в названии команды указан тип ‘f’ (float), то значения всех параметров должны принадлежать отрезку [0,1], при этом по умолчанию значение alpha-компоненты устанавливается равным 1.0, что соответствует полной непрозрачности. Если указан тип ‘ub’ (unsigned byte), то значения должны лежать в отрезке [0,255].

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

    Для управления режимом интерполяции цветов используется команда void glShadeModel(GLenummode) вызов которой с параметром GL_SMOOTH включает интерполяцию (установка по умолчанию), а с GL_FLAT отключает.

    Например, чтобы нарисовать треугольник с разными цветами в вершинах, достаточно написать:

    GLfloat BlueCol[3]={0,0,1};
    glBegin(GL_TRIANGLE);
    glColor3f(1.0, 0.0, 0.0); //красный
    glVertex3f(0.0, 0.0, 0.0);
    glColor3ub(0,255,0); //зеленый
    glVertex3f(1.0, 0.0, 0.0);
    glColor3fv(BlueCol); //синий
    glVertex3f(1.0, 1.0, 0.0);
    glEnd();

    Для задания цвета фона используется команда void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha). Значения должны находиться в отрезке [0,1] и по умолчанию равны нулю. После этого вызов команды void glClear(GLbitfield mask) с параметром GL_COLOR_BUFFER_BIT устанавливает цвет фона во все буфера, доступные для записи цвета (иногда удобно использовать несколько буферов цвета).

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

    • void glNormal3[b s i f d](type coords)
    • void glNormal3[b s i f d]v(type coords)

    Задаваемый вектор может не иметь единичной длины, но он будет нормироваться автоматически в режиме нормализации, который включается вызовом команды glEnable(GL_NORMALIZE).Команды

    • void glEnable(GLenum mode)
    • void glDisable(GLenum mode)

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

    Вообще, внутри командных скобок glBegin() и glEnd() можно производить вызов лишь нескольких команд, в которые входят glVertex..(), glColor..()glNormal..(), glRect..(), glMaterial..() и glTexCoord..().

    Последние две команды будут рассматриваться ниже, а с помощью команды void glRect[s i f d]( GLtype x1, GLtype y1, GLtype x2, GLtype y2 ), void glRect[s i f d]v( GLtype *v1, GLtype *v2 ) можно нарисовать прямоугольник в плоскости z=0 с координатами противоположных углов (x1,y1) и (x2,y2), либо набор прямоугольников с координатами углов в массивах v1 и v2.

    Кроме задания самих примитивов можно определить метод их отображения на экране, где под примитивами в данном случае понимаются многоугольники.

    Однако сначала надо определить понятие лицевых и обратных граней.

    Под гранью понимается одна из сторон многоугольника, и по умолчанию лицевой считается та сторона, вершины которой обходятся против часовой стрелки. Направление обхода вершин лицевых сторон можно изменить вызовом команды void glFrontFace(GLenum mode) со значением параметра mode равным GL_CW, а отменить- с GL_CCW.

    Чтобы изменить метод отображения многоугольника используется команда void glPolygonMode(GLenum face, Glenum mode)

    Параметр mode определяет, как будут отображаться многоугольники, а параметр face устанавливает тип многоугольников, к которым будет применяться эта команда и может принимать следующие значения:

    • GL_FRONT для лицевых граней
    • GL_BACK для обратных граней
    • GL_FRONT_AND_BACK для всех граней

    Параметр mode может быть равен:

    • GL_POINT при таком режиме будут отображаться только вершины многоугольников.
    • GL_LINE при таком режиме многоугольник будет представляться набором отрезков.
    • GL_FILL при таком режиме многоугольники будут закрашиваться текущим цветом с учетом освещения и этот режим установлен по умолчанию.

    Кроме того, можно указывать, какой тип граней отображать на экране. Для этого сначала надо установить соответствующий режим вызовом команды glEnable(GL_CULL_FACE), а затем выбрать тип отображаемых граней с помощью команды void glСullFace(GLenum mode)

    Вызов с параметром GL_FRONT приводит к удалению из изображения всех лицевых граней, а с параметром GL_BACK- обратных (установка по умолчанию).

    Кроме рассмотренных стандартных примитивов в библиотеках GLU и GLUT описаны более сложные фигуры, такие как сфера, цилиндр, диск (в GLU) и сфера, куб, конус, тор, тетраэдр, додекаэдр, икосаэдр, октаэдр и чайник(в GLUT). Автоматическое наложение текстуры предусмотрено только для фигур из библиотеки GLU (создание текстур в OpenGL будет рассматриваться ниже).

    Например, чтобы нарисовать сферу или цилиндр, надо сначала создать объект специального типа GLUquadricObj с помощью команды

    GLUquadricObj* gluNewQuadric(void)

    а затем вызвать соответствующую команду:

    • void gluSphere(GLUquadricObj * qobj, GLdouble radius, GLint slices, GLint stacks)
    • void gluCylinder(GLUquadricObj * qobj, GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint stacks)

    где параметр slices задает число разбиений вокруг оси z, а stacks – вдоль оси z.

    Более подробную информацию об этих и других командах построения примитивов можно найти приложении.

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

    Массивы вершин

    Если вершин много, то чтобы не вызывать для каждой команду glVertex..(), удобно объединять вершины в массивы, используя команду

    void glVertexPointer( GLint size, GLenum type,
    GLsizei stride, void *ptr )

    которая определяет способ хранения и координаты вершин. При этом size определяет число координат вершины (может быть равен 2, 3, 4), type определяет тип данных (может быть равен GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE). Иногда удобно хранить в одном массиве другие атрибуты вершины, и тогда параметр stride задает смещение от координат одной вершины до координат следующей; если stride равен нулю, это значит, что координаты расположены последовательно. В параметре ptr указывается адрес, где находятся данные.

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

    • void NormalPointer(GLenum type, GLsizei stride, void*pointer)
    • void ColorPointer(GLintsize, GLenum type, GLsizei stride, void *pointer)

    Для того, чтобы эти массивы можно было использовать в дальнейшем, надо вызвать команду

    void glEnableClientState(GLenum array)

    с параметрами GL_VERTEX_ARRAY, GL_NORMAL_ARRAY, GL_COLOR_ARRAY соответственно. После окончания работы с массивом желательно вызвать команду

    void glDisableClientState(GLenum array)

    с соответствующим значением параметра array.

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

    void glArrayElement(GLint index)

    которая передает OpenGL атрибуты вершины, используя элементы массива с номером index. Это аналогично последовательному применению команд вида glColor..(…), glNormal..(…), glVertex..(…) c соответствующими параметрами. Однако вместо нее обычно вызывается команда

    void glDrawArrays(GLenum mode,
    GLint first, GLsizei count)

    рисующая count примитивов, определяемых параметром mode, используя элементы из массивов с индексами от first до first+count-1. Это эквивалентно вызову команды glArrayElement() с соответствующими индексами.

    В случае если одна вершина входит в несколько примитивов, то вместо дублирования ее координат в массиве удобно использовать ее индекс.

    Для этого надо вызвать команду

    void glDrawArrays(GLenum mode, GLsizei count,
    GLenum type, void *indices)

    где indices– это массив номеров вершин, которые надо использовать для построения примитивов, type определяет тип элементов этого массива: GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT, а count задает их количество.

    Списки изображений

    Если нужно несколько раз обращаться к одной и той же группе команд,эти команды можно объединить в так называемый список изображений (display list) и вызывать его при необходимости. Для того, чтобы создать новый список изображений надо поместить все команды, которые должны в него войти между командными скобками:

    • void glNewList(GLuint list, GLenum mode)
    • void glEndList()

    Для различения списков используются целые положительные числа, задаваемые при создании списка значением параметра list, а параметр mode определяет режим обработки команд, входящих в список:

    • GL_COMPILE команды записываются в список без выполнения
    • GL_COMPILE_AND_EXECUTE команды сначала выполняются, а затем записываются в список

    После того, как список создан, его можно вызвать командой

    void glCallList(GLuint list)

    указав в параметре list идентификатор нужного списка. Чтобы вызвать сразу несколько списков, можно воспользоваться командой

    void glCallLists(GLsizei n,
    GLenum type, const GLvoid *lists)

    вызывающей n списков с идентификаторами из массива lists, тип элементов которого указывается в параметре type. Это могут быть типы GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_INT, GL_UNSIGNED_INT >и некоторые другие. Для удаления списков используется команда

    void glDeleteLists(GLint list, GLsizei range)

    которая удаляет списки с идентификаторами ID из диапазона list <=ID<= list+range-1.



  • Источник: http://docs.com.ru
    Категория: Мои статьи | Добавил: vistamedia (06.10.2007) | Автор: Вячеслав
    Просмотров: 2857 | Комментарии: 1 | Рейтинг: 0.0/0 |
    Всего комментариев: 0
    Добавлять комментарии могут только зарегистрированные пользователи.
    [ Регистрация | Вход ]

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

    Форма входа

    Поиск

    Статистика

    Наш опрос

    Оцените мой сайт
    Всего ответов: 52

    Мини-чат

    200
    Бесплатный хостинг uCoz