Opengl распределение компонент rgb в 3d пространстве


Содержание

5.2 Визуализация 2D примитивов в OpenGL. Основы.

Визуализация буквы с помощью OpenGL 2D примитивов

Теперь заменим код:

На код, который будет рисовать букву А.

Сейчас все заключается в том, чтобы правильно указать координаты вершин. Буква будет рисовать с помощью 2 кривых (первая кривая выделена красным цветом, вторая кривая — синим).

То, как выглядит буква и координаты соответствующих вершин, вы можете видеть на рисунке 2.
Рисунок 2. 2D координаты точек и последовательность их соединений.
Код рисования буквы А (буква будет рисоваться двумя линиями).

Пример нарисованной в программе буквы – на рисунке 3.
Рисунок 3. Результат работы функции, визуализирующей 2D кривые.
Мы познакомились с основными принципами рисования примитивов в OpenGL.

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

Текстурные форматы в OpenGL 3.3 и выше

Текстуры в OpenGL довольно быстро эволюционировали от простых одно- и двухмерных массивов из нескольких (от 1 до 4) однобайтовых компонент. Сейчас текстуры могут хранить в себе данных самых разных типов и различными способами. Соответственно у текстуры есть как формат, сообщающих об извлекаемых из нее данных, так и внутренний формат, задающий хранение пикселов в памяти GPU и внутреннее представление пикселов.

Полностью все возможные форматы текстур и типы текстур представлены на следующей диаграмме (рис 1.).

Рис 1. Различные типы текстур в современном OpenGL.

В качестве допустимых (внешних) форматов текстур в современном OprnGL возможны следующие значения:

  • Цветовые текстуры — GL_RED, GL_BLUE, GL_GREEN, GL_RG, GL_RGB, GL_RGBA, GL_BGR, GL_BGRA;
  • Цветовые целочисленные — GL_RED_INTEGER, GL_BLUE_INTEGER, GL_GREEN_INTEGER, GL_RG_INTEGER, GL_RGB_INTEGER, GL_RGBA_INTEGER, GL_BGR_INTEGER, GL_BGRA_INTEGER;
  • depth — GL_DEPTH_COMPONENT;
  • depth-stencil — GL_DEPTH_STENCIL.

Внутренний формат уточняет что именно и как именно хранится для данной текстуры. Ниже рассматриваются различные типы текстур и допустимые Внутренний формат уточняет что именно и как именно хранится для данной текстуры. Ниже рассматриваются различные типы текстур и допустимые внутренние форматы для них.

Цветовые (color) форматы

Цветовые форматы обычно делятся на нормализованные, floating-point и целочисленные форматы. Также есть группа специальных форматов, не попадающих в эту классификацию.

Классические текстуры (например с внутренним форматом GL_RGBA8) являются пример так называемых нормализованных текстур — в памяти каждая компонента тексела хранится как целое число из заданного числа бит. При чтении значений из такой текстуры каждое такое целое число автоматически переводится в float из отрезка [0,1] или [-1,1] (для нормализованных текстур со знаком — signed normalized).

Нормализованные текстуры со знаком трактуют каждую компоненту как целое число со знаком (в отличии от обычных нормализованных, считающих целочисленные значения беззнаковыми) и переводят их в отрезок [-1,1]. Общая схема преобразования для любых нормализованных текстур — это линейная функция, переводящая минимальное целое число данного типа в 0 или -1, а максимальное целое число — в +1. OpenGL поддерживает нормализованные текстуры с 8 и 16 битами на компоненту.

Ненормализованные текстуры при чтении возвращают значения того типа, значения которого хранятся для каждого тексела без преобразования типов. При этом значение может быть как целочисленным (знаковым или беззнаковым, 8/16/32 битовым) или же floating-point (16 или 32 бита).

Для обозначения внутренних форматов, соответствующих подобным структурам используют довольно простые соглашение. Константа формата имеет вид GL_[components][bits][type]. Здесь через components обозначено число компонент в текстуре в виде R, RG, RGB или RGBA, через bits число бит на компоненту, а через type тип компоненты в виде I (знаковая целочисленная), UI (беззнаковая целочисленная) и F (floating-point). Если явно не задан тип, то текстура считается беззнаковой нормализованной (например GL_RGBA8), для знаковых нормализованных в конце добавляется _SNORM.

Таким образом, формат для целочисленной (со знаком) текстуры с четырьмя 16-битовыми компонентами имеет вид GL_RGBA16I, двух-компонентной floating-point текстуры с 32-битовыми значениями имеет вид GL_RG32F, однокомпонентной 8-битовой нормализованной текстуры со знаком имеет вид GL_R8_SNORM и т.д.

Кроме этих внутренних форматов текстур также существует еще несколько внутренних форматов, явно не вписывающихся в вышеприведенную схему. Прежде всего к ним относятся так называемые «пакованные» текстуры, когда число бит, отводимых на одну компоненту не кратно 8. Это GL_R3_G3_B2, GL_RGB5_A1 и GL_RGBA10_A2. В каждом из них тексел занимает целое число байт (один байт в первых двух случаях и четыре в последнем), однако сами компоненты являются наборами бит внутри этих байт (так для GL_R3_G3_B2 байт разбит на три группы — 3, 3 и 2 бита). Все эти текстуры являются нормализованными беззнаковыми.

Рис 2. Строение тексела для формата GL_RGBA10_A2.

Рис 3. Строение тексела для формата GL_R3_G3_B2.

Также есть упакованный floating-point-формат — GL_R11F_G11F_B10F. Для этого формата 32 бита, отводимые на тексел разбиты на три группы (11, 11 и 10 бит). Каждая группа представляет собой специальный вид floating-point числа — знаковый бит отсутствует вообще, под мантиссу отводится 6 или 5 бит, еще 5 бит отводится под экспоненту.

Рис 4. Строение компонент для формата GL_R11F_G11F_B10F.

Еще одним экзотическим floating-point форматом является GL_RGB9_E5. Это вариация floating-point формата, только здесь на три отдельно лежащих 9-битовых беззнаковых мантиссы приходится одна общая 5-битовая экспонента. При этом для перевода компонент используются следующие формулы (через r, g, b и e обозначены значения соответствующих полей из тексела как беззнаковые целые числа).

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

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

Последней категорией нестандартных цветовых форматов являются сжатые форматы. Для этих текстур непосредственно в памяти GPU хранятся сжатые данные и на ходу распаковываются. За счет сжатия происходит заметный выигрыш в объеме (и скорости обращения к) памяти. Однако для того, чтобы можно было по адресу тексела быстро получить его распакованное значения приходится использовать специальные блочные форматы. Обычно для таких текстур изображение разбивается на блоки 4×4 тексела и каждый такой блок сжимается в заранее определенное форматом число бит. За счет этого получается обеспечить быстрый доступ к любому текселу текстуры.

Стандартными внутренними форматами для сжатых текстур являются форматы, основанные на сжатии RGTC1/RGCT2 и S3TC (DXT1/DXT3/DXT5). Обратите внимание, что все они являются нормализованными и что в эти текстуры нельзя осуществлять рендеринг. Ниже приводится полный список этих форматов — GL_COMPRESSED_RED_RGTC1, GL_COMPRESSED_SIGNED_RED_RGTC1, GL_COMPRESSED_RG_RGTC2, GL_COMPRESSED_SIGNED_RG_RGTC2, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_COMPRESSED_RGB_S3TC_DXT3_EXT и GL_COMPRESSED_RGB_S3TC_DXT5_EXT.

Форматы для depth-текстур

Здесь существует всего два варианта — нормализованный (значения хранятся в памяти как беззнаковые целые числа с заданным числом бит и при чтении и записи приводятся к отрезку [0,1]) и 32-битовые floating-point.

Соответственно нормализованные внутренние форматы могут быть GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24 и GL_DEPTH_COMPONENT32. Для ненормализованных текстур есть всего один формат — GL_DEPTH_COMPONENT32F.

Комбинированные depth-stencil форматы

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

Поддерживаются всего два таких формата — нормализованный GL_DEPTH24_STENCIL8 и floating-point GL_DEPTH32F_STENCIL8.

Класс TexFormat

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

По этой ссылке можно скачать исходный код класса TexFormat к этой статье.

Copyright © Alexey V. Boreskov 2003-2011

Терминология 3D-графики

2D Graphics

Двумерная графика. Графика, *действие* в которой происходит в одной плоскости. Например, пользовательский интерфейс.

3D Graphics

Трехмерная графика. Визуальное отображение трехмерной сцены или объекта. Для представления трехмерной графики на двумерном устройстве (дисплей) применяют рендеринг (см. Rendering).

3D Pipeline

3D конвейер. Процесс построения 3D-изображения можно разделить на три последовательных этапа. На первом этапе объект преобразуется в мозаичную модель, т.е. происходит его разделение на множество многоугольников (полигонов). Следующий этап включает в себя геометрические преобразования и установки освещения. Наконец, заключительный этап, так называемый «рендеринг» (rendering), который является наиболее важным для качества 3D-изображения, создает двумерное изображение из полученных на предыдущих этапах многоугольников.

Alpha

Коэффициент прозрачности. В описание цвета (RGB) может входить специальный канал, называемый альфа-каналом, который отвечает за прозрачность данного цвета. Т.о., цвет описывается как ARGB.

Alpha Blending (Alpha pixel blending)

Реальный мир состоит из прозрачных, полупрозрачных и непрозрачных объектов. Alpha Blending — это способ передачи информации о прозрачности полупрозрачным объектам. Эффект прозрачности и просвечивания достигается путем смешивания значений цветов исходного и результирующего пикселей. Разделение изображения на многоугольники производится с использованием маски, плотность которой зависит от прозрачности объекта. В результате цвет точки является комбинацией цветов переднего и заднего плана. Обычно, Alpha имеет нормализованное значение от 0 до 1 для каждого цветного пиксела. Новый пиксел = (alpha)(цвет пиксела А) + (1 — alpha)(цвет пиксела В)

Alpha Buffer

Альфа буфер. Дополнительный буфер, в котором содержится информация о прозрачности, таким образом, пиксел имеет четырехзначное представление (RGBA), и в 32-разрядном буфере содержится 24 бита информации о цвете, т.е. 8 бит на каждый из цветов (красный, зеленый и синий), и 8 бит на значение alpha.
См. также Transparency

Ambient

Световой источник, который светит одинаково во всех направлениях. Все объекты освещаются с равной интенсивностью.

Anti-aliasing

Анти-алиасинг. Способ обработки (интерполяции) пикселов для получения более четких краев (границ) изображения (объекта). Наиболее часто используемая техника для создания плавного перехода от цвета линии или края к цвету фона. В некоторых случаях результатом является смазывание (blurring) краев.

без Анти-алиасинга с Анти-алиасингом

Atmospheric Effect

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

Back buffer

Вторичный буфер. Область памяти, в которой рассчитываются объекты трехмерной сцены. Вывод изображения на экран осуществляется через Front Buffer (первичный буфер). Обычно процесс копирования содержимого вторичного буфера синхронизируется с обратным ходом луча ЭЛТ монитора. Таким образом достигается плавная смена кадров.

Bitmap

Способ кодирования изображения пиксел за пикселом.

Bilinear (bi-linear) Filtering

Метод устранения искажений изображения (устранение «блочности» текстур при их увеличении). При медленном вращении или движении объекта (приближение/удаление) могут быть заметны перескакивания пикселов с одного места на другое, т.е. появляется блочность. Для снижения этого эффекта при билинейной фильтрации берется взвешенное среднее значение цвета четырех смежных текстурных пикселов (texels) и в результате определяется цвет текстуры.

BitBLTs

BitBLT = Bit Block Transfer. БитБлет. Наиболее важная функция для ускорения графики в средах, использующих оконный интерфейс GUI (Graphic User Interface). BitBLT — фактически означает просто перемещение блока данных из одного места в другое, которое производится с учетом требований графической памяти. Например, эта функция используется при каждом перемещении окна, таким образом BitBLT — просто передача блока пикселов. Более сложное использование этой функции связано с ситуациями, требующими некоторого преобразования исходных данных, например, когда каждый «одноцветный» бит исходных данных расширяется до «цветного» с использованием цветовых палитр переднего или заднего плана перед тем, как он будет выведен на экран.

Blending

Блендинг. Комбинирование двух или более объектов с использованием некоторого базиса пикселов.

Buffer

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

Bump Texture Mapping

В отличии от texture mapping, технология bump mapping подразумевает использование, как минимум, еще одной (обычно в оттенках серого) текстуры, которая служит в качестве карты для рельефа, который должен проявится при визуализации. Эта технология разработана для придания дополнительной детализации и объемности объектам без изменения их геометрических размеров. В случае если bump map будет не статичной, а анимированной, то можно достичь эффектов визуального изменения геометрии объекта во времени

Chroma Keying

Chroma Keying, или текстурная прозрачность — возможность определять основной цвет в карте текстур и делать его прозрачным в процессе текстурирования изображения. В связи с тем, что не все объекты легко моделируются с использованием многоугольников, сhroma keying используется при включении в сцену сложных объектов в виде карт текстур.

Color

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

Colored lighting

Цветовое освещение. Освещение источниками разного цвета, при этом происходит смешение цвета. Совсем недавно цветовое освещение стало использоваться в новейших 3D играх (Quake2, Unreal, Prey, Half Life).

Computer graphics

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

Depth Cueing

Уменьшение интенсивности освещения текстур при удалении объекта от точки наблюдения.

Directional

Световой источник, который освещает одинаково все объекты сцены, как бы из бесконечности в определенном направлении. Обычно используется для создания удаленных световых источников (таких как Солнце).

Dithering

Способ получения изображения 24-битного качества с использованием 8- или 16-битных буферов. Два цвета используются для моделирования третьего, и обеспечиваются плавные переходы между элементами изображения.

Double Buffering


Двойная буферизация. Представьте себе старый трюк аниматоров: нарисованный на уголках стопки бумаги персонаж мультика со слегка изменяемым положением на каждом следующем листе; затем, пролистав всю стопку, отогнув уголок, мы увидим плавное движение нашего героя. Практически такой же принцип работы имеет и Double Buffering в 3D анимации, т.е. следующее положение персонажа уже нарисовано до того, как текущая страница не пролистана. Без применения двойной буферизации движущееся изображение не будет иметь требуемой плавности, т.е. будет прерывистым. Для двойной буферизации требуется наличие двух областей, зарезервированных в буфере кадров трехмерной графической платы; обе области должны соответствовать размеру изображения, выводимого на экран.
Метод использования двух буферов для получения изображения: один для отображения картинки, другой для рендеринга. В то время, как отображается содержимое одного буфера, в другом происходит рендеринг. Когда очередной кадр обработан, буфера переключаются (меняются местами). Таким образом наблюдатель все время видит отличную картинку. (см. Back Buffer)

Environment Map-Bump Mapping

Технология, являющаяся дальнейшим развитием Bump Mapping. В этом случае, помимо базовой текстуры объекта, применяется еще две текстуры:
1. Текстура, являющаяся отрендеренным вариантом трехмерной сцены вокруг объекта (environment map).
2. Текстура — карта рельефа (bump map).
Самостоятельно и совместно с Procedural Texturing данная технология позволяет получить такие натуральные эффекты, как отражение, отражение в кривом зеркале, дрожжание поверхностей, искажение изображения, вызываемое водой и теплым воздухом, трансформация искажений по шумовым алгоритмам, имитация туч на небе и др.

Flat Shading (Flat)

Метод затенения, называемый также постоянным затенением. Поверхность объекта, построенного с использованием этого метода, получается наиболее низкого качества, и изображение выглядит как бы поделенным на блоки. Flat Shading даёт (более) худший результат, чем, допустим, метод Gourad, но, в то же время, и работает значительно быстрее.

Вид blending для объекта с фиксированными цветом и пикселами, удаляющимися от точки наблюдения.

Fogging

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

FPS, frames per second

Частота смены кадров. Чтобы оценить быстродействие системы трехмерной визуализации, достаточно запустить приложение, динамически создающее трехмерные сцены, и подсчитать число кадров в секунду, которое система способна отобразить. Однако, единого, достаточно авторитетного теста такого рода еще не создано. Большинство имеющихся тестов, основаны на фрагментах трехмерных игр и проверяют поведение графической карты на весьма ограниченном наборе функций.
Например, известная фирма Ziff Davis, выпустила тестовый пакет 3D Winbench’98.

Frame buffer

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

Front buffer

Первичный буфер. Область памяти, из которой происходит вывод кадра на экран (расчет производится в back buffer).

Gamma

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

Gamma Correction

Перед выводом на дисплей линейные данные RGB должны быть обработаны (скорректированы) для компенсации гаммы (нелинейной составляющей) дисплея.

Gouraud Shading (Smooth shading)

Затенение методом Гуро (или плавное затенение), один из наиболее популярных алгоритмов затенения, который обеспечивает прорисовку плавных теней вокруг изображаемого объекта, что позволяет изображать трехмерные объекты на плоском экране.
Метод назван по имени его разработчика, француза Генри Гуро. Gouraud Shading, или цветовая интерполяция — процесс, с помощью которого цветовая информация интерполируется по поверхности многоугольника для определения цветов в каждом пикселе. Информация о цвете связывается с каждым пикселом каждого многоугольника, с использованием линейной интерполяции по всему множеству многоугольников.
Затенение Гуро также работает, считывая информацию о цвете каждого треугольника, на которые разбита поверхность объекта, и плавно интерполирует интенсивность красного, зеленого и голубого цветов по трем координатам.
Этот метод уменьшает «блочность» изображения (смотри Flat Shading ) и используется для отображения металлических и пластиковых поверхностей.
В результате действия этого алгоритма должен создаваться эффект, заставляющий глаза зрителя экстраполировать информацию о глубине и кривизне поверхности изображаемого объекта.

Hidden Surface Removal

Удаление скрытых поверхностей. Метод определения видимых для наблюдателя поверхностей. Позволяет не отображать невидимые из данной точки поверхности объекта.

Interpolation

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

Interactive

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

Lighting

Существуют разные методы, использующие реалистичные графические эффекты для отображения 3D объектов на двумерном дисплее. Один из них — освещение. Используются разные уровни яркости («светло-темно») при отображении объекта для придания ему объема.

Line Buffer

Линейный буфер — буфер памяти, используемый для хранения одной линии видеоизображения. Если горизонтальное разрешение дисплея установлено равным 640 и для кодирования цвета используется схема RGB, то линейный буфер будет иметь размер 640х3 байт. Линейный буфер обычно используется в алгоритмах фильтров.

MIP Mapping

Multum in Parvo — с латыни переводится как «много в одном». Метод улучшения качества текстурных изображений при помощи использования текстур с разным разрешением для различных объектов одного и того же изображения, в зависимости от их размера и глубины. Таким образом, в памяти хранятся несколько копий текстурированного изображения в различных разрешениях. В результате этого изображение остается качественным при приближении к объекту и при удалении от него. При использовании этого метода Вы увидите изображение в высоком разрешении, находясь близко от объекта, и изображение в низком разрешении при удалении от объекта. MIP Mapping снижает мерцание и «зашумленность» изображения, возникающие при texture mapping.
Mip mapping использует некоторые умные методы для упаковки данных о текстурах изображения в памяти. Чтобы использовать Mip mapping, необходимо, взяв все размеры текстур и умножив это число на два, построить одну карту наибольшего размера. Все карты меньшего размера обычно фильтруются и становятся усредненными и уменьшенными версиями самой большой карты.

Occlusion

Эффект перекрытия в трехмерном пространстве одного объекта другим.

Palletized Texture

Формат хранения текстур в сжатом виде (1-, 2-, 4- и 8-битный формат вместо 24-битного). Обеспечивает возможность хранения большего числа текстур в меньшем объеме памяти.

Parallel point

Световой источник, который освещает равномерно все объекты параллельным пучком света.

Perspective Correction

Один из способов создания реалистичных объектов. Рассматриваются величины Z (глубина) при разделении объекта на многоугольники. При создании современных игр разработчики обычно используют довольно большого размера треугольники для описания поверхности объекта, и используют текстурные карты для более точного и детального изображения. Без этого качество картинки было бы гораздо хуже.
Если 3D объект движется от наблюдателя, то уменьшаются его линейные размеры (высота и ширина). Без использования функции perspective correction объект будет дергаться и двигаться нереалистично. С каждым уровнем скорректированной перспективы происходят изменения на пиксел в зависимости от глубины. Так как при этом происходит деление на пикселы, то требуются очень интенсивные вычисления.

Pipeline

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

Pixel

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

Pixel blending

Метод смешивания цветов текущего пикселя и пикселя, находящегося уже в буфере кадра, для получения выходного пикселя. Если ввести следующие обозначения: R1, G1, B1, A1, где каждый из символов соответственно представляет красную, зеленую, синюю и альфа компоненты текущего пикселя. Тогда R2, G2, B2, A2 — аналогично для пикселя, уже находящегося в буфере.

Source Alpha Pixel Blending — добавление прозрачности, т.е. (R1*A1+R2*(1-A1), G1*A1+G2*(1-A1), B1*A1+B2*(1-A1))

Add Pixel Blending — суммирование цветов, т.е. (R1+R2, G1+G2, B1+B2)

Modulate Pixel Blending — модуляция цветов, т.е. (R1*R2, G1*G2, B1*B2)

Phong Shading

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

Point

Световой источник, который светит одинаково во всех направлениях из одной точки (например, лампочка в комнате).

Procedural Texturing techniques (программное текстурирование)

Procedural Texturing techniques (или программное текстурирование) — это метод наложения реалистичных текстур «на лету», т.е. путем математических аппроксимаций структуры таких материалов как дерево, мрамор , камень и др. (Сравните с простым текстурированием)

До последнего времени Procedural Texturing редко использовалось как в программных, так и акселерированных игровых и других движках реального времени. В первом случае, ввиду того, что подобные вычисления требуют колоссальных мощностей математического сопроцессора для приемлемой скорости рендеринга, а во втором случае — потому, что «шумовые» алгоритмы Перлина, которые используются для генерации таких текстур — не стандартны и имеют много вариаций. Более того, процесс генерации текстур, различных по типу, требует различных схемных подходов, в то время как традиционное наложение текстур требует одинакового схемного решения для загрузки любого изображения. После появления на свет технологии MMX от Intel ситуация изменилась, и уже существуют разработки программной реализации «шума» Перлина на основе этой технологии, которые позволяют накладывать текстуры «на лету» со скоростью, сравнимой с простым текстурированием.

Характерные особенности:

  • Доступны любые качество и разрешение текстур, возможна их моментальная смена «на лету»(+)
  • Трехмерность(+)
  • Высокие требования к производительностиCPU(-)
  • Минимальные требования к количеству памяти компьютера(+)
  • Процесс сложноуправляем(-)
  • Ограниченность применения. Неприменим для имитации людей, картин, торговых марок и этикеток, рисунков и т.д.(-)

Projection

Процесс преобразования трех размерностей в две. Т.е. преобразование видимой части 3D объекта для отображения на двумерном дисплее.

Rasterization

Разделение объекта на пикселы.

Ray Tracing

«Трассировка лучей» — один из самых сложных и качественных методов построения реалистических изображений. Наиболее распространен вариант «обратной трассировки лучей»: от глаза наблюдателя, через пиксел строящегося изображения проводят луч и, учитывая все его отражения от объектов, вычисляют цвет этого пиксела.

Система цветообразования, в которой конечный цвет получается за счет смешения, с различной интенсивностью, трех основных цветов: красного (Red), зеленого (Green) и синего (Blue). Самое известное устройство, которое использует систему RGB, это цветной монитор.

Real-time

Режим реального времени; при этом иммитируемые события происходят так же, как и в реальной жизни. Для достижения этого используется синхронизация со встроенным таймером компьютера.

Rendering

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

Rendering Engine

Дословно — устройство рендеринга. Часть графической системы, которая рисует 3D- примитивы, такие как треугольники или другие простые многоугольники. Практически во всех реализациях системы rendering engine отвечает за интерполяцию краев (границ) объектов и заполнение пикселами многоугольников.

Resolution

Разрешение. Количество пикселей, представленное битами в видеопамяти, или адресуемое разрешение. Видеопамять может организовываться соотношением пикселов (битов) по оси x (пикселы на строке) к числу пикселов по оси y (столбцы) и к размеру отводимой памяти на представление глубины цвета. Стандартная видеопамять VGA 640 пикселов на 480 пикселов и, обычно, с глубиной представления цвета 8 бит. Чем выше разрешение, тем более детально изображение, и тем больше нужно хранить о нем информации. Но не вся хранимая информация может быть отображена на дисплее.

Scissors Clip (Scissoring)

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

Set-up Engine

Set-up engine позволяет драйверам передавать многоугольники в rendering engine в виде информации об адресах их вершинах, в то время, как, обычно, информация предварительно обрабатывается центральным процессором и передается в терминах изменения (дельт) границ, цвета и текстуры. Таким образом, set-up engine переносит обработку соответствующих данных с центрального процессора на графический чипсет, сокращая таким образом требования к скорости шины на 30% для обработки маленьких, случайно расположенных треугольников, и на пропорционально большее значение для больших многоугольников.

В растровой графике примитивы формируются с помощью преобразования линий развертки, каждая из которых пересекает примитив в двух точках (Р-левая и Р-правая). Последовательность пикселов на линии, расположенная между этими двумя точками, называется span. Каждый пиксел внутри span содержит значения величин Z, R, G, B.

Specular highlights

Световая характеристика, которая определяет то, как свет будет отражаться от объектов.

Световой источник, похожий на точечный. Он светит не во всех направлениях, а в пределах некого конуса. Освещаются только объекты, попадающие в этот конус.

Stippling

Создание контурных изображений, т.е. «рисование пунктиром».

Tessellation

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

Texel

Элемент текстуры — определенный пиксель в текстуре.

Texture

Двумерное изображение хранящееся в памяти компьютера или графического акселератора в одном из пиксельных форматов. В случае хранения в сжатом виде на дисках компьютера текстура может представлять собой обычный бит-мап, который мы привыкли видеть в форматах bmp, jpg, gif и т.д. Перед использованием текстура разворачивается в памяти и может занимать объем в десятки раз больший первоначального размера. Существует порядка двух десятков более или менее стандартизированных пиксельных форматов текстур.

Texture Anti-aliasing

Удаление нежелательных искажений растровых изображений с помощью интерполяции текстурных изображений.


Texture Mapping

(Наложение текстур)

Традиционно термином texture mapping в трехмерном моделировании называют процесс наложения двухмерной текстуры на трехмерный объект (текстура как бы натягивается на объект) для придания ему соответствующего внешнего вида. Таким образом, например, производится «раскрашивание» моделей монстров и игроков в трехмерных играх типа Quake и др.

Характерные особенности простого наложения текстур:

  • Обработка не требует значительных вычислительных мощностей(+)
  • Высокие требования к количеству оперативной памяти компьютера(-)
  • Загрузка текстуры производится исключительно из ОЗУ(-)
  • Как правило, смена разрешения требует смены текстуры(-)
  • Можно использовать и отображать практически любые изображения, будь то фотографии или рисунки.(+)
  • Простота реализации(+)

Transformation

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

Transparency

Прозрачность. В компьютерной графике цвет часто описывается в терминах RGB величин или величинами красного, зеленого и синего цвета. Существует еще коэффициент Alpha (альфа), являющийся дополнительным компонентом цвета, который используется для смешения. Коэффициент Alpha может также использоваться в качестве величины, отвечающей за степень прозрачности, т.е. величины, определяющей возможность видеть сквозь цвет (или нет). Наиболее важное значение коэффициент Alpha, или прозрачность, имеет в 3D графике благодаря его использованию для создания нерегулярных объектов, применяя для этого лишь несколько многоугольников.

triangle strip and fans

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

Tri-linear Filtering (Tri-linear MIP Mapping)

Метод уменьшения искажений в картах текстур, использующий билинейную фильтрацию для четырех текстурных пикселов из двух ближайших MIP-карт и их дальнейшую интерполяцию.
Для получения изображения берется взвешенное среднее значение результатов двух уровней билинейной фильтрации. Полученное изображение — более четкое и менее мерцающее.
Текстуры, с помощью которых формируется поверхность объекта, изменяют свой вид в зависимости от изменения расстояния от объекта до положения глаз зрителя. При движущемся изображении, например, по мере того, как объект удаляется от зрителя, карты текстур должны уменьшаться в размерах вместе с уменьшением размера отображаемого объекта. Для того, чтобы выполнить это преобразование, графический процессор фильтрует карты текстур вплоть до (соответствующего) размера, необходимого для покрытия поверхности объекта, при этом изображение остается естественным, т.е. объект не деформируется непредвиденным образом. Для того, чтобы избежать таких непредвиденных изменений, большинство графических программ создают серии пред-фильтрованных карт текстур с уменьшенным разрешением, этот процесс называется mip mapping. Затем графическая программа автоматически определяет, какую карту текстур использовать, основываясь на деталях карты текстур изображения, которое уже выведено на экран. Соответственно, если объект уменьшается в размерах, размер карты текстур тоже уменьшается.

True color

Цвет с глубиной представления 24 или 32 бит.

Vertex

Точка в трехмерном пространстве, где соединяются несколько линий.

Z-buffer

Часть графической памяти, в которой хранятся расстояния от точки наблюдения до каждого пиксела (значения Z). Z-buffer определяет, какая из многих перекрывающихся точек наиболее близка к плоскости наблюдения.
Также, как большее число битов на пиксель для цвета в буфере кадра соответствует большему количеству цветов, доступных в системе изображения, так и количество бит на пиксель в z-буфере соответствует большему числу элементов. Обычно, z-буфер имеет не менее 16 бит на пиксел для представления глубины цвета. Аппаратные акселераторы 3D графики могут иметь собственный z-буфер на графической карте, чтобы избежать удвоенной нагрузки на системную шину при передаче данных. Некоторые реализации Z-buffer используют для хранения не целочисленное значение глубины а значение с плавающей запятой от 0 до 1.

Z-buffering

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

Z-sorting

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

Геометрические примитивы OpenGL

Вершины надо задавать обязательно применительно к какому-нибудь примитиву. Рисование примитива начинается с вызова функции glBegin(), которой в качестве параметра передается константа, обозначающая тип примитива. Завершается примитив функцией glEnd(). Прототипы этих функций:

void glBegin( Glenum mode );

void glEnd( void );

Между вызовами glBegin() и glEnd() делаются вызовы glVertex*(). Например, для описания многоугольника, показанного на рис. 3.5 слева, надо вызвать следующие функции:

glVertex2d( 0.0, 0.0 );

glVertex2d( 0.0, 3.0 );

glVertex2d( 3.0, 3.0 );

glVertex2d( 4.0, 1.5 );

glVertex2d( 3.0, 0.0 );

Рис. 3.5.Примитивы двух типов: многоугольник и множество точек.

Если в качестве типа примитива вместо GL_POLYGON указать GL_POINTS, то будет нарисовано множество из 5-ти точек (рис. 3.5, справа). В табл. 3.1 перечислены все допустимые типы примитивов, которые можно указывать при вызове glBegin().

Таблица 3.1.Имена и назначение геометрических примитивов.

Имя константы Назначение
GL_POINTS Отдельные точки
GL_LINES Пары вершин, являющиеся концами отрезков
GL_POLYGON Граница простого выпуклого многоугольника
GL_TRIANGLES Тройки вершин, которые интерпретируются как вершины треугольников
GL_QUADS Четверки вершин, которые интерпретируются как вершины четырехугольников
GL_LINE_STRIP Вершины ломаной линии
GL_LINE_LOOP Вершины замкнутой ломаной линии (то же, что и предыдущий тип, но последняя и первая вершина соединяются автоматически)
GL_TRIANGLE_STRIP Связная полоса из треугольников (триангулированная полоса)
GL_TRIANGLE_FAN Веер из треугольников
GL_QUAD_STRIP Связная полоса из четырехугольников (квадрированная полоса)

На рис. 3.6 показаны примеры примитивов, перечисленных в табл. 3.1. Предполагается, что между glBegin() и glEnd() перечислено n вершин (v, v1, v2, . vn-1). Как следует из рис. 3.6, кроме точек, отрезков и многоугольников, в OpenGL есть еще несколько специальных типов примитивов.

Рис. 3.6.Типы геометрических примитивов.

Свойства точек, отрезков и многоугольников

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

Точки

Экранный размер точки задается с помощью функции:

void glPointSize( float size );

где size – диаметр точки в пикселах (должен быть больше 0.0, по умолчанию 1.0).

Количество пикселей, которые будут закрашены на экране при отображении данной точки, зависит от того, включено сглаживание или нет. Если сглаживание выключено, то size округляется до целого числа и на экране текущим цветом закрашивается квадрат со стороной size пикселей.

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

void CALLBACK display();

auxInitDisplayMode( AUX_SINGLE | AUX_RGBA );

auxInitPosition( 0, 0, 200, 200 );

auxInitWindow( «Лекция 3, Программа 3.1» );

glClearColor( 0.0, 0.0, 0.0, 0.0 );

void CALLBACK display()

// В 1-й строке три точки диаметром 2 пиксела, без сглаживания

glColor3d( 1, 0, 0 );

glVertex2d( 50, 180 );

glColor3d( 0, 1, 0 );

glVertex2d( 100, 180 );

glColor3d( 0, 0, 1 );

glVertex2d( 150, 180 );

// Во 2-й строке три точки диаметром 5 пикселов, без сглаживания

glColor3d( 1, 0, 0 );

glVertex2d( 50, 100 );

glColor3d( 0, 1, 0 );

glVertex2d( 100, 100 );

glColor3d( 0, 0, 1 );

glVertex2d( 150, 100 );

// В 3-й строке три точки диаметром 10 пикселов, со сглаживанием

glColor3d( 1, 0, 0 );

glVertex2d( 50, 20 );

glColor3d( 0, 1, 0 );

glVertex2d( 100, 20 );

glColor3d( 0, 0, 1 );

glVertex2d( 150, 20 );

// Принудительное завершение всех операций рисования

Программа 3.1. Рисование точек разного размера при

выключенном и включенном сглаживании.

Отрезки

У отрезков в OpenGL можно задавать толщину и стиль (точечный пунктир, штриховой пунктир, штрих-точка и т.п.). Толщина отрезка (в пикселах) по умолчанию равна 1.0, для ее изменения имеется функция:

void glLineWidth( GLfloat width );

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

Стиль отрезка (тип пунктира) устанавливается функцией glLineStipple():

void glLineStipple( GLint factor, GLushort pattern);

После вызова этой функции надо обязательно разрешить использование стиля с помощью glEnable(), например:

glLineStipple( 1, 0x3F07 );

Тип пунктира определяется шаблоном – 16-ти битным числом pattern, в котором биты, равные 0 и 1, обозначают пустые и закрашенные пикселы. Размер шаблона можно увеличить с помощью множителя factor, который задает количество повторений каждого бита шаблона.

В приведенном примере задан шаблон 0x3F07 (в двоичной форме это число 0011111100000111). При рисовании отрезка сначала закрашиваются 3 пиксела, затем 5 пропускаются, затем 6 закрашиваются, 2 пропускаются (шаблон обрабатывается справа налево, от младших битов к старшим). Если задать factor=2, то шаблон будет задавать следующее правило рисования: закрасить 6 пикселов, 10 пропустить, 12 закрасить, 4 пропустить. На рис. 3.7 показаны отрезки различных стилей и соответствующие значения шаблона и множителя.

Рис. 3.7.Пунктирные отрезки.

Рисование отрезков нескольких стилей и различной толщины демонстрируется в программе 3.2. Результат работы этой программы показан на рис. 3.8. Интересно отметить эффект, который достигается программе 3.2 за счет того, что отрезок рисуется не сразу целиком, а из нескольких отдельных сегментов.

Рис. 3.8.Отрезки различных стилей и разной толщины.

void drawOneLine( double x1, double y1, double x2, double y2 );

void CALLBACK display();


auxInitDisplayMode( AUX_SINGLE | AUX_RGBA );

auxInitPosition( 0, 0, 400, 150 );

auxInitWindow( «Лекция 3, Программа 3.2» );

glClearColor( 0.0, 0.0, 0.0, 0.0 );

void drawOneLine( double x1, double y1, double x2, double y2 )

glVertex2d( x1, y1 );

glVertex2d( x2, y2 );

void CALLBACK display()

glColor3d( 1.0, 1.0, 1.0 ); // Все отрезки рисуются белым цветом

// В 1-й строке 3 отрезка разных стилей

glLineStipple( 1, 0x0101 ); // Точечный пунктир

drawOneLine( 50.0, 125.0, 150.0, 125.0 );

glLineStipple( 1, 0x00FF ); // Штриховой пунктир

drawOneLine( 150.0, 125.0, 250.0, 125.0 );

glLineStipple( 1, 0x1C47 ); // Штрих-точка-штрих

drawOneLine( 250.0, 125.0, 350.0, 125.0 );

// Во 2-й строке 3 отрезка толщиной 5 пикселей и разных стилей

glLineStipple( 1, 0x0101 );

drawOneLine( 50.0, 100.0, 150.0, 100.0 );

glLineStipple( 1, 0x00FF );

drawOneLine( 150.0, 100.0, 250.0, 100.0 );

glLineStipple( 1, 0x1C47 );

drawOneLine( 250.0, 100.0, 350.0, 100.0 );

// В 3-й строке 6 отрезков стиля штрих-точка-штрих, которые

// образуют один большой отрезок

glLineStipple( 1, 0x1C47 );

for ( int i = 0; i 0), которая вычисляется по формуле:

где xi и yi есть экранные координаты i-й вершины n-угольника. Операция Å является обычным сложением, за исключением того, что n Å 1 = 1:

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

void glCullFace( GLenum mode );

Параметр mode равен GL_FRONT, GL_BACK или GL_FRONT_AND_BACK. Перед вызовом этой функции надо включить режим отсечения сторон многоугольников с помощью вызова glEnable( GL_CULL_FACE ).

В программе 3.3 иллюстрируются режимы рисования разных сторон многоугольников на примере 6-ти гранной пирамиды без основания. Клавишами курсора ее можно повернуть под разными углами, при этом явно видно различие между внешней и внутренней поверхностями пирамиды. Обратите внимание, что если задать вершинам многоугольника разные цвета, то OpenGL автоматически выполняет цветовую интерполяцию при заливке многоугольника.

void CALLBACK resize( int width, int height );

void CALLBACK display();

void CALLBACK addAngleX();

void CALLBACK subAngleX();

void CALLBACK addAngleY();

void CALLBACK subAngleY();

// Расположение пирамиды относительно осей X и Y

int angle_x = 0, angle_y = 0;

auxInitDisplayMode( AUX_RGBA | AUX_DEPTH | AUX_DOUBLE );

auxInitPosition( 50, 10, 400, 400);

auxInitWindow( «Лекция 3, Программа 3.3» );

// Включение ряда параметров OpenGL

// Задание положения и направления нулевого источника света

glLightfv( GL_LIGHT0, GL_POSITION, pos );

glLightfv( GL_LIGHT0, GL_SPOT_DIRECTION, dir );

// Регистрация обработчиков событий

auxKeyFunc( AUX_UP, subAngleX );

auxKeyFunc( AUX_DOWN, addAngleX );

auxKeyFunc( AUX_RIGHT, addAngleY );

auxKeyFunc( AUX_LEFT, subAngleY );

void CALLBACK resize( int width, int height )

glViewport( 0, 0, width, height );

gluPerspective( 60.0, (double)width/(double)height, 1.0, 20.0 );

gluLookAt( 0,0,5, 0,0,0, 0,1,0 );

void CALLBACK display()

const double BASE_R = 1; // Радиус основания

const double PYRAM >

const double PI = 3.14159;

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

Свет и материалы в OpenGL

Файлы к лабораторной работе Вы можете скачать здесь.

Создание заготовки приложения

  • Выполните команду меню оболочки File/New/Project .
  • В появившемся окне мастера создания заготовки проекта задайте имя проекта LightAndMaterial , выберите каталог размещения проекта, как показано на снимке
  • После нажатия кнопки OK в следующем окне оставьте установки мастера по умолчанию и щелкните на кнопке Finish
  • Переименуйте функцию _tmain() файла LightAndMaterial.cpp в main() , измените в заголовке определения функции main() тип возвращаемого значения , чтобы код выглядел так
  • Через панель Solution Explorer оболочки откройте файл stdafx.h, который создал мастер проекта, и добавьте в самое его начало после инструкции # pragma once код подключения стандартных заголовочных файлов для операционной системы и OpenGL

В этой лабораторной работе мы опять выполним несколько упражнений, которые упакуем в одно приложение . Соответственно, для вызова каждого отдельного упражнения предусмотрим свой пункт контекстного меню . Код приложения будем постепенно наращивать по мере выполнения упражнений рассматриваемой темы.

Глаз человека и оптические системы

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

Илон Маск рекомендует:  Пример работы с объектной моделью документа DOM firstChild и lastChild

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

Упрощенная структура глаза человека показана на рисунке.

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

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

При цветовосприятии сказывается двойственная природа света.С одной стороны, свет — это электромагнитные волны с видимым спектром, равным цветам радуги. Вспомним школьную «запоминалку»: Каждый Охотник Желает Знать Где Сидит Фазан (Красный-Оранжевый-Желтый-Зеленый-Голубой-Синий-Фиолетовый). На рисунке в центре приведена видимая часть спектра света, по краям которой расположены области света, невидимые невооруженным приборами глазом.

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

Различные поверхности могут иметь разные свойства: блестящие поверхности отражают почти весь свет в нужных направлениях, матовые поверхности рассеивают фотоны равномерно в разные стороны. В OpenGL применяется один из двух цветовых режимов: RGB и режим индексации цвета. Если говорить более точно, то вместо RGB применяется модель цвета RGBA, где последняя буква аббревиатуры означает Alpha — канал. Альфа — канал задает прозрачность освещаемых объектов: 1 — абсолютно непрозрачный ( opacity ), 0 — абсолютно прозрачный ( transparency ).

Pygame, OpenGL: проецирование 3D-значений rgb на 2D-поверхность

С помощью Pygame и OpenGL я рисую красную и зеленую сферы в 3D — близко и далеко в направлении z:

Я хотел бы получить значения RGB этого изображения в 2D плоскости моего обзора. Я застрял с моим подходом Pygame и OpenGL, отмеченным как 1 и 2 в следующем фрагменте кода, поскольку они возвращают только черные пиксели.

Как я могу проецировать из 3D на заданную 2D поверхность? Я пытался вставить gluUnproject до glReadPixels , но я не совсем понимаю, что возвращает эта функция, здесь:


obj_x = 0,0414213539903457, obj_y = 0,0414213539903457, obj_z = -0.09999999851062896:

Относительно 2: Как я могу сказать glReadPixels для чтения с определенной поверхности?

Параметры glReadPixels определяют прямоугольник окна, который должен быть прочитан. Первые 2 параметра задают координаты окна первого пикселя, считываемого из буфера кадра, а 3-й и 4-й параметры определяют размеры прямоугольника пикселя (ширина и высота).

Параметры для чтения всего окна (framebuffer): 0, 0, border_x, border_y :

Обратите внимание, что вы можете выбрать значения цвета в виде целочисленных значений ( GL.GL_BYTE , GL.GL_INT ) или значений с плавающей запятой в диапазоне [0.0, 1.0] ( GL.GL_FLOAT ).

Например, следующий код напечатает 1.0, потому что красный цвет канала красной точки в середине представления читается:

Построения в пространстве

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

Параметры вида
В предыдущей главе мы убедились, что объем сцены ограничен кубом с координатами точек вершин по диагоналям (-1, -1, -1) и (1, 1, 1). Начнем дальнейшее изучение с того, что увеличим объем этого пространства. Проект из подкаталога ExOl нам поможет. На сцене присутствует все тот же треугольник, одну из вершин которого можно перемещать по оси Z нажатием клавиши , значение координаты вершины выводится в заголовке окна. Теперь мы видим треугольник целиком в пределах большего, чем раньше, объема.
Код перерисовки окна выглядит так:

wglMakeCurrent(Canvas.Handle, hrc);
glViewport(0, 0, ClientWidth, ClientHeight);
9lPushMatrix;
glFrustum (-1, 1, -1, 1, 3, 10); // задаем перспективу
glTranslatef(0.0, 0.0, -5.0); // перенос объекта по оси 1
9lClearColor (0.5, 0.5, 0.75, 1.0);
glClear (GL_COLOR_BUFFER_BIT) ;
glColor3f (1.0, 0.0, 0.5);
glBegin (GLJTRIANGLES);
glVertex3f (-1, -1, 0);
glVertex3f (-1, 1, 0) ;
glVertexSf (1, 0, h);
glEnd;
glPopMatnx;
SwapBuffers (Canvas.Handle);
wglMakeCurrent (0, 0) ;

Основная последовательность действий заключена между командами giPushMatrix и glPopMatrix. Если этого не делать, то при каждой перерисовке окна, например, при изменении его размеров, сцена будет уменьшаться в размерах.
Здесь встречается новая для нас команда — giFrustum, задающая параметры вида, в частности, определяющие область воспроизведения в пространстве. Все, что выходит за пределы этой области, будет отсекаться при воспроизведении. Первые два аргумента задают координаты плоскостей отсечения слева и справа, третий и четвертый параметры определяют координаты плоскостей отсечения снизу и сверху. Последние аргументы задают расстояния до ближней и дальней плоскостей отсечения, значения этих двух параметров должны быть положительными — это не координаты плоскостей, а расстояния от глаза наблюдателя до плоскостей отсечения.

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

Теперь все, что рисуется с нулевым значением координаты Z, не будет видно наблюдателю, поскольку ближнюю плоскость отсечения мы расположили на расстоянии трех единиц от глаза наблюдателя, располагающегося в точке (0, 0, 0). Поэтому перед воспроизведением треугольника смещаем систему координат на пять единиц вниз.
Треугольник удалился от наблюдателя, и его вершины располагаются уже не на границе окна, а сместились вглубь экрана.

Замечание
В главе 6 мы узнаем, как соотнести пространственные и оконные координаты, если видовые параметры заданы с помощью команды glFrustum

Переходим к следующему примеру — проекту из подкаталога Ех02 отличие от первого примера состоит в том, что команды giPushMatrix И glPopMatrix удалены, а перед вызовом команды giFrustum стоит вызов команды giboadidentity. Будем понимать это как действие «вернуться в исходное состояние». При каждой перерисовке экрана перед заданием видовых параметров это следует проделывать, иначе объем сцены будет последовательно отсекаться из предыдущего.

Замечание
Устанавливать видовые параметры не обязательно при каждой перерисовке экрана, достаточно делать это лишь при изменении размеров окна.

Это несложное соображение предваряет следующий пример — проект из подкаталога Ех03. Для повышения надежности работы приложения пользуемся явно получаемой ссылкой на контекст устройства, а не значением свойства canvas.Handle Сразу же после получения контекста воспроизведения делаем его текущим в обработчике события create формы, а непосредственно перед удалением освобождаем контекст в обработчике Destroy.
Теперь такие параметры OpenGL, как цвет фона и цвет примитивов, можно задавать единственный раз — при создании формы, а не выполнять это действие каждый раз при перерисовке экрана. В отличие от всех предыдущих проектов, в данном появляется отдельный обработчик события, связанного с изменением размеров окна. Напомню, раньше при этом событии выполнялся тот же код, что и при перерисовке окна.
Во всех оставшихся примерах, как правило, будет присутствовать отдельный обработчик события, связанного с изменением размеров окна. В этом обработчике задается область вывода и устанавливаются параметры вида, после чего окно необходимо перерисовать:

procedure TfrmGL.FormResize(Sender: TObject);
begin
glViewport (0, 0, ClientWidth, ClientHeight);
glLoadldentity;
giFrustum (-1, 1, -1, 1, 3, 10); // видовые параметры
glTranslatef (0.0, 0.0, -5.0); // начальный сдвиг системы координат
InvalidateRect(Handle, nil. False);
end;

Код, связанный с перерисовкой окна, теперь сокращается и становится бо-лее читабельным:

Procedure TfrmGL.FormPaint(Sender: TObject);
WClear (GL_COLOR_BUFFER_BIT);
9lBegin (GLJTRIANGLES) ;
glVertex3f (-1, -1, 0) ;
glVertex3f (-l, 1, 0);
glVertex3f (1, 0, h);
glEnd;
SwapBuffers(DC);
end;

Хоть мы уже и рисуем пространственные картинки, однако почувствовать трехмерность пока не могли. Попробуем нарисовать что-нибудь действительно объемное, например, куб (проект из подкаталога Ex04). Результат работы программы — на рис. 3. 1.

Рис. 3. 1. В будущем мы получим более красивые картинки, начинаем же с самых простых

Для придания трехмерности сцене поворачиваем ее по осям:

procedure TfrmGL. FormResize(Sender: TObject);
begin
glViewport(0, 0, ClientWidth, ClientHeight);
glLoadIdentity;
glFrustum (-1, 1, -1, 1, 3, 10); // задаем перспективу
// этот фрагмент нужен для придания трехмерности
glTranslatef (0. 0, 0. 0, -8. 0); // перенос объекта — ось Z
glRotatef (30. 0, 1. 0, 0. 0, 0. 0); // поворот объекта — ось X
glRotatef (70. 0, 0. 0, 1. 0, 0. 0); // поворот объекта — ось Y
InvalidateRect(Handle, nil, False);
end;

Построение куба сводится к построению шести квадратов, отдельно для каждой стороны фигуры:

glBegin (GL_QUADS);
glVertex3f (1. 0, 1. 0, 1. 0);
glVertex3f (-1. 0, 1. 0, 1. 0);
glVertex3f (-1. 0, -1. 0, 1. 0);
glVertex3f (1. 0, -1. 0, 1. 0);
glEnd;

glBegm (GLJ2UADS);
glVertex3f (1. 0, 1. 0, -1. 0);
glVertex3f (1. 0, -1. 0, -1. 0);
glVertex3f (-1. 0, -1. 0, -1. 0),
glVertex3f (-1. 0, 1. 0, -1. 0);
glEnd;

glBegin (GL_QUADS);
glVertex3f (-1. 0, 1. 0, -1. 0);
glVertex3f (-1. 0, 1. 0, -1. 0);
glVertex3f (-1. 0, -1. 0, 1. 0);
glVertex3f (-1. 0, -1.0, 1.0);
glEnd;

glBegin (GL_QUADS);
glVertex3f (1. 0, 1. 0, 1. 0);
glVertex3f (1. 0, -1. 0, 1. 0);
glVertex3f (1. 0, -1. 0, -1. 0);
glVertex3f (1. 0, 1. 0, -1. 0);
glEnd;

glBegin (GL_QUADS);
glVertex3f (-1. 0, 1. 0, -1. 0);
glVertex3f (-1. 0, 1. 0, 1. 0);
glVertex3f (1. 0, 1. 0, 1. 0);
glVertex3f (1. 0, 1. 0, -1. 0);
glEnd;

glBegin(GL_QUADS);
glVertex3f (-1. 0, -1. 0, -1. 0);
glVertex3f (1. 0, -1. 0, -1. 0);
glVertex3f (1. 0, -1. 0, 1. 0);
glVertex3f (-1. 0, -1. 0, 1. 0);
glEnd;

Код получается громоздким, согласен.

Замечание
Для некоторых базовых фигур мы сможем в будущем найти возможность сократить код, но в общем случае объемные объекты строятся именно так, т e из отдельных плоских примитивов.

Я хочу предостеречь вас на случай, если вы желаете прямо сейчас потренироваться и нарисовать что-нибудь интересное. Пока что наши примеры сильно упрощены, могут быть только учебной иллюстрацией и не годятся в качестве шаблона или основы для построения более серьезных программ Получившаяся картинка действительно трехмерная, но пространственность здесь только угадывается: куб залит монотонным цветом, из-за чего плохо понятно, что же нарисовано. Сейчас это не очень важно, мы только учимся задавать видовые параметры. Для большей определенности в ближайших примерах будем рисовать каркас куба, как, например, в следующем примере — проекте из подкаталога Ex05 (рис. 3. 2).

Рис. 3. 2. Для ориентировки в пространстве будем рисовать каркасную модель куба

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

glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);

Получившаяся картинка иллюстрирует важную вещь: использование команды glFrustum приводит к созданию перспективной проекции. Хорошо видно, что ребра куба не параллельны друг другу и имеют точку схода где-то на горизонте.
Чтобы помочь вам лучше разобраться с одной из важнейших команд библиотеки OpenGL, я написал пример, расположенный в подкаталоге Ех06. В этом примере аргументы команды — переменные:

glFrustum (vLeft, vRight, vBottom, vTop, vNear, vFar);

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

If Key = VK_SPACE then begin
If ssShift in Shift then begin // нажат Shift, удаляемся
vLeft: = vLeft — 0. 1;
vRight: = vRight + 0. 1;
vBottom: = vBottom — 0. 1;
vTop: = vTop + 0. 1;
end
else begin // приближаемся
vLeft: = vLeft + 0. 1;
vRight: = vRight — 0. 1;
vBottom: = vBottom + 0. 1;
vTop: = vTop — 0. 1;
end;
FormResize(nil);
end;

Аналогично клавишами управления курсором можно манипулировать значениями каждой из этих четырех переменных в отдельности, а клавиши и отвечают за координаты ближней и дальней плоскостей отсечения.
Так, если чересчур близко к наблюдателю расположить заднюю плоскость отсечения, дальние ребра куба станут обрезаться (рис. 3. 3).

Рис. 3. 3. Вот что происходит с кубом, если заднюю плоскость приближать слишком близко

Еще один способ приблизить к сцене точку зрения — уменьшать значение координаты передней плоскости отсечения. Возможно, это более практичный способ, поскольку у первого способа есть один недостаток: если принижаться к сцене чересчур близко, картинка переворачивается и при дальнейишем «приближении» удаляется, оставаясь перевернутой.

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

Теперь мы перейдем к другой проекции — ортографической, параллельной. Посмотрим проект из подкаталога Ех07, результат работы которого показан на рис. 3.4.

Рис. 3.4. Великий Леонардо нашел бы эту картинку нелепой, но чертежник должен быть довольным

Отличается эта проекция от перспективной именно отсутствием перспективы. В обработчике изменения размеров окна видовые параметры задаются с помощью команды giortho:

procedure TfrmGL.FormResize(Sender: TObject);
begin
glViewport(0, 0, ClientWidth, ClientHeight);
glLoadldentity;
glOrtho (-2, 2, -2, 2, 0, 15.0)
glTranslatef (0.0, 0.0, -10.0);
glRotatef (30.0, 1.0, 0.0, 0.0)
glRotatef (60.0, 0.0, 1.0, 0.0)
// видовые параметры
// перенос системы координат по оси Z
// поворот системы координат по оси X
// поворот системы координат по оси Y
InvalidateRect (Handle, nil, False);
end;

Аргументы команды giortho имеют точно такой же смысл, что и у giFrustum, но последние два аргумента могут иметь отрицательное значение. Помимо этих двух команд, OpenGL предоставляет еще несколько возможностей установки видовых параметров, например, библиотека glu содержит команду giuOrtho2D. Эта команда имеет четыре аргумента, смысл которых такой же, как и у giortho. По своему действию она эквивалентна вызову giortho с указанием значения расстояния до ближней плоскости отсечения равным минус единице, и расстоянием до дальней плоскости отсечения равным единице.
Как при такой проекции выглядит куб из предыдущих примеров, показано на рис. 3.5, а проект находится в подкаталоге Ех08.

Рис. 3.5. Командой gluOrtho2D следует пользоваться с осторожностью, но с точки зрения скорости воспроизведения она является оптимальной

Обратите внимание, что здесь отсутствует начальный сдвиг по оси Z:

procedure TfrmGL.FormResize(Sender: TObject);
begin
glViewport(0, 0, ClientWidth, ClientHeight);
glLoadldentity;
gluOrtho2D (-2, 2, -2, 2); // задаем перспективу
glRotatef (30.0, 1.0, 0.0, 0.0); // поворот объекта — ось X
glRotatef (60.0, 0.0, 1.0, 0.0); // поворот объекта — ось Y
InvalidateRect(Handle, nil, False);
end;

Куб рисуется вокруг глаза наблюдателя и проецируется на плоскость экрана. Согласно установкам этой команды передняя и задняя части нашего куба частично обрезаются.
Следующая команда, которую мы рассмотрим, пожалуй, наиболее популяр-на в плане использования для первоначального задания видовых параметров. Команда gluPerspective, как ясно из ее названия, также находится в библиотеке glu. Проект примера содержится в подкаталоге Ех09, а полу-ЧаЮщаяся в результате работы программы картинка показана на рис. 3.6.

Рис. 3.6. Так работает команда gluPerspective

Смысл аргументов команды поясняется в комментариях:

Procedure TfrmGL.FormResize(Sender: TObject);
begin
glViewport(0, 0, ClientWidth, ClientHeight);
glLoadIdentity
// задаем перспективу
gluPerspective(30. 0, // угол видимости в направлении оси Y
ClientWidth / ClientHeight, // угол видимости в направлении оси X
1 0, // расстояние от наблюдателя до ближней плоскости отсечения
15. 0); // расстояние от наблюдателя до дальней плоскости отсечения
glTranslatef (0. 0, 0. 0, -10. 0); // перенос — ось Z
glRotatef (30. 0, 1. 0, 0. 0, 0. 0); // поворот-ось X
glRotatef (60. 0, 0. 0, 1. 0, 0. 0); // поворот-ось Y
InvalidateRect (Handle, nil, False);
end;

Замечание
В главе 4 мы узнаем, как соотносятся аргументы команд gluPerspective и glFrustum.

С перспективами, конечно, надо попрактиковаться. Настоятельно рекомендую разобраться с примером из подкаталога Ex10. Клавиши управления курсором позволяют манипулировать значениями первых двух аргументов команды gluPerspective. При уменьшении первого аргумента происходит приближение глаза наблюдателя к сцене, уменьшение второго аргумента приводит к тому, что сцена растягивается в поперечном направлении (рис. 3 7).

Рис. 3. 7. Изменения в видовых установках приводят к трансформации объектов на экране

Замечание
Как правило, в качестве значения второго аргумента команды gluPerspective, так называемого аспекта, задают отношение ширины и высоты области вывода.

В проекте из подкаталога Ex11 я объединил все примеры на изученные команды установки видовых параметров (рис. 3. 8).

Рис. 3. 8. Команды задания видовых параметров

Вы имеете возможность еще раз уяснить различия между этими способами подготовки сцены.Библиотека glu располагает еще одной вспомогательной командой, имеющей отношение к рассматриваемой теме — gluLookAt. У нее девять аргументов: координаты позиции глаза наблюдателя в пространстве, координаты точки, располагающейся в центре экрана, и направление вектора, задающего поворот сцены (вектор «up»).
При использовании этой команды можно обойтись без начальных операций со сдвигом и поворотом системы координат. Ее работу демонстрирует проект из подкаталога Exl2 (рис. 3. 9).

Рис. 3. 9. Командой gluLookAt удобно пользоваться при перемещениях точки зрения в пространстве

При задании параметров вида ограничиваемся минимумом команд:

glLoadIdentity;
gluPerspective (50. 0, ClientWidth / ClientHeight, 2. 0, 10. 0);
gluLookAt (2.7, 2, 2.5, 0.4, 0.5, 0.5, О, О, 1);
InvalidateRect(Handle, nil, False);

На рис 3.10 показаны экранные формы, выдаваемые программой из подкаталога Ех13.

Рис 3. 10. Команду gluLookAt нужно изучить основательно

В них можно менять значения всех аргументов команды gluLookAt, и поскольку этих аргументов сравнительно много, я создал вспомогательную форму, в полях редактирования которой выводятся и задаются пользователем координаты всех девяти аргументов Рекомендую обязательно попрактиковаться с этим примером, чтобы «почувствовать» работу параметров Обратите внимание, что, как подчеркивается в документации, вектор «up» не должен быть параллельным линии, соединяющей точку зрения и контрольную точку

Матрицы OpenGL
При обсуждении команд giRotatef и giTransiatef мы уже обращали внимание на то, что их действие объясняется с позиций линейной алгебры Например, про команду giTransiatef в соответствующем файле справки говорится, что эта команда умножает текущую матрицу на матрицу переноса Аналогично giRotatef сводится к операции перемножения текущей матрицы на матрицу поворота Многие изученные нами команды, действие которых я объяснял по их функциональному смыслу, в действительности описываются с помощью базовых понятий раздела математики, изучающего вопросы, связанные с геометрическими операциями, например, такими, как проекция Надеюсь, отсутствие ссылок на линейную алгебру нисколько не помешало вам при изучении предыдущего материала Если я говорю, что команда giscale является командой масштабирования (и умалчиваю о том, что она сводится к операции перемножения матриц), то рассчитываю на то, что читателю данной информации будет вполне достаточно для успешного использования этой команды на практике
Я сознательно не включил в лекции изложение математических основ для всех подобных команд, поскольку замыслил ее прежде всего как практическое руководство по компьютерной графике Мне кажется, что многие читатели не очень хотели бы особо углубляться в дебри формул Это отнюдь не значит, что я пытаюсь принизить значение математической грамотности Просто мой опыт преподавания подсказывает, что очень многие программисты предпочитают руководства, делающие упор на практике К счастью, существует масса литературы по компьютерной графике, уделяющей много внимания математике, где подробно рассказывается об операциях перемножения матриц и прочих подобных вопросах Если вы все-таки испытаете необходимость в более основательной теоретической подготовке, найти подходящее пособие не составит большого труда. Однако и в нашей практической книжке без представления о некоторых специальных терминах не обойтись. Ограничимся самым необходимым.В OpenGL имеется несколько важных матриц Матрица модели («modelview matrix») связана с координатами объектов Она используется для того, чтобы в пространстве построить картину как бы видимую глазу наблюдателя. Другая матрица, матрица проекций («piojection matirx»), связана с построением проекций пространственных объектов на плоскость.Матрица проекций, имея координаты точки зрения, строит усеченные («clip») координаты, по которым после операций, связанных с перспективой, вычисляются нормализованные координаты в системе координат устройства («normalized device coordinates») После трансформаций, связанных с областью вывода, получаются оконные координаты.Координаты вершин в пространстве и на плоскости проекций четырехмерные, помимо обычных координат есть еще w-координата Поэтому матрица модели и матрица проекций имеют размерность 4×4. Перенос и поворот системы координат сводится к операции перемножения матриц, связанных с текущей системой координат, и матриц переноса и поворота Библиотека OpenGL располагает набором команд, связанных с этими операциями, а также имеет универсальную команду для перемножения матриц giMuitMatrix При желании и наличии математической подготовки этой командой можно заменить команды переноса, поворота и ряд других Посмотрим, как это делается.
В проекте из подкаталога Ех14 команда начального переноса заменена командой giMuitMatrix Для этого введен массив 4×4, хранящий матрицу переноса:

rat . Array [0. 3, 0. 3] of GLfloat,

Матрица переноса заполняется нулями, кроме элементов главной диагонали, которые должны быть единицами, и последней строки, содержащей вектор переноса В примере перемещение осуществляется только по оси Z:

mt [0, 0] := 1;
№ [1, 1] := 1;
№ [2, 2] := 1;
mt [3, 3] := 1;
mt [3, 2] := -8;

Стартовый сдвиг теперь осуществляется так:

Если надо вернуться к исходной позиции, текущая матрица заменяется матрицей с единицами по диагонали и равными нулю всеми остальными элементами, единичной матрицей Это и есть действие команды giLoadidentity Она является частным случаем более универсальной команды giLoadMatnx, предназначенной для замены текущей матрицы на заданную, ссылка на которую передается в качестве аргумента.
Без примера эти команды не усвоить, поэтому загляните в подкаталог Exl5 Массив mt аналогичен единичной матрице. Команда glLoadidentity отсутствует, вместо ее вызова используется явная загрузка единичной матрицы:

После знакомства с матричными операциями становится яснее технический смысл команд glPushMatrix и glPopMatrix, запоминающих в стеке текущую матрицу и извлекающих ее оттуда. Такая последовательность манипуляций выполняется быстрее, чем вызов glLoadMatrix. To же самое можно сказать и по поводу glLoadidentity, т. e. единичная матрица загружается быстрее
командой glLoadidentity, чем glLoadMatrix.

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

Узнать, какая сейчас установлена матрица, можно с помощью команды glGet. Во всех наших предыдущих примерах мы задавали параметры вида применительно к матрице, установленной по умолчанию Определим, какая это матрица.
Подкаталог Ex16содержит проект, который нам в этом поможет Обработчик Resize формы дополнен выводом в заголовке окна имени текущей матрицы:

var
wrk: GLUInt; begin
glGetIntegerv (GL_MATRIX_MODE, @wrk);
case wrk of
GL_MODELVIEW: Caption: = ‘GL_MODELVIEW’;
GL_PROJECTION: Caption: = ‘GL_PROJECTION’;
end;

Запустив проект, выясняем, что по умолчанию в OpenGL установлена матрица модели. Итак, во всех предыдущих примерах операции производились над матрицей модели. В любой момент мы можем выяснить значение этой матрицы. Проиллюстрируем примером — проектом из подкаталога Exl7. При создании формы массив 4×4 заполняется матрицей модели:

glGetFloatv (GL_MODELVIEW_MATRIX, @mt);

при выборе пункта меню появляется вспомогательное окно, в котором выводится текущая матрица модели (рис 3. 11).

Рис. 3. 11. Вывод содержимого матрицы модели

При первом появлении окна эта матрица совпадает с единичной матрицей по главной диагонали единицы, все остальные элементы равны нулю Команда glLoadidentity в этой программе также заменена явной загрузкой матрицы. В проекте имеется возможность варьировать загружаемую матрицу, по нажатию на кнопку «OK» она устанавливается в модели. Сейчас у вас имеется прекрасная возможность попрактиковаться в матричных операциях. Например, числа на главной диагонали являются масштабными множителями по осям. Поменяйте первые из этих двух чисел произвольно для масштабирования по осям X и Y.

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

Если вы внимательно прочитали этот раздел, то полученных знаний о матрицах теперь достаточно для того, чтобы нарисовать все что угодно Осталось узнать еще одну команду — glMatrixMode Она позволяет установить текущую матрицу.
Проекты всех предыдущих примеров подходят только для простейших пространственных построений, таких как рисование каркаса кубика Для более сложных программ они не годятся из-за одного упрощения.
для корректного вывода пространственных фигур параметры вида задаются при Установленной матрице проекции, после чего необходимо переключить- ся в пространство модели. То есть обычная последовательность здесь, например, такая:

glViewport(0, 0, ClientWidth, ClientHeight);
glMatnxMode (GL_PROJECTION);
glLoadldentity;
glFrustum (-1, I, -I, I, 3, 10);
glMatrixMode (GL_MODELVIEW);
glLoadldentity;

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

Напоминаю, что параметры вида обычно помещаются в обработчике изменения размеров окна Стартовые сдвиги и повороты обычно располагаются здесь же, а код воспроизведения сцены заключается между командами glPushMatrix И glPopMatrix. Иногда поступают иначе: код воспроизведения начинается с glLoadldentity, а далее идут стартовые трансформации и собственно код сцены

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

Сейчас мы рассмотрим пару примеров на темы предыдущей главы, которые мы не могли рассмотреть раньше из-за того, что в них используются команды задания вида. Начнем с проекта из подкаталога Ех18 — модификации классической программы, поставляемой в составе OpenGL SDK. Программа рисует точку и оси системы координат (рис. 3.12).


Рис. 3.12. Результат работы проекта Point Test

Клавишами управления курсором можно передвигать точку, нажатием клавиши ‘W’ размер точки можно уменьшать, если же нажимать эту клавишу есте с , то размер точки увеличивается. Первая цифровая клавиша задает режим сглаживания точки. Обратите внимание, что при установленном режиме сглаживания размер получающегося кружочка ограничен некоторыми пределами, для точек больших размеров он не вписывается точно в квадрат точки. Если точнее, то в примере рисуется две точки — вторая рисуется зеленым цветом посередине первой. Вторая точка не изменяется в размерах и всегда выводится несглаженной, но с единичным размером. Пример можно использовать в качестве простого теста, скорость передвижения большой по размеру точки позволяет оценить возможности компьютера. Посмотрите внимательно, как в этом примере задаются видовые параметры. Здесь интересно то, что плоскости отсечения привязаны к текущим размерам окна:

glViewport(О, О, ClientWidth, ClientHeight);
glMatrixMode(GL_PROJECTION);
glLoadldentity;
gluOrtho2D(-ClientWidth/2, ClientWidth/2, -ClientHeight/2, ClientHeight/2);
glMatrixMode(GL_MODELVIEW);
glLoadldentity;

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

glBegin(GL_POINTS);
glVertex3fv (@point);
glEnd;

Замечание
Напоминаю, что эта — векторная — форма команд является оптимальной по скоростным показателям

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

glViewport(0, 0, ClientWidth, ClientHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity;
gluOrtho2D (-175, 175, -175, 175);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;

Поэтому при изменении размеров окна картинка масштабируется так, что вся сцена всегда вписывается в экран.
Клавиша ‘W’ все так же позволяет регулировать ширину линий, первые две цифровые клавиши отведены для управления двумя режимами воспроизведения: первый режим включает штриховку линий, второй задает сглаживание отрезков.
Управляя этими тремя параметрами, можно получать разнообразные картинки, например такую, как приведена на рис. 3. 13.

Рис. 3. 13. Получить такие узоры на самом деле легко

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

glLineWidth(size); // ширина отрезка
If model // режим, задающий штриховку отрезков
then glEnable(GL_LINE_STIPPLE) // использовать штриховку
else glDisable(GL_LINE_STIPPLE); // не использовать штриховку
If mode2 // режим, задающий сглаженность отрезков
then glEnable (GL_LINE_SMOOTH) // сглаживать отрезки
else glDisable(GL_LINE_SMOOTH); // не использовать сглаженность
glPusnMatrix; // запоминаем систему координат
For i: = 0 to 71 do begin // цикл рисования 72 отрезков
glRotatef(5. 0, 0, 0, 1); // поворот на пять градусов

glColor3f(1. 0, 1. 0, 0. 0); //цвет отрезков — желтый
glBegin(GL_LINE_STRIP); // примитив — отрезок
glVertex3fv(@pntA); // указатель на начало отрезка
glVertex3fv(@pntB); / указатель на конец отрезка
glEnd;

glColor3f(0. 0, 1. 0, 0. 0); // цвет точек — зеленый
glBegin(GL_POINTS); // примитив — точка
glVertex3fv(@pntA); // точка в начале отрезка
glVertex3fv(@pntB); // точка в конце отрезка
glEnd;
end;
glPopMatrix; // возвращаемся в первоначальную систему координат

Буфер глубины
При создании контекста воспроизведения в число параметров формата пикселов входят размеры разделов памяти, предоставляемой для нужд OpenGL, или буферов. Помимо буферов кадра, в OpenGL присутствуют еще три буфера: буфер глубины, буфер трафарета и вспомогательный буфер
Для специальных нужд могут использоваться еще буфер выбора и буфер обратной связи, они подготавливаются пользователем по мере надобности.
Работа с буферами будет нами подробно изучена в соответствующих разделах курса. В этом разделе мы познакомимся с буфером глубины. Как ясно из его названия, он используется для передачи пространства При воспроизведении каждого пиксела в этот буфер записывается информация о значении координаты Z пиксела, так называемая оконная Z. Если на пиксел приходится несколько точек, на экран выводится точка с наименьшим значением этой координаты.
При пространственных построениях отказ от использования буфера глубины приводит к неверной передаче пространства. Посмотрим, в чем тут дело. Для удобства отладки я написал процедуру, строящую оси координат и помечающую оси буквами X, Y и Z:

Procedure Axes;
var
Color: Array [1.. 4] of GLFloat;
begin
glPushMatrix;
glGetFloatv (GL_CURRENT_COLOR, @Color),

glScalef (0. 5, 0. 5, 0. 5);

glColor3f (0, 1, 0);

glBegin (GL_LINES);
glVertex3f (0, 0, 0);
glVertex3f (3, 0, 0);
glVertex3f (0, 0, 0);
glVertex3f (0, 3, 0);
glVertex3f (0, 0, 0);
glVertex3f (0, 0, 3);
glEnd;

// буква X
glBegin (GL_LINES)
glVertex3f (3. 1,-0. 2, 0. 5);
glVertex3f (3. 1,0. 2, 0. 1);
glVertex3f (3. 1,-0. 2, 0. 1);
glVertex3f (3. 1,0. 2, 0. 5);
glEnd;

// буква Y
glBegin (GL_LINES);
glVertex3f (0. 0, 3. 1, 0. 0);
glVertex3f (0. 0, 3. 1, -0. 1);
glVertex3f (0. 0, 3. 1, 0. 0);
glVertex3f (0. 1, 3. 1, 0. 1);
glVertex3f (0. 0, 3. 1, 0. 0);
glVertex3f (-0. 1, 3. 1, 0. 1);
glEnd;

// буква Z
glBegin (GL_LINES);
glVertex3f (0. 1, -0. 1, 3. 1);
glVertex3f (-0. 1, -0. 1, 3. 1);
glVertex3f (0. 1, 0. 1, 3. 1);
glVertex3f (-0. 1, 0. 1, 3. 1);
glVertex3f (-0. 1, -0. 1, 3. 1);
glVertex3f (0. 1, 0. 1, 3. 1);
glEnd;

// Восстанавливаем значение текущего цвета
glColor3f (Color [1], Color [2], Color [3])

обраатите внимание, что оси рисуются зеленым цветом, а цветовые установ-запоминаются во вспомогательном массиве, по которому они восстанавливаются в конце работы процедуры.
Оси выходят из точки (0, 0, 0) и визуализируются лишь в положительном направлении. Проект находится в подкаталоге Ex20. В качестве основы взят пример glFrustum, в который добавлен вызов вышеописанной процедуры и убрано контурное отображение куба. Результат работы программы приведен на рис. 3. 14. Видно, что пространство передается некорректно, куб полностью загораживает оси координат, хотя оси должны протыкать грани куба.

Рис. 3. 14. Без использования буфера глубины пространство сцены передается некорректно

В следующем примере, проекте из подкаталога Ex21, та же сцена выводится верно (рис. 3. 15).

Рис. 3. 15. Теперь правильно: оси протыкают грани куба

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

glEnable (GL_DEPTH_TEST); // включаем режим тестирования глубины

Код сцены начинается с очистки двух буферов: буфера кадра и буфера глубины:

glClear (GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // + буфер глубины

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

Замечание
О содержимом буфера глубины мы будем говорить еще неоднократно

С буфером глубины связаны две команды: glDepthFunc И glDepthRange. Хоть они применяются довольно редко, представление о них иметь не помешает. Первая из этих команд задает правило, по которому происходит сравнение значения оконного Z перед выводом пиксела. По умолчанию установлено значение GL_LESS — выводить на экран точки с минимальным значением оконной Z. Остальные значения приводят чаще всего к тому, что вообще ничего не будет выведено.
Вторая команда задает распределение оконной координаты Z при переводе из нормализованных координат в оконные. На рис. 3. 16 приведен результат работы программы (проект из подкаталога Ex22), где такое распределение установлено в обратное принятому по умолчанию:

glDepthRange (1, 0);

Рис. 3. 16. В любой момент можно увидеть то, что скрыто от глаза наблюдателя

Источник света
Предыдущие примеры вряд ли могут удовлетворить кого-либо в силу своей невыразительности. Рисуемый кубик скорее угадывается, все грани покрыты монотонным цветом, за которым теряется пространство. Теперь мы подо-шли к тому, чтобы увеличить реализм получаемых построений.
Вот в следующем примере кубик рисуется более реалистично — рис. 3. 17, проект из подкаталога Ex23.

Рис. 3. 17. Нa сцене появился источник света

При создании окна включается источник света:

glEnable (GL_LIGHTING); // разрешаем работу с освещенностью
glEnable(GL_LIGHTO); // включаем источник света

Это минимальные действия для включения источника света. Теперь в сцене присутствует один источник света с именем 0.
При необходимости можно «установить» несколько источников, для этого точно так же используется команда glEnable, например:

glEnable (GL_LIGHT1); // включаем источник света 1

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

glBegin (GL_QUADS);
glNormal3f(0. 0, 0. 0, 1. 0);
glVertex3f(1. 0, 1. 0, 1. 0);
glVertex3f(-1. 0, 1. 0, 1. 0);
glVertex3f(-1. 0, -1. 0, 1. 0);
glVertex3f(1. 0, -1. 0, 1. 0);
glEnd;

glBegin(GL_QUADS);
glNormal3f(-1. 0, 0. 0, 0. 0);
glVertex3f(-1. 0, 1. 0, 1. 0);
glVertex3f(-1. 0, 1. 0, -1. 0);
glVertex3f <-1. 0, -1. 0, -1. 0)
glVertex3f(-1. 0,-1. 0,1. 0);
glEnd;

glBegin(GL_QUADS);
glNormal3f(0. 0, 1. 0, 0. 0);
glVertex3f(-1. 0, 1. 0, -1. 0);
glVertex3f(-1. 0, 1. 0, 1. 0);
glVertex3f(1. 0, 1. 0, 1. 0);
glVertex3f(1. 0, 1. 0, -1. 0);
glEnd;

Теперь поговорим о некоторых деталях.
выясним какое максимальное число источников света мы можем использовать. Проект из подкаталога Ex24 в заголовке окна выводит это получаемое при создании окна, с помощью команды glGet:

glGetintegerv (GL_MAX_LIGHTS, @wrk);
Caption: = intToStr (wrk);

Вектора нормалей строятся перпендикулярно каждой стороне куба. В силу того, что наш кубик строится вокруг точки (0, 0, 0), аргументы glNomal3f в данном случае совпадают с точкой пересечения диагоналей каждой грани куба. Чтобы уяснить, насколько важно верно задавать вектор нормали, посмотрите пример, располагающийся в подкаталоге Ex25.
Здесь рисуется только передняя грань кубика, тремя клавишами управления курсором можно менять координаты вектора нормали. При этом меняется вид картинки: освещенность площадки передается в соответствии с текущим значением вектора нормали.
Вектор нормали не обязательно должен исходить именно из середины площадки, достаточно того, чтобы он был параллелен действительному вектору нормали к площадке. Это иллюстрирует проект из подкаталога Ex26, где теми же клавишами можно передвигать площадку в пространстве, а вектор нормали задается единожды при создании окна:

glNormal3f(-1. 0, 0. 0, 0. 0);

Где бы ни располагалась в пространстве площадка, она освещается единообразно.

Замечание
По умолчанию источник света располагается где-то в бесконечности, поэтому освещенность площадки не меняется вместе с ее перемещением.

В примере из подкаталога Ex27 клавишей задается поворот площадки в пространстве вокруг оси Y, чтобы можно было взглянуть на площадку с разных точек зрения. Если мы смотрим на заднюю сторону площадки, то видим, что она окрашивается черным цветом. В некоторых ситуациях необходимо, чтобы при таком положении точки зрения наблюдателя примитив не отображался вообще, например, при воспроизведении объектов, образующих замкнутый объем, нет необходимости тратить время на воспроизведение примитивов, заведомо нам не видимых, раз они повернуты к нам задней стороной. Запомните, как это делается, разобрав проект из подкаталога Ex28.
Здесь площадка не рисуется, если повернута к наблюдателю задней стороной. Для этого необходимо включить отсечения задних сторон многоугольников:

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

Объемные объекты
Подчеркну еще раз, что для объемных построений используются все те же десять изученных нами во второй главе примитивов.
В следующем примере строится объемный объект на базе тестовой фигуры, знакомой нам по первой главе (рис. 3. 18). Проект расположен в подкаталоге Ex29.

Рис. 3. 18. Теперь деталь стала объемной

Шестнадцать отдельных многоугольников образуют то, что наблюдателю представляется единым объемным объектом.
В проекте введена переменная, ответственная за режим воспроизведения детали:

mode
(POINT, LINE, FILL) = LINE;

В зависимости от значения этой переменной устанавливается режим рисования многоугольников:

case mode of
POINT: glPolygonMode (GL_FRONT_AND_BACK, GL_POINT);
LINE: glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
FILL: glPolygonMode (GL_FRONT_AND BACK, GL FILL);
end;

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

(GL COLOR MATERIAL);

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

Замечание
Необходимо сказать, что при рисовании очень больших объектов происходят сильные искажения, связанные с перспективой, поэтому рекомендуется объекты масштабировать таким образом, чтобы их линейные характеристики лежали в пределах 100.

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

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(400, 400);
glutInitWindowPosition(50, 50);
glutCreateWindow(argv[0]);

Программисты, работающие с Delphi, также могут пользоваться этой библиотекой. В приложении я привожу адрес, по которому можно получить заголовочный файл для подключения библиотеки Однако при программировании на Delphi мы не получим независимости от операционной системы, а из команд библиотеки чаще всего программистов интересует только набор функций для построения некоторых объемных фигур Поэтому вместо нестандартной библиотеки я предпочитаю использовать модуль DGLUT pas — перенос на Delphi исходных файлов библиотеки glut Во многих последующих примерах будут встречаться обращения к этоМУ модулю, соответствующие команды начинаются с префикса glut Например, для рисования куба с единичной длиной ребра вместо двух десятков строк теперь достаточно одной

glutSolidCube (1. 0);

Ломимо куба, модуль (и библиотека) содержит команды воспроизведения сферы, тора, конуса и некоторых правильных многогранников, таких как раэдр и додекаэдр Есть здесь и команда для рисования классического объекта для тестовых программ машинной графики — чайника. Правильные многогранники строятся как совокупность многоугольников, о том, как создаются остальные объекты этой библиотеки, мы поговорим позднее. Для получения представления о модуле DGLUT разберите несложный пример, проект из подкаталога Ех30. По выбору пользователя можно построить любой объект модуля в одном из режимов точками, линиями или сплошной поверхностью Для задания типа объекта и режима воспроизведения введены переменные типа «перечисление»

mode: (POINT, LINE, FILL) = FILL;
glutob]: (CUBE, SPHERE, CONE, TORUS, DODECAHEDRON,
ICOSAHEDRON, TETRAHEDRON, TEAPOT) = CUBE;

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

case mode of
POINT glPolygonMode (GL_FRONT_AND_BACK, GL_POINT);
LINE: glPolygonMode (GL_FRONT_ANDJ3ACK, GL_LINE);
FILL: glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
end;
case glutobj of
TEAPOT: glutSolidTeapot (1. 5);
CUBE: glutSolidCube (1. 5);
SPHERE: glutSolidSphere (1. 5, 20, 20);
CONE: glutSolidCone (0. 5, 1. 5. 20, 20);
TORUS: glutSolidTorus (0. 5, 1 5, 20, 20);
DODECAHEDRON: glutSolidDodecahedron;
ICOSAHEDRON: glutSolidIcosahedron;
TETRAHEDRON: glutSolidTetrahedron; end,

Нажимая на первые три цифровые клавиши, можно задавать режим, четвертая клавиша позволяет переключать тип объектаЖ

If Key = 52 then begin
inc (glutobj); // установить следующее значение
If glutobj > High (glutob]) then glutobj: = Low (glutobj);
InvalidateRect(Handle, nil, False);

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

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

Для конуса задаются радиус основания, высота и пара чисел, задающих гладкость построений
У тора параметры следующие внутренний и внешний радиусы и все те же два числа, задающих, насколько плавной будет поверхность рисуемой фигуры
В этом примере режим задается способом, знакомым нам по предыдущим примерам — командой glPolygonMode Для каркасного изображения объектов модуль располагает серией команд, аналогичных тем, что мы используем в этом примере, но с приставкой glutWire вместо glutSolid
Следующий пример показывает, как можно манипулировать объектами исходного набора, чтобы получить другие объемные фигуры В подкаталоге Ex31 содержится проект, представляющий модификацию классической программы из SDK B программе моделируется рука робота в виде двух параллелепипедов (рис 3 19).

Рис. 3. 19. В программе рукой манипулятора можно управлять

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

glTranslatef (-1. 0, 0. 0, 0. 0);
glRotatef (shoulder, 0. 0, 0. 0, 1 0);
glTranslatef (1 0, 0 0, 0. 0);
glPushMatrix; // запомнить текущий масштаб
glScalef (2 0, 0. 4, 1. 0); // для вытягивания куба в параллелепипед
glutSolidCube(1. 0); // в действительности — параллелепипед
alPopMatrix; // вернуть обычный масштаб
// вторая часть руки робота
glTranslatef (1. 0, 0. 0, 0. 0);
glRotatef (elbow, 0. 0, 0. 0, 1. 0);
glTranslatef (1. 0, 0. 0, 0. 0);
glPushMatrix;
glScalef (2. 0, 0. 4, 1. 0);
glutSolidCube(1. 0);
glPopMatrix;

В программе клавишами , , и можно задавать относительное положение «суставов» руки, эмулируя управление рукой робота Первой цифровой клавишей можно менять режим воспроизведения, клавишами управления курсором можно менять положение точки зрения наблюдателя

Илон Маск рекомендует:  Что такое код getinputstate

Quadric-объекты библиотеки glu
Библиотека glu предоставляет набор команд, без которых, в принципе, можно обойтись, но с их использованием решение многих задач сильно упрощается.
Для упрощения построений некоторых поверхностей второго порядка вводится серия команд, основой которых являются квадратичные (quadric) объекты — собственный тип этой библиотеки
Как и раньше, познакомимся с новой темой на примерах, для чего откройте проект gluDisk dpr в подкаталоге Ex32 To, что получается в результате работы программы, изображено на рис 3.20.

Рис. 3.20. Проект иллюстрирует использование quadnc-объектов

Пример этот является переложением на Delphi классической программы из набора примеров OpenGL SDK Прежде всего обратите внимание, что видовые параметры задаются в зависимости от пропорций окна, чтобы при любых значениях габаритов окна сцена не выходила за его пределы:

ClientW > then glOrtho (0. 0, 50. 0, 0. 0, 50. 0 * ClientHeight / ClientWidth,
-1. 0, 1. 0)
else glOrtho (0. 0, 50. 0 * ClientWidth / ClientHeight, 0 0, 50 0,
-1. 0, 1. 0);

Для работы с командами библиотеки glu вводится переменная специального:

При создании окна вызываем команду, создающую quadnc-объект, без этого действия обращение к объекту приведет к исключениям:

Режим воспроизведения объекта задается командой giuQuadncDrawstyie, первым аргументом команды указывается имя quadnc-объекта По умолчанию стиль задается сплошным, так что чаще всего нет надобности вызывать эту команду Она также является аналогом команды giPoiygonMode и всегда может быть ею заменена, за исключением случая использования с аргументом GLU_SILHOUETTE При установлении такого режима рисуется только граничный контур фигуры, отдельные сектора не рисуются, как это сделано при рисовании третьего диска рассматриваемого примера
Диск строится с помощью команды giuDisk Помимо имени объекта ее аргументами задаются внутренний и внешний радиусы, затем два числа, задающих число разбиений диска по оси Z и число концентрических окружностей при разбиении диска Смысл этих величин хорошо виден при каркасном режиме воспроизведения, когда в получающейся паутинке хорошо видны отдельные сектора диска
В этом примере показано, как нарисовать дугу диска, сектор, «кусок пирога» Это делается с помощью команды giuPartiaiDisk, первые пять napavseT-ров которой полностью аналогичны параметрам giuDisk, а остальные задают начальный угол и угол развертки Углы задаются в градусах По окончании работы память, используемую quadnc-объектами, необходимо освобождать Сделать это нужно до освобождения контекста воспроизведения:

gluDeleteQuadric (quadObj); // удаление объекта
wglMakeCurrent (О, О);
wglDeleteContext(hrc) ;

Замечание
Согласно файлу справки, обращение к удаленному объекту невозможно Может быть, вы столкнетесь здесь с тем же неожиданным явлением что и я поставьте вызов этой команды сразу же за его созданием и запустите программу У меня не возникло никаких исключений, что меня удивило и озадачило Тем не менее, я все же порекомендую не забывать удалять quadnc-объекты.

Библиотека glu имеет средства работы с возможными исключениями, для этого предназначена команда giuQuadncCaiiback Первый аргумент, как обычно, имя quadnc-объекта, вторым аргументом может быть только константа GLU_ERROR, третий аргумент — адрес функции, вызываемой при исключении.
В заголовочном файле отсутствует описание константы GLU_ERROR, вместо нее можно использовать присутствующую там константу GLU_TESS_ERROR, либо самому определить такую константу.


Замечание
Точнее, описание константы не отсутствует, а закомментировано.

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

procedure FNGLUError;
begin
ShowMessage (‘Ошибка при работе с quadric-объектом1’),
end,
const
GLUJiRROR = GLU TESS ERROR;

Замечание
Процедура, используемая в таких ситуациях, не может присутствовать в описании класса. При описании подобных процедур рекомендую использовать ключевое слово stdcall.

Разу после создания quadnc-объекта задается функция, вызываемая при исключениях, здесь передаем адрес пользовательской процедуры:

WuQuadricCallbackfquadOb], GLU_ERROR, @FNGLUError);

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

gluQuadricDrawStyle (quadObn, GL_LINE);

Замечание
Исключение при этом не снимается, так что после этого сообщения появляется сообщение операционной системы об аварийной ситуации Можно использовать защищенный режим, чтобы такого сообщения не появлялось.

Если при обращении к команде gluQuadricCallback в качестве адреса функции задать значение, равное nil, возникающие исключения будут сниматься, а выполнение программы будет продолжаться со строки, следующей за строкой, породившей исключение.
Помимо диска, библиотека располагает командами рисования еще некоторых объектов, например, команда gluCylinder предназначена для построения цилиндров и конусов. Параметры команды следующие: имя объекта, радиусы основания и верхней части и два числа, задающих частоту разбиения. Команда gluSphere, как ясно из ее имени, упрощает построение сферы У нее четыре аргумента, второй аргумент является радиусом сферы, остальные параметры традиционны для этой серии команд.
Все объекты библиотеки glu я собрал в одном проекте, располагающемся в подкаталоге Ex34. Этот пример аналогичен примеру на объекты библиотеки glut: по выбору пользователя рисуется объект заданного типа.
Здесь также демонстрируется действие команды gluquadricOrientation, задающей направление нормали к поверхности объекта, внутрь или наружу Еще можно выяснить, как сказывается на виде объекта работа команды gluQuadricNormals, определяющей, строятся ли нормали для каждой вершины, для всего сегмента либо вообще не строятся.
Нажимая на первые четыре цифровые клавиши, можно задавать значения параметров: режим, тип объекта, ориентацию нормалей и правило их построения.
В проекте введены соответствующие перечисления:

mode: ‘POINT, LINE, FILL, SILHOUETTE) = FILL;
gluobj: (SPHERE, CONE, CYLINDER, DISK) = SPHERE;
orientation: (OUTS > (NONE, FLAT, SMOOTH) = SMOOTH;

Первым аргументом всех команд является имя quadric-объекта, все возможные константы перечисляются в конструкциях case:

case mode of // режим воспроизведения
POINT: gluQuadricDrawStyle (quadObj, GLU_POINT); // точки
LINE: gluQuadricDrawStyle (quadOb], GLU_LINE); // линии
FILL: gluQuadricDrawStyle (quadObj, GLU_FILL); // сплошным
SILHOUETTE: gluQuadricDrawStyle (quadObj, GLU_SILHOUETTE); // контур
end;
case orientation of // направление нормалей
INSIDE: gluQuadricOrientation (quadObj, GLU_INSIDE); // внутрь
OUTSIDE: gluQuadricOrientation (quadOb], GLU_OUTSIDE); // наружу
end;
case normals of // правило построения нормалей
NONE: gluQuadricNormals (quadObj, GLU_NONE); // не строить
FLAT: gluQuadricNormals (quadObj, GLU_FLAT); // для сегмента
SMOOTH: gluQuadricNormals (quadObj, GLU_SMOOTH); // для каждой вершины
end;
case gluob3 of// тип объекта
SPHERE: gluSphere (quadOb], 1. 5, 10, 10); // сфера
CONE: gluCylinder (quadObj, 0. 0, 1. 0, 1. 5, 10, 10); // конус
CYLINDER: gluCylinder (quadOb3, 1. 0, 1. 0, 1. 5, 10, 10) // цилиндр
DISK: gluDisk (quadObj, 0. 0, 1. 5, 10, 5); // диск
end;

На рис. 3. 21 приводится одна из возможных картинок, получаемых с помощью этого проекта.

Рис. 3. 21. С помощью этого примера вы можете познакомиться со всеми quadric-объектами Первым делом

Следующие примеры этого раздела также являются переложением на Delphi классических примеров из SDK. Первым делом посмотрите проект из подкаталога Ех35 — вступительный пример к нескольким следующим проектам и одновременно простейший пример на использование объектов библиотеки glu.
Здесь просто рисуется сфера, модель звезды, наблюдатель находится над полюсом (рис. 3.22).

Рис. 3.22. Упрощенная модель звезды

Проект следующего примера располагается в подкаталоге Ех32, а экранная форма приложения приведена на рис. 3.23.
Здесь моделируется уже планетарная система, клавишами управления курсором можно задавать положение планеты относительно звезды и угол поворота ее вокруг собственной оси.

Рис. 3.23. Добавилась планета

Замечание
Начинающим я рекомендую хорошенько разобраться с этим примером, здесь впервые для нас в пространстве присутствует несколько объектов, располагающихся независимо друг от друга.

На базе одного quadric-объекта можно строить сколько угодно фигур; не обязательно для каждой из них создавать собственный объект, если параметры всех их идентичны.
В этом примере на базе одного объекта рисуется две сферы:

// рисуем солнце
gluSphere (quadOb], 1.0, 15, 10);
// рисуем маленькую планету
glRotatef (year, 0.0, 1.0, 0.0);
glTranslatef (2.0, 0.0, 0.0);
glRotatef (day, 0.0, 1.0, 0.0);
gluSphere (quadOb], 0.2, 10, 10);

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

Рис. 3.24. В этом примере смотрим на систему с другой точки зрения

WPush слУжит хорошей иллюстрацией на использование команд glPushMatrix и glPopMatrix: солнце и планета поворачиваются по отдельно-
°тносительно базовой системы координат:

glPushMatnx;
//Рисуем солнце
glpUshMatrix;
glRotatef (90. 0, 1. 0, 0. 0, 0. 0); // поворачиваем прямо
gluSphere (quadObj, 1. 0, 15, 10);
glPopMatrix;
// рисуем маленькую планету
glRotatef (year, 0. 0, 1. 0, 0. 0);
glTranslatef (2. 0, 0. 0, 0. 0);
glRotatef (day, 0. 0, 1. 0, 0. 0);
glRotatef (90. 0, 1. 0, 0. 0, 0. 0); // поворачиваем прямо
gluSphere (quadObj, 0. 2, 10, 10);
glPopMatrix;

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

Рис. 3. 25. Совсем несложно получить полусферу или четверть сферы

Для вырезки части пространства используется новая для нас команда glclipplane. Для вырезки можно использовать несколько плоскостей, эта команда идентифицирует используемые плоскости. Первый аргумент -символическое имя плоскости вырезки, второй — адрес массива, задающего эту плоскость. Символические имена начинаются с CL__CLip_PLANE, дальше следует цифра, нумерация начинается с нуля.
При каждой вырезке отсекается полупространство, массив задает вектор, определяющий остающуюся часть. Вектор не должен быть обязательно перпендикулярен осям, как в нашем примере.
Для получения четверти сферы проделываем две вырезки: сначала обрезаем нижнее полупространство, удаляя все вершины с отрицательным значением координаты Y, затем отсекаем левое полупространство, т. e. удаляются вершины с отрицательным значением координаты X:

const eqn: Array [0.. 3] of GLdouble = (0. 0, 1. 0, 0. 0, 0. 0);
eqn2: Array [0.. 3] of GLdouble = (1. 0, 0. 0, 0. 0, 0. 0);
// удаление нижней половины, для у и служат для приближения/удаления модели. Пробелом и клавишей ввода можно задавать различные режимы воспроизведения.В примере используются объекты библиотеки glu и модуля DGLUT. Нет смысла его подробно разбирать, остановимся только на некоторых моментах. напоминаю, что для воспроизведения объектов с различным пространстнным положением перед собственно воспроизведением каждого из них система координат перемещается в нужную точку в пространстве. Для удобства ориентирования в пространстве перед каждой трансформацией системы координат текущую матрицу (систему координат) запоминаем вызовом команды glPushMatrix, затем возвращаемся в эту систему координат вызовом glPopMatrix. Это будет выполняться быстрее, чем обратные переносы но было бы лучше вычислять координаты следующего объекта в текущей системе координат.Для разнообразия передние квадратные фары автомобиля строятся с использованием команды gluDisk: если последние два аргумента команды задать равными четырем, строится ромб, перед построением система координат поворачивается:

glRotatef(45, 0. 0, 0. 0, 1. 0);
gluDisk (quadObj, 0. 0, 0. 1, 4, 4);

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

glRotatef(180, 1. 0, 0. 0, 0. 0); glRectf (0. 1, 0. 1, -0. 1, -0. 1);

Вместо этого можно было бы просто задать нужный вектор нормали:

glNormal3f (0, 0, -1);

Эту программу вы можете взять в качестве шаблона, чтобы поупражняться построениях. Проектировать системы из множества объектов без использования редакторов может оказаться трудным делом для новичков. Могу посоветовать воспользоваться приведенной выше процедурой построения осей текущей системы координат: обращайтесь к ней каждый раз, когда необходимо выяснить, «где я сейчас нахожусь».

Сплайны и поверхности Безье
Мы теперь можем без труда рисовать кубики, сферы, цилиндры и многие другие аналогичные фигуры, но, конечно, этого недостаточно, и рано или поздно возникнет потребность нарисовать поверхность произвольной формы. В этом разделе мы узнаем, как это сделать.
Подход, предлагаемый библиотекой OpenGL для изображения криволинейных поверхностей, традиционен для компьютерной графики: задаются координаты небольшого числа опорных точек, определяющих вид искомой поверхности. В зависимости от способа расчета опорные точки могут лежать наполучаемой поверхности, а могут и не располагаться на ней.
Сглаживающие поверхности называются сплайнами. Есть много способов построения сплайнов, из наиболее распространенных нас будут интересовать только два: кривые Безье (Bezier curves, в некоторых книгах называются «сплайны Бежье») и В-сплайны (base-splines, базовые сплайны). Изучение этой темы начнем с простейшего, с двумерных кривых.
Операционная система располагает функциями GDI, позволяющими строить кривые Безье.
В примере — проекте из подкаталога Ex40 — модуль OpenGL не используется, это пример как раз на использование функций GDI. Он является модификацией одного из проектов первой главы, но теперь рисуется не квадрат и круг, а кривая Безье по четырем точкам — рис. 3. 27.

Рис. 3. 27. Функции GDI позволяют строить кривые Безье

вершины заданы в массиве четырех величин типа Tpoint, этот тип в модуле windows. pas:

Const
Points: Array [0.. 3] of TPoint =
((x: 5; y: 5), (x: 20; y: 70), (x: 80; y: 15), (x: 100; y: 90));

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

PolyBezier (dc, Points, 4);

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

For i: = 0 to 3 do
Ellipse (dc, Points [i]. x — 3, Points [i]. y — 3,
Points [i]. x + 3, Points [i]. y + 3);

В случае четырех опорных точек кривая всегда будет начинаться точно в первой точке и приходить в последнюю точку. Если их переставить местами, вид кривой не изменится, что не произойдет и если переставить местами вторую и третью опорные точки. Опорные точки могут располагаться в пространстве произвольно, т e. не требуется, чтобы они, например, равномерно располагались по интервалу. Если необходимо продолжить кривую, добавляется по три точки для каждого последующего сегмента.
Теперь посмотрим, как нарисовать кривую Безье, используя команды OpenGL. Откройте проект из подкаталога Ex41. Получаемая кривая показана на рис. 3. 28.

Рис. 3. 28. Кривая Безье, построенная с помощью функций библиотеки OpenGL

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

glMaplf (GL_MAPl_VERTEX_3, 0. 0, 1. 0, 3, 4, @ctrlpoints);
glEnable (GL_MAPl_VERTEX_3);

Первый параметр команды glMapi — символическая константа, значение GL_MAPi_VERTEx_3 соответствует случаю, когда каждая контрольная точка представляет собой набор трех вещественных чисел одинарной точности, т. e. координаты точки Значения второго и третьего аргументов команды определяют конечные точки интервала предварительного образа рассчитываемой кривой. Величины ноль и один для них являются обычно используемыми, подробнее мы рассмотрим эти аргументы чуть ниже.
Четвертый параметр команды, «большой шаг», задает, сколько чисел содержится в считываемой порции данных. Как говорится в документации, контрольные точки могут содержаться в произвольных структурах данных, лишь бы их значения располагались в памяти друг за другом.
Последние два параметра команды — число опорных точек и указатель на массив опорных точек.
Для построения кривой можно использовать точки или отрезки’ вместо команды, задающей вершину, вызывается команда glEvalcoord, возвращающая координаты рассчитанной кривой»

glBegln(GL__LINE_STRIP);
For i: = 0 to 30 do
glEvalCoordlf (i / 30. 0);
glEnd;

Аргумент команды — значение координаты u. В данном примере соединяются отрезками тридцать точек, равномерно расположенных на кривой Теперь выясним правила отображения интервала Если в этом примере третий параметр команды glMaplf задать равным двум, то на экране получим половину первоначальной кривой. Для получения полной кривой надо при воспроизведении взять интервал в два раза больший:

glBegln(GL_LINE_STRIP); For i: = 0 to 60 do
glEvalCoordlf(i / 30. 0); glEnd;

Если же этот параметр задать равным 0. 5, то при отображении интервала с u в пределах от нуля до единицы получаемая кривая не будет останавливаться на последней опорной точке, а экстраполироваться дальше. Если в этом нет необходимости, конечное значение параметра цикла надо взять равным 15 Чтобы действительно разобраться в подобных вопросах, надо обязательно попрактиковаться, посмотреть, какие кривые строятся для различных набо-Ров опорных точек. Здесь вам поможет проект из подкаталога Ex42, где cpe-Ди четырех опорных точек имеется одна выделенная. Клавишами управления курсором можно менять положение выделенной точки, при нажатии на Пробел выделенной становится следующая точка набора. Выделенная точка Рисуется красным Обратите внимание, что простой перерисовки окна при изменении в массиве опорных точек недостаточно, необходимо заново обратиться к командам, «заряжающим» вычислитель, чтобы пересчитать кривую:

If Key = VK_SPACE then begln
// выделенной становится следующая точка набора
selpoint: = selpoint + 1;
If selpoint > High (selpoint) then selpoint: = Low (selpoint);
InvalidateRect(Handle, nil, False);
end;
If Key = VK_LEFT then begln
// сдвигаем выделенную точку влево
ctrlpoints [selpoint, 0]: = ctrlpoints [selpoint, 0] — 0. 1;
// пересчитываем кривую по измененному массиву опорных точек
glMaplf(GL_MAPl_VERTEX_3, 0. 0, 1. 0, 3, 4, @ctrlpoints);
glEnable (GL_MAPl_VERTEX__3);
InvalidateRect(Handle, nil, False); // перерисовка окна
end;

С помощью этого примитивного редактора можно построить замысловатые фигуры и заодно получить представление о кривых Безье.

Замечание
Не получится расположить все четыре точки на кривой, если только это не линейная функция.

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

wrk: Array [0.. 1] of GLfloat;
begln
glGetMapfv (GL_MAPl_VERTEX_3, Caption: = FloatToStr (wrk[0]
GL_DOMAIN, @wrk); + ‘, ‘ + FloatToStr(wrk[l]);

Из файла справки вы можете узнать, как получить значения всех остальных параметров вычислителя.
Построить кривую можно и другим способом. Посмотрим пример из подкаталога Ex44, отличающийся от предыдущего примера на кривые Безье следующим: сразу после включения вычислителя вызывается команда, строящая одномерную сетку, т. e. рассчитывающая координаты набора точек на интервале:

glMapGridlf (30, 0, 1);

Первый аргумент — количество подинтервалов, далее задается интервал по координате u. После того как сетка построена, вывод ее осуществляется одной командой:

glEvalMeshl (GL_LINE, 0, 30);

Первый аргумент — режим воспроизведения, отрезками или точками, остальные аргументы задают номера первой и последней точек рассчитанной сетки.
Получив представление о кривых, мы можем перейти к поверхностям Безье. Соответствующий пример располагается в подкаталоге Ex45, а результат работы программы показан на рис. 3. 29.

Рис. 3. 29. Классический пример на построение поверхности Безье

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

glMap2f (GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, @ctrlpoints);
glEnable (GL_MAP2_VERTEX_3);
glMapGrid2f (20, 0. 0, 1. 0, 20, 0. 0, 1. 0);

У команды glMap2f аргументов больше, чем у glMapif, но теперь нам легко понять их смысл. Первый аргумент — константа, определяющая тип рассчитываемых величин, в данном случае это координаты точек поверхности. Последний аргумент — указатель на массив контрольных точек поверхности.
Второй и третий параметры задают преобразования по координате u поверхности. Четвертый аргумент, как и в предыдущем случае, задает, сколько вещественных чисел содержится в порции данных — здесь мы сообщаем, что каждая точка задана тремя координатами. Пятый аргумент — количество точек в строке структуры, хранящей данные.
Следующие четыре аргумента имеют смысл, аналогичный предыдущим четырем, но задают параметры для второй координаты поверхности, координаты v. Значение восьмого аргумента стало равно двенадцати путем пере- множения количества чисел, задающих координаты одной вершины (3), на количество точек в строке массива (4).
После задания параметров вычислителя он включается вызовом команды giEnabie, после чего вызывается одна из пары команд, позволяющих построить поверхность — команда giMapGnd2f, рассчитывающая двумерную сетку. Первый и четвертый аргументы этой команды определяют количество разбиений по каждой из двух координат поверхности, остальные параметры имеют отношение к отображению интервалов.
Собственно изображение поверхности, двумерной сетки, осуществляется вызовом второй команды из тандема:

glEvalMesh2 (GL_FILL, 0, 20, О, 20);

Первый аргумент команды задает режим воспроизведения, следующие две пары чисел задают количество подинтервалов разбиения по каждой координате поверхности. В примере мы берем по 20 разбиений, столько же, сколько задано в команде giMapGnd2f, чтобы не выходить за пределы интервалов, но это не обязательно, можно брать и больше.
Если смысл параметров, связанных с отображением интервалов, вам кажется не совсем ясным, рекомендую вернуться к примеру по кривой Безье и еще раз его разобрать.
Замечу, что режим воспроизведения можно варьировать также с помощью Команды glPolygonMode.
Обратите внимание, что в рассматриваемом примере используется режим автоматического расчета нормалей к поверхности:

Без этого режима поверхность выглядит невыразительно, но его использование допускается только в случае поверхностей, рассчитываемых вычислителем.
В проекте введены два режима: один управляет тем, как строится поверхность — сплошной или линиями. Второй режим задает, надо ли выводить опорные точки. Нажимая на клавиши ввода и пробела, можно менять текущие значения этих режимов.
При нажатой кнопке мыши при движении курсора поверхность вращается по двум осям, что позволяет хорошо рассмотреть ее с разных позиций Для осуществления этого режима введен флаг, булевская переменная Down, которая принимает истинное значение при удерживаемой кнопке мыши, и две вспомогательные переменные, связанные с экранными координатами указателя.
В момент нажатия кнопки запоминаются координаты курсора; при движении курсора сцена поворачивается по двум осям на угол, величина которого зависит от разницы текущей и предыдущей координат курсора:

procedure TfrmGL.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
If Down then begin // кнопка мыши нажата
glRotatef (X — wrkX, 0.0, 1.0, 0.0);// поворот по горизонтали экрана
glRotatef (Y — wrkY, 1.0, 0.0, 0.0);// поворот по вертикали экрана
InvalidateRect(Handle, nil, False); // перерисовать экран
wrkX := X; // запоминаем координаты курсора
wrkY := Y;
end;
end;

Но при изменении размеров окна система координат возвращается в первоначальное положение.
Точно так же, как и в случае с кривой Безье, для воспроизведения поверхности можно воспользоваться командой glEvalCoord2f. Отрезки строятся по вершинам, предоставленным вычислителем:

9lBegm (GL_LINE_STRIP) ;
For i := 0 to 30 do For ] := 0 to 30 do
glEvalCoord2f (i / 30, j / 30);
glEnd;

Мы уже изучили множество параметров команды glEnable, задающей режимы воспроизведения и, в частности, позволяющей использовать вычислители. Я должен обязательно привести пример на команду, позволяющую определить, включен сейчас какой-либо режим или вычислитель — команду glisEnabled. Ее аргумент — константа, символ определяемого режима, а результат работы, согласно документации, имеет тип GLboolean. Мы знаем о небольшой проблеме Delphi, связанной с этим типом, так что для вас не должно стать откровением то, что обрабатывать возвращаемое значение мы будем как величину булевского типа.
Приведу здесь простой пример, в котором до и после включения режима окрашивания поверхностей выводится сообшение о том, доступен ли этот режим:

If glIsEnabled (GL_COLOR_MATERIAL) = TRUE
then ShowMessage (‘COLOR_MATERIAL is enabled’)
else ShowMessage (‘COLOR_MATERIAL is disabled’);

Соответствующий проект располагается в подкаталоге Ex47.
В этом разделе мы рассмотрели, как в OpenGL строятся кривые и поверхности Безье, позже рассмотрим еще несколько примеров на эту тему.

NURBS-поверхности
Один из классов В-сплайнов, рациональные В-сплайны, задаваемые на неравномерной сетке (Non-Uniform Rational B-Spline, NURBS), является стандартным для компьютерной графики способом определения параметрических кривых и поверхностей.
Библиотека glu предоставляет набор команд, позволяющих использовать этот класс поверхностей. Будем знакомиться с соответствующими командами непосредственно на примерах и начнем с проекта из подкаталога Ex48, где строится NURBS-кривая по тем же опорным точкам, что и в первом примере на кривую Безье. Вид получающейся кривой тоже ничем не отличается от кривой, приведенной на рис. 3. 28.
Для работы с NURBS-поверхностями в библиотеке glu имеются переменные специального типа, используемые для идентификации объектов:

При создании окна объект, как обычно, создается:

А в конце работы приложения память, занимаемая объектом, высвобождается:

Замечание
В отличие от quadric-объектов, удаленные NURBS-объекты действительно более недоступны для использования.

Для манипулирования свойствами таких объектов предусмотрена специальная команда
библиотеки, в примере она используется для задания гладкости поверхности и режима воспроизведения. Гладкость кривой или поверхности задается допуском дискретизации: чем меньше это число, тем поверхность получается более гладкой:

gluNurbsProperty (theNurb, GLU_SAMPLING_TOLERANCE, 25. 0);

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

gluNurbsCurve (theNurb, 8, @curveKnots, 3, @ctrlpoints, 4, GL_MAPl_VERTEX_3 );

Первый аргумент — имя NURBS-объекта, вторым аргументом задается количество параметрических узлов кривой, третий аргумент — указатель на массив, хранящий значения этих узлов. Следующий параметр — смещение, т. e. сколько вещественных чисел содержится в порции данных, далее следует указатель на массив опорных точек. Последний аргумент равен порядку (степени) кривой плюс единица.
В документации указывается, что количество узлов должно равняться количеству опорных точек плюс порядок кривой. Количество опорных точек достаточно взять на единицу больше степени кривой, квадратичная кривая однозначно определяется тремя точками, кубическая — четырьмя и т. д. Так что для построения квадратичной кривой необходимо задавать шесть узлов:

gluNurbsCurve (theNurb, 6, 8curveKnots, 3,
@ctrlpoints, 3, GL_MAPl_VERTEX_3);

Значения параметрических узлов в массиве или другой структуре, хранящей Данные, должны быть упорядочены по неубыванию, т. e. каждое значение не может быть меньше предыдущего. Обычно значения первой половины Узлов берутся равными нулю, значения второй половины задаются единичными, что соответствует неискаженной кривой, строящейся на всем интервале от первой до последней опорной точки. прежде чем мы сможем перейти к NURBS-поверхности, рекомендую самостоятельно поработать с этим примером, посмотреть на вид кривой при различных значениях параметров. Не забывайте о упорядоченности этих значе- ний, и приготовьтесь к тому, что их набор не может быть совершенно произвольным: в некоторых ситуациях кривая строиться не будет.
Теперь мы можем перейти к поверхностям, и начнем с проекта из подкаталога Ex49 — модификации классического примера на эту тему, в котором строится холмообразная NURBS-поверхность (рис. 3. 30).

Рис. 3. 30. Классический пример на использование NURBS-поверхности

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

Сделано это из-за того, что поверхность при построении масштабируется, и чтобы автоматически рассчитанные нормали «не уплыли», и используется этот режим.
Режим воспроизведения меняется по нажатию клавиши ввода, для его установки используется та же команда gluNurbsProperty:

If solid
then gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL)
else gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_OUTLINE_POLYGON);


Команда собственно построения заключена в специальные командные скобки:

gluBeglnSurface (theNurb); gluNurbsSurface (theNurb,
8, @knots,
8, @knots,
4 * 3,
3,
@ctrlpoxnts,
4, 4,
GL__MAP2_VERTEX_3);
gluEndSurface (theNurb);

В данном примере эти скобки не обязательны, поскольку они обрамляют единственную команду.
Если вы внимательно разобрали примеры предыдущего раздела, то большинство параметров команды gluNurbsSurface не должны вызывать вопросов, они аналогичны параметрам команд для построения поверхности Безье.
Так, шестой и седьмой параметры задают «большой шаг» по каждой координате, ассоциированной с поверхностью, т. e. сколько вещественных чисел содержится в строке структуры данных и сколько вещественных чисел задают отдельную точку. Восьмой параметр — адрес массива контрольных точек, а последним параметром задается символическая константа, определяющая тип возвращаемых значений; в данном случае ими являются трехмерные координаты вершин.
В примере задано шестнадцать контрольных точек, располагающихся равномерно по координатам X и Y в пределах квадрата, третья координата для точек, лежащих на границе квадрата, равна -3, для внутренних опорных точек эта координата равна 7. Таким способом массив заполняется для получения холмообразной поверхности. Если по заданным опорным точкам построить поверхность Безье, то увидим точно такой же холмик, как и в рассматриваемом примере.
Отличает NURBS-поверхности то, что параметризируемы. Так, предпоследние два параметра задают степень (порядок) поверхности по координатам u и v. Задаваемое число, как сказано в документации, должно быть на единицу больше требуемой степени. Для поверхности, кубической по этим координатам, число должно равняться 4, как в нашем примере. Порядок нельзя задавать совершенно произвольным, ниже мы разберем имеющиеся ограничения.
Второй параметр команды — количество узлов в параметризации по и-на-правлению, третьим параметром задается адрес массива, хранящего значения узлов. Третий и четвертый параметры команды имеют аналогичный смысл, но для второго направления.
Массивы узлов должны заполняться значениями, упорядоченными по не-Убыванию.
Как сказано в файле справки, при заданных uknot_count и vknot_count количествах узлов, uorder и vorder порядках количество опорных точек должно
ВНяться (uknot_count — uorder) x (vknot_count — vorder). Так что при изменении порядка по координатам необходимо подбирать и все остальные параметры поверхности. если вы хотите подробнее узнать о параметризации и ее параметрах, то обратитесь к литературе с подробным изложением теории NURBS-поверхностей.
Вданном примере используется один массив для задания узлов по обоим направлениям, а в проекте из подкаталога Ex50 используется два отдельных массива — для каждой координаты задаются свои значения узлов Поверхность строится не на всем интервале, а на части его, т. е. происходит подобие отсечения.
Чаще всего используются «кубические» NURBS-поверхности. Для иллюстрации построения поверхностей других порядков предназначен проекч из подкаталога Ех51, где берется «квадратичная» поверхность
Библиотека glu предоставляет также набор команд для вырезки кусков NURBS-поверхностей. Примером служит проект из подкаталога Ех52 Опорные точки поверхности располагаются в пространстве случайно, а за)ем из поверхности вырезается звездочка — рис. 3.31.

Рис. 3.31 Команды библиотеки glu позволяют строить невыпуклые многоугольники

Для хранения точек на границе вырезаемой области — звездочки — введен массив:

trim: Array [Q..2Q, 0..1] of GLfloat;

Массив заполняется координатами точек, лежащих поочередно на двух вложенных окружностях:

procedure InitTrim;
var
i: Integer;
begin
Eor i := 0 to 20 do
If Odd(i) then begin // нечетные точки — на внешней окружности
trim [i, 0] := 0.5 » cos (i * Pi / 10) + 0.5;
trim [i, 1] := 0.5 * sin (i * Pi / 10) + 0.5;
end
else begin // четные точки — на внутренней окружности
trim (if 0] := 0.25 * cos (i * Pi / 10) + 0.5;
trim [i, 1] := 0.25 * sin (i * Pi / 10) + 0.5;
end;
end;

Внутри операторных скобок построения NURBS-поверхности вслед за командой giuNurbsSurface задаем область вырезки:

gluBeginTrirr (theNurb) ;
gluPwlCurve (theNurb, 21, Qtnm, 2, GLU_MAP1_TRIM__21 ;
gluFndlrim (theNurb);

При задании области вырезки используются опять-таки специальные командные скобки, собственно область вырезки задается командой gluPwlCurve. Команда задает замкнутую кусочно-линейную кривую, часть NURBS-поверхности, не вошедшая внутрь этой области, не воспроизводится. Второй аргумент — количество точек Гранины, третий — адрес массива этих точек, четвертым параметром является символьная константа, задающая тип вырезки.
В примере при каждом нажатии пробела вызывается процедура инициализации поверхности, так что вид звездочки каждый раз получается разным,
случайным.
Следующий пример (подка!алог Ех53) также иллюстрирует вырезку NURBS-поверхности (рис. 3 32).

Рис. 3.32. Вырезка внутри NURBS-поверхности

Здесь вырезается внутренняя область поверхности, для чего задаются две линии, связанные с вырезкой:
gluBeginTrm (theNurb);

gluPwlCurve (theNurb, 5, SedgePt, 2, GLU_MAPl_TRIM_2);
gluEndTrim (theNurb);
gluBeginlrim (theNurb);
gluNurbsCurve (theNurb, 8, @curvcKnots, 2,
@curvo?t, 4, GLU_MAPl_TRIM_2);
gluPwlCurve (theNurb, 3, PpwlPt, 2, GLL_MAP1_TRIM_2) ;
SluEnaTrim (theNurb);

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

edgePt : Array [0..4, 0..1] of GLfloat = ((0.0, 0.0), (1.0, 0.0),
(1.0, 1.0), (0.0, 1.0), (0.0, 0.0));

Теперь следующая область вырезки ограничивает внутреннюю вырезаемую область. В примере эта область состоит из двух кривых; если это усложняет понимание программы, строку с вызовом giuNurbsCurve можете удалить, а массив граничных точек дополните замыкающей точкой:

pwlPt : Array [0..3, 0..1] of GLfloat = ((0.75, 0.5), (0.5, 0.25),
(0.25, 0.5), (0.75, 0.5));

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

Дисплейные списки
В этом разделе мы познакомимся с одним из важнейших понятий OpenGL Дисплейные списки представляют собой последовательность команд, запо-
минаемых для последующего вызова. Они подобны подпрограммам и являются удобным средством для кодирования больших систем со сложной иерархией.
Знакомство начнем с проекта из подкаталога Ех55, классического примера на эту тему Работа приложения особо не впечатляет: рисуется десять треугольников и отрезок (рис 3.33).

Рис. 3.33. Первый пример на использование дисплейных списков

В начале работы вызывается процедура инициализации, где создается (описывается) дисплейный список:

const
listName : GLUint = 1; // идентификатор списка
procedure init;
begin
glNewList (listName, GL_CCMPILE>; // начало описания списка
glColor3f (1.0, 0.0, 0.0);
glBegin (GL_TRIANGLES);
glVertex2f (0.0, 0.0);
glVertex2f (1.0, 0.0);
glVertex2f (0.0, 1.0);
glEnd;
glTranslatef (1.5, 0.0, 0.0);
glEndList; // конец описания списка
end;

Описание списка начинается с вызова команды giNewList, первым аргументом которой является целочисленный идентификатор — имя списка (в примере именем служит константа). Вторым аргументом команды является символическая константа; здесь мы указываем системе OpenGL на то, что список компилируется для возможного последующего использования. Все следующие до glEndList команды OpenGL и образуют дисплейный список, единую последовательность.
В данном примере в списке задается красный цвет примитивов, далее сле-Дуют команды для построения одного треугольника, заканчивается список Переносом системы координат по оси X.
В коде воспроизведения кадра цвет примитивов задается зеленым, затем десять раз вызывается описанный список.

glColor3f (0.0, 1.0, 0.0); // текущий цвет — зеленый
glPushMatrix; // запомнили систему координат
For i := 0 to 9 do // десять раз вызывается список с именем
glCallList (listName);
drawLine; // построить отрезок
glPopMatnx; // вернулись в запоганенную систему координат

Списки вызываются командой gl call List, единственным аргументом которой является идентификатор вызываемого списка.
В примере после каждого вызова списка номер один система координат смещается, поэтому на экране получается ряд треугольников, а отрезок рисуется вслед за последним треугольником. Хотя цвет примитивов задается зеленым, в списке он переустанавливается в красный, поэтому отрезок рисуется именно таким цветом.
Новички часто путаются, считая дисплейные списки полной аналогией подпрограмм. Отличает списки от подпрограмм то, что списки не имеют параметров, команды при вызове списка действуют точно так же, как и при создании списка. Если при описании списка используются переменные, то при вызове списка значения этих переменных никак не используются. Впрочем, ряд команд, помещенных в список, при вызове будут отрабатываться каждый раз заново, в документации приведен список таких команд.

Замечание
Я много раз встречал утверждение, что использование списков эффективно по причине того, что компиляция их приводит к более быстрому выполнению — в списке не запоминаются промежуточные команды. Если это и так, то ощутить «на глаз» ускорение работы приложения при использовании дисплейных списков как-то не получается. Тем не менее, я настоятельно рекомендую их использовать, это приводит к повышению читабельности и упрощению кода.

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

qlDeleteLists (listNamer 1);

Первый аргумент команды — имя первого удаляемого списка, второй параметр — количество удаляемых списков. Удаляются все списки, следующие за указанным. Стоит напомнить, что удалять списки необходимо до освобождения контекста воспроизведения.
В продолжение темы рассмотрим проект из подкаталога Ех56, Результат работы приложения приведен на рис. 3.34.

Рис 3.34. В этом примере цветовые настройки при использовании списка не сбиваются

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

]_i=,-cName := glGenLists (1);

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

glPopAttrib:
glNewList (listName, GL_COMPILE);
glPushAttrib (GL_CURRENT_BIT); // запомнили текущие атрибуты цвета
glColor3fv (@color_vector); // установили нужный цвет
glBegin (GL_TRIANGLES); // отдельный треугольник
glVertex2f (0.0, 0.0);
glVertex2f (1.0, 0.0);
glVertex2f (0.0, 1.0) ;
glEnd;
glTranslatef (1.57 0.0, 0.0);
glPopAttrib; // восстановили запомненные настройки
glSnaList;

Аналогично командам сохранения и восстановления системы координат, эта пара команд позволяет запоминать в отдельном стеке текущие настройки и возвращаться к ним. Аргумент команды glPushAttrib — символическая константа — задает, какие атрибуты будут запоминаться (значение в примере соответствует запоминанию цветовых настроек). Из документации можно Узнать, что существует целое множество возможных констант для этой команды; если вам покажется сложным разбираться со всеми ними, то пользуйтесь универсальной константой GLJ\LLJYTTRIB_BITS, в этом случае в стеке атрибутов запомнятся разом все текущие установки.
При перерисовке окна текущая система координат запоминается перед вы-Зовом списков и восстанавливается перед прорисовкой отрезка:

glPushMatrix;
For i := 0 to 9 do
glCallList (listName);
glPopMatrix;
drawLine;

Поэтому в примере отрезок рисуется поверх ряда треугольников.
Следующий пример, проект из подкаталога Ех57, является очень простой иллюстрацией на использование команды giisList, позволяющей узнать, существует ли в данный момент список с заданным именем. Аргумент команды — имя искомого списка, возвращает команда величину типа GLbooiean (как всегда в таких случаях, в Delphi обрабатываем результа! как булевскую переменную):

If giisList (listName)
then ShowMessage (‘Список с именем listName существует’)
else ShowMessage (‘Списка с именем listName не существует’);

Теперь перейдем к проекту из подкаталога Ех58. Результат работы программы отличается от первого примера этого раздела только тем, что рисуется восемь треугольников вместо десяти, но суть примера не в этом: здесь иллюстрируется то, что при описании списка можно использовать вызов других списков. В примере описано три списка:

const // идентификаторы используемых списков
listNamel : GLUint = 1;
listName2 : GLUint = 2;
listName3 : GLUint = 3;
procedure unit;
var
i : GLuint;
begin
glNewList (listNamel, GL_COMPILE); // список 1 — отдельный треугольник
glColorSf (1.0, 0.0, 0.0);
glBegin (GL_TRIANGLES);
glVertex2f (0.0, 0.0);
glVertex2f (1,0, 0.0);
glVertex2f (0.0, 1.0);
glEnd;
glTranslatef (1.5, 0.0, 0.0);
glEndList;

glNewList (listName2, GL_COMPILE); // список 2 — четыре треугольника
For i := 0 to 3 do
glCallList (listNamel); // вызывается прежде описанный список 1
glEndList;
glNewList
glCallList (listName21; // вызывается прежде описанный список 2
glEndList;
end;

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

const list : Array [0..1] of GLUint = (2, 3);

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

glCallLists (2, GL_INT, @list);

Первый аргумент команды — количество вызываемых списков, второй аргумент указывает смещение, третий аргумент — указатель на структуру, содержащую имена вызываемых списков. Смещение задано согласно описанию используемого массива из указанного в файле справки списка возможных констант.
В примере вначале вызывается список с именем 2 и строятся первые четыре треугольника. Затем вызывается список с именем 3, который состоит во вторичном вызове списка номер 2.

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

Последний пример этого раздела, проект из подкаталога Ех59, по своему Функциональному действию ничем не отличается от предыдущего, однако Для нас в нем интересна новая команда — giListBase. Смысл ее очень прост — она задает базовое смещение для имен вызываемых списков.
В примере это смещение задано единичным:

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

Ust : Array [0..1] of GLUint = (1, 2);

При вызове списков командой glCallLists к их именам
прибавляется заданное смещение.

Tess-обьекты
Мозаичные (tesselated — мозаичный) объекты являются последним нововведением библиотеки glu, предназначены они для упрощения построений нс-выпуклых многоугольников.
После того как мы изучили основные объекты библиотеки glu, нам будет несложно освоиться с собственно tess-объектами.
Рабога с ними в DeIplhi сопряжена с трудностями, возникающими из-за того, что раздел заголовочного файла, посвященный этим объектам, содержит несколько ошибок. Рис. 3. 39 демонстрирует работу примера проекта, расположенного в под каталоге Ex60.

Рис. 3. 35. Tess-объекты можно использовать для тех же целей, что и NURBS-поверхности

В программе определен особый тип для хранения координат одной точки:

type
TVector = Array [Q.. 2] of GLdouble;

Для уверенной работы команд данного раздела следует использовать именно гип удвоенной точности.
В процедуре инициализации описана переменная специального типа, введенного для работы с мозаичными объектами. Она инициализируется приблизительно так же, как другие объекты библиотеки glu, но, конечно, другой командой:

tobj: gluTesselator;
.
tobj: = gluNewTess;

Теперь посмотрим, как подготавливается список для левой фигуры, квадрата с треугольным отверстием внутри.
С помощью команды gluTessCallback задаются адреса процедур, вызываемых на различных этапах рисования tess-объекта, например:

При начале рисования объекта мы не планируем особых манипуляций, поэтому просто передаем адрес процедуры qlBegln.
Если же нам по сценарию потребуется выполнить какие-то дополнительные действия, необходимо описать пользовательскую несвязанную (не являющуюся частью описания класса) процедуру, обязательно указав ключевое слово stdCall, и передавать адрес эгой процедуры.
Синтаксис описания подобных процедур описан в справке по команде glutessCallback. Например, если мы хотим, чтобы перед воспроизведением примитива подавался бы звуковой сигнал, необходимо вызвать следующую процедуру:

procedure beglnCalIback(which GLenum); stdcall;
begln
MessageBeep (MB_OK);
glBegln(which);
end;
.
gluTessCallback(tobj, GLU_TESS_BEGIN, @ BeglnCallback);

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

gluTessCallback (tobj, GLU_TESS_VERTEX, @glVertexJdv); // вершина
glutessCallback(tobj, GLU_TESS_SND, 3glEnd); // конец рисования

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

procedure errorCallback(errorCode: GLenum); stdcall;
begln
ShowMessage (gluErrorString(errorCode));
end;
.
gluTessCallback(tobj, GLU_TESS_ERROR, @errorCallback]; //ошибка

Команда gluErrorstring возвращает строку с описанием возникшей ошибки. Описание ошибки выдается на русском языке (на языке локализации операционной системы), так что с диагностикой проблем не будет.
Координаты вершин квадрата и треугольника вырезки хранятся в структурах, единицей данных которых должна быть тройка вещественных чисел:

const
rect: Array [0.. 3] of TVector = ((50. 0, 50. 0, 0. 0),
(200. 0, 50. 0, 0. 0),
(200. 0, 200. 0, 0. 0),
(50. 0, 200. 0, 0. 0));
tri: Array[0.. 2] ofTVector= ((75. 0, 75. 0, 0. 0),
(125. 0, 175. 0, 0. 0),
(175. 0, 75. 0, 0. 0));

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

glNewList(l, GL_COMPILE);
glColor3f(0. 0, 0. 0, 1-0); //цвет-синий
gluTessBeglnPolygon (tobj, nil); // начался tess-многоугольник
gluTessBeglnContour(tobj); // внешний контур — квадрат
gluTessVertex(tobj, @rect[0], @rect[Q]); // вершиныквадрата
gluTessVertex(tobj, @rect[l], @rect[l]);
gluTessVertex(tob], @rect[2], @rect[2]);
gluTessVertex(tobj, @rect[3], @rect[3]);
gluTessEndContour (tobj);
glutessBeglnContour(tobj); // следующие контуры задают вырезки
gluTessVertex(tobj, @tri[0], @tri[0]); // треугольник
gluTessVertex(tobi, @tn[l], @tri[l]);
gluTessVertex(tobj, @tn[2], @tri[2]);
gluTessEndContour(tobj);
gluTessEndPolygon(tobj); // закончили с tess-многоугольником
glEndList;

При перерисовке окна просто вызывается список с именем 1.
После того как список описан, tess-объект можно удалить, это делается в конце процедуры инициализации:

Замечание
Обратите внимание: при вызове списка сами объекты библиотеки glu уже не используются. Точно так же вы можете удалять quadric-объекгы сразу после описания всех списков, использующих их.

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

procedure vertexCallback (vertex: Pointer); stdcall;
begln
glColor3f (random, random, random);
glVertex3dv (vertex);
end;
.
gluTessCallback

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

glNewList(2, GL_COMPILE);
gluTessBeglnPolygon (tobj, nil);
gluTessBeglnContour(tobj>; For i: = 0 to 20 do
gluTessVertex(tobj, @star [i], @star [i]);
gluTessEndContour (tobj);
gluTessEndPolygon (tob]);
glEndList;

Прототип одной из используемых команд мне пришлось переписать:

procedure gluTessBeginPolygon (tess: GLUtesselator; polygon_data:
Pointer); stdcall; external GLU32;

To, что записано в стандартном заголовочном файле, расходится с документацией и приводит к ошибке.
Мы рассмотрели простейший пример на использование tess-объектов, и надеюсь, вы смогли оценить, как удобно теперь становится рисовать невыпуклые многоугольники.
В качестве исследования можете задать контурный режим воспроизведения многоугольников, чтобы увидеть, как строятся получающиеся фигуры. Приведу еще несколько примеров на мозаичные объекгы. Подкаталог Ex6l содержит проект, где строится объект в виде звездочки (рис. 3. 36)

Илон Маск рекомендует:  Что такое код icap_fetch_event

Рис. 3. 36. Звездочка построена по координагам пяти вершин

На первый взгляд вам может показаться, что этот пример проще предылу-щего, однако это не так: звездочка задана координатами пяти вершин, перечисляемых в том порядке, в котором обычно дети рисуют пятиконечные звезды. Мне пришлось изрядно потрудиться, чтобы этот пример заработал.
Если попытаться в предыдущем примере задать вершины звезды именно по такому принципу, мы получим сообщение об ошибке «требуется составной ответный вызов» (на русском). To есть серверу надо подсказать, как обрабатывать ситуации с пересечениями границ контура.
Почерпнув всю требуемую информацию из файла справки, я написал следующую процедуру и задал ее адрес для вызова обработчика пересечения:

gluTessCallback(tODJ, GLU_TESS_COMBTNE, @combineCallback;;

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

gluTessPiOpertyiCobj, GLU_TESS_WINDING R’JLE, GLU_TESS_WINDING POSlTIVE);

Обратите внимание, что значение первой символической константы в программе переопределено, в файле opengl. pas это значение задано неверно.
Предоставляю вам еще один пример на эту тему, проект из подкаталога Ex62. Не стану разбирать этот пример, чтобы не испугать начинающих Если он вам покажется трудным, можете отложить его подробное изучение на потом — до того момента, когда вы будете чувствовать себя с OpenGL совсем уверенно.
По ходу подготовки этого примера я обнаружил массу ошибок все в том же заголовочном файле, так что мне самому он дался очень тяжело.

Таймеры и потоки
В этом разделе мы познакомимся с различными способами создания анимации. При этом нам придется сосредоточиться на вопросах, больше связанных с операционной системой, чем с OpenGL.
Для самых простейших задач вполне подходит использование обычного таймера Delphi.
Посмотрим проект из подкаталога Ex63. Это немного модифицированный пример второй главы, в котором вокруг курсора рисовалось облачко отрезков. Теперь это облачко постоянно меняется так, что картинка еще более напоминает бенгальский огонь.
Первое изменение в проекте заключается в том, что на форме появился таймер, обработчик которого заключается в том, что десять раз в секунду окно перерисовывается. При каждой перерисовке окна вокруг курсора рисуется множество отрезков со случайными координатами конца.
Важно обратить внимание на действия, позволяющие ускорить работу приложения. Вместо традиционного для проектов De]phi вызова метода Refresn окно перерисовывается вызовом функции API (мы уже убедились, насколько это значимо):

procedure TfrmGL. TimerlTimer;
begln
lnvalidateRect(Handle, nil, False);
end;

Цвет окна формы я намеренно задал ярко-синим, чтобы проиллюстрировать, как важно в таких приложениях бороться за каждую миллисекунду. Если в обработчике таймера поставить Refresh, то при каждой перерисовке окно мерцает, по нему пробегают синие полосы Впрочем, чожет случиться и так, что на вашем компьютере такие эффекты не возникают, тут многое зависит от характеристик «железа».
Также для ускорения работы в этом примере вместо canvas. Handle используется явно полученная ссылка на контекст устройства.
Код перерисовки окна максимально сокращен, в нем оставлено только то, что не переносится в другие обработчики. Включение режима штриховки и задание области вывода перемещены в обработчики onCreate и onResize Формы, соответственно. В таких приложениях также желательно использовать перехватчик сообщения WM_pAiNT вместо обработчика onPaint. Это сделано в следующем примере, проекте из подкаталога Ex64, в котором экране двигаются по кругу шесть кубиков (рис. 3. 37).

Рис. 3. 37. При работе программы кубики вращаются по кругу

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

For i: = 0 to 5 do begln
wrkX [i] : = sin (Pi / 3 * i);
wrkY [i] : = соз (Pi / 3 * j);
end;

Поворот всей системы с течением времени обеспечивается тем, что в обработчике таймера значение переменной, связанной с углом поворота, увеличивается, после чего экран перерисовывается:


Angle: = Angle + 1; // значение угла изменяется каждый «тик»
If Angle >= 60. 0 then Angle: = 0. 0;
InvalidateRect(Handle, nil, False);

Ядро кода воспроизведения кадра выглядит так:

glPushMatrix; // запомнили начальную систему координат
glRotatef(Angle, 0. 0, 0. 0, 1. 0); // поворот системы на угол
Angle по 2 <Цикл рисования шести кубиков>
For i: = С to 5 do begln glPushMatrix; // запомнили систему координат
glTranslatef(wrkX [i], wrkY [i], 0. 0); // перенос системы координат
glRotatef(-60 * i, 0. 0, 0. 0, 1. 0); // поворот кубика
glutSolidCube (0. 5);
glPopMatrix; end;
// рисуем кубик
// вернулись в точку
glPopMatrix; // вернулись в первоначальную систему координат

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

Замечание
Приведет это к тому, что кубики будут вращаться все быстрее и быстрее: теперь они при каждом тике таймера поворачиваются на все более увеличивающийся угол Angle относительно предыдущего положения Можно использовать более оптимальный прием в подобных примерах не использовать управляющую переменную (здесь это Angle>, не использовать команды glPushmatrix и glPopMatrix, а код кадра начинать с поворота на угол, константу. С точки зрения скорости это оптимально, но может нарушать сценарий кадра’ ведь при изменении размеров окна мы принудительно возвращаем объекты сцены в первоначальную систему координат, и кубики резко дергаются.

Использование системного таймера является самым простым решением задачи, но имеет очевидные недостатки. На маломощных компьютерах уже этот пример выводит кадры рывками, а если количество объектов перевалит за два десятка, то удовлетворительную скорость воспроизведения можно будет получить только на очень хороших машинах.
Следующий пример, проект из подкаталога Ex65, является продолжением предыдущего, здесь рисуется пятьдесят параллелепипедов (рис. 3. 38).

Рис. 3. 38. Эту систему мы возьмем в качестве тестовой для сравнения методов создания анимации

Вся система вращается по двум осям, по оси Y вращение происходит с удвоенной скоростью:

glRotatef(2 * Angle, 0. 0, 1. 0, O. 0>; // поворот по ось Y
glRotatef(Angle, 0. 0, 0. 0, 1. 0>; // поворот по оси Z

Интервал таймера я задал равным 50 миллисекунд, т. e. экран должен обновляться двадцать раз в секунду. Попробуем выяснить, сколько кадров В секунду выводится в действительности.
Это делается в проекте из подкаталога Ex66. Введены переменные, счетчики кадров и количество кадров в секунду:

newCount, frameCount, lastCount: LongInt;
fpsRate: GLfloat;

При запуске приложения инициализируем значения:

lastCount: = GetTickCount;
frameCount: = 0; ;

Функиия API GetTickCount возвращает количество миллисекунд, прошедших с начала сеанса работы операционной системы
При воспроизведении кадра определяем, прошла ли очередная секунда, и вычисляем крличество кадров, выведенных за эту секунду:

newCount: = GetTickCount; // текущее условное время
Inc(frameCount); // увеличиваем счетчик калроя
If (newCount — lastCount) > 1000 then begln // прошла секунда
// определяем количество выведенных кадров
fpsRate: = frameCount * 1000 / (newCount — lastCounU;
// выводим в заголовке количество кадров
Caption: = ‘FPS — ‘ + FloatToStr lastCount: = newCount; // запоминаем текущее время
frameCount: — 0; // обнуляем счетчик кадров
end;

Получающееся количество воспроизведенных кадров в секунду зависит от многих факторов, в первую очередь, конечно, от характеристик компьютера, и я не могу предсказать, какую цифру получите именно вы. Будет совсем неплохо, если эта цифра будет в районе двадцати.
Но если задаться целью увеличить скорость работы этого примера, то выяснится, что сделать это будет невозможно, независимо от характеристик компьютера. Сколь бы малым не задавать интервал таймера, выдаваемая частота воспроизведения не изменится, системный таймер в принципе не способен обрабатывать тики с интервалом менее пятидесяти миллисекунд Еще один недостаток такого подхода состоит в том, что если обработчик тика таймера не успевает отработать все действия за положенный интервал времени, то последующие вызовы этого обработчика становятся в очередь. Это приводит к тому, что приложение работает с разной скоростью на различных компьютерах.
Мы не будем опускать руки, а поишем другие способы анимации, благо их существует несколько. Рассмотрим еще один из этих способов (я его нахожу привлекательным), состоящий в использовании модуля MMSystem (Multimedia System). Мультимедийный таймер позволяет обрабатывать события с любой частотой, настолько часто, насколько это позволяют сделать ресурсы компьютера.
Посмотрим на соответствующий пример — проект из подкаталога Ex67 Здесь рисуются все те же пятьдесят параллелепипедов, но часгота смены кадров существенно выше.
Список подключаемых модулей в секции implementation дополнился модулем MMSystem:

uses DGLUT, MMSystem;

Мультимедийный таймер также нуждается в идентификаторе, как и обычный системный, описываем его в разделе private класса формы:

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

procedure TimeProc (uTimerID, uMessage: UINT; dwUser, dwl, dw2: DWORD; stdcall;
// значение угла изменяется каждый «тик»
With frmGL do begln
Angle: = Angle + 0. 1;
If Angle >= 360. 0 then Angle: — 0. 0;
InvalidateRect (HandJe, nil, False);
end;
end;

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

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

Самым важным в этой цепочке действий является, конечно, команда установки таймера, timeSetEvent. Первый аргумент команды — интервал таймера в миллисекундах. Вгорой аргумент — разрешение таймера, т. e. количество миллисекунд, ограничивающее время на отработку каждого тика таймера. Есди это число задано нулем, как в нашем примере, то обработка таймера Должна происходить с максимальной точностью.

Замечание
В документации рекомендуется задавать ненулевое значение для уменьшения системных потерь

Следующий параметр — адрес функции, ответственной за обработку каждого тика. Четвертый параметр редко используется, им являются задаваемые пользователем данные возврата Последним параметром является символи ческая константа, при этом значение TIME_PERIODIC соответствует обычному поведению таймера.
Итак, в примере каждые две миллисекунды наращивается угол поворота системы и перерисовывается экран.
Конечно, за две миллисекунды экран не будет перерисован, однако те кадры, которые компьютер не успевает воспроизвести, не будут накапливаться. При использовании мультимедийного таймера сравнительно легко планировать поведение приложения во времени: на любом компьютере и в любой ситуации система будет вращаться с приблизительно одинаковой скоростью, просто на маломощных компьютерах повороты будут рывками В продолжение темы посмотрите проект из подкаталога Ex68, где все тот же мультфильм рисуется на поверхности рабочего стола, подобно экранным заставкам. Во второй главе мы уже рассматривали похожий пример, здесь добавлена анимация, а команда glviewPort удалена, чтобы не ограничивать область вывода размерами окна приложения.

Замечание
Напоминаю, что такой подход срабатывает не на каждой карте

Наиболее распространенным способом построения анимационных приложений является использование фоновой обработки, альтернативы таймерам. Разберем, как это делается.
В Delphi событие onidle объекта Application соответствует режиму ожидания приложением сообщений. Все, что мы поместим в обработчике этого события, будет выполняться приложением беспрерывно, пока оно находится в режиме ожидания.
Переходим к примеру, проекту из подкаталога Ex69. Сценарий приложения не меняем, чтобы можно было сравнить различные способы. Отличает пример то, что в нем отсутствуют какие-либо таймеры; код, связанный с анимацией, перешел в пользовательскую процедуру:

procedure TfrmGL. Idle (Sender: TObject; var Done: boolean);
begln
With frmGL do begln
Angle: = Angle + 0. 1;
If Angle >= З60. 0 then Angle: = 0. 0;
Done: = False; // обработка завершена
InvalidateRect end;
end;

Второй параметр Done используется для того, чтобы сообщить системе, требуется ли дальнейшая обработка в состоянии простоя, или алгоритм завершен. Обычно дается False, чтобы не вызывать функцию WaitMessage.
При создании окна устанавливаем обработчик события onidle объекта Application:

Вот и все необходимые действия, можете запустить приложение и сравнить этот метод с предыдущим.

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

При использовании последнего способа у нас нет никакого контроля над выполнением кода, есть только весьма приблизительное представление о том. сколько времени будет затрачено на его выполнение. Скорость работы приложения в этом случае полностью зависит от загруженности системы, другие приложения могут с легкостью отнимать ресурсы, и тогда работа нашего приложения замедлится, Запустиге одновременно приложения, coответствующие мультимедийному таймеру и фоновой обработке Активное приложение будет генерировать большую частоту кадров, но приложение, построенное на таймере, менее болезненно реагирует на потерю фокуса,
В таких случаях можно повышать приоритет процесса, этот прием мы рассмотрим в главе 5.
Замечу, что при запуске приложения или изменении ею размеров требуется несколько секунд, чтобы работа вошла в нормальный режим, поэтому первые цифры, выдаваемые в качестве частоты воспроизведения, не являются особо надежными.
Теперь посмотрим, как реализовать фоновый режим в проектах, основанных только на функциях API. Это иллюстрирует проект из подкаталога Ex70.
Пользовательская функция idle содержит код, связанный с изменениями кадра.
Для отслеживания состояния активности окна заведен флаг AppActive, а оконная функция дополнилась обработчиком сообщения, связанного с активацией окна:

WM__ACTIVATEAPP:
If (wParara = WA^ACTIVE) or (wParam = WA__CLICKACTIVE)
then AppActive: = True
else AppActive: = False;

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

While True do begln
// проверяем очередь на наличие сообщения
If PeekMessage (Message, 0, 0, 0, pm_NoRemove) then begln
// в очереди присутствует какое-то сообщение
If not GetMessage(Message, 0, 0, 0)
then Break // сообщение WM_QUIT, прервать вечный цикл
else begln // обрабатываем сообщение
TranslateMessage(Message);
DispatchMessage(Message);
end;
end
else // очередь сообщений пуста
If AppActive
then Idle // приложение активно, рисуем очередной кадр
else WaitMessage; // приложение не активно, ничего не делаем
end;

Надеюсь, все понятно по комментариям, приведу только небольшие пояснения.
Функция PeekMessage с такими параметрами, как в этом примере, не удаляет сообщение из очереди, чтобы обработать его в дальнейшем традиционным способом.
Функция idle в этом примере вызывается при пустой очереди только в случае активности приложения.
Код можно немного сократить, если не акцентироваться на том, активно ли приложение; в данном же случае минимизированное приложение «засыпает». не внося изменений в кадр.
Рассмотрим еше один прием, использующийся для построения анимационных приложений и заключающийся в зацикливании программы. Для начала приведу самый простой способ зацикливания программы (проект из подкаталога Ex71).
Сценарий не изменился, рисуется все то же подобие шестерни. Никаких таймеров и прочих приемов, просто обработчик перерисовки окна заканчивается приращением управляющей переменной и командой перерисовки региона (окна):

Angle: = Angle + 0. 1;
If Angle >= 360. 0 then Angle: = 0. 0;
InvalidateRect(Handle, nil, False);

Все просто: воспроизведя очередной кадр, подаем команду на воспроизведение следующего.
В этом методе, как, впрочем, и в предыдущем, при уменьшении размеров окна частота кадров увеличивается, но и вращение происходит быстрее, здесь так же отсутствует контроль за поведением системы.
Приложение «замирает», будучи минимизированным
Если присмотреться внимательнее к поведению этого примера, то можно заметить некоторые необычные вещи, например, при наведении курсора на системные кнопки в заголовке окна подсказки не появляются. А при попытке активизации системного меню окна появляется явная накладка в работе приложения, область меню требуется перерисовать, двигая курсор в пределах его границ. Короче, зацикливание программы приводит к тому. что ожидающие сообщения могут и не обрабатываться. Может, это и не страшно, но ведь у нас нет гарантии, что мы обнаружили все странности работы приложения.
Решение проблемы состоит в использовании функции processMessages объекта Application, приостанавливающей работу приложения, чтобы система могла обрабатывать сообщения из очереди
Посмотрим на проект из подкаталога Ex72. Перед перерисовкой окна обрабатываем все сообщения очереди, вызвав функцию ProcessMessages, однако этого добавления не достаточно, иначе приложение невозможно будет закрыть. В примере введен вспомогательный флаг closed, принимающий исгинное значение при попытке пользователя или системы закрыть приложение:

procedure TfrmGL. FormCloseQuery(3ender: TObject; var Car. Close: Boolean;
begin
Closed: = True end;

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

If not Closed then begin
Angle: = Angle + 0. 1;
If Angle >= 360. 0 then Angle: = 0, 0;
Application. ProcessMessages;
InvaIidateRect end;

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

Canvas. TextOut (0, 0, ‘FPS — ‘ + FloatToStr (fpsRate));

При выводе на поверхность окна с помощью функций GDI не должно возникать проблем ни с одной картой, а вот если попытаться текст выводить на метку, то проблемы, скорее всего, возникнут со всеми картами: метка не будет видна.
Следующий пример, проект из подкаталога Ex73, является очередным моим переводом на Delphi классической программы, изначально написанной на С профессиональными программистами корпорации Silicon Graphics. Экран заполнен движущимися точками так, что у наблюдателя может появиться ощущение полета в космосе среди звезд (рис. 3. 39).

Рис. 3. 39. Проект Stars создает иллюзию полета в космосе

Предусмотрены два режима работы программы, управление которыми осуществляется нажатием пробела и клавиши T’. После нажатия пробела некоторые звезды летят по «неправильной» траектории, после нажатия второй управляющей, клавиши происходит «ускорение» полета на некоторое время.
Последний прием, который мы разберем в этом разделе, основан на использовании потоков. Проект из подкаталога Ex74 иллюстрирует этот прием на примере хорошо нам знакомой по предыдущим упражнениям вращающейся шестерни. В программе введен тип, ответственный за используемый поток:

type
TGLThread = class (TThread)
protected
procedure Execute;
override; // метод обязательно переопределяется
procedure Paint; // пользовательский метод потока
end;

Два метода потока описаны в традиционном стиле:

procedure TGLThread. Paint;
begln
With frmGL do begln
Angle: = Angle + 0. 1;
If Angle >=o 360. 0 then Angle: = 0, 0;
InvalidateRect(Handle, nil. False);
end;
end;
procedure TGLThread. Execute;
begln repeat
Synchronize (Paint); // синхронизация потоков
until Terminated;
end;

После создания окна поток инициализируется и запускается:

GLThread: = TGLThread, Create (False);

По окончании работы приложения выполняются стандартные действия:

GLThread. 3uspend; // приостановить поток
3LThread. Free; // удалить поток

В этом разделе мы рассмотрели несколько способов организации анимационных программ. У каждого из этих методов есть свои достоинства и свои недостатки, и вы вольны самостоятельно решать, какой из этих способов является для вас наиболее подходящим. Если при уменьшении размеров окна частота воспроизведения увеличивается, это является положительной стороной метода, но если в данном методе невозможно предугадать поведение программы на разных машинах, то это можно отнести к его недостаткам. Повторю, чго обработка ожидания является самым распространенным способом, и при обычной нагрузке системы он должен показать максимальную частоту воспроизведения.
В оставшихся примерах лекций вы можете встретить самые разные из этих способов, я не стану придерживаться какого-либо одного. Приведу еще один пример на анимацию, проект из подкатачога Ex75, где используется обычный системный таймер. В примере рисуется фонтан из двyx тысяч точек (рис. 3. 40).

Рис. 3. 44. Проект Fontain, две тысячи капель

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

UpdatePOINT(i: Word); // процедура перемещения капли
begin
Points[i] [Qj: = points[i] [0] + motion[i] [0]; // изменение координат
points[i][l]. =points[i][l] +motion[i][l];
points[i][2]: =points[i][2] +motion[i][2];
If points[i][1] = 360. 0 then Angle: = 0. 0;
tme: = GetTickCount;

НОВОСТИ ФОРУМА
Рыцари теории эфира
01.10.2020 — 05:20: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]69vJGqDENq4[/Youtube][/center]
[center]14:36[/center]
Osievskii Global News
29 сент. Отправлено 05:20, 01.10.2020 г.’ target=_top>Просвещение от Вячеслава Осиевского — Карим_Хайдаров.
30.09.2020 — 12:51: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Ok]376309070[/Ok][/center]
[center]11:03[/center] Отправлено 12:51, 30.09.2020 г.’ target=_top>Просвещение от Дэйвида Дюка — Карим_Хайдаров.
30.09.2020 — 11:53: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]VVQv1EzDTtY[/Youtube][/center]
[center]10:43[/center]

интервью Раввина Борода https://cursorinfo.co.il/all-news/rav.
мой телеграмм https://t.me/peshekhonovandrei
мой твиттер https://twitter.com/Andrey54708595
мой инстаграм https://www.instagram.com/andreipeshekhonow/

[b]Мой комментарий:
Андрей спрашивает: Краснодарская синагога — это что, военный объект?
— Да, военный, потому что имеет разрешение от Росатома на манипуляции с радиоактивными веществами, а также иными веществами, опасными в отношении массового поражения. Именно это было выявлено группой краснодарцев во главе с Мариной Мелиховой.

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html

Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html

Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

Построение реалистичных трехмерных изображений в стандарте OpenGL (стр. 1 из 3)

«Построение реалистичных трёхмерных изображений в стандарте OpenGL»

Компьютерная графика в настоящее время уже вполне сформировалась как наука. Существует аппаратное и программное обеспечение для получения разнообразных изображений – от простых чертежей до реалистичных образов естественных объектов. Компьютерная графика используется практически во всех научных и инженерных дисциплинах для наглядности восприятия и передачи информации. Знание её основ в наше время необходимо любому учёному или инженеру. Она властно вторгается в бизнес, медицину, рекламу, индустрию развлечений и многие другие области.

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

Для программирования графики можно использовать одну из стандартных библиотек, тем более, что их достаточно много. Графический стандарт OpenGL, разработан и утверждён в 1992 году девятью ведущими фирмами, среди которых: DigitalEquipmentCorporation, Evans & Sutherland, Hewlett-PackardCo., IBMCorp., IntelCorp., IntergraphCorp., SiliconGraphicsInc., SunMicrosystemsInc. и конечно же MicrosoftCorp.

В основу стандарта была положена библиотека IRISGL, разработанная SiliconGraphics. Это достаточно простая в изучении и использовании графическая система, обладающая при этом поразительно широкими возможностями: стабильность, надёжность, переносимость, простота использования.

Построение реалистических изображений

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

Рис. 1.Эффект одновременного контраста

Яркость центрального квадрата на обоих рисунках равна 0.5, а охватывающего – 0.8 на левом и 0.2 на правом рисунке. Похожее на одновременный контраст явление существует и для цветов.

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

Рис. 2.Эффект полос Маха

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

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

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

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

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

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

Световая энергия, падающая на поверхность, частично поглощается и превращается в тепло, а частично отражается и пропускается. Объект можно увидеть, только если он отражает или пропускает свет. Количество поглощенной, отраженной или пропущенной энергии зависит от длины волны света. Если объект поглощает лишь определенные длины волн, то у света, исходящего от объекта, изменяется распределение энергии – объект выглядит цветным. Цвет объекта определяется поглощаемыми длинами волн. Свойства отраженного света зависят от строения, направления и формы источника света, а также от ориентации и свойств поверхности. Отраженный от объекта свет может быть диффузным или зеркальным. Свойством диффузного отражения, т.е. равномерного по всем направлениями рассеивания света, обладают матовые поверхности. При этом кажется, что поверхности имеют одинаковую яркость независимо от угла обзора. Для таких поверхностей справедлив закон, устанавливающий соответствие между количеством отраженного света и косинусом угла между направлением L на точечный источник света и нормалью к поверхности (рис. 3), т.е. количество отраженного света не зависит от положения наблюдателя, а определяется материалом объекта и длиной волны света. Для представления диффузного отражения от цветных поверхностей расчеты проводятся отдельно для основных составляющих цвета.

Рис. 3.Падающий свет и нормаль к поверхности

Зеркальное отражение происходит от внешней поверхности объекта. Если осветить ярким светом яблоко, то в результате зеркального отражения возникнет световой блик, а свет, отраженный от остальной части яблока, появится вследствие диффузного отражения. При этом в том месте, где находится световой блик, яблоко кажется не красным, а скорее белым, т.е. окрашенным в цвет падающего света. Если изменить положение головы, то сместится и световой блик. Это объясняется тем, что блестящие поверхности неодинаково отражают свет по всем направлениям. От идеально отполированной поверхности свет отражается только в том направлении, для которого углы падения и отражения совпадают. Это означает, что наблюдатель сможет увидеть зеркально отраженный свет только в том случае, если угол α равен нулю (рис. 4). Таким образом, интенсивность зеркального отражения зависит от угла падения, длины волны падающего света и свойств вещества.

Рис. 4.Зеркальное отражение

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


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

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

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

Создание простой фигуры в 3D-формате, OpenGL и DirectX

Решил написать небольшой FAQ по этой теме:)

Создание простой фигуры в 3D-формате, OpenGL, Delphi (вообще, процедуры OpenGL везде почти одинаковы).
В Delphi должен быть заранее модуль OpenGL.pas. Его надо включить в раздел uses:

Далее надо будет создать процедуру:

В раздел var надо добавить:

Обработать события формы OnCreate, OnDestroy и OnResize так:

Итак, мы закончили ту часть, которая обязательна (будет ещё немного, но это после того, как Вы научитесь создавать объекты). Теперь пора задуматься о самих 3D объектах. В OpenGL есть так называемые листы . Они служат для облегчения создания 3D графики — распределения её на части. Чтобы открыть слои, есть процедура glNewList(ListIndex, mode:cardinal) — чтобы открыть и glEndList — чтобы закрыть создание листа. После glEndList он нарисуется. ListIndex — номер листа. Если указать уже использовавшийся номер листа, то этот номер будет перерисован .Mode — желательно установить GL_COMPILE. Для рисования также надо ставить ограничители рисования — glBegin(mode:cardinal) — начало рисования и glEnd — конец рисования. Mode — режим рисования. Их существует несколько, но мы сначала разберём, как это всё рисовать. 2D графику не буду задевать, сразу про 3D. Есть такая процедура glVertex3F(x,y,z:single), которая рисует точки. Обратите внимание: эти точки задаются в вещественном типе данных single, потому что, допустим, если x=1, то это не значит, что x=1 пиклель. Это больше, чем 1 пиксель, а чтобы сделать меньше, надо использовать дробную часть — x=0,001, например. То же самое с y и z.

  • GL_POINTS — рисует точки (один вызов glVertex3F — одна точка)
  • GL_LINES — рисует линии (два вызова glVertex3F — одна линия)
  • GL_LINE_STRIP — рисует ломанную (каждый вызов glVertex3F даёт новую точку, куда будет доведена линия. Это всё будет продолжаться, пока не поставить glEnd).
  • GL_LINE_LOOP — рисует ломанную (использование glVertex3F такое же, как и в предыдущий раз, после glEnd последняя точка соединяется с первой).
  • GL_TRIANGLES — рисует треугольники (три вызова glVertex3F — один треугольник)
  • GL_TRIANGLE_STRIP — рисует треугольники с общей стороной
  • GL_TRIANGLE_FAN — рисует треугольники с общей стороной, но по другому (редко пригождается)
  • GL_QUADS — рисует четырёхугольник (четыре вызова glVertex3F — один четырёхугольник)
  • GL_QUAD_STRIP — рисует четырёхугольники с общей стороной
  • GL_POLYGON — рисует многоугольник (каждый вызов glVertex3F даёт новую точку, куда будет доведена линия. Это всё будет продолжаться, пока не поставить glEnd, после этого фигура закрашивается).

Насчёт закрашивания. Цвет задаётся процедурой glColor3F(r,g,b:single). R — красный, G — зелёный, B — синий. Параметры задаются в пределах от 0 до 1. Например: glVertex3f(0,0,0) — чёрный, glVertex3f(1,1,1) — белый, glVertex3f(1,0,0) — красный, glVertex3f(0.5,0.5,0.5) — серый и т. д. Цвет можно задавать и так — glVertex4f(r,g,b,a:single). Здесь «a» — параметр Alpha (прозрачность).

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

Это всё желательно прописать в Form1.OnCreate. Если вы создаёте какой-нибудь большой проект (где множество объектов), желательно в Form1.OnResize в

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

Теперь поработаем над перемещением в 3d пространстве. Создаём новый тип:

В раздел var и const добавляем:

В FormCreate добавляем:

Добавляем на форму TTimer, Interval=50. Обрабатываем его событие OnTimer:

Form1.OnMouseMove, OnPaint и onKeyDown:

07.01.2010, 19:22

DirectX, openGl или создание компьютерной графики
Добрый вечер уважаемые читатели форума. Как я уже говорил ранее я закончил с теорией языка C#.

Создание простой фигуры в трёхмерной системе координат
Необходимо нарисовать типа трехмерную фигуру, без opengl. Нашел похожее тут.

OpenGL.Урок 1. простой OpenGL-проект
Не компилируется кодю #include void DrawLine() < .

Directx or OpenGL
Что лучше учить Directx или OpenGL? OpenGL кроссплатформенный и ничем не уступает directx но почему.

DirectX vs OpenGL
Не подскажите, пожалуйста, что лучше DirectX или OpenGL для создания игр на C#, что легче и под.

06.06.2010, 16:54 2

Рассмотрим создание 3D геометрии в DirectX 9 на C++. Т.к. в реальности никаких кубиков вы сами создавать не будет — есть 2 варианта:
1. Загрузка геометрии из 3D моделей
2. Генерация геометрии

Попробуем сгенерировать 3D поверхность

Все что нам понадобится это включить данные файлы:

Идем дальше. для того чтобы описывать геметрию в 3D пространстве используются массивы точек. Из называют вершинами. Естественно что вершина должна содержать как минимум 3 координаты (местоположение) но в нашем случае добавим ещё и цвет (чтобы поверхность была раскрашена)

Обратите внимание — FVF статический член и в принципе внутри объектов не присутствует — это важно. FVF — сумма флагов которая дает DirectX понять какую структуру вершины мы создали.

Добрались до программы:

В принципе — без комментариев, стандартные функции создания Windows окна, а в начале идут наши переменные параметры, название коотрых говорит за себя (кроме того они прокомментированы)

Внимание — проверка ошибок опущена Итак после этого всего мы имеет проинициализированный DirectX и готовы к основному коду приложения.

Выше мы говорили о вершинах. Теперь нужно завести место где мы будем их хранить — вершинный буфер.

Прокомментированы только значимые параметры, остальные «по умолчанию»
Итак мы создали буфер вершин который может хранить LandscapeSize * LandscapeSize точек, т.е. получаем такую квадратную сетку. Чтобы у нас получилась поверхность, надо для каждой точки задать координаты, при этом x, z координаты мы условимся брать в соотвествии с «сеткой» (представьте тетрадный лист) а высоту точек в этой сетке будем записывать в координату y. Так откуда же брать координату y? Можно из файла — изображения, а можно вычислять по определенной формуле, для упрощения возьмём вариант с формулой

float dist — переменная расчитывающая расстояние от центра поверхности до края
VData->Pos.y — расчитывается как мы и договорились по какой то формуле ) можете тут поэкспериментировать сами
VData->Pos.x, VData->Pos.z — заполняются по «сетке»
VData->Color — синий цвет вершины пропорционален высоте, вообще говоря если вы измените формулу просчета y то расчет цвета нужно будет тоже поменять

А теперь большая проблема — мы отлично описали «поверхность» по точкам и высотам, но DirectX такое вывести не сможет. Ему подавай полигоны — треугольники. Поэтому нам нужен ещё один буфер — на этот раз индексный.
Индексный он потому что содержит «индексы» т.е. номера вершин, номера откуда? Из вершинного буфера, т.е. они будут работать в паре. Причем отрисовка полигонов будет в принципе задаваться из индексного буфера, если у нас будет вершинный буфер с кучей вершин но пустой индексный буфер то ничего не отрисуется
Или ещё так можно объяснить — вершинный буфер выступает в роли базы данных где указаны разные точки, и индексный буфер указывает в каком порядке использовать эти данные чтобы вывести на экран то что нужно (в нашем случае много полигонов которые образуют поверхность)
Создадим индексный буфер

sizeof(short) * 2 * ( LandscapeSize — 1 ) * ( LandscapeSize — 1 ) * 3 — вес всех индексов. Т.е. вес однго индеса умножить на их количество. Количество индексов — давайте подумаем. Нам нужно отобразить полигоны. Полигон это 3 вершины. В одном квадрате (смотря по сетке) 2 полигона. А самих квадратов в сетке NxN будет (N-1)x(N-1). Вот и получилась такая формула.

Осталось заполнить индексный буфер:

Закрываем — открываем как и в прошлом случае. То что в < >позволяет заполнить по 6 индексов, т.е. мы описываем 2 треугольника. А 2 треугольника будут формировать изогнутый квадрать по диагонали, множество таких квадратов и сформируют поверхность
По форумулам — y это «строка» в сетке. y * LandscapeSize — текущая строка. (y+1) * LandscapeSize — та что ниже. x — текущая точка, x+1 следующая.

Осталось немного — показать DirectX где мы находимся в этом мире (установить камеру просмотра) и матрицу проекции:

D3DRS_CULLMODE — D3DCULL_CW — показываем треугольники только одной стороной (поверхность мы будем смотреть только с одной стороны, так что тратить время на визуализацию обеих видеокарте не нужно)
D3DRS_LIGHTING — FALSE — у нас нет источников света, так что если не отключить освещение то все будет в темноте и мы ничего не увидим
Остальное — связано с Z буфером, для правильности порядка отображения поверхности

Теперь скажем видеокарте что будем выводить графику из наших буферов

Изменением точки Eye мы меняем положение камеры, чтобы она летала вокруг нашей поверхности (в статике смотреть скучно ). Потом применяем это, чтобы DirectX знал что мы создали новую точку просмотра
Device->DrawIndexedPrimitive отрисовываем вершинный буфер (установленный выше) по индексному буферу (аналогично установленный выше)
D3DPT_TRIANGLELIST параметр означает что мы выводим список треугольников, собственно мы только и занимались тем что его составляли так что думаю это понятно
LandscapeSize * LandscapeSize количество вершин
2 * ( LandscapeSize — 1 ) * ( LandscapeSize — 1 ) количество треугольников

Выход из приложения — по кнопке ESC
и вот что нужно сделать под конец — освободить ресурсы DirectX

Opengl распределение компонент rgb в 3d пространстве

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

Примитивы OpenGL

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

Четырехугольник (Quad)

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

Все примитивы в OpenGL задаются с помощью следующей конструкции:

glBegin(GL_QUADS);
glVertex3f(-0.5f, -0.5f, 0.0f );
glVertex3f(-0.5f, 0.5f, 0.0f );
glVertex3f( 0.5f, 0.5f, 0.0f );
glVertex3f( 0.5f, -0.5f, 0.0f );
glEnd();

В качестве параметра команде glBegin передается тип примитивов, которые будут описываться между командами glBegin / glEnd . Внутри указываются координаты вершин примитива.

Обратите внимание на формат команды glVertex 3 f . Тройка означает, что команде передаются три аргумента. В данном случае это координаты в формате ( x , y , z ) . Сигнатура f означает, что параметры этой команды имеют тип float .

Рисуем куб

void DrawCube(GLfloat size)
<
glBegin(GL_QUADS);
// левая грань
glVertex3f( -size / 2, -size / 2, -size / 2);
glVertex3f( -size / 2, size / 2, -size / 2);
glVertex3f( -size / 2, size / 2, size / 2);
glVertex3f( -size / 2, -size / 2, size / 2);
// правая грань
glVertex3f( size / 2, -size / 2, -size / 2);
glVertex3f( size / 2, -size / 2, size / 2);
glVertex3f( size / 2, size / 2, size / 2);
glVertex3f( size / 2, size / 2, -size / 2);
// нижняя грань
glVertex3f( -size / 2, -size / 2, -size / 2);
glVertex3f( -size / 2, -size / 2, size / 2);
glVertex3f( size / 2, -size / 2, size / 2);
glVertex3f( size / 2, -size / 2, -size / 2);
// верхняя грань
glVertex3f( -size / 2, size / 2, -size / 2);
glVertex3f( -size / 2, size / 2, size / 2);
glVertex3f( size / 2, size / 2, size / 2);
glVertex3f( size / 2, size / 2, -size / 2);
// задняя грань
glVertex3f( -size / 2, -size / 2, -size / 2);
glVertex3f( size / 2, -size / 2, -size / 2);
glVertex3f( size / 2, size / 2, -size / 2);
glVertex3f( -size / 2, size / 2, -size / 2);
// передняя грань
glVertex3f( -size / 2, -size / 2, size / 2);
glVertex3f( size / 2, -size / 2, size / 2);
glVertex3f( size / 2, size / 2, size / 2);
glVertex3f( -size / 2, size / 2, size / 2);
glEnd();
>

Прим. В этом примере моделируется только геометрия куба. Для правильного освещения и наложения текстуры для каждой грани потребуется задать дополнительные свойства: нормаль и текстурные координаты.

Аффинные преобразования

В статье “Аффинные преобразования в пространстве“ были подробно рассмотрены аффинные преобразования пространства. Посмотрим, каким образом они присутствуют в OpenGL .

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

Аффинные преобразования в OpenGL

void glTranslatef(float x, float y, float z);
void glTranslated(double x, double y, double z);

Параллельный перенос на вектор ( x , y , z )

void glRotatef(float angle, float x, float y, float z);
void glRotated(double angle, double x, double y, double z);

Поворот против часовой стрелки вокруг вектора из начала координат в точку ( x , y , z )

void glScalef(float x, float y, float z);
void glScaled(double x, double y, double z);

Масштабирование в x , y , z раз по соответствующим осям

Прим. Команда glScale * может принимать произвольные аргументы, в т.ч. один из параметров может быть равен нулю. В этом случае преобразование не будет аффинным.

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

glMultMatrix(const TYPE *m);

* m – указатель на массив из 16 значений типа TYPE записанных в память по столбцам. Для примера запишем код преобразования скоса:

Матрица этого преобразования:

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

Пример. Нарисовать единичный куб, повернутый вокруг вектора (1, 1, 1) на 30 градусов.

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

Разберем функцию отрисовки, которая рисует единичный куб в начале локальной системы координат:

GLvoid Engine::Draw(GLvoid)
<
glClear(GL_COLOR_BUFFER_BIT); // Очищается буфер кадра

glColor3f(0.7f, 0.25f, 0.55f ); // Задается текущий цвет примитивов
glutWireCube( 1.0f ); // Рисуется проволочный куб со стороной 1
>

Прежде, чем рисовать что-то на экране, требуется его очистить. Это делается командой glClear c параметром GL _ COLOR _ BUFFER _ BIT . Если этого не сделать, всё будет нарисовано поверх предыдущего кадра:

Далее выбираем цвет примитивов, в данном случае белый. Значение каждого из параметров должно лежать в границах 0.0 — 1.0 и представляет собой долю каждой из трех составляющих: красной, зеленой и синей. Последняя команда рисует единичный проволочный куб с центром в начале координат.

Теперь мы хотим повернуть куб на 30 градусов вокруг вектора (1, 1, 1) . Казалось бы для этого требуется перед отрисовкой куба вставить команду glRotatef (30.0 f , 1.0 f , 1.0 f , 1.0 f ) :

glClear(GL_COLOR_BUFFER_BIT); // Очищается буфер кадра

glRotatef (30.0 f , 1.0 f , 1.0 f , 1.0 f ) ; // Поворот на 30 градусов вокруг вектора (1, 1, 1)

glColor3f(0.7f, 0.25f, 0.55f ); // Задается текущий цвет примитивов

glutWireCube( 1.0f ); // Рисуется проволочный куб со стороной 2
>

Однако такой вариант будет доворачивать систему координат при каждой перерисовке. Т.о. получим серию изображений вместо одного:

Решением этой проблемы будет поворот системы координат в обратном направлении после перерисовки:

glClear(GL_COLOR_BUFFER_BIT); // Очищается буфер кадра

glRotatef (30.0 f , 1.0 f , 1.0 f , 1.0 f ) ; // Поворот на 30 градусов вокруг вектора (1, 1, 1)

glColor3f(0.7f, 0.25f, 0.55f ); // Задается текущий цвет примитивов

glutWireCube( 1.0f ); // Рисуется проволочный куб со стороной 2

glRotatef ( — 30.0 f , 1.0 f , 1.0 f , 1.0 f ) ; / / Поворот на -30 градусов вокруг вектора (1, 1, 1)

У такого подхода есть несколько недостатков:

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

Эта проблема легко решается, если перед выполнением преобразований запомнить текущую локальную систему координат, а после отрисовки в точности восстановить её. Делается это парой команд glPushMatrix / glPopMatrix . При чём тут матрицы будет рассмотрено в следующей статье. Финальный код будет выглядеть следующим образом :

glClear(GL_COLOR_BUFFER_BIT); // Очищается буфер кадра

glPushMatrix(); // Запоминается локальная система координат
glRotatef (30.0 f , 1.0 f , 1.0 f , 1.0 f ) ; // Поворот на 30 градусов вокруг вектора (1, 1, 1)

glColor3f(0.7f, 0.25f, 0.55f ); // Задается текущий цвет примитивов

glutWireCube( 1.0f ); // Рисуется проволочный куб со стороной 2
glPopMatrix(); // Восстанавливается локальная система координат

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL