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

Спроектированный для решения «больших» производственных задач, стандарт OpenGL благодаря своей универсальности, стабильности и кроссплатформности снискал уважение не только у корпоративных пользователей, но и у разработчиков, создающих более приземленные приложения: компьютерные игры, программы представления трехмерных сцен, проигрыватели, системы моделирования, редакторы трехмерной графики. К счастью — и к сожалению — новая сфера приложений накладывает свои требования к базовому программному обеспечению, основным из которых является интенсивное развитие стандарта соответственно бурному совершенствованию смежных областей, в первую очередь графических акселераторов и алгоритмов обработки. Для корпоративных применений, напротив, стандарты должны быть консервативны и стабильны. В результате, производители «тяжелых» решений получили в свое распоряжение отличную библиотеку и несколько универсальных надстроек наподобие Open Inventor и Open Optimiser [1]. А поставщики графических микросхем для ПК и все их коллеги, стремящиеся поспевать за потребительским рынком, самостоятельно создавали расширения OpenGL, которые стали вдвое больше самого стандарта. Противоречия между производителями также не идут OpenGL на пользу. Например, компания nVidia предоставляет разработчикам специальные драйверы и инструментарий, которые расширяют OpenGL, но совместимы только с ее графическими микросхемами. Еще запутанней ситуация с аппаратными технологиями сглаживания каркасных сеток: ATI и nVidia предлагают несовместимые между собой алгоритмы, при этом подход nVidia требует предварительной адаптации каркаса моделей. Аналогичная история произошла и с шейдерами; впрочем, в последнем случае все обошлось простым переименованием версий таким образом, что по версии шейдера теперь можно определять, для чьих микросхем он создавался.

Между тем, Microsoft разработала свой собственный вариант программирования мультимедиа-приложений DirectX. К основным преимуществам этой библиотеки можно отнести легкость создания приложений, работающих с медиа-информацией и более высокую производительность для платформы Windows. Однако DirectX обладает и серьезными недостатками: это отсутствие версий библиотеки для других платформ; не устоявшаяся 3D-часть библиотеки, Direct3D; нежелание корпоративных пользователей переходить на стандарт, который никак не может обрести законченный вид.

Сторонникам OpenGL не остается ничего другого, как под давлением рынка совершенствовать стандарт.

От рабочих станций к ПК и обратно

Анонсировав в 1992 году базовый стандарт, SGI продолжала развитие библиотеки путем создания высокоуровневых надстроек, основной задачей которых было предоставление разработчику средств более быстрого и надежного создания приложений. Со временем они завоевали заслуженное место в корпоративном секторе, но не в области приложений для ПК. Несмотря на то что в Сети можно найти множество как свободных, так и коммерческих реализаций Open Inventor, вряд ли там найдется 3D-редактор, игрушка, «движок» или проигрыватель трехмерных сцен, использующий эту высокоуровневую платформу. А ведь Open Inventor — отличное средство повышения уровня разработки, а значит и сокращения сроков реализации. Что же мешает?

Можно назвать целый ряд серьезных причин: необходимость дополнительной подготовки специалистов; навязываемая модель сцены, которая может быть неоптимальной для некоторых задач; быстрое старение технологий; нерешенные проблемы производительности OpenGL-приложений на платформе Windows.

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

Между тем, значительной экономии за счет повышения уровня разработки можно достичь, обратившись, скажем, к свободной кроссплатформной библиотеке Java 3D. Это прекрасный пример системы, которая позволяет решать весьма сложные задачи с помощью более простого языка программирования и библиотеки, имеющей встроенные средства работы с иерархией сцены, механизмы интерполяции, различные виды анимации, контроль уровнем детализации моделей, удобные средства интерактивного взаимодействия и т.д. Java 3D интегрирована с другими библиотеками, в том числе, Java Sound и Java 2D. Вместе с тем, разработчику нет необходимости изучать такой мощный инструмент, как OpenGL.

Теперь рассмотрим вторую причину — навязываемая модель сцены. Речь идет о надстройках, подобных Open Inventor, которые представляют собой модель описания сцены, имеющую довольно жесткую структуру, собственные механизмы построения и обхода иерархии, отслеживания событий, настройки объектов, взаимодействия с пользователем и т.д. Предоставляя более простой и универсальный интерфейс для создания сцен, Open Inventor одновременно в значительной степени привязывает разработчика к определенным алгоритмам, неэффективным применительно к отдельным задачам и практически всегда недостаточным для интерактивных 3D-программ. Кроме того, многие разработчики, длительное время работающие над мультимедиа-приложениями, сформировали собственные взгляды на построение структуры сцены, наилучшим образом оптимизированной под конкретные изделия.

К этому прибавляются значительные потери производительности приложений OpenGL на платформе Windows. К примеру, популярная игра Quake, реализованная на базе OpenGL, на платформе Linux вдвое быстрее своего Windows-варианта.

В последнее время появляется достаточно много альтернативных решений. Так называемые «движки» (engine), ориентированные на разработчиков и опытных пользователей, имеют в своем арсенале мощные библиотеки и иной инструментарий, позволяющий решать многие задачи без непосредственного кодирования. Производительность и функциональность таких систем значительно выше, чем у их аналогов, построенных на базе высокоуровневых библиотек. Во многих движках предусмотрены: физическое моделирование процессов, скелетная анимация персонажей, средства конвертирования моделей и сцен из 3D-редакторов непосредственно в данные приложения, развитые средства интерактивного взаимодействия пользователя с приложением и другие функции. Бесплатно распространяющиеся вместе со своими исходными кодами Genesis3D и Crystal Space — яркие образцы такого типа решений. Еще одним достоинством «движков» является их независимость не только от операционных систем, но зачастую и от библиотек (тот же Crystal Space способен работать с драйверами OpenGL и DirectX).

Такой подход применительно к некоторым прикладным задачам является более выигрышным, чем использование Open Inventor. С другой стороны, разработчики, заботящиеся о максимальной эффективности, предпочитают работать именно на базовом уровне — с OpenGL или DirectX. К сожалению, в этом случае OpenGL для платформы Windows несколько проигрывает. Так, из-за большего абстрагирования от оборудования и отсутствия заметного прогресса в части взаимодействия с ним, приложения OpenGL заметно отстают по интерактивности от своих аналогов на DirectX.

Об экстенсивном развитии технологий. Рассмотрим, к примеру, моделирование поверхностей. Сложные поверхности с плавными обводами давно уже выстраиваются не «повершинно», а с помощью алгоритмов, основанных на полиномах. Тот же Optimiser предоставляет возможность создания поверхностей путем подбора коэффициентов для различных полиномов: плазовые поверхности; билинейные поверхности Куна; поверхности общего вида, получаемые натягиванием на образующие; эрмитовы сплайновые поверхности; неоднородные рациональные B-сплайновые поверхности (NURBS). У каждой из них есть свои преимущества и недостатки, но главная проблема состоит в том, что при построении даже в меру сложной модели приходится использовать куски нескольких различных полиномов.

Допустим, необходимо создать носовой обтекатель самолета, выполняемый вместе с воздухоприемником. Для этого нужно задать несколько сечений, определяющих форму носового обтекателя и несколько — для задания формы воздухоприемника. Если оба узла описать в одном полиноме (например, NURBS), появятся избыточные описывающие форму управляющих вершин: их число будет равняться не арифметической сумме раздельных объектов, а их произведению. Это связано с особенностью моделей, представленных полиномами, в которых управляющие вершины равномерно распределены по всей поверхности. И если в какой-то части поверхности требуется повысить плотность расположения управляющих вершин (скажем, для моделирования выступа сложной формы), то по параллели и меридиану всей модели будет создано дополнительное количество вершин. «Наведенные» вершины не несут информации о поверхности, а предназначены только для балансировки самого полинома и его алгоритма, т.е. являются вынужденным балластом (рис. 1). При сложных конфигурациях поверхности такой метод построения теряет смысл — число вершин NURBS-поверхности становится сравнимым с числом вершин полигональной сетки. Поэтому практикуется раздельное моделирование каждой детали и использование специальных алгоритмов сопряжения. В результате, программа включает в себя алгоритмы построения поверхностей полиномов, их стыковки, построения переходных поверхностей и симплификации. Как итог — неэффективный и раздутый код.

Рис. 1. Развертка NURBS-поверхности с участком локальной детализации. А — участок локальной детализации, В — наведенные вершины

Но существуют и другие методы построения поверхностей, пришедшие в мир ПК из киноиндустрии. Разработчикам корпоративных приложений есть что перенять у аниматоров. Здесь для создания сложных поверхностей в техническом и органическом моделировании широко используются эффективные механизмы — деление и сглаживание каркасных сеток с малым количеством вершин. Для моделирования поверхности необходимо задать довольно простую объемную геометрическую фигуру, после чего перед рендерингом могут выполняться операции деления и сглаживания. Ранее таким методом не удавалось достичь высококачественного результата поверхности, но компания Newtek, продемонстрировав его возможности в своем продукте LightWave3D, заставила по-новому взглянуть на эту технологию.

Рис. 2. Монолитная модель, построенная методом деления и сглаживания
Рассмотрим построение модели из предыдущего примера методом сглаживания (рис. 2). Как видно, введение локальной детализации не приводит к усложнению основной части модели. Используя этот метод, даже школьники довольно быстро учатся создавать сложные модели транспортных средств и человекоподобных персонажей, применяя для этого пакеты 3DstudioMax, LightWave3D, Maya и т.д.

Еще одно достоинство такого метода состоит в необходимости использования симплификаторов поверхности, что сильно упрощает алгоритм: теперь полное описание модели хранится в наборе вершин, число которых на порядок меньше требуемого для отображения. «Выгоден» такой метод и при пересылке данных по каналам связи, так как абсолютно никакой информации о поверхности не теряется. Таким образом, значительная часть алгоритмов, наработанных для библиотеки Optimiser, вряд ли понадобится большинству разработчиков программ для ПК. С другой стороны, технологии из области развлечений наверняка будут полезны корпоративному сектору.

Итак, в SGI, интенсивно развивая средства повышения уровня разработки для корпоративного рынка, оставили без внимания платформу ПК, что может стать причиной раскола разработчиков программного обеспечения на два лагеря, DirectX и OpenGL.

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

Недавно компания nVidia, еще один крупный игрок рынка графических процессоров, в содружестве с Microsoft обнародовала свой новый язык Cg (от Computer Graphics), компилятор и несколько инструментальных средств к нему, документацию и множество примеров [2]. Этот бесплатный инструментарий призван помочь в создании эффективные 3D-приложения с использованием языка шейдеров нового поколения в средах Windows и Linux, OpenGL и DirectX. Стоит отметить, что спецификация языка шейдеров от nVidia полностью совместима с аналогом в DirectX 9.

OpenGL 2.0

Новая редакция OpenGL является во многом концептуальным шагом. Разработчики стандарта пытаются подогнать платформу не только к современным технологиям, но и создать гибкую платформу для будущих усовершенствований. Поэтому значительные перемены ориентированны на модернизацию внутренней архитектуры API. Но для того чтобы переход на новый стандарт был как можно мягче, разрабатывается сразу два его варианта. Первый описывает интерфейс, позволяющий без модификаций запускать приложения, написанные для OpenGL 1.3, но пользователей заранее уведомляют, что в будущем он поддерживаться не будет. Второй вариант, «чистый» OpenGL 2.0, естественно, является основным рабочим документом.

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

Реализация методов управления памятью и синхронизацией служат для решения других назревших проблем — недостаточной производительности OpenGL [7] и отсутствия средств взаимодействия с мультимедиа-данными других типов. Поддержка звуком и видео координируется с рабочей группой Khronos, развивающей интереснейший проект OpenML. Главной задачей этой библиотеки является создание интегрированной среды для разработки приложений, использующих мультимедиа данные. А от OpenGL 2.0 в целях совместимости требуется соответствие ей по механизмам управления памятью, синхронизации и контроля времени.

Есть в OpenGL 2.0 еще одно заметное нововведение — объекты. В основном это связанно с реструктуризацией механизма управления памяти, в которой объекты играют далеко не последнюю роль. Можно предположить, что такой подход облегчит разработчикам создание приложений.

Шейдеры как основа программируемости

В таких известных компаниях как Pixar и SideFX написание шейдеров является неотъемлемой частью подготовки сцен для фотореалистичного рендеринга. Небольшие программки, написанные на Си-подобном языке, выполняют генерацию текстур и формируют поверхности объектов. Благодаря шейдерам многие процессы создания поверхности объектов и их текстурирования процедурными методами выполняются гораздо технологичней.

Такой подход во многом идет вразрез с последними тенденциями в области моделирования сцен. Рынок уже насыщен мощными интерактивными визуально-контролируемыми средствами построения сцен и моделей, и те, кто привык пользоваться мощными интерактивными средствами проектирования, больше не желают описывать модели и сцены в текстовых файлах. Но, несмотря на это, технология шейдеров находит себе применение во все новых программных продуктах. Для интерактивного применения шейдеров эффективным является путь создания визуальных интерфейсов — принципиальная функциональность задается в виде алгоритма, а настройка параметров осуществляется визуально, с быстрым рендерингом и визуальной оценкой результата. Поэтому вполне естественным является переход аппаратных и программных платформ рендеринга в реальном времени на подобную технологию. Ведь именно реалистичность виртуальных сцен — главное достижение первых графических процессоров с поддержкой шейдеров для ПК от ATI [8]и nVidia.

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

  • Vertex Processor - управление параметрами вершин и их трансформацией;
  • Fragment Processor - растровая обработка;
  • Unpack Processor - распаковка текстур в память графического процессора;
  • Pack Processor - архивирование битовых карт из памяти процессора в память приложения.

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

Несмотря на то что разработчики OpenGL 2.0 [5] определяют четкую границу между программируемостью при помощи шейдеров и фиксированной функциональностью (традиционный подход), наилучших результатов пока можно достичь, используя смешанное программирование 3D-приложений. С другой стороны, язык шейдеров является узкоспециализированным и в OpenGL 2.0 не может полностью заменить собой традиционный подход. Основной причиной этого является принцип работы шейдера, в соответствии с которым все элементы обрабатываются последовательно, по одному.

Заключение

Развитие стандарта OpenGL сегодня угрожающе отстает от потребностей рынка. Лидеры без оглядки на остальных нащупывают свой собственный путь, игнорируя предложения разнообразных рабочих групп, в том числе и тесно связанную с OpenGL 2.0 инициативу OpenML. Как следствие, раскол в сфере мультимедиа-программирования углубляется. Между тем, не нужно забывать, что за поддержку стандарта OpenGL ратует сегодня большая часть производителей, поэтому разработчикам приложений можно посоветовать осваивать новейшие технологии. n

Литература
  1. Евгений Валентинов, Open Inventor как средство разработки интерактивных графических приложений. // Открытые системы, 1997, № 6
  2. Питер Коуэн, nVidia представляет графический язык Cg. // Computerworld Россия, 2002, № 28-29
  3. Виктор Коваленко, OpenGL, что дальше. // Открытые системы, 1998, № 3
  4. Александр Медведев, Технология TRUFORM от ATI: кривая как красивейшее расстояние между двумя точками. // www.ixbt.com/video/ati-truform.shtml
  5. OpenGL 2.0 - Out To Save Programmable Graphics. www6.tomshardware.com/graphic/02q1/020222

Виталий Галактионов (vit3d@mail.ru) — преподаватель Дома научно-технического творчества молодежи (Москва).


Шейдер — программируемый эффект, программа-функция, которая выполняется ускорителем наравне со стандартными («зашитыми») функциями, пишутся при помощи набора микрокоманд, т.е. на языке подобном ассемблеру. В ранних программах рендеринга шейдерами назывались части, которые отвечали за закраску (shading) поверхностей. До появления шейдеров, в ускорителях реализовывался стандартный набор эффектов, возможности которого жестко ограничивали качество графики. Шейдеры разделяются на пиксельные (обрабатывающие пиксели) и вершинные (обрабатывающие вершины треугольников или других примитивов).


Особенности программирования шейдеров

Если не считать спецификации производителей, то в прессе нет описания технологии шейдеров, поэтому имеет смысл рассмотреть ее подробнее. Шейдеры в OpenGL 2.0 представляют собой код на Си-подобном языке, а в программе они описываются в виде массива байт, идентификатор которого является указателем на этот массив: const GLubyte *sampleVertexShader = «тело шейдера».

Используя идентификатор и функции OpenGL 2.0, в исходном коде приложения следует выполнить определенную последовательность операций для подготовки шейдера к запуску:

  1. создать объект типа шейдер:

    Glhandle sampleVS = glCreateShaderObject (GL_VERTEX_SHADER);
  2. выполнить связывание новой ссылки с массивом описания шейдера:

    glAppendShader (sampleVS, sampleVertexShader);
  3. скомпилировать шейдер:

    glCompileShader (sampleVS);
  4. создать специальный объект Programm Object, который служит контейнером шейдеров:

    Glhandle ProgObj = glCreateProgramObject();
  5. связать шейдер с ProgrammObject:

    glAttachShaderObject(ProgObj, sampleVS);
  6. выполнить связывание внутри Programm Object, который может содержать и шейдер Fragment:

    glLinkProgram (ProgObj);
  7. cделать Programm Object текущим:

    glUseProgramObject (ProgObj);

C этого момента шейдером является ProgObj; он и будет выполняться внутри блока glBegin/glEnd. Поэтому, если ему планируется передать параметры, то это нужно сделать после активации шейдера, но до блока glBegin/glEnd.

Существует три вида параметров, передаваемых в шейдер:

  • uniform - импортируются из приложения;
  • varying - экспортируются шейдером Vertex в шейдер Fragment;
  • built-in - встроенные, содержат параметры вершины для шейдера Vertex или фрагмента пикселов для шейдера Fragment.

Если разработчику надо передать некоторые параметры из приложения в шейдер, то ему необходимо воспользоваться функциями glLoadUniformN, где N — тип параметра. Такую операцию можно выполнить одной строкой:

glLoadUniform3f (glGetUniformLocation

(ProgObj, «ambientColor»), 0.1, 0.1, 0.1);

Функция glGetUniformLocation возвращает указатель на расположение в шейдере переменной ambientColor, которая должна быть объявлена при помощи идентификатора uniform: uniform vec3 ambientColor.

Три числа, переданных glLoadUniform3f в качестве параметров, являются значениями переменной типа vec3.

Второй вид переменных шейдеров — varying. Переменные, объявленные посредством такого модификатора, служат для передачи параметров из Vertex в Fragment, (естественно, для каждой вершины по отдельности). Соответственно в Vertex шейдерах они доступны только для записи, а в Fragment — только для чтения. Форма же объявления одинакова для обоих типов шейдеров:

varying vec3 normal.

Проверка совместимости параметров varying в шейдерах Vertex и Fragment осуществляется во время связывания.

Третий вид переменных — встроенные, обновляющие свои значения для каждой новой вершины и, в свою очередь, также делятся на две части, стандартные и определяемые пользователем. Стандартные представляют атрибуты вершин. В OpenGL их определено 22, а с учетом параметров привязки текстур и ряда других набегает все 32. К сожалению, не все графические процессоры допускают использование полного набора переменных; поэтому введено понятие активных переменных, минимальное число которых в OpenGL 2.0 равняется 16. Если в теле шейдера используется больше встроенных параметров, чем допускает графический процессор, компиляцию выполнить не удастся.

Разработчик может определить новые параметры в приложении и задавать их текущие значения в теле блока glBegin/glEnd. В случае шейдера Vertex для этого необходимо объявление нового параметра:

void VertexAttributes{1234}{sfd}v

(uint location, uint count, T *values)
;

В самом шейдере к переменной можно будет обращаться непосредственно по имени.

В программировании трехмерной графики широко используются алгоритмы аффинных преобразований. В связи с этим наряду с типами integer, float, bool при программировании шейдеров в OpenGL 2.0 можно использовать векторы vec(2-4) и квадратные матрицы mat(2-4). Их компонентами являются значения типа float. И к компонентам векторов, и к компонентам матриц разработчик имеет прямой доступ. Большинство операций (умножение, деление, сложение, и т.п.) могут выполняться между данными различного типа. Так, можно трехкомпонентный вектор умножить на матрицу с тремя столбцами, а от матрицы — отнять скалярное значение. На мой взгляд, операции с операндами разных типов в рабочем документе по OpenGL 2.0 описаны недостаточно; остается надеяться, что в будущем этот вопрос будет проработан более детально.

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

В остальном же в распоряжении разработчика шейдеров имеются весьма привычные возможности языка Си: работа с массивами, условные операторы (if-else), циклы (for, while, do-while) возможность определения собственных функций и т.п.