Основные принципы компьютерной визуализации стандарт opengl
Компьютерная графика -- это область информатики, в которой рассматриваются алгоритмы и технологии визуализации данных. Развитие компьютерной графики определяется в основном двумя факторами: реальными потребностями потенциальных пользователей и возможностями аппаратного и программного обеспечения. Потребности потребителей и возможности техники неуклонно растут, и на сегодняшний день компьютерная графика активно используется в самых различных сферах. Можно выделить следующие области применения компьютерной графики:
- Визуализация информации.
- Моделирование процессов и явлений.
- Проектирование технических объектов.
- Организация пользовательского интерфеса.
Организация пользовательского интерфеса
В последние 5-7 лет визуальная парадигма при организации интерфеса между компьютером и конечным пользователем стала доминирующей. Оконный графический интерфейс встроен во многие современные операционные системы. Уже достаточно стандартизован набор элементов управления, которые используются при построении такого интерфейса. Большинство пользователей уже привыкло к такой организации интерфейса, который позволяет пользователям чувствовать себя более комфортно и повышать эффективность взаимодействия.
Всё это, говорит о том, что в самой операционной системе должны быть уже реализованы достаточно большое количество функций для визуализации элементов управления. Например, операционная система Windows предоставляет разработчикам GDI (Graphics Device Interface). Как показывает практика для некоторых приложений возможностей, предоставляемых системной API, вполне достаточно для визуализации обрабатываемых данных (построения простейших графиков, представления моделируемых объектов и явлений). Но такие недостатаки, как низкая скорость отображения, отсутствие поддержки трехмерной графики не способствуют ее использованию для визуализации научных данных и компьютерного моделирования. В некоторых научных и технических программах со сложным графическим выводом требуются функции для более быстрой, мощной и гибкой визуализации вычисленных данных, моделируемых явлений, проектируемых объектов.
Устройство OpenGL
OpenGL можно сравнить с большим конечным автоматом, который имеет множество состояний и функций для их изменения. Под состоянием OpenGL в основном имеют ввиду контекст OpenGL. Во время работы с OpenGL мы будем проходить через несколько меняющих состояния функций, которые будут менять контекст, и выполняющие действия в зависимости от текущего состояния OpenGL.
Например, если мы перед отрисовкой передадим OpenGL команду использовать линии вместо треугольников, то OpenGL все последующие отрисовки будет использовать линии, пока мы не изменим эту опцию, или не поменяем контекст.
Моделирование процессов и явлений
Современные графические системы обладают достаточной производительностью для создания сложных анимационных и динамических изображений. В системах моделирования, которые также называются симуляторами, пытаются получить и визуализировать картину процессов и явлений, которые происходят или могли бы происходить в реальности. Самым известным и наиболее сложным примером такой системы является симулятор полётов, который используют для моделирования обстановки и процесса полёта при обучении пилотов. В оптике симуляторы используются для моделирования сложных, дорогостоящих или опасных явлений. Например, моделирование формирования изображения или моделирования процессов в лазерных резонаторах.
Именования
Графика
- GL_POINTS — каждая вершина задает точку
- GL_LINES — каждая отдельная пара вершин задает линию
- GL_LINE_STRIP — каждая пара вершин задает линию (т.е. конец предыдущей линии является началом следующей)
- GL_LINE_LOOP — аналогично предыдущему за исключением того, что последняя вершина соединяется с первой и получается замкнутая фигура
- GL_TRIANGLES — каждая отдельная тройка вершин задает треугольник
- GL_TRIANGLE_STRIP — каждая следующая вершина задает треугольник вместе с двумя предыдущими (получается лента из треугольников)
- GL_TRIANGLE_FAN — каждый треугольник задается первой вершиной и последующими парами (т.е. треугольники строятся вокруг первой вершины, образуя нечто похожее на диафрагму)
- GL_QUADS — каждые четыре вершины образуют четырехугольник
- GL_QUAD_STRIP — каждая следующая пара вершин образует четырехугольник вместе с парой предыдущих
- GL_POLYGON — задает многоугольник с количеством углов равным количеству заданных вершин
- glBegin ( GL_QUADS ) ;
- glColor3f ( 1.0 , 1.0 , 1.0 ) ;
- glVertex2i ( 250 , 450 ) ;
- glColor3f ( 0.0 , 0.0 , 1.0 ) ;
- glVertex2i ( 250 , 150 ) ;
- glColor3f ( 0.0 , 1.0 , 0.0 ) ;
- glVertex2i ( 550 , 150 ) ;
- glColor3f ( 1.0 , 0.0 , 0.0 ) ;
- glVertex2i ( 550 , 450 ) ;
- glEnd ( ) ;
Базовые объекты: Шейдеры и шейдерные программы.=
Shader — это небольшая программа которая выполняется на графическом ускорителе(GPU) на определённом этапе графического конвейера. Если рассматривать шейдеры абстрактно, то можно сказать, что это этапы графического конвейера, которые:
- Знают откуда брать данные на обработку.
- Знают как обрабатывать входные данные.
- Знают куда записать данные для дальнейшей их обработки.
Но как же выглядит графический конвейер? Очень просто, вот так:
Пока в этой схеме нас интересует только главная вертикаль, которая начинается с Vertex Specification и заканчивается на Frame Buffer. Как уже говорилось ранее, каждый шейдер имеет свои входные и выходные параметры, которые отличаются по типу и количеству параметров.
Кратко опишем каждый этап конвейера, чтобы понимать, что он делает:
- Вершинный шейдер — нужен для обработки данных 3D координат и всех других входных параметров. Чаще всего в вершинном шейдере производятся вычисление положения вершины относительно экрана, расчёт нормалей (если это необходимо) и формирование входных данных в другие шейдеры.
- Шейдер тесселяции и шейдер контроля тесселяции — эти два шейдера отвечают за детализацию примитивов, поступающих из вершинного шейдера и подготавливают данные для обработки в геометрическом шейдере. Сложно описать в двух предложениях на что способны эти два шейдера, но чтобы у читателей было небольшое представление приведу пару изображений с низким и высоким уровнем теселяции:
Шейдеры OpenGL пишутся на специальном С-подобном языке GLSL из которого они компилируются и линкуются в шейдерную программу. Уже на данном этапе кажется, что написание шейдерной программы это крайне трудоёмкое занятие, т.к. нужно определить 5 ступеней графического конвейера и связать их воедино. К большому счастью это не так: в графическом конвейере по умолчанию определены шейдеры тесселяции и геометрии, что позволяет нам определить всего два шейдера — вершинный и фрагментный (иногда его назвают пиксельным шейдером). Лучше всего рассмотреть эти два шейдера на классическом примере:
Эти два простых шейдера ничего не вычисляют лишь передают данные дальше по конвейеру. Обратим внимение как связаны вершинный и фрагментный шейдеры: в вершинном шейдере объявлена out переменная Color в которую будет записан цвет после выполнения главной функции, в то время как в фрагментном шейдере объявлена точно такая же переменная с квалификатором in, т.е. как и описывалось раньше фрагментный шейдер получает данные из вершинного посредством нехитрого прокидывания данных дальше через конвейер (но на самом деле не всё так просто).
Замечание: Если в фрагментном шейдере не объявить и не проинициализировать out переменную типа vec4, то на экран ничего выводиться не будет.
Базовые объекты: Буферы и Вершинные массивы
Я думаю не стоит объяснять что такое буферные объекты, лучше рассмотрим как создать и заполнить буффер в OpenGL.
Ничего сложно в этом нет, привязываем сгенереный буффер к нужному таргету (позже узнаем к какому) и загружаем данные указывая их размер и тип использования.
GL_STATIC_DRAW — данные в буфере изменяться не будут.
GL_DYNAMIC_DRAW — данныe в буфере будут изменяться, но не часто.
GL_STREAM_DRAW — данные в буфере будут изменяться при каждом вызове отрисовки.
Отлчно, теперь в памяти GPU расположенные наши данные, скомпилирована и слинкована шейдерная программа, но остаётся один нюанс: как программа узнает откуда брать входные данные для вершинного шейдера? Данные мы загрузили, но никак не указали откуда шейдерной программе их брать. Эту задачу решает отдельный тип объектов OpenGL — вершинные массивы.
Как и с буферами вершинные массивы лучше рассмотреть на примере их конфигурации
Создание вершинных массивов ничем не отличается от создания других OpenGL объектов, самое интересное начинается после строчки: Вершинный массив (VAO) запоминает все привязки и конфигурации проводимые с ним, в том числе и привязывание буферных объектов для выгрузки данных. В данном примере такой объект всего один, но на практике их может быть несколько. После чего производится конфигурация вершинного атрибута с определённым номером:
- Номер атрибута, который мы хотим сконфигурировать.
- Количество элементов, которые мы хотим взять. (Т.к. входная переменная вершинного шейдера с layout = 0 имеет тип vec3, то мы берём 3 элемента типа float)
- Тип элементов.
- Нужно ли нормализовывать элеметы, если речь идёт о векторе.
- Смещение для следующей вершины (Т.к. у нас последовательно расположены координаты и цвета и каждый имеет тип vec3, то смещаемся на 6 * sizeof(float) = 24 байта).
- Последний аргумент показывает какое смещение брать для первой вершины. (для координат этот аргумент равен 0 байт, для цветов 12 байт)
Всё теперь мы готовы отрендерить наше первое изображение
Не забудьте привязать VAO и шейдерную программу перед вызовом отрисовки.
Если вы всё сделали правильно, то вы должны получить вот такой результат:
Результат впечатляет, но откуда в треугольнике градиентная заливка, ведь мы указали всего 3 цвета: красный, синий и зелёный для каждой отдельной вершины? Это магия шейдера растеризации: дело в том, что во фрагментный шейдер попадает не совсем то значение Color которое мы установили в вершинном. Вершин мы передаём всего 3, но фрагментов генерируется намного больше (фрагментов ровно столько же сколько закрашенных пикселей). Поэтому для каждого фрагмента берётся среднее из трёх значений Color в зависимости от того насколько близко он находится к каждой из вершин. Это очень хорошо прослеживается у углов треугольника, где фрагменты принимают то значение цвета, которое мы указали в вершинных данных.
Забегая чуть вперёд скажу, что текстурные координаты передаются точно так же, что позволяет с лёгкостью накладывать текстуры на наши примитивы.
Думаю на этом стоит закончить данную статью, самое сложное уже позади, но самое интересное только начинается. Если у вас есть вопросы или вы увидели ошибку в статье, напишите об этом в комментариях, я буду очень признателен.
В следующей статье мы рассмотрим трансформации, узнаем о unifrom переменных и научимся накладывать текстуры на примитивы.
Здравствуйте. Несколько недель назад я начинал серию переводов статей по изучению OpenGL. Но на 4 статье один хабровчанин заметил, что мои переводы могут нарушать лицензию, по которой распространяются учебные материалы, предоставленные в исходной статье. И действительно, мои переводы нарушали лицензию. Для разрешения этой проблемы я обратился к авторам того набора уроков, но так и не смог добиться нормального ответа. По этой причине я связался с автором другого, не менее (а возможно даже и более) крутого, набора уроков по OpenGL: Joey de Vries. И он дал полное разрешение на перевод его набора уроков. Его уроки гораздо более обширные, чем прошлый набор, поэтому эти переводы растянутся на долго. И я обещаю, будет интересно. Заинтересовавшихся прошу под кат.
Также я встал на распутье: либо я опишу все основы вроде создания окна и контекста в одной статье, чтобы не плодить статьи, но в таком случае такую огромную статью не всякий осилит; либо я также как и раньше буду переводить, опираясь на иерархию оригинала. Я решил выбрать второй вариант.
На счет уроков по Vulkan: к сожалению мне тяжело сейчас написать уроки по данному API по причине скудной видеокарты на данный момент, которая просто не поддерживает Vulkan API, поэтому уроки по данному API будут только после обновления видеокарты.
Давайте начнем
Теперь вы немного узнали про OpenGL как о спецификации, так и о библиотеке. Узнали примерный алгоритм работы и несколько особенностей, используемых OpenGL. Не расстраивайтесь, если что-то недопоняли, далее мы пошагово пройдемся по всем этапам и вы увидите достаточно примеров, чтобы разобраться во всех хитросплетениях OpenGL. Если вы уже готовы начать — то мы можем начать создавать OpenGL контекст и наше первое окно прямо тут.
Знакомство с OpenGL нужно начать с того, что OpenGL — это спецификация. Т.е. OpenGL лишь определяет набор обязательных возможностей. Реализация же зависит от конкретной платформы.
OpenGL является кроссплатформенным, независимым от языка программирования API для работы с графикой. OpenGL — низкоуровневый API, поэтому для работы с ним неплохо иметь некоторое представление о графике в целом и знать основы линейной алгебры.
Core-profile и Immediate mode (Мгновенный режим)
Раньше, использование OpenGL предполагало разработку в Immediate mode (также известен как фиксированный конвейер (fixed function pipeline)), которая была проста в использовании для рисования графики. Большинство функционала OpenGL было скрыто в библиотеках и у разработчиков не было свободы в понимании вычислений, производимых OpenGL.
Разработчики требовали большей гибкости в разработке и позже спецификация стала более гибкой, а разработчики получили больше контроля над процессом отрисовки их графики. Immediate mode был прост в использовании и понимании, но он был крайне неэффективным. По этой причине спецификация указала Immediate mode как устаревший, и начиная с версии 3.2 начала мотивировать программистов использовать Core-profile режим, который исключал весь устаревший функционал.
При использовании core-profile, OpenGL заставляет нас пользоваться современными практиками. Когда мы пытаемся использовать устаревшие функции, OpenGL выбрасывает ошибку и прекращает отрисовку. Преимущества использования современных практик — это гибкость и эффективность, но к сожалению бОльшая сложность в изучении. Immediate mode является бОльшей абстракцией и он скрывает большое количество реальной работы, выполняемой OpenGL и поэтому его было легко изучать, но трудно разобраться, как OpenGL на самом деле работает. Современный подход требует от разработчика полного понимания OpenGL и графического программирования в целом и хоть это немного сложнее, такая схема позволяет добиться большей гибкости, эффективности.
Это причина, почему наши уроки основаны на Core-Profile OpenGL версии 3.3.
Хоть он немного и сложнее, но это того стоит.
Сейчас уже вышли гораздо более новые версии OpenGL (на момент написания 4.5) и вы можете спросить: зачем мы должны изучать OpenGL 3.3, когда уже вышел 4.5? Ответ довольно прост. Все старшие версии OpenGL, начиная от версии 3.3 не добавляют различные полезные возможности без изменения основной механики. Новые версии просто предоставляют немного более эффективные или более удобные способы выполнения одних и тех же операций. В результате все концепты и техники, применимые к OpenGL 3.3 можно применить к новым версиям OpenGL.
Использование новейших версий OpenGL сопряжено с одной проблемой. Исполнять новейшие API смогут только современные видеокарты.
Технологии компьютерной графики
В современных научных и технических приложениях сложный графическая вызуализация реализуется с использованием библиотеки OpenGL, которая стала стандартом de facto в области трёхмерной визуализации. Библиотека OpenGL представляет собой высокоэффективный программный интерфейс к графическому аппаратному обеспечению. Наибольшую призводительность эта библиотека позволяет достичь в аппаратных системах работающих на основе современных графических ускорителей (аппаратное обеспечение, освобождающее процессор и выполняющее вычисления, необходимые для визуализации).
Архитектура и алгоритмы были библиотеки разработаны в 1992 году специалистами фирмы Silicon Graphics, Inc. (SGI) для собственного аппаратного обеспечения графических рабочих станций Iris. Через несколько лет библиотека была портирована на многие аппаратно-программные платформы (в том числе Intel+Windows) и сегодня является надёжной многоплатформенной библиотекой.
Библиотека OpenGL является бесплатно распространяемой, что является ее несомненным достоинством и причиной столь широкого использования.
OpenGL является не объектно-ориентированной, а процедурной библиотекой (около сотни комманд и функций), написанная на языке С. С одной стороны - это недостаток (компьютерная графика - благодатная область использования объектно-ориентированного программирования), но зато работать с OpenGL могут программисты работающие на C++, Delphi, Fortran и даже Java и Python.
Фирма Microsoft тоже разработала и предлагает использовать для подобных целей мультимедийную библиотеку DirectX. Эта библиотека широко используется в игровых и мультимедийных приложениях, а в научных и технических приложениях распространения не получила. Причина заключатся, скорее всего в том, что DirectX работает только под Windows.
Рендеринг 3D графики — непростое занятие, но крайне интересное и захватывающее. Эта статья для тех, кто только начинает знакомство с OpenGL или для тех кому интересно, как работают графические конвейеры, и что они из себя представляют. В этой статье не будет точных инструкций, как создать OpenGL контекст и окно или как написать своё первое оконное приложение на OpenGL. Связанно это с особенностями каждого языка программирования и выбором библиотеки или фреймворка для работы с OpenGL (Я буду использовать C++ и GLFW) тем более в сети легко найти туториал под интересующий вас язык. Все примеры, приведённые в статье, будут работать и на других языках с немного изменённой семантикой команд, почему это так, расскажу чуть позже.
Визуализация информации
В большинстве научных статей и отчетов не обойтись без визуализации данных. Достойная форма представления данных -- это хорошо структурированная таблица с точными значениями функции в зависимости от некоторых переменных. Но часто более наглядной и эффективной формой визуализации данных является графическая, а, например, при моделировании и обработке изображений -- единственно возможная. Некоторые виды отображения информации различного происхождения перечислены в следующей таблице:
Многие программы для финансовых, научных, технических расчётов используют эти и некоторые другие способы визуализации данных. Визуальное представление информации является прекрасным инструментом при проведении научных исследований, наглядным и веским аргументом в научных статьях и дискуссиях.
Основы программы на OpenGL
- GLUT_RGBA — включает четырехкомпонентный цвет (используется по умолчанию)
- GLUT_RGB — то же, что и GLUT_RGBA
- GLUT_INDEX — включает индексированный цвет
- GLUT_DOUBLE — включает двойной экранный буфер
- GLUT_SINGLE — включает одиночный экранный буфер (по умолчанию)
- GLUT_DEPTH — включает Z-буфер (буфер глубины)
- GLUT_STENCIL — включает трафаретный буфер
- GLUT_ACCUM — включает буфер накопления
- GLUT_ALPHA — включает альфа-смешивание (прозрачность)
- GLUT_MULTISAMPLE — включает мультисемплинг (сглаживание)
- GLUT_STEREO — включает стерео-изображение
- void glutDisplayFunc (void (*func) (void)) — задает функцию рисования изображения
- void glutReshapeFunc (void (*func) (int width, int height)) — задает функцию обработки изменения размеров окна
- void glutVisibilityFunc (void (*func)(int state)) — задает функцию обработки изменения состояния видимости окна
- void glutKeyboardFunc (void (*func)(unsigned char key, int x, int y)) — задает функцию обработки нажатия клавиш клавиатуры (только тех, что генерируют ascii-символы)
- void glutSpecialFunc (void (*func)(int key, int x, int y)) — задает функцию обработки нажатия клавиш клавиатуры (тех, что не генерируют ascii-символы)
- void glutIdleFunc (void (*func) (void)) — задает функцию, вызываемую при отсутствии других событий
- void glutMouseFunc (void (*func) (int button, int state, int x, int y)) — задает функцию, обрабатывающую команды мыши
- void glutMotionFunc (void (*func)(int x, int y)) — задает функцию, обрабатывающую движение курсора мыши, когда зажата какая-либо кнопка мыши
- void glutPassiveMotionFunc (void (*func)(int x, int y)) — задает функцию, обрабатывающую движение курсора мыши, когда не зажато ни одной кнопки мыши
- void glutEntryFunc (void (*func)(int state)) — задает функцию, обрабатывающую движение курсора за пределы окна и его возвращение
- void glutTimerFunc (unsigned int msecs, void (*func)(int value), value) — задает функцию, вызываемую по таймеру
Texture Assembly (сборка текстур)
Для создания реалистичных изображений в OpenGL используются текстуры, которые можно наложить на любой геометрический примитив, приведя в соответствие каждой из его вершин координаты текстуры. Текстура представляет собой растровое изображение, размеры которого для повышени производительности должны быть кратны степени 2. В памяти может храниться несколько вариантов текстуры разного размера, которые выбираются в зависимости от масштаба или степени детализации (mipmapping). Готовая текстура потоянно хранится в памяти и просто копируется непосредственно в буфер кадра.
Fragment Operations (операции над фрагментами)
Перед тем как полученые значения цвета каждого пиксела будут действительно размещены в буфере кадра каждый фрагмент может быть дополнительно обработан. Во-первых, именно на этом этапе присходит наложение текстур из памяти. Затем выполняются операции вырезания, определения прозрачности, наложения шаблонов и масок, логические операции и наложение изображений, моделирование тумана. Только после этого обработанные фрагменты помещаются в активный буфер кадра, пикселам которого присваиваются значения окончательно отображаемого цвета.
Механизм состояний в OpenGL
Для определения атрибутов отображения, включения и выключения различных режимов библиотека OpenGL использует понятие состояния. Если установить какие-либо атрибуты и режимы, то они будут использоваться до тех пор пока не будут отменены или изменены. Атрибуты изменяются специальнами командами (например установка цвета glColor), а управление режимами осущетсвляется с помощью команд glEnable и glDisable, которые вызываются с различными аргументами. Каждый атрибут и режим имеет значение по умолчанию. Текущее значение атрибута моно определить с использованием команд типа glGet. Полезным инструментом при создании сложных изображений является стек атрибутов, в котором можно сохранить значения атрибутов с использованием команды glPushAttrib(), после этого изменять атрибуты формировать примитивы, а затем вернут состояние атрибутов обратно с помощью glPopAttrib().
Rasterization (дискретизация)
В процессе дискретизации из векторных и растровых данных создаются фрагменты. Каждый фрагмент соответствует пикселу буфера кадра. На этом этапе формируются заполненные цветом многоугольники, пунктирные линии заданной ширины, точки заданного размера, применяется сглаживание (antialiasing).
Модель формирования изображения в OpenGL
В библиотеке OpenGL формирование изображения осуществляется на основе объектно-ориентированной модели, в которую входят следующие независимые сущности:
- предметы, которые составляют моделируемое пространство (сцену);
- источник(и) освещения;
- наблюдатель.
Так же как и в реальном мире, предметы существуют и не зависят от того, с какой стороны на них смотрит наблюдатель и каким образом они освещены. Их положение определяется в глобальной системе координат, а свойства (например, цвет, прозрачность, шероховатость) определяются дополнительными атрибутами. Аналогично определяется положение и свойства источников освещения, которые тоже входят в состав сцены, но в отличие от предметов не отражают свет, а излучают его.
Роль наблюдателя может играть любой прибор способный формировать и регистрировать изображение (глаз, камера и т.п.) В библиотеке OpenGL и сама сцена, и её изображение является не реальным, в виртуальным. В качестве наблюдателя используется модель камеры, которая отображает синтезированное изображение в некоторой области (порт вывода) на экране монитора. Угол зрения наблюдателя всегда ограничен, поэтому изображение будет иметь конечные размеры, определённые отсекающей рамкой. Изменяя положение и другие характеристики наблюдателя можно формировать изображение любой части сцены в любом масштабе.
Evaluators (вычислители)
При отображении векторные геометрические примитивы должны быть представлены в виде (иногда достаточно большого) набора вершин. Однако многие полезные и часто используемые кривые и поверхности могут быть описаны гораздо меньшим количеством параметров. Вычислители позволяют по простому описанию геометрического примитива сформировать набор вершин. При этом можно определять количество вершин и тем самым управлять качеством и производительностью визуализации.
Результатом прохождения вычислителя является сцена представленная только в виде простейших геометрических примитивов - вершин.
Объекты в OpenGL
Библиотеки OpenGL написаны на C и имеют многочисленные API к ним для разных языков, но тем не менее это C библиотеки. Множество конструкций из языка С не транслируются в высокоуровневые языки, поэтому OpenGL был разработан с использованием большого количества абстракций, одной из этих абстракций являются объекты.
Объект в OpenGL — это набор опций, который определяет его состояние. Любой объект в OpenGL можно описать его (id) и набором опций, за который он отвечает. Само собой, у каждого типа объектов свои опции и попытка настроить несуществующие опции у объекта приведёт к ошибке. В этом кроется неудобство использования OpenGL: набор опций описывается C подобной структурой идентификатором которого, зачастую, является число, что не позволяет программисту найти ошибку на этапе компиляции, т.к. ошибочный и правильный код семантически неотличимы.
С таким кодом вы будете сталкиваться очень часто, поэтому когда вы привыкнете, что это похоже на настройку конечного автомата, вам станет намного проще. Данный код лишь показывает пример того, как работает OpenGL. В последствии будут представлены реальные примеры.
Но есть и плюсы. Основная фишка этих объектов состоит в том, что мы можем объявлять множество объектов в нашем приложении, задавать их опции и когда бы мы не запускали операции с использованием состояния OpenGL мы можем просто привязать объект с нашими предпочитаемыми настройками. К примеру этом могут быть объекты с данными 3D модели или нечто, что мы хотим на этой модели отрисовать. Владение несколькими объектами позволяет просто переключаться между ними в процессе отрисовки. С таким подходом мы можем сконфигурировать множество объектов нужных для отрисовки и использовать их состояния без потери драгоценного времени между кадрами.
Чтобы начать работать с OpenGL нужно познакомиться с несколькими базовыми объектами без которых мы ничего не сможем вывести на экран. На примере этих объектов мы поймём как связывать данные и исполняемые инструкции в OpenGL.
Расширения
Отличной возможностью OpenGL является поддержка расширений. В то время, когда производители видеокарт представляют новую технологию или новую обширную оптимизацию для отрисовки, в драйверах появляется расширение, относящееся к этому событию. Если аппаратное обеспечение, на котором запущено приложение, поддерживает расширение, значит разработчик может использовать функционал, предоставляемый этим расширением для более продвинутой, или эффективной отрисовки графики. Таким образом графический программист может использовать новые технологии без ожидания их реализация в новых версиях OpenGL, просто проверив поддержку технологии видеокартой. Зачастую, если какое-то расширение пользуется большим спросом, его реализуют как часть следующей версии OpenGL.
Разработчику надо лишь проверить доступность расширения (или использовать библиотеку расширения). Такой подход позволяет программисту выполнять действия более эффективно, основываясь на имеющихся у него расширениях:
C OpenGL 3.3 нам редко будут нужны расширения, но когда будут нужны, необходимые инструкции будут предоставлены.
Конечный автомат
OpenGL по своей сути — это большой конечный автомат: набор переменных, определяющий поведение OpenGL. Под состоянием OpenGL в основном имеется ввиду контекст OpenGL. В процессе использования OpenGL, мы часто изменяем состояния, устанавливая некоторых опции, управляем буферами, а затем отрисовываем, используя текущий контекст.
Когда мы говорим OpenGL, что мы хотим начать отрисовывать, к примеру, линии, вместо треугольников, то мы меняем состояние OpenGL, изменяя опцию, отвечающую за то как OpenGL должен рисовать. После изменения состояния OpenGL, на отрисовку линий, все последующие функции отрисовки будут отрисовывать линии вместо треугольников.
Во время работы с OpenGL мы будем проходить через несколько меняющих состояния функций, которые будут менять контекст, и через несколько меняющий состояния функций, выполняющие действия в зависимости от текущего состояния OpenGL. До тех пор, пока вы держите в голове тот факт, что OpenGL — это большой конечный автомат, большинство функционала будет вам понятна.
Framebuffer (буфер кадра)
Буфер кадра - это область памяти, которая представляет собой пиксельный массив отображаемый на экране. Библиотека OpenGLможет работать сразу с несколькими буферами кадров (пока один буфер отображается другие уже формируются). Это позволяет добиться эффективного формирования и визуализации анимационных кадров.
Пиксельное изображение из буфера кадра может быть считано в системную память, обработано как обычный массив, упаковано, сохранено и снова использовано.
Pixel Operations (операции над пикселами)
Пикселы из массивов, размещённых в системной памяти распаковываются, преобразуются из формата хранения в формат отображения. При необходимости они масштабируются или смещаются. Результат фиксируется и также направляется на этап дискретизации или сохраняется в памяти как текстура.
Объекты
Библиотеки OpenGL написаны на C и имеют множественные ответвления, но в основном это C библиотека. Поскольку большинство конструкций из языка C не транслируется в высокоуровневые языки OpenGL был разработан с использованием большого количества абстракций. Одной из таких абстракций является система объектов в OpenGL.
Объект в OpenGL — это набор опций, которые представляют подмножество состояний OpenGL. К примеру мы можем создать объект, описывающий конфигурацию отрисовки окна; мы можем задать размер, количество цветов и так далее. Такой объект можно представить C-подобной структурой:
Примитивные типы
Заметьте, что при использовании OpenGL рекомендуется использовать примитивы, заданные OpenGL. Вместо использования float записывать его с приставной GL. Тоже самое для int, uint char, bool и так далее. OpenGL определяет разметку памяти для его GL примитивов для обеспечения кроссплатформенности, поскольку некоторые операционные системы могут иметь иную разметку. Использования OpenGL примитивов позволяет добиться полной кроссплатформенности вашего приложения.
Каждый раз, когда мы хотим использовать объекты в основном мы запишем это как-то так:
Этот небольшой участок кода — то, что вы будете часто встречать во время работы с OpenGL. В начале мы создаем объект и сохраняем ссылку на него в виде идентификационного номера (id). (Реальные данные объекта спрятаны в реализации). Затем мы привязываем объект к требуемой части контекста (Расположение целевого объекта окна из примера задано, как `GL_WINDOW_TARGET`). Затем мы устанавливаем значения опций окна и, в конце концов, отвязываем объект, установив id в 0. Значения, установленные нами продолжают храниться в объекте, доступ к которому мы можем получить через objectId и восстановить их снова привязав объект к GL_WINDOW_TARGET.
Данный код лишь показывает пример того, как работает OpenGL. В последствии будут представлены реальные примеры.
Основная фишка этих объектов состоит в том, что мы можем объявлять множество объектов в нашем приложении, задавать их опции и когда бы мы не запускали операции с использованием состояния OpenGL мы можем просто привязать объект с нашими предпочитаемыми настройками. К примеру этом могут быть объекты с данными 3D модели или нечто, что мы хотим на этой модели отрисовать. Владение несколькими объектами позволяет просто переключаться между ними в процессе отрисовки.
Проектирование технических объектов
Проектирование является одной из основных стадий создания изделия в технике. Современные графические системы позволяют наглядно визуализировать проектируемый объект, что способствует скорейшему выявлению и решению многих проблем. Разработчик судит о своей работе не только по цифрам и косвенным параметрам, он видит предмет проектирования на свойм экране. Компьютерные системы позволяют организовать интерактивное взаимодействие с проектируемым объектом и иммитировать изготовление макета из пластичного материала. CAD-системы существенно упрощают и ускоряют работу инженера-конструктора, освобождая его от рутинного процесса черчения.
Вступление
Прежде чем мы начнем наше путешествие нам стоило бы разобраться что такое OpenGL. В основном под OpenGL понимают API (Интерфейс Программирования Приложений), который предоставляет большой набор функций, которые мы можем использовать для управления графикой и изображениями. Но на самом деле OpenGL это скорее спецификация, разработанная и поддерживаемая Khronos Group.
Спецификация OpenGL описывает каким будет результат выполнения каждой конкретной функции и что она должна делать. А уже реализация этих спецификаций лежит на плечах разработчиков. И поскольку спецификация не описывает детали реализации, соответственно имеют право на существование различные реализации OpenGL, по крайней мере пока они соответствуют спецификациям.
Люди, разрабатывающие OpenGL библиотеки, зачастую, являются производителями видеокарт. Каждая видеокарта, которую вы покупаете, поддерживает конкретные версии OpenGL из набора библиотек, разработанных для данной серии видеокарт. При использовании Apple системы, OpenGL библиотеки поддерживаются Apple, под Linux существуют комбинации версий от поставщиков и пользовательских адаптаций этих библиотек. Это также означает, что если используемая вами версия OpenGL показывает странное поведение, значит, с большой вероятностью — это ошибка производителей видеокарт.
Так как большинство реализаций разрабатываются производителями видеокарт, для исправления багов требуется обновить драйвера видеокарты. Это одна из причин, почему почти все уроки рекомендуют обновлять драйвера на видеокарту.
Khronos выложила в публичный доступ все спецификации для всех версий OpenGL. Заинтересовавшийся читатель может найти спецификации OpenGL 3.3 (именно эту версию OpenGL мы будем использовать) здесь. Спецификации отлично показывают правила работы всех функций.
Конвейерная архитектура OpenGL
Какова бы ни была модель формирования изображения, реализованная в библиотеке, в конечном итоге всё равно отображается растровая картинка. Процесс формирования растрового изображения в библиотеке OpenGL имеет конвейерную архитектуру, которая заключается в том, чтобы разбить требуемые действия на независимые операции, выполняемые одновременно в отдельном программном или аппаратном блоке. Именно эта архитектура позволяет добиться высокой эффективности при решении сложных графических задач. Архитектура конвейера OpenGL представлена на рисунке:
В библиотеке OpenGL используется два типа данных:
- векторные данные (вершины, линии, многоугольники);
- растровые данные (пикселы, растровые изображения, битовые массивы).
Простейшим векторным примитивом библиотеки OpenGL является вершина. Это простейший векторный объект, который может быть отображён на экране (в виде точки). Все остальные векторные объекты должны быть представлены в виде набора вершин. Простейшим растровым примитивом библиотеки OpenGL является пиксел. Это простейший растровый объект, который может быть отображен на экране. Все остальные растровые объекты представляют собой массивы пикселов.
Прежде чем отобразиться на экране векторные и растровые данные проходят разные пути по конвейеру визуализации.
Первая программа
Теперь мы знаем основы работы с OpenGL. Можно написать простую программу для закрепления знаний.
Начнем с того, что нужно подключить заголовочный файл GLUT:
Теперь мы уже знаем, что писать в main. Зарегистрируем два обработчика: для рисования содержимого окна и обработки изменения его размеров. Эти два обработчика по сути используются в любой программе, использующей OpenGL и GLUT.
Теперь надо написать функцию-обработчик изменений размеров окна. Зададим область вывода изображения размером со все окно при помощи команды glViewport (х, у, ширина, высота). Затем загрузим матрицу проекции glMatrixMode (GL_PROJECTION), заменим ее единичной glLoadIdentity () и установим ортогональную проекцию. И наконец загрузим модельно-видовую матрицу glMatrixMode (GL_MODELVIEW) и заменим ее единичной.
В итоге получим:
- GL_COLOR_BUFFER_BIT — для очистки буфера цвета
- GL_DEPTH_BUFFER_BIT — для очистки буфера глубины
- GL_ACCUM_BUFFER_BIT — для очистки буфера накопления
- GL_STENCIL_BUFFER_BIT — для очистки трафаретного буфера
Все! Можно компилировать. Должно получиться что-то вроде этого:
Весь код целиком (для кто не осилил статью).
В принципе ничего сложного в этом нет, по крайней мере если вы уже сталкивались с графикой до этого.
Картинка, отображаемая на экране монитора, представляет собой двумерный массив простейших элементов изображения (растр пикселов). Каждый из пикселов отображается на экране в соответствующем положении определённым цветом заданным в системе RGB. Графические библиотеки операционных систем, которые используются в основном для формирования пользовательского интерфейса, предоставляют функции именно для формирования растрового изображения (отображение двумерных графических примитивов, определения свойств карандаша и кисти, заливки цветом областей растра и т.п.) При визуализации, моделировании и проектировании этих возможностей явно недостаточно. В-первую очередь, хотелось бы работать не с целочисленными индексами растра, а с моделью в трёхмерной системе координат. Во-вторых, требуются эффективные операции для реалистического отображения сложных объектов с учетом свойств материалов и освещения. В-третьих, необходимы эффективные операции для организации интерактивного взаимодействия со сценой в целом и с каждым объектом в отдельности. Такие возможности предоставляет нам библиотека OpenGL.
Формирование векторных примитивов
Для формирования графических примитивов используются функции glBegin и glEnd, которые как открывающая и закрывающая скобки заключают в себя список вершин.
//Функция визуализации
void Draw(void)
//Очистка буфера кадра
glClear(GL_COLOR_BUFFER_BIT);
//Установка цвета отображения
glColor3d(1.0, 1.0, 1.0);
//Начать формирование примитива
glBegin(GL_LINES);
//Установка вершин
glVertex2d(.0, .0); // 0
glVertex2d(-1., -1.); // 1
glVertex2d(-1., .0); // 2
glVertex2d(-1., 1.); // 3
glVertex2d(.0, 1.); // 4
glVertex2d(1., 1.); // 5
glVertex2d(1., .0); // 6
glVertex2d(1., -1.); // 7
glVertex2d(.0, -1.); // 8
//Завершить формирование примитива
glEnd();
>
Вершины отображаются тем или иным способом в зависимости от константы заданной в качестве параметра функции glBegin:
Display List (дисплейные списки)
Практически все данные, описывающие векторные или растровые примитивы могут быть сохранены в дисплейном списке и использованы повторно. Это существенно ускоряет процесс формирования сложных растровых примитивов и экономит память.
Что такое OpenGL?
OpenGL — cпецификация, определяющая платформонезависимый программный интерфейс для написания приложений, использующих двумерную и трёхмерную компьютерную графику. OpenGL не является реализацией, а только описывает те наборы инструкций, которые должны быть реализованы, т.е. является API.
Каждая версия OpenGL имеет свою спецификацию, мы будем работать начиная с версии 3.3 и до версии 4.6, т.к. все нововведения с версии 3.3 затрагивают мало значимые для нас аспекты. Перед тем как начать писать своё первое OpenGL приложение, рекомендую узнать какие версии поддерживает ваш драйвер(сделать это можно на сайте вендора вашей видеокарты) и обновить драйвер до последней версии.
Per-Vertex Operations and Primitive Assembly (обработка вершин и сборка примитивов)
На этом этапе над каждой вершиной выполняются операции необходимые для ее последующего отображения: выполняются преобразования системы координат, проецирование координат на двумерную поверхность экрана, определение цвета вершины с учётом свойств материала и освещение, наложение текстур и т.п. Также в зависимости от режима осуществляется сборка примтивов в линии и многоугольники. На этом шаге выполняется отсечение невидимых линий и вершин. Они могут просто не попадать в порт и глубину вывода, а могут закрываться другими примитивами.
Результатом этого этапа является полное описание геометрических примитивов и их свойств, размещённых в пространстве в соответствии с установленной системой координат, и готовое к этапу дискретизации.
Читайте также: