Графика OpenGL


В этом разделе мы научимся создавать трехмерные изображения с помощью функций библиотеки OpenGL, для того чтобы в следующей главе разработать Windows-приложение, которое можно рассматривать как инструмент просмотра результатов научных расчетов. Материал этого раздела позволит вам постепенно войти в курс дела и овладеть очень привлекательной технологией создания и управления трехмерными изображениями. Сначала мы рассмотрим основные возможности библиотеки OpenGL, затем научимся управлять функциями OpenGL на примере простых приложений консольного типа и лишь после этого приступим к разработке Windows-приложения.
  
Обзор возможностей библиотеки OpenGL
Читатель, наверное, знает, что OpenGL это оптимизированная, высокопроизводительная графическая библиотека функций и типов данных для отображения двух-и трехмерной графики. Стандарт OpenGL был утвержден в 1992 г. Он основан на библиотеке IRIS GL, разработанной компанией Silicon Graphics (www.sgi.com). OpenGL поддерживают все платформы. Кроме того, OpenGL поддержана аппа-ратно. Существуют видеокарты с акселераторами и специализированные SD-кар-ты, которые выполняют примитивы OpenGL на аппаратном уровне.
Материал первой части этого урока навеян очень хорошей книгой (доступной в online-варианте) издательства Addison-Wesley «OpenGL Programming Guide, The Official Guide to Learning OpenGL». Если читатель владеет английским языком, то мы рекомендуем ее прочесть.
  
Подключаемые библиотеки
Microsoft-реализация OpenGL включает полный набор команд OpenGL, то есть глобальных функций, входящих в ядро библиотеки OPENGL32.LIB и имеющих префикс gl (например, glLineWidth). Заметьте, что функции из ядра библиотеки имеют множество версий, что позволяет задать желаемый параметр или настройку любым удобным вам способом. Посмотрите справку по функциям из семейства glColor*. Оказывается, что задать текущий цвет можно 32 способами. Например, функция:
void glColorSb(GLbyte red, GLbyte green, GLbyte blue);
определяет цвет тремя компонентами типа GLbyte, а функция
void glColor4dv(const GLdouble *v) ;
задает его с помощью адреса массива из четырех компонентов.
С учетом этих вариантов ядро библиотеки содержит более 300 команд. Кроме того, вы можете подключить библиотеку утилит GLU32.LIB, которые дополняют основное ядро. Здесь есть функции управления текстурами, преобразованием координат, генерацией сфер, цилиндров и дисков, сплайновых аппроксимаций кривых и поверхностей (NURBS — Non-Uniform Rational B-Spline), а также обработки ошибок. Еще одна, дополнительная (auxiliary) библиотека GLAUX.LIB позволяет простым способом создавать Windows-окна, изображать некоторые SD-объекты, обрабатывать события ввода и управлять фоновым процессом. К сожалению, эта библиотека не документирована. Компания Microsoft не рекомендует пользоваться ею для разработки коммерческих проектов, так как она содержит код цикла обработки сообщений, в который невозможно вставить обработку других произвольных сообщений.
Примечание
Тип GLbyte эквивалентен типу signed char, a GLdouble — типу double. Свои собственные типы используются в целях упрощения переносимости на другие платформы. Список типов OpenGL мы приведем ниже. Четвертый компонент цвета определяет прозрачность цвета, то есть способ смешивания цвета фона с цветом изображения. Некоторые команды OpenGL имеют в конце символ v, который указывает, что ее аргументом должен быть адрес массива (вектора). Вектор в математике — это последовательность чисел (координат), единственным образом задающих элемент векторного пространства. Многие команды имеют несколько версий, позволяя в конечном счете задать вектор разными способами.
Около двадцати Windows GDI-функций создано специально для работы с OpenGL. Большая часть из них имеет префикс wgl (аббревиатура от Windows GL). Эти функции являются аналогами функций с префиксом glx, которые подключают OpenGL к платформе X window System. Наконец, существует несколько Win32-функций для управления форматом пикселов и двойной буферизацией. Они применимы только для специализированных окон OpenGL.
  
Ограничения Microsoft
К сожалению, Microsoft-реализация OpenGL имеет ряд ограничений, которые не дают в полной мере использовать всю мощь библиотек. Перечислим те из них, которые приведены в документации MSDN.

  • Нет поддержки более новой и популярной библиотеки GLUT, которая в некотором роде аналогична библиотеке GLAUX. Эта проблема решается с помощью Интернет. Вы достаете glut32.dll, glut32.tib и glut.h, которые затем помещаете в следующие папки: WINNT\System32,...\VC7\Lib\H...VC7\Inctude\GL После этого следует указать компоновщику на необходимость подключения glut32.lib. Это делается вместе с подключением других двух библиотек opengl32.lib и glu32.lib (см. ниже).
  • Изображение OpenGL можно вывести на печать только с помощью метафайлов (списка рисующих команд GDI). При этом надо учитывать специфику, описанную в документации.
  • Нет поддержки стереоскопических изображений.
  • OpenGL и GDI-графику можно совмещать только в окне с одинарной буферизацией.
  • Windows имеет одну системную цветовую палитру, которая применяется ко всему экрану, поэтому окно OpenGL не может иметь собственной аппаратной палитры, но может иметь собственную логическую палитру.
  • Окно OpenGL не поддерживает динамический обмен данными (DDE), обмен с помощью механизма Clipboard и OLE. Однако существуют обходные пути для использования операций с Clipboard
  • Библиотеки классов, такие как Volumizer и Open Inventor, которые обеспечивают более высокий уровень конструирования 3-D графики, не включены в состав Microsoft-реализации OpenGL. Это, на мой взгляд, является очень серьезным недостатком.

  
Примитивы OpenGL
Моделью или объектом в OpenGL называется структура в памяти, конструируемая из геометрических примитивов: точек, линий и полигонов, которые, в свою очередь, задаются своими вершинами (vertices). Из этих моделей OpenGL создает изображение в специально подготовленном окне. Процесс создания и демонстрации изображения называется передачей (rendering) изображения OpenGL. Конечным изображением является множество пикселов — мельчайших видимых элементов экранной поверхности. Информация о цвете пикселов размещена в памяти в виде битовых плоскостей (bitplanes). Так называется область памяти, которая содержит только один бит информации обо всех пикселах окна. В совокупности плоскости составляют буфер кадра (framebuffer), который содержит информацию, необходимую для того, чтобы дисплей отобразил все пикселы окна OpenGL.
OpenGL изображает графические примитивы (точки, сегменты линий или многоугольники), используя при этом множество независимо управляемых режимов (modes). Для задания примитивов, установки режимов и выполнения других операций необходимо вызывать функции OpenGL, или, как принято говорить, давать последовательность команд OpenGL Примитивы задаются своими вершинами, то есть точками трехмерного пространства. Кроме координат с каждой вершиной ассоциируются такие данные, как цвет, направление нормали (перпендикуляра), параметры текстуры и флаги границы (edge flags). Текстурами называются готовые bitmap-изображения, которые накладываются на многоугольники каркаса модели и вносят в нее эффект поверхности реального материала.
  
OpenGL — автомат с конечным числом состояний
OpenGL работает по принципу конечного автомата, то есть автомата, который в каждый момент времени находится в одном из состояний, принадлежащих конечному множеству допустимых значений. В документации вы можете встретить в применении к OpenGL термины state machine (конечный автомат) и assembly line (конвейер). Некоторые команды (вызовы функций OpenGL) переводят автомат в различные состояния или режимы, которые остаются неизменными до тех пор, пока не придет следующая команда изменения состояния. Текущий цвет, как вы видели, является одним из состояний. Другими состояниями являются:

  • узор линий или полигонов (stipple patterns);
  • тип проективных или обзорных преобразований (projection and viewing transformations);
  • режимы рисования полигонов;
  • режимы упаковки пикселов;
  • расположение источников света и свойства материалов.

Многие переменные, определяющие состояния, переключаются с помощью функций glEnable (включить) или gioisable (выключить). Каждая переменная состояния или режим имеет значение по умолчанию, и в любой точке программы вы можете узнать текущее состояние. Обычно для этой цели используется одна из 6-ТИ команд: glGetBooleanv, glGetDoublev, glGetFloatv, glGetlntegerv, glGetPointerv или glisEnabled. Выбор зависит от типа данных, которые задают состояние. Некоторые переменные состояния (state variables) заполняются более специфичными командами, например: glGetLight*, glGetError, glGetPolygonStipple. Множество состояний можно сохранить в стеке атрибутов командами glPushAttrib или glPushClientAttrib. Обычно так делают для того, чтобы временно изменить что-то, а затем восстановить состояния с помощью одной из команд: glPopAttrib, glPopClientAttrib.
  
Конвейер передачи OpenGL
Команды OpenGL претерпевают одинаковый порядок обработки, проходя через последовательность стадий, называемых конвейером обработки OpenGL (processing or rendering pipeline). Схема конвейера приводится во многих источниках, приведем ее и мы (рис. 6.1), для того чтобы не отсылать читателя к другим книгам. Ниже следует краткое описание его основных блоков.
Списки команд OpenGL (Display Lists)
Все данные, описывающие геометрию или отдельные пикселы, могут быть сохранены в списках команд (display lists) для последующего использования. Альтернатива — немедленное использование (immediate mode). При вызове списка командой glCallList сохраненные данные из списка начинают двигаться по конвейеру так же, как и в режиме немедленного использования.
Вычислители (Evaluators)
Все геометрические примитивы описываются своими вершинами. Параметрические кривые и поверхности могут изначально-быть описаны контрольными точками или базовыми функциями (обычно полиномиальными). Вычислители — это методы, которые генерируют координаты вершин, нормали к поверхности, координаты текстур и цвета точек, опираясь на контрольные точки.
Сборка примитивов
На этом этапе происходит преобразование вершин в примитивы. Пространственные координаты (х, у, z) преобразовываются с помощью матриц размерностью (4 х 4). Основная цель — получить экранные, двухмерные координаты из трехмерных, мировых координат. Если включен режим генерации текстуры, то она создается на этом этапе. Освещенность вычисляется исходя из координат вектора нормали, расположения источников света, отражающих свойств материала, углов конусов света и параметров его аттенюации (ослабления). В результате получается цвет пиксела. Важным моментом на этапе сборки примитивов (primitive assembly) является отсечение (clipping), то есть удаление тех частей геометрии, которые попадают в невидимую часть пространства. Точечное отсечение пропускает или не пропускает вершину. Отсечение линий или полигонов подразумевает не только удаление вершин, но и возможное добавление некоторых (промежуточных) вершин. На этом этапе происходит учет перспективы, то есть уменьшение тех деталей сцены, которые расположены дальше от точки наблюдения, и увеличение тех деталей, которые расположены ближе. Здесь используется понятие видимого объема (viewport). Режим заполнения промежуточных точек полигона тоже играет роль на этапе сборки.
Операции с пикселами (Pixel Operations)
Данные о пикселах следуют в конвейере OpenGL параллельным путем. Данные, хранимые в массивах системной памяти, распаковываются с учетом набора возможных форматов, затем масштабируются, сдвигаются и обрабатываются так называемой картой пикселов (pixel map). Результат записывается либо в память текстуры, либо посылается на следующий этап — растеризацию. Отметьте, что возможна обратная операция считывания пикселов. При этом также Выполняются операции: масштабирование, сдвиг, преобразование и упаковка и помещение в системную память. Существуют специальные операции копирования данных из буфера кадра (framebuffer) в другую его часть или в буфер текстуры.
Сборка текстуры (Texture Assembly)
Текстуры — это bitmap-изображения, накладываемые на поверхности геометрических объектов для придания эффекта фактуры реального материала. Текстурные объекты создаются в OpenGL для упрощения их повторного использования. Использование текстур сопряжено с большими затратами, поэтому в работе с ними применяют специальные ресурсы, такие как texture memory. Так называют быструю видеопамять, приоритет использования которой отдается текстурным объектам.
Растеризация
Так называют преобразование как геометрических, так и данных о пикселах во фрагменты. Каждый фрагмент соответствует пикселу в буфере кадра. При вычислении цвета фрагмента учитывается большое количество факторов: узор штриховки полигона или линии, толщина линии и размер точки, сглаживание зубчатости линий, тень объекта, режим заполнения полигона, учет глубины изображения (факт видимости или невидимости) и др.
Операции с фрагментами
Каждая точка уже двухмерного изображения характеризуется цветом, глубиной (значением координаты Z) и данными о текстуре. Такая точка вместе с сопутствующей информацией называется фрагментом. Фрагмент изменяет соответствующий ему пиксел в буфере кадра, если он проходит пять тестов:

  • Pixel ownership-тест, который проверяет принадлежность контексту, то есть не закрыт ли фрагмент другим окном;
  • Scissor-тест, который проверяет принадлежность вырезаемому прямоугольнику, который задается функцией glScissor;
  • Alpha-тест, который проверяет четвертый компонент цвета — прозрачность фрагмента с помощью функции glAlphaFunc;
  • Stencil-тест, используемый при создании специальных эффектов. Он, например, проверяет, не попал ли фрагмент в промежуток регулярного узора;
  • Depth-buffer-тест, который проверяет, не закрыт ли фрагмент другим фрагментом с меньшей координатой Z.

Кроме того, фрагмент претерпевает другие изменения.

  • текстурирование — это генерация текстурного элемента (texel) на основе texture memory;
  • вычисление дымки (fog);
  • смешивание (blending);
  • интерполяция цвета (dithering);
  • логические операции;
  • маскирование с помощью трафарета (bitmask).

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

  1. Основная линия конвейера осуществляет преобразование по схеме: Вершины > Примитивы * Фрагменты > Пикселы.
  2. Параллельная линия обработки исходных данных задает непосредственно пикселы.
  3. Примитивы, заданные в трехмерном пространстве, преобразуются в двухмерное изображение с помощью растеризации.
  4. Каждая точка уже двухмерного изображения характеризуется цветом, глубиной (значением координаты Z) и данными о текстуре. Такая точка вместе с сопутствующей информацией называется фрагментом.
  5. Фрагмент изменяет соответствующий ему пиксел в буфере кадра, если он проходит пять тестов.
  6. Каждая вершина вместе с характеризующими ее данными обрабатывается конвейером OpenGL независимо и последовательно. Это означает, что каждый примитив будет полностью изображен до того, как выполнится следующая команда.

Более подробную функциональную схему конвейера вы можете увидеть в разделе MSDN: Platform SDK/OpenGL/Overview/Introduction to OpenGL/OpenGL Processing Pipeline.
  
Анимация
На примере многочисленных хранителей экрана (screen-saver) вы видели, как гладко работает анимация в OpenGL. OpenGL использует два буфера памяти (front and back). Первый (front-буфер) отображается на экране, второй в это время может обрабатываться процессором. Когда обработка закончится, то есть очередная сцена будет готова, вы можете произвести быстрое переключение буферов (swap), обеспечивая тем самым гладкую анимацию изображения. При обмене копирование массивов не происходит, изменяется лишь значение указателя (адреса) отображаемого блока памяти. Отметьте, что процесс рисования в back-буфер происходит быстрее, чем в front, так как большинство видеокарт запрещают редактировать изображение в момент вертикальной развертки, а это происходит 60-90 раз в секунду.
Рассмотрим основную схему алгоритма анимации, используемого в OpenGL-при-ложениях. В кино эффект движения достигается тем, что каждый кадр проецируется на экран в течение короткого промежутка времени, затем шторка проектора моментально закрывается, пленка продвигается на один кадр, вновь открывается шторка и цикл повторяется. Период цикла равен 1/24 с или даже 1/48 с в современных кинопроекторах. Современные компьютеры допускают смену кадра (refresh rate) до 120 раз в секунду. Рассмотренный алгоритм можно записать так в цикле по всем кадрам:

  1. сотри старое изображение;
  2. создай новое изображение;
  3. задержи изображение на какое-то время.

Если реализовать анимацию по такой схеме, то эффект будет тем более удручающим, чем ближе к 1/24 с подходит время создания изображения, так как полное изображение существует на экране лишь долю периода. Большую часть периода мы видим процесс рисования.
OpenGL предоставляет возможность двойной буферизации (аппаратной или программной — зависит от видеокарты). Алгоритм анимации в этом случае таков: пока проецируется первый кадр, создается второй. Переключение кадров происходит только после того, как закончится формирование второго кадра. Пользователь никогда не видит незавершенный кадр. Эту схему можно представить в виде проектора с двумя кадрами пленки. В момент демонстрации первого второй стирается и вновь рисуется. Новый алгоритм можно записать в цикле по кадрам:

  1. Сотри старое изображение в back-буфере.
  2. Создай в нем новое изображение.
  3. Переключи буферы (front-back).

Последний шаг алгоритма не начнет выполняться, пока не закончится предыдущий шаг — создание нового кадра в back-буфере. Ожидание этого события (конец рисования в невидимый буфер) дополняется ожиданием завершения цикла прямого хода развертки экрана. Поэтому самая большая частота смены изображений равна текущему значению частоты кадров дисплея. Допустим, что эта частота равна 60 Гц, тогда частота смены изображений будет 60 fps (frames per second — кадров в секунду). Если время рисования занимает немногим более 1/60 с (пусть 1/45 с), то один и тот же кадр будет проецироваться два такта цикла развертки и частота смены изображений реально будет 30 fps. Промежуток времени между 1/30 с и 1/45 с процессор простаивает (is idle). Если время подготовки невидимого кадра нестабильно (плавает), то частота смены изображений может измениться скачком, что воспринимается как неприятная помеха. Для сглаживания этого эффекта иногда искусственно добавляют небольшую задержку, с тем чтобы немного снизить частоту, но сделать ее стабильной. Отметьте, что OpenGL не имеет команды переключения буферов, так как такая команда всегда зависит от платформы. Мы будем пользоваться функцией SwapBuf f ers(HDC hdc), входящей в состав Windows API.
  
Другие функции OpenGL
Другие функции OpenGL позволяют размещать объекты на трехмерной сцене, выбирать точку размещения глаза наблюдателя (камеру), передвигать эту точку. Неотъемлемой частью трехмерной графики является освещение материалов. Конвейер OpenGL использует специальные алгоритмы подсчета цвета любого фрагмента с учетом заданных свойств материала и источников света. Моделирование атмосферных эффектов (тумана, дыма, дымки) делает изображения более реалистичными. Функции моделирования тумана, дыма, загрязнений или просто эффекта присутствия воздуха можно найти в справочной системе по ключевому слову Fog.
Механизм anti-aliasing сглаживает неровные края линий, отображаемых на компьютерном дисплее при низком графическом разрешении. Anti-aliasing является стандартной техникой в компьютерной графике и заключается в изменении цвета точек вблизи границ изломов. Техника Gouraud-тени сглаживает тень трехмерного объекта для достижения тонких различий цветов на специфической поверхности.
Четвертая составляющая цветового кода RGBA носит название alpha. Alpha-смешивание позволяет комбинировать цвет обрабатываемого фрагмента с цветом точек, которые уже хранятся в буфере, моделируя тем самым прозрачность воздуха, стекла или другого материала. Этот эффект используют при демонстрации распределения поля внутри замкнутого объема. Достаточно дать пользователю возможность управлять степенью прозрачности границ, и он сможет рассматривать результаты вычислений (например, поверхности равного уровня искомого поля) внутри трехмерных объектов.
OpenGL не предоставляет разработчику команд для описания сложных моделей. В ней есть примитивные геометрические объекты: точки, линии и многоугольники. Разработчик должен сам сконструировать модели, основываясь на этих нескольких простых примитивах. Но есть библиотеки, например классы Open Inventor, которые реализуют более сложные модели. Разработчик может использовать эти библиотеки для построения своих собственных.
Конвейер OpenGL реализует процедурный, а не описательный подход к созданию изображения. Он выполняет функцию сервера, который обслуживает клиента — приложение, генерирующее последовательность команд. Коды сервера могут выполняться на другом компьютере, сервер может одновременно поддерживать несколько контекстов передачи OpenGL, а клиент может подключаться к любому из них. Однако система Windows полностью контролирует память, отводимую под буфер кадра (frame buffer). OpenGL может вступить в игру только после того, как Windows создаст и подготовит окно для передачи (rendering) изображения OpenGL.

Контекст передачи изображения


Окно OpenGL имеет свой собственный формат пикселов. Необходимым условием ее работы является установка pixel-формата экранной поверхности в контексте устройства HDC, а следовательно, и в окне, с которым он связан. Формат устанавливается один раз, повторная установка недопустима, так как может привести к сбоям в работе подсистемы управления окнами (Windows Manager). После установки формата (вызов SetPixelFormat) следует создать контекст передачи изображения OpenGL, описатель которой имеет тип HGLRC. Контекст передачи (rendering context) создается функцией wglCreateContext с учетом выбранного формата пикселов. Контекст передачи изображения — это связь OpenGL с Windows. Создание этого контекста требует, чтобы обычный контекст существовал и был явно указан в параметре wglCreateContext. Контекст HGLRC использует тот же формат пикселов, что и HDC.
Несмотря на сходство, эти контексты различны. HDC содержит информацию относящуюся к функциям GDI, a HGLRC — к функциям OpenGL. Поток, вызывающий функции OpenGL, должен предварительно объявить контекст передачи текущим (current). Иначе вызовы не будут иметь эффекта. Уничтожать контекст передачи надо после отсоединения его от потока. Несколько контекстов передачи могут одновременно рисовать в окне OpenGL, но только один из них (тот, который ассоциирован с HDC) может быть текущим или активным в потоке.
Для описания формата пикселов экранной поверхности в OpenGL используется структура PIXELFORMATDESCRIPTOR. Прежде всего pixel format — это описание цветового режима, действующего в данном окне. Например, если видеокарта может работать в режиме передачи 256 цветов, то для кодирования цвета каждого пиксела в этом режиме необходимо иметь 8 бит памяти. В этом случае говорят о 8-битовой глубине поверхности рисования или окна OpenGL. Иногда в таком случае говорят о 8-битовой глубине цвета. Существуют режимы с 15-битовой глубиной (32 768 цветов), 16-битовой (65 536 цветов), 24-битовой (16 миллионов цветов). Выбор формата зависит от возможностей карты и намерений разработчика. Кроме глубины цвета к pixel-формату относятся такие настройки, как:

  • тип буферизации (одинарная или двойная);
  • схема образования цвета (RGBА или на основе палитры);
  • количество бит для буфера глубины, то есть буфера Z-координат изображения (ось Z считается направленной из глубины экрана к наблюдателю);
  • поддержка регулировки прозрачностью (alfa) и др.

Вы можете выбрать один из более чем 20 готовых pixel-форматов или задать произвольную комбинацию параметров и попросить найти ближайшую возможную ее реализацию. Microsoft GDI-реализация OpenGL вносит свои коррективы в возможные варианты реализации pixel-формата, а аппаратная поддержка меняется в зависимости от производителя и может значительно расширить его возможности. Каждое окно OpenGL должно иметь свой собственный pixel-формат.
По умолчанию Windows не вырезает (в смысле перерисовки) дочерние окна из клиентской области родительского окна, поэтому при создании окнам OpenGL следует задать бит стиля WS_CLIPCHILDREN. В этом случае система не позволяет рисовать родительскому GDI-окну в пределах дочернего окна OpenGL. Несколько окон OpenGL, каждое со своим форматом, могут быть отображены одновременно, поэтому необходимо установить еще один бит стиля WS_CLIPSIBLINGS, чтобы предотвратить рисование в окне соседа. Для окон OpenGL недопустим стиль CS_PARENTDC (CM. MSDN).
  
Подготовка окна
Подготовку контекста передачи OpenGL надо рассматривать как некий обязательный ритуал, в котором порядок действий давно определен и без которого нельзя начинать творческую работу по созданию сцены OpenGL. Стоит где-то промахнуться, и вы увидите молчаливый белый экран. Сначала надо подготовить окно так, чтобы вызовы функций OpenGL начали работать. В этой процедуре выделяют следующие шаги:

  • установка стиля окна;
  • обработка сообщения WM_ERASEBACKGROUND и отказ от стирания фона;
  • установка pixel-формата;
  • создание контекста устройства (НОС) и контекста передачи (HGLRC);
  • специфическая обработка сообщения WM_SIZE;
  • обработка сообщения WM_PAINT;
  • освобождение контекстов при закрытии окна.

Чтобы использовать функции библиотеки OpenGL в вашем приложении, надо убедиться, что в системном каталоге Windows присутствуют модули OpenGL32.dll и GLU32.dll. Они должны быть там, так как компания Silicon Graphics (авторы пакета OpenGL) постаралась, чтобы поддержка OpenGL на платформе Windows была максимально доступна и достаточно надежна. Однако хочу предупредить, что я встречал системы, в которых контекст передачи (rendering context) OpenGL работает ненадежно — появляются пятна пробелов и задержка перерисовки, если работа идет не в полноэкранном режиме. Если это есть, то должно проявляться при запуске любой программы, использующей OpenGL. Причина, видимо, в драйвере видеопамяти.
Типы данных
OpenGL использует свои собственные типы данных, которые должны соответствовать аналогичным типам той платформы, на которой библиотека установлена. В Microsoft-реализации соответствие типов задано в файле заголовков GL.H так, как показано ниже. Эта таблица понадобится вам при анализе примеров и при разработке собственного кода:
typedef unsigned int GLenum;
typedef unsigned char GLboolean;
typedef unsigned int GLbitfield;
typedef signed char GLbyte;
typedef short GLshort;
typedef int GLint;
typedef int GLsizei;
typedef unsigned char GLubyte;
typedef unsigned short GLushort;
typedef unsigned int GLuint;
typedef float GLfloat;
typedef float GLclampf;
typedef double GLdouble;
typedef double GLclampd;
typedef void GLvoid;
  
Создание консольного проекта
Для исследования возможностей функций библиотек OpenGL целесообразно создать простой проект консольного типа, в котором для работы с другим (Windows) окном будут использованы функции дополнительной библиотеки OpenGL, описанной в файле GLAUX.LIB. Рассмотрим последовательность шагов для создания нового проекта консольного типа.

  1. На странице VS Home Page выберите команду Create New Project и в окне появившегося диалога New Project выберите тип проекта Visual C++ Projects, а в качестве шаблона (в поле Templates) — Managed C++ Empty Project.
  2. Задайте имя проекта Console, желаемое местоположение папки с проектом и нажмите ОК.
  3. Поставьте фокус на элемент Console в окне Solution Explorer, вызовите контекстное меню и выберите команду Add > Add New Item.
  4. В окне диалога Add New Item перейдите в список Templates и выберите строку C++File(.cpp).
  5. В поле Name того же диалога задайте имя файла OG.cpp и нажмите кнопку Open.

Далее вы будете вводить код в окно редактора Studio.Net (вкладка OG.cpp). Для того чтобы компоновщик подключил все библиотеки OpenGL, произведите настройку проекта.

  1. Поставьте фокус на элемент Console в окне Solution Explorer и дайте команду Project > Properties или ее эквивалент View t Property Pages.
  2. В окне открывшегося диалога Console Property Pages выберите элемент дерева Linker * Input.
  3. Переведите фокус в поле Additional Inputs окна справа и добавьте в конец существующего текста имена файлов с описаниями трех библиотек: OPENGL32.LIB GLU32.LIB GLAUX.LIB. Убедитесь в том, что все имена разделены пробелами и нажмите ОК.

В новый пустой файл OG.cpp поместите следующий код приложения, которое для создания Windows-окна пользуется услугами библиотеки GLAUX.LIB. Для этого необходимо к проекту консольного типа подключить файл Windows.h1:
#include <Windows.h>
#include <math.h>
//====== Подключение заголовков библиотек OpenGL
#include <GL\gl.h>
# include <GL\glu.h>
#include <GL\Glaux.h>
//=====Макроподстановка для изображения одной линии
#define Line(xl,yl,x2,y2) \
glBegin(GL_LINES); \
glVertex2d ( (xl), (yl)); \
glVertex2d ((x2),(y2)); \
glEnd() ;
//====== Реакция на сообщение WM_PAINT
void _stdcall OnDraw()
{
//====== Стираем буфер кадра (framebuffer)
glClear (GL_COLOR__BUFFER_BIT) ;
//====== Выбираем черный цвет рисования
glColorSf (0., 0., 0.);
//=== В 1-м ряду рисуем 3 линии с разной штриховкой
glEnable (GL_LINE_STIPPLE);
glLineWidth (2.);
glLineStipple (1, 0x0101); // dot
Line (50., 125., 150., 125.);
glLineStipple (1, OxOOFF); // dash
Line (150., 125., 250., 125.);
glLineStipple (1, OxlC47); // dash/dot/dash
Line (250., 125., 350., 125.);
//====== Во 2-м ряду то же, но шире в 6 раз
glLineWidth (6.);
glLineStipple (1, 0x0101); // dot
Line (50., 100., 150., 100.);
glLineStipple (1, OxOOFF); // dash
Line (150., 100., 250., 100.);
glLineStipple (1, OxlC47); // dash/dot/dash
Line (250., 100., 350., 100.);
//== Во 3-м ряду 7 линий являются частями
//== полосы (strip). Учетверенный узор не прерывается
glLineWidth (2.);
glLineStipple (4, OxlC47); // dash/dot/dash
glBegin (GL_LINE_STRIP);
for (int i =1; i < 8; i++)
glVertex2d (50.*i, 75.); glEnd() ;
//== Во 4-м ряду 6 независимых, отдельных линий
//== Тот же узор, но он каждый раз начинается заново
for (i = 1; i < 7; i++)
{
Line (50*1, 50, 50* (i+1), 50);
}
//====== во 5-м ряду 1 линия с тем же узором
glLineStipple (4, OxlC47); // dash/dot/dash
Line (50., 25., 350., 25.);
glDisable (GL_LINE_STIPPLE); glFlush ();
}
//===== Реакция на WM_SIZE
void _stdcall OnSize (int w, int h)
{
glViewport (0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode (GL_PROJECTION); glLoadldentity();
//====== Режим ортографической проекции
gluOrtho2D (0.0, double(w), 0.0, double(h));
}
//====== Настройки
void Init()
{
//====== Цвет фона - белый
glClearColor (1., 1., 1., 0.);
//====== Нет интерполяции цвета при растеризации
glShadeModel (GL_FLAT); }
void main()
{
//=== Установка pixel-формата и подготовка окна OpenGL
auxInitDisplayMode (AUX_SINGLE | AUX_RGB);
auxInitPosition (200, 200, 550, 250);
auxInitWindow("My Stipple Test");
Init() ;
auxReshapeFunc (OnSize);
// Кого вызывать при WM_SIZE auxMainLoop(OnDraw);
// Кого вызывать при WM_PAINT
}
Функция main содержит стандартную последовательность действий, которые производятся во всех консольных приложениях OpenGL. С ней надо работать как с шаблоном приложений рассматриваемого типа. Первые три строчки функции main устанавливают pixel-формат окна OpenGL. Заботу о его выборе взяла на себя функция auxInitDisplayMode из вспомогательной библиотеки. В параметре мы указали режим использования только одного (front) буфера (бит AUX_SINGLE) и цветовую схему без использования палитры (бит AUX_RGB).
В функции init обычно производят индивидуальные настройки конечного автомата OpenGL. Здесь мы установили белый цвет в качестве цвета стирания или фона окна и режим заполнения внутренних точек полигонов. Константа GL_FLAT соответствует отсутствию интерполяции цветов. Вызов функции auxReshapeFunc выполняет ту же роль, что и макрос ON_WM_SIZE в MFC-приложении. Происходит связывание сообщения Windows с функцией его обработки. Все функции обработки должны иметь тип void _stdcall. Вы можете встретить и эквивалентное описание этого типа (void CALLBACK). Имена функций OnDraw и OnSize выбраны намеренно, чтобы напомнить о Windows и MFC. В общем случае они могут быть произвольными. Важно запомнить, что последним в функции main должен быть вызов auxMainLoop.
В OnSize производится вызов функции glviewport, которая задает так называемый прямоугольник просмотра. Мы задали его равным всей клиентской области окна. Конвейер OpenGL использует эти установки так, чтобы поместить изображение в центр окна и растянуть или сжать его пропорционально размерам окна. Аффинные преобразования координат производятся по формулам:
Xw=(X+1)(width/2)+X0
Yw=(Y+1)(height/2)+Y0
В левой части равенств стоят оконные координаты:

  • (X, Y) — это координаты изображаемого объекта. Мы будем задавать их при формировании граничных точек линий командами glvertex2d;
  • (Хо, Yo) — это координаты левого верхнего угла окна. Они задаются первым и вторым параметрами функции glviewport;
  • сомножители в формуле (width и height) соответствуют третьему и четвертому параметрам (w, h) функции glviewport и равны текущим значениям размеров окна.

Как видно из подстановки в формулу, точка с координатами (0,0) попадет в центр окна, а при увеличении ширины или высоты окна (width или height) координаты изображения будут увеличиваться пропорционально. Вызов
glMatrixMode (GL_PROJECTION);

определяет в качестве текущей матрицу проецирования, а вызов glLoadldentity делает ее равной единичной матрице. Следующий за этим вызов
gluOrtho2D (0.0, double(w), 0.0, double(h));
задает в качестве матрицы преобразования матрицу двухмерной ортографической (или параллельной) проекции. Изображение будет отсекаться конвейером OpenGL, если его детали вылезают из границ, заданных параметрами функции gluOrtho2D.
  
Штриховка линий
Основные действия разворачиваются в функции перерисовки. Здесь мы рисуем несколько линий, изменяя узор их штриховки. Режим штриховки линий включается командой glEnable (GL_LINE_STIPPLE). Узор штриховки задается параметрами функции glLineStipple. Первый параметр является коэффициентом повторения, а второй определяет сам узор. Он должен быть 16-битовой константой или переменной, последовательность бит которой определяет последовательность фрагментов при растеризации линии. Порядок использования битов возрастающий, то есть нулевой бит используется первым. Каждому пикселу соответствует один бит, что характеризует ситуацию: текущий цвет либо включен (бит равен 1), либо выключен (бит равен 0). Алгоритм станет очевидным, если вы запустите приложение (Ctrl+F5), устраните возможные ошибки и увидите результат. Обратите внимание на различие штриховки в 3-й и 4-й строках. Попытайтесь объяснить различие. Создайте несколько своих собственных узоров штриховых (stippled) линий.

Штриховка полигонов


Теперь применим штриховку (stipple) к полигонам. Режим штриховки включается и выключается стандартным способом:
glEnable (GL_POLYGON_STIPPLE) ;
glDisable (GL_POLYGON_STIPPLE);
Bitmap-узор (pattern) штриховки надо предварительно подготовить в массиве такой размерности, чтобы заполнить bitmap площадью 32x32 = 1024 пиксела. Размерность массива с узором определяется так: 1024 бита можно разместить в 128 переменных по одному байту. Мы разместим их в 16 строках по 8 байт. Имеем 16 х х 8 х 8 = 1024 бита (или пиксела).
Массивы объявлены глобально. Адреса массивов с узором подаются на вход функции glPolygonStipple:
GLubyte gSpade[] = // Узор - пики
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xlf, 0xff, Oxff, 0xf8, 0xlf, 0x00, 0x00, 0xf8,
0x01, OxcO, 0x03, 0x80, 0x00, 0x70, 0xOe, 0x00,

Функцию OnDraw замените целиком. Так же поступайте и дальше. Следите лишь за изменениями в функциях main, OnSize и init, которые понадобятся при радикальной смене режима передачи (rendering).

Позже мы перейдем к передаче трехмерной графики, а пока режим тот же — двухмерная графика:
void _stdcall OnDraw()
{
//====== Стираем окно
glClear (GL_COLOR_BUFFER_BIT);
//====== Цвет фона (синеватый)
glColor3f (0.3f, 0.3f, 1.);
//== Рисуем сначала unstippled rectangle (без узора)
//== Rect - это тоже полигон
glRectf (20., 20., 115., 120.);
glColor3f (1., 0., 0.); // Меняем цвет на красный
glEnable (GL_POLYGON_STIPPLE); // Включаем штриховку
glPolygonStipple (gStrip); // Задаем узор
glRectf (120., 20., 215., 120.); // Рисуем
glColorSf (O.,0.,0.); // Меняем цвет на черный
glPolygonStipple (gSpade);
// Меняем узор glRectf (220., 20., 315., 120.);
glPolygonStipple (gStrip); // Меняем узор
glColor3f (0., 0.6f, 0.3f);
glRectf (320., 20., 415., 120.);
//== Готовимся заполнить более сложный, невыпуклый
//== (nоn convex) полигон
glPolygonStipple (gSpade);
glColorSd (0.6, O.f, 0.3f);
//======= Шесть вершин по три координаты
float c[6][3] =
{
420.,120.,0.,
420.,70.,0.,
470.,20.,0., 520., 70.,0.,
520.,120.,0.,
470.,100.,0.
};
//== Здесь мы специально выбираем nоn convex полигон,
//== чтобы увидеть как плохо с ним обходится OpenGL
glBegin (GL_POLYGON) ;
for (int i=0; i<6; i++)
glVertex3fv(c[i] ) ;
glEnd() ;
glDisable (GL_POLYGON_STIPPLE) ;
glFlush ();
}
Запустите и убедитесь в том, что последний полигон потерял одну точку. Затем замените цикл задания его вершин на:
for (int i=5; i>=0; i--) glVertex3fv(c[i]) ;
Здесь мы изменили порядок обхода вершин и начали с вогнутой вершины. Запустите и убедитесь в том, что теперь в полигоне есть все шесть вершин. OpenGL не гарантирует точную передачу вогнутых полигонов. Поэтому для надежной передачи их надо предварительно разбивать на выпуклые части. Если этими частями будут треугольники, то процесс разбиения называется tessellation (мозаика). Есть специальные функции для тесселяции полигонов. Их рассмотрение выходит за рамки этой книги. Попробуйте самостоятельно задать рассмотренный выше полигон в виде двух выпуклых четырехугольников. Для этого посмотрите справку по функции glBegin с параметром GL_QUADS.
Полигоны можно рисовать либо закрашенными (режим — GL_FILL), либо в скелетном виде (GL_LINE), либо в виде намеков (GL_POINT). Испробуйте все режимы на примере невыпуклой звезды. При рисовании точками попробуйте предварительно дать команду glPointSize (5):
void _stdcall OnDraw()
{
glClear (GL_COLOR_BUFFER_BIT);
glColor3d (1.,0.4, 1.);
//=== 2 угла, характеризующие звезду и
//=== 2 характерные точки
double pi = 4. * atan(l.),
al = pi / 10., a2 = 3. * al,
xl = costal), yl = sin(al)',
x2 = cos(a2), y2 = sin(a2);
//=== Мировые координаты вершин нормированной звезды
double с[5][3] =
{
0., 1., 0.,
-х2, -у2, 0.,
xl, yl, 0.,
-xl, yl, 0.,
х2, -у2, 0.,
};
//====== Оконные координаты
for (int i=0; i<5; i+t)
{
c[i][0] = 250 + 100*c[i][0];
c[i][l] = 100 + 100*c[i] [1] ;
}
//=== Режим заполнения полигона - скелетный
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//=== Задаем вершины полигона
glBegin(GL_POLYGON);
for (i=0; i<5; i++)
glVertex3dv(c[i] ) ;
glEnd() ;
glFlush() ;
}
  
Как убирать внутренние линии
Каждой вершине по умолчанию присваивается булевский признак (флаг) того, что из нее может выходить видимое ребро (линия). Если надо отменить рисование линии, например скрыть тесселяцию вогнутого полигона, то можно снять флаг ребра командой glEdgeFlag(GL_FALSE); для текущей вершины. Затем можно вновь установить его, когда дело дойдет до ребер, которые должны быть видимы — команда glEdgeFlag(GL_TRUE);. Попробуйте самостоятельно вставить флаги ребер в следующем фрагменте программы так, чтобы скрыть линию соединения двух четырехугольников. Попробуйте затем заменить алгоритм так, чтобы изобразить ту же фигуру в виде одного полигона:
void _stdcall OnDraw()
{
glClear (GL_COLOR_BOFFER_BIT); glColorSd (1., 0.4, 1.);
//====== Вогнутый шестиугольник, но мы зададим его
//====== в виде двух четырехугольников
float с[6][3] =
{
200. 200.,0.,
200. 100.,0.,
250. 20.,0.,
300. 100.,0.,
300. 200.,0.,
250. 100.,0.,
};
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glBegin(GL_QUADS);
glVertex3fv(c[5])
glVertex3fv(c[0])
glVertex3fv(c[l])
glVertex3fv(c[2])
glVertex3fv(c[5])
glVertex3fv(c[2])
glVertex3fv(c[3])
glVertex3fv(c[4])
glEnd();
glFlush ();
}

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика