Главная страница

Opengl red Book (русская версия)


Скачать 6.68 Mb.
НазваниеOpengl red Book (русская версия)
АнкорRedBook_OpenGL.pdf
Дата22.04.2017
Размер6.68 Mb.
Формат файлаpdf
Имя файлаRedBook_OpenGL.pdf
ТипРеферат
#5085
страница1 из 43
  1   2   3   4   5   6   7   8   9   ...   43

OpenGL Red Book (русская версия)
Содержание
Глава 1. Введение в OpenGL
Глава 2. Управление состоянием и рисование геометрических объектов
Глава 3. Вид
Глава 4. Цвет
Глава 5. Освещение
Глава 6. Цветовое наложение, сглаживание, туман и смещение полигонов
Глава 7. Списки отображения
Глава 8. Отображение пикселей, битовых карт, шрифтов и изображений
Глава 9. Текстурирование
Глава 10. Буфер кадра
Глава 11. Тесселяция и квадрические поверхности
Глава 12. Вычислители и NURBS
Глава 13. Режим выбора и обратный режим
Глава 14. Трюки и советы
Приложение A. Переменные состояния
Приложение B. Вычисление векторов нормалей
Приложение C. Основы GLUT
Приложение D. Порядок операций
Приложение E. Однородные координаты и матрицы преобразований
Приложение F. Советы
Приложение G. Инвариантность OpenGL
Приложение H. OpenGL и оконные системы
Глава 1. Введение в OpenGL
1.1 Что такое OpenGL?
OpenGL – это программный интерфейс к графической аппаратуре. Этот интерфейс состоит приблизительно из 250 отдельных команд (около 200 команд в самой OpenGL и еще 50 в библиотеке утилит), которые используются для указания объектов и операций, которые необходимо выполнить, чтобы получить интерактивное приложение, работающее с трехмерной графикой.
Библиотека OpenGL разработана как обобщенный, независимый интерфейс, который может быть реализован для различного аппаратного обеспечения. По этой причине сама OpenGL не включает функций для создания окон или для захвата пользовательского ввода; для этих операций вы должны использовать средства той операционной системы, в которой вы работаете. По тем же причинам в OpenGL нет высокоуровневых функций для описания моделей трехмерных объектов. Такие команды позволили бы вам описывать относительно сложные фигуры, такие как автомобили, части человеческого тела или молекулы. При использовании библиотеки
OpenGL вы должны строить необходимые модели при помощи небольшого набора геометрических примитивов – точек, линий и многоугольников (полигонов).
Тем не менее, библиотека, предоставляющая описанные возможности может быть построена поверх OpenGL. Библиотека утилит OpenGL (OpenGL Utility Library -- GLU) предоставляет множество средств для моделирования, например, квадрические поверхности, кривые и поверхности типа NURBS. GLU – стандартная часть любой реализации OpenGL. Существуют также и более высокоуровневые библиотеки, например, Fahrenheit Scene Graph (FSG), которые построены с использованием OpenGL и распространяются отдельно для многих ее реализаций.

В следующем списке коротко описаны основные графические операции, которые выполняет OpenGL для вывода изображения на экран.
1. Конструирует фигуры из геометрических примитивов, создавая математическое описание объектов (примитивами в OpenGL считаются точки, линии, полигоны, битовые карты и изображения).
2. Позиционирует объекты в трехмерном пространстве и выбирает точку наблюдения для осмотра полученной композиции.
3. Вычисляет цвета для всех объектов. Цвета могут быть определены приложением, получены из расчета условий освещенности, вычислены при помощи текстур, наложенных на объекты или из любой комбинации этих факторов.
4. Преобразует математическое описание объектов и ассоциированной с ними цветовой информации в пиксели на экране. Этот процесс называется растеризацией (или растровой разверткой).
В течение всех этих этапов OpenGL может производить и другие операции, например, удаление частей объектов, скрытых другими объектами. В дополнение к этому, после того, как сцена растеризована, но до того, как она выводится на экран, вы можете производить некоторые операции с пиксельными данными, если это необходимо.
В некоторых реализациях (например, в реализации для системы X Window), OpenGL разработана таким образом, чтобы работать даже в том случае, если компьютер, которые отображает графику не то же самый, на котором запущена ваша графическая программа. Это может происходить в случае, если работа происходит в сетевом окружении, состоящем из множества компьютеров, соединенных между собой. В данной ситуации компьютер, на котором функционирует программа, и вызываются команды
OpenGL, является клиентом, в то время как компьютер, осуществляющий отображение, является сервером. Формат пересылки команд OpenGL от клиента серверу (или протокол) всегда один и тот же, так что программа может работать по сети даже в том случае, если клиент и сервер – совершенно различные компьютеры. В несетевом случае один и тот же компьютер является и клиентом, и сервером.
1.2 Немного OpenGL кода
Поскольку при помощи библиотеки OpenGL можно делать так много вещей, программа, использующая ее, может быть весьма сложна. Однако базовая структура полезной программы может быть достаточно простой: ее задачами являются инициализация нескольких переменных или переключателей, контролирующих, как OpenGL осуществляет визуализацию изображения и, далее, указание объектов для отображения.
До того, как будет приведен небольшой пример, определимся с некоторыми терминами.
Визуализация (rendering) – это процесс, с помощью которого компьютер создает изображения из моделей. Эти модели или объекты строятся из геометрических примитивов – точек, линий и полигонов, которые в свою очередь, определяются своими вершинами (vertices).
Результирующее изображение состоит из пикселей отображенных на экране. Пиксель – это самый маленький видимый элемент, который монитор может поместить на свой экран.
Информация о пикселях (например, какого цвета они должны быть) организована в памяти в виде битовых поверхностей (bitplanes). Битовая плоскость – это область памяти, содержащая один бит информации на каждый пиксель экрана. Этот бит может определять, например, насколько красным должен быть конкретный пиксель. Сами битовые плоскости организованы в буфер кадра, который содержит всю информацию необходимую монитору, чтобы контролировать цвет и интенсивность всех пикселей на экране.

Теперь посмотрим, на что может быть похожа OpenGL – программа. Пример 1-1 отображает белый квадрат на черном фоне, показанный на рисунке 1.
Рисунок 1.1. Белый квадрат на черном фоне
Пример 1-1. Фрагмент OpenGL – кода
#include <
все
_
что
_
необходимо
.h> main()
{
ИнициализироватьОкно(); glClearColor(0.0,0.0,0.0); glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0,1.0,1.0); glOrtho(0.0,1.0,0.0,1.0,-1.0,1.0); glBegin(GL_POLYGON); glVertex3f(0.25,0.25,0.0); glVertex3f(0.75,0.25,0.0); glVertex3f(0.75,0.75,0.0); glVertex3f(0.25,0.75,0.0); glEnd(); glFlush();
ОбновитьОкно();
}
Первая строка функции main() инициализирует окно на экране.
ИнициализироватьОкно() обозначает то место в тексте, куда надо поместить функции создания окна, зависимые от оконной системы, в которой вы работаете и не являющиеся командами OpenGL. Две следующих строки являются командами OpenGL, окрашивающими окно в черный цвет: glClearColor() задает цвет, в который окно будет окрашиваться при его очистке, glClear() как раз и очищает окно. После того, как
«очищающий цвет» задан, окно всегда будет окрашиваться именно в него при вызовах
glClear(). Этот цвет может быть изменен повторным вызовом glClearColor(). Похожим же образом glColor3f() задает цвет, который будет использоваться для рисования объектов – в данном случае белый. Начиная с этого момента, все объекты будут рисоваться белыми до тех пор, пока не последует замена этого цвета на другой при помощи одной из команд OpenGL.

Следующая OpenGL – команда, использованная в программе, glOrtho(), определяет координатную систему, на которую полагается OpenGL при отрисовке финального изображения и проецировании изображения на экран. Вызовы, заключенные в команды glBegin() и glEnd(), определяют объект, который следует нарисовать – в данном случае полигон с 4 вершинами. «Углы» полигона определяются командами
glVertex3f(). Как можно догадаться по значениям аргументов, которые являются координатами (x, y, z), полигон представляет собой квадрат в плоскости z=0.
Наконец, вызов glFlush() позволяет быть уверенным в том, что команды OpenGL действительно выполнились, а не были сохранены в буфере в ожидании дальнейших команд. ОбновитьОкно() – это опять-таки метка для функций, зависящих от оконной системы.
На самом деле этот фрагмент кода не слишком хорошо структурирован. Могут возникнуть вопросы: «Что если мы попытаемся изменить размер окна или переместить его?» «Необходимо ли устанавливать координатную систему каждый раз при отрисовке квадрата?» Позже ИнициализироватьОкно() и ОбновитьОкно() будут заменены на реально работающие вызовы, которые, однако, требуют реструктуризации кода, что сделает его более эффективным.
1.3 Синтаксис команд OpenGL
Как можно было заметить из простой программы, приведенной в предыдущем разделе, в названии команд OpenGL используется префикс gl и заглавные буквы для отделения слов, составляющих название команды (вспомним, например, glClearColor()).
Подобным же образом OpenGL определяет константы, начинающиеся с GL_ со всеми заглавными буквами и символом подчеркивания для отделения слов (например,
GL_COLOR_BUFFER_BIT).
Кроме того, можно было обратить внимание на казалось бы лишние буквы и цифры в названии некоторых команд (например, 3f в glColor3f() и glVertex3f()).
Действительно, слова Color (цвет) в названии glColor3f() достаточно, чтобы определить команду меняющую текущий цвет. Однако в библиотеке определено несколько версий такой команды, принимающих данные в различных форматах. Более конкретно, 3 в названии команды glColor3f() означает, что она получает три аргумента, существует и другая версия, получающая четыре аргумента. Буква f означает, что аргументы имеют формат числа с плавающей точкой. Подобное соглашение об именовании команд позволяет программисту использовать желаемый, более удобный для него формат аргументов.
Некоторые команды воспринимают до восьми различных типов данных. Буквы, используемые в качестве суффиксов для указания конкретного типа данных в реализации OpenGL для ISO - стандарта языка C приведены в таблице 1-1. Отдельные реализации OpenGL (например, для языка C++ или Ada) могут не следовать описанной схеме точно.
Таблица 1-1. Суффиксы команд и соответствующие им типы аргументов
Суффиксы Тип данных
Типично соответствующий тип
языка С
Тип, определенный в
OpenGL b целое 8 бит signed char
GLbyte s целое 16 бит short
GLshort i целое 32 бита int или long
GLint, GLsizei f число с плавающей точкой
32 бита float
GLfloat, GLclampf d число с плавающей точкой
64 бита double
GLdouble, GLclampd ud беззнаковое целое 8 бит unsigned char
GLubyte, GLboolean
us беззнаковое целое 16 бит unsigned short
GLushort ui беззнаковое целое 32 бита unsigned int или unsigned long
GLuint, GLenum, GLbitfield
Таким образом, следующие две команды glVertex2i(1,3); glVertex2f(1.0,3.0); эквивалентны за тем исключением, что первая принимает координаты вершины в виде
32-разрядного целого, а вторая – в виде числа с плавающей точкой одинарной точности.
Замечание
:
Производители реализаций
OpenGL имеют право выбирать
, какие типы данных языка
C использовать
, для представления типов
OpenGL.
Если в
тексте программы использовать типы
, определенные
OpenGL, вместо явного указания типов языка
C, можно избежать проблем при переносе приложения на другую реализацию
OpenGL.
Некоторые команды последней буквой в своем имени могут иметь v, это означает, что команда принимает указатель на вектор (или массив) величин, а не несколько отдельных аргументов. Многие команды имеют и векторную, и не векторную версии, но среди тех, которые не попадают в это число, одни работают только с индивидуальными аргументами, в то время как другие в обязательном порядке требуют указатель на вектор величин в качестве части или всех своих аргументов. Следующий фрагмент показывает пример использования векторной и не векторной версии команды установки текущего цвета. glColor3f(1.0,0.0,0.0);
GLfloat color_array[]={1.0,0.0,0.0}; glColor3fv(color_array);
Кроме всего прочего, OpenGL определяет тип GLvoid, который чаще всего применяется в векторных версиях команд.
Далее в этом пособии будем мы ссылаться на команды по их базовому имени и звездочке в конце (например, glColor*()), что означает, что приводимая информация относится ко всем версиям определенной команды. Если информация специфична только для подмножества версий одной команды, это будет отмечено при помощи добавления к вышеописанной записи части суффикса (например, glVertex*v() означает все векторные версии команды установки вершины).
1.4 OpenGL как машина состояния (state machine)
OpenGL – это машина состояния. Вы задаете различные переменные состояния, и они остаются в действии, сохраняя свое состояние, до тех пор, пока вы же их не измените.
Как вы уже видели, текущий цвет – это переменная состояния. Вы можете установить цвет в красный, синий, белый и так далее, и после этого все объекты будут рисоваться этим цветом до тех пор, пока вы его не измените на что–либо другое. Текущий цвет – это только одна из многих переменных состояния, имеющихся в OpenGL. Другие управляют такими аспектами, как текущая видовая и проекционная трансформации, шаблоны для линий и полигонов, режимы отображения полигонов, соглашения об упаковке пикселей, позиции и характеристики источников света, параметры материалов для объектов и многое другое. Многие переменные относятся к возможностям OpenGL, которые можно включать или выключать командами glEnable() или glDisable().
Каждая переменная состояния имеет свое значение по умолчанию, и в любой момент вы можете опросить систему на предмет ее текущего значения. Обычно, чтобы это
сделать, используется одна из следующих 6 команд: glGetBooleanv(),
glGetDoublev(), glGetFloatv(), glGetIntegerv(), glGetPointerv(), glIsEnabled().
Выбор конкретной команды зависит от того, в каком формате вы ожидаете ответ на запрос. Для некоторых переменных состояния имеются более специфические команды опроса (такие как, glGetLight*(), glGetError() или glGetPolygonStipple()). Кроме того, вы можете сохранять наборы значений переменных состояния в стеке атрибутов командами glPushAttrib() или glPushClientAttrib(), временно изменять их значения и позже восстанавливать из стека командами glPopAttrib() или glPopClientAttrib().
Для временного изменения переменных состояния и восстановления их исходных значений следует пользоваться именно этими командами, так как они реализованы более эффективно по сравнению с отдельными командами запросов.
1.5 Конвейер визуализации OpenGL
Большинство реализаций OpenGL имеют сходный порядок операций или этапов обработки, называемый конвейером визуализации OpenGL (OpenGL rendering pipeline).
Этот порядок показан на рисунке 1-2 и, хотя он не является жестким правилом для всех реализаций, тем не менее, дает представление о том, что делает OpenGL.
Диаграмма демонстрирует конвейер (в духе сборочных линий Генри Форда), который используется OpenGL для обработки данных. Геометрические данные (вершины, линии и полигоны) проходят путь, включающий оценку и повершинные операции, в то время как пиксельные данные (пиксели, изображения и битовые карты) в части общего процесса обрабатываются иначе. Оба типа данных проходят через одинаковые финальные шаги (растеризация и операции над фрагментами) до того, как результирующие пиксельные данные записываются в буфер кадра.
Рисунок 1.2. Порядок операций

Теперь более детально рассмотрим ключевые этапы конвейера визуализации OpenGL.
1.5.1 Списки
Все данные, геометрические или пиксельные могут быть сохранены в списках (display lists) для текущего или последующего использования. (Альтернативой занесению данных в списки является их немедленная обработка, известная как непосредственный режим.) Когда исполняется список, сохраненные в нем данные обрабатываются также, как если бы они поступали в непосредственном режиме.
1.5.2 Вычислители

Все геометрические примитивы описываются своими вершинами. Параметрические кривые и поверхности могут быть изначально описаны с помощью контрольных точек и полиномиальных функций, называемых базисными функциями. Вычислители предоставляют метод для определения реальных вершин, представляющих поверхность, по ее контрольным точкам. Этот метод – полиномиальная аппроксимация, он позволяет получить нормаль поверхности, координаты текстуры, цвета и значения координат в пространстве.
1.5.3 Повершинные операции
Для геометрических данных следующим этапом является выполнение повершинных операций (per-vertex operations). В течение этого этапа вершины преобразуются в примитивы. Некоторые типы вершинных данных трансформируются матрицами чисел с плавающей точкой размерности 4х4. Пространственные координаты проецируются с позиции в 3D мире в позицию на вашем экране.
Если активизированы дополнительные возможности библиотеки OpenGL, то этот этап становится еще более сложным. Если используется текстурирование, координаты текстуры могут быть сгенерированы и изменены на этом шаге. Если используется освещение, вычисления, связанные с ним, производятся с использованием трансформированных вершин, нормалей поверхностей, позиций источников света, свойств материала, а также другой информации, позволяющей вычислить цветовую величину.
1.5.4 Сборка примитивов
Отсечение – большая часть сборки примитивов – это уничтожение частей геометрии, выпадающих за полупространство, определенное плоскостью. Отсечение точек просто отвергает или не отвергает вершину; отсечение линий или полигонов может добавить дополнительные вершины в зависимости от ситуации (того, как именно линия или полигон отсекаются).
В любом случае, после этого процесса производится перспективное разделение, которое заставляет более дальние объекты выглядеть меньше, чем ближние. Затем выполняются операции с портом просмотра (viewport) и глубиной (координатой z). Если включено распознавание лицевых граней, и примитив является полигоном, то на этом шаге грань может быть отвергнута в зависимости от теста на лицевые грани. В зависимости от режима рисования полигонов, они могут быть нарисованы в виде точек или линий.
Результатом этого этапа являются завершенные примитивы, то есть трансформированные и отсеченные вершины со связанными цветом, глубиной и, иногда, координатами текстуры.
1.5.5 Операции над пикселями
В то время как геометрические данные движутся по конвейеру своим путем, пиксельные данные двигаются иным маршрутом. Первым делом массивы данных из системной памяти распаковываются, то есть преобразуются из какого-либо формата, в формат с необходимым числом компонент. Далее данные масштабируются, базируются и обрабатываются пиксельными картами. Результат сжимается и записывается в текстурную память или отправляется на этап растеризации.
Если пиксельные данные читаются из буфера кадра, над ними выполняются пиксельные операции (pixel-transfer operations). Затем результаты упаковываются в соответствующий формат и возвращаются в массив в системной памяти.

Существуют специальные операции копирования пикселей (pixel copy operations) для копирования данных из одной части буфера кадра в другие или из буфера кадра в текстурную память.
1.5.6 Наложение текстуры
Приложения OpenGL могут накладывать текстурные изображения на геометрические объекты, чтобы заставить их выглядеть более реалистично. Если используется несколько изображений текстур, разумно поместить их в объекты текстуры, чтобы можно было легко переключаться между ними.
Некоторые реализации OpenGL могут иметь дополнительные ресурсы для ускорения операций с текстурами. Например, может существовать специализированная быстрая текстурная память. Если такая память присутствует, текстурным объектам могут быть назначены приоритеты, чтобы управлять использованием этого ограниченного и весьма ценного ресурса.
1.5.7 Растеризация
Растеризация – это процесс преобразования геометрических и пиксельных данных во фрагменты. Каждый фрагмент соответствует пикселю в буфере кадра. Шаблоны линий и полигонов, толщина линии, размер точек, модель заливки, вычисления связанные с наложением для поддержки сглаживания принимаются в расчет при развертке двух вершин в линию или вычислении внутренних пикселей полигона. Каждый фрагмент имеет ассоциированные с ним значения цвета и глубины.
1.5.8 Операции над фрагментами
До того, как величины будут сохранены в буфере кадра, над ними производится серия операций, которые могут изменить или даже выбросить некоторые фрагменты. Все эти операции могут быть включены или выключены.
Первая операция, которая может быть произведена – это текстурирование, когда из текстурной памяти для каждого фрагмента генерируется и накладывается на него тексел (элемент текстуры). Также могут производиться (в порядке выполнения) вычисления тумана, тест отреза (scissor test), альфа-тест, тест трафарета (stencil test) и тест буфера глубины (для удаления невидимых поверхностей). Если фрагмент не проходит один из включенных тестов, это может закончить его путь по конвейеру.
Далее могут быть произведены наложение, смешивание цветов (dithering), логические операции и маскирование с помощью битовой маски. Наконец, фрагмент заносится в соответствующий буфер, где становится пикселем.
1.6 Библиотеки, связанные с OpenGL
OpenGL предоставляет мощный, но примитивный набор команд и все высокоуровневое рисование должно производиться в терминах этих команд. Кроме того, программы
OpenGL должны использовать нижележащие механизмы оконной системы. Существует несколько библиотек, которые могут облегчить программирование. Среди них имеются следующие:

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

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

Window расширения OpenGL (GLX) предоставляются в виде добавочных функций с префиксом glX. Для Microsoft Windows 95/98/Me/NT/200/XP функции WGL предоставляют интерфейс от Windows к OpenGL. Почти все они имеют префикс wgl.
Для IBM OS/2 функции менеджера презентаций имеют префикс pgl. Для Apple существует интерфейс AGL, чьи функции имеют соответствующий префикс (agl).

OpenGL Utility Toolkit (GLUT) – это независимая от оконной системы библиотека, написанная Марком Килгардом, чтобы скрыть сложности API различных оконных систем. Все функции библиотеки имеют префикс glut. Исходный код библиотеки GLUT для систем Microsoft Windows 95/98/NT/Me/XP и X Window может быть получен по
Интернет – адресу http://reality.sgi.com/opengl/glut3/glut3.html
. На данной странице помимо самого кода содержится информация о текущей версии GLUT.

Fahrenheit Scene Graph (FSG) – объектно-ориентированный набор, основанный на
OpenGL, он предоставляет объекты и методы для создания интерактивных трехмерных графических приложений. FSG, написанный на C++, предоставляет заранее построенные объекты и встроенную модель событий для взаимодействия с пользователем, высокоуровневые компоненты приложений для создания и редактирования трехмерных сцен, а также возможность обмена данными в различных форматах. FSG распространяется совершенно отдельно от OpenGL.

Используя OpenGL, компания Silicon Graphics создала вспомогательную библиотеку для упрощения написания программ – примеров (OpenGL Auxiliary Library -- GLAUX).
Код этой библиотеки поставляется в составе Microsoft Platform SDK и может быть использован в пользовательских программах или в образовательных целях.
1.7 Заголовочные файлы
Для всех OpenGL приложений необходимо включать заголовочный файл gl.h. Также большинство приложений используют GLU и должны включать файл glu.h. Таким образом, практически каабой исходный файл приложения OpenGL начинается со следующих строк:
#include
#include
Для систем Microsoft Windows требуется включение файла windows.h до включения gl.h или glu.h, так как некоторые макросы, используемые в этих файлах, определены внутри windows.h.
Если вы хотите получить доступ к библиотеке поддержки OpenGL оконной системой, например GLX, AGL, PGL или WGL, должны быть включены дополнительные файлы.
Например, для вызовов функций GLX, требуется добавить в код следующие строки:
#include
#include
Для Microsoft Windows доступ к функциям WGL можно получить включением строки:
#include
Если предполагается использовать GLUT для управления задачами, связанными с окнами, следует добавить ее заголовочный файл:
#include
Замечание
: glut.h гарантирует
, что включены также gl.h и
glu.h, так что нет необходимости включать все три файла
. glut.h также гарантирует
, что все специфичные для оконной системы макросы определены должным образом
, до включения gl.h и
glu.h.
Для повышения переносимости
GLUT- программ
, включайте только glut.h и
не включайте gl.h или glu.h.

Многие приложения OpenGL также используют стандартную библиотеку языка C, поэтому является частой практикой включать в исходный текст заголовочные файлы не связанные с графикой:
#include
#include
1.8 Сборка проекта
Помимо включения в исходный текст директив компилятора для добавления заголовочных файлов необходимо также проследить за тем, что во время сборки проекта к нему будут добавлены нужные библиотеки импорта. Например, в операционных системах Microsoft Windows библиотека OpenGL (в любой ее реализации) представлена динамической библиотекой opengl32.dll, а GLU – файлом glu32.dll.
Библиотека импорта, присоединенная к проекту вашего приложения, позволяет ему во время выполнения загружать нужные динамические библиотеки и вызывать их функции. Для названных динамических библиотек соответствующими библиотеками импорта являются opengl32.lib и glu32.lib, находящиеся, как правило, в одном из подкаталогов компилятора.
Если помимо средств операционной или оконной системы вы используете еще какие- либо библиотеки, то, возможно, придется добавлять в проект и другие библиотеки импорта. Иногда (как, например, в случае с GLUT) в самом заголовочном файле содержится директива компилятору включить в проект нужные библиотеки импорта, но так бывает не всегда. Сигналом к тому, что нужные ссылки на библиотеки отсутствуют, чаще всего, является то, что компилятор просто отказывается собирать проект.
Кроме того, нужные динамические библиотеки должны быть в зоне досягаемости вашего готового приложения. Как правило, это означает, что они должны находиться либо в одном каталоге с исполняемым файлом, либо (и это случается чаще) они должны быть помещены в системную директорию Microsoft Windows (конкретный путь зависит от конкретного компьютера, но в общем виде этот путь можно записать как
[Каталог Windows]\System).
1.9 GLUT
Как вы уже знаете, OpenGL содержит набор команд, но разработана как независимая от оконной или операционной системы. Как следствие, в ней нет команд для открытия окон или чтения событий от клавиатуры или мыши. К несчастью нельзя создать полноценное приложение без того, чтобы, как минимум, открыть окно, а наиболее интересные приложения требуют взаимодействия с пользователем посредством устройств ввода или используют иные средства операционной или оконной системы. Во многих случаях завершенные программы представляют собой наиболее интересные примеры, поэтому мы будем использовать GLUT для упрощения открытия окон, захвата пользовательского ввода и так далее. Если у вас имеется реализация OpenGL и GLUT для вашей системы, все примеры будут работать без изменений (или почти без изменений).
Кроме того, в отличие от OpenGL, чьи команды ограничены рисованием примитивов,
GLUT содержит функции для рисования более сложных трехмерных объектов, таких как сфера, куб, торус (бублик) и чайник. (Обратите внимание на то, что GLU также содержит функции для рисования этих объектов, а также цилиндров, конусов и многого другого.)
Библиотеки GLUT может быть недостаточно для построения полномасштабного OpenGL приложения (например, в силу отсутствия полного контроля над форматом пикселей или созданием контекста OpenGL), но это хорошая начальная точка для изучения

OpenGL. Остальная часть данного раздела посвящена наиболее часто применяемым группам функций GLUT. Функции неуказанные здесь, рассмотрены в приложении A данного пособия.
1.9.1 Управление окном
void glutInit (int argc, char **argv);
glutInit() должна быть вызвана до любых других GLUT – функций, так как она инициализирует саму библиотеку GLUT. glutInit() также обрабатывает параметры командной строки, но сами параметры зависят от конкретной оконной системы. Для системы X Window, примерами могут быть –iconic, -geometry и –display. (Параметры, передаваемые glutInit(), должны быть теми же самыми, что и параметры, передаваемые в функцию main()). void glutInitDisplayMode (unsigned int mode);
Указывает режим отображения (например, RGBA или индексный, одинарной или двойной буферизации) для окон, создаваемых вызовами glutCreateWindow(). Вы также можете указывать, имеет ли окно ассоциированные с ним буфер или буферы глубины, трафарета и аккумуляции. Аргумент mask – это битовая комбинация, полученная при помощи операции OR и следующих констант: GLUT_RGBA или
GLUT_INDEX (для указания цветового режима), GLUT_SINGLE или GLUT_DOUBLE (для указания режима буферизации), а также константы для включения различных буферов
GLUT_DEPTH, GLUT_STENCIL, GLUT_ACCUN. Например, для окна с двойной буферизаций, RGBA – цветовым режимом и ассоциированными буферами глубины и трафарета, используйте GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL.
Значение по умолчанию – GLUT_RGBA | GLUT_ SINGLE (окно с однократной буферизацией в RGBA - режиме). void glutInitWindowSize (int width, int height); void glutInitWindowPosition (int x, int y);
Запрашивают окно определенного размера и в определенном месте экрана соответственно. Аргументы (x, y) определяют, где будет находиться угол окна относительно всего экрана. width и height определяют размер окна (в пикселях).
Начальные размеры и место размещения окна могут быть перекрыты последующими вызовами. int glutCreateWindow (char *name);
Открывает окно с предварительно установленными характеристиками (режимом отображения, размером и так далее). Строка name может быть отображена в заголовке окна, но это зависит от конкретной оконной системы. Окно не отображается до того, как произведен вход в glutMainLoop(), поэтому до вызова этой функции нельзя рисовать что-либо в окно.
Возвращаемая целая величина представляет собой уникальный идентификатор окна.
Этот идентификатор может быть использован для управления несколькими окнами в одном приложении (каждое со своим контекстом OpenGL) и рисования в них.
1.9.2 Функции управления событиями
После того, как окно создано, но до входа в главный цикл программы, вы должны зарегистрировать функции обратного вызова, используя следующие функции GLUT.
void glutDisplayFunc (void (*func)(void));
Позволяет указать функцию (аргументом func), которая будет вызываться каждый раз, когда содержимое окна требует перерисовки. Это может случиться, когда окно открывается, разворачивается, освобождается его часть, ранее перекрытая другим окном, или вызывается функция glutPostRedisplay(). void glutReshapeFunc (void (*func)(int width, int height));
Позволяет указать функцию, которая вызывается каждый раз при изменении размера окна или его позиции на экране. Аргумент func – это указатель на функцию, которая принимает два параметра: width – новая ширина окна и height – новая высота окна.
Обычно func вызывает glViewport() для отсечения графического вывода по новым размерам окна, а также настраивает проекционную матрицу для сохранения пропорций спроецированного изображения в соответствии с новыми размерами порта просмотра.
Если glutReshapeFunc() не вызывается или ей передается NULL (для отмены регистрации функции обратного вызова), вызывается функция изменения метрик по умолчанию, которая вызывает glViewport (0,0,width,height). void glutKeyboardFunc (void (*func)(unsigned int key, int x, int y));
Задает функцию func, которая вызывается, когда нажимается клавиша, имеющая
ASCII-код. Этот код передается функции обратного вызова в параметре key. В параметрах x и y передается позиция курсора мыши (относительно окна) в момент нажатия клавиши. void glutMouseFunc (void (*func)(int button, int state, int width, int height));
Указывает функцию, которая вызывается при нажатии или отпускании кнопки мыши.
Параметр button может иметь значения GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON или
GLUT_RIGHT_BUTTON. Параметр state может иметь значения GLUT_UP или GLUT_DOWN в зависимости от того отпущена или нажата кнопка мыши. В параметрах x и y передаются координаты курсора мыши (относительно окна) в момент наступления события. void glutMotionFunc (void (*func)(int x, int y));
Указывает функцию, которая будет вызываться при движении мыши внутри окна в то время, как на ней нажата одна или несколько клавиш. В параметрах x и y передаются координаты курсора мыши (относительно окна) в текущий момент. void glutPostRedisplay (void);
Помечает, что текущее окно требует перерисовки. После этого при любой возможности будет вызвана функция перерисовки окна, зарегистрированная вызовом
glutDisplayFunc().
1.9.3 Загрузка палитры
Если вы работаете в индексном режиме, то можете к своему удивлению обнаружить, что в OpenGL нет команд для загрузки цвета в цветовую таблицу. Дело в том, что процесс загрузки палитры целиком зависит от оконной системы. В GLUT существует обобщенная функция для загрузки одного цветового индекса с соответствующим RGB значением.
void glutSetColor (Glint index, GLfloat red, GLfloat green, GLfloat blue);
Загружает в палитру по индексу index, RGB-значение, определенное параметрами red, green и blue. Последние три параметра нормализуются до диапазона [0.0, 1.0].
1.9.4 Рисование трехмерных объектов
Многие программы примеры используют простые трехмерные объекты для иллюстрации различных методов и техник визуализации изображения. GLUT содержит несколько функций для рисования таких объектов. Все эти функции работают в непосредственном режиме. Каждая из них имеет два варианта: первый рисует объект в виде проволочного каркаса и не генерирует нормалей, второй рисует объект сплошным и генерирует нормали поверхности (для чайника помимо этого генерируются координаты текстуры).
Если используется освещение, следует выбирать сплошную версию объекта. Все объекты рисуются с учетом текущих параметров, например, цвета и характеристик материала. Кроме того, все объекты рисуются центрированными относительно текущих модельных координат. void glutWireSphere (GLdouble radius, GLint slices, GLint stacks); void glutSolidSphere (GLdouble radius, GLint slices, GLint stacks);
Рисуют проволочную или сплошную сферу с радиусом radius, количеством частей
(полигонов из которых состоит сфера) slices – вокруг оси z и stacks – вдоль оси z. Для того, чтобы понять, что означает вокруг оси z и вдоль нее, представьте себе, что вы смотрите в длинную трубу. В данном случае направление вашего обзора совпадает с осью z трубы. Она может быть мысленно разделена как вдоль (на длинные фрагменты), так и поперек (на кольца). После таких разбиений труба фактически состоит из множества мелких кусочков. В случае сферы количество разбиений поперек задается параметром stacks, а количество разбиений вдоль – параметром slices. Из этого следует, что чем больше разбиений, тем более гладкой выглядит сфера на экране, но тем больше вычислений требуется для ее рисования. void glutWireCube (GLdouble size); void glutSolidCube (GLdouble size);
Рисуют проволочный или сплошной куб с длиной ребра size. void glutWireTorus (GLdouble innerRadius, GLdouble outerRadius, GLint nsides,
GLint rings); void glutSolidTorus (GLdouble innerRadius, GLdouble outerRadius, GLint nsides,
GLint rings);
Рисуют проволочный или сплошной торус (бублик) с внешним радиусом outerRadius и внутренним радиусом innerRadius. Параметр rings задает желаемое число колец из которых будет состоять торус, параметр nsides – из скольких частей будет состоять каждое кольцо. void glutWireCone (GLdouble radius, GLdouble height, GLint slices, GLint stacks); void glutSolidCone (GLdouble radius, GLdouble height, GLint slices, GLint stacks);
Рисуют проволочный или сплошной конус радиусом radius, высотой height. Значение параметров slices и stacks аналогично таким же параметрам для сферы. void glutWireIcosahedron (void); void glutSolidIcosahedron (void); void glutWireOctahedron (void);
void glutSolidOctahedron (void); void glutWireTetrahedron (void); void glutSolidTetrahedron (void); void glutWireDodecahedron (GLdouble radius); void glutSolidDodecahedron (GLdouble radius);
Рисуют проволочные или сплошные икосаэдр, октаэдр, тетраэдр и додекаэдр соответственно (единственный параметр последний пары функций задает радиус додекаэдра). void glutWireTeapot (GLdouble size); void glutSolidTeapot (GLdouble size);
Рисуют проволочный или сплошной чайник размера size.
1.9.5 Управление фоновым процессом
Вы можете указать функцию, которая будет вызываться в том случае, если нет других сообщений, то есть во время простоя приложения. Это может быть полезно для выполнения анимации или другой фоновой обработки. void glutIdleFunc (void (*func)(void));
Задает функцию, выполняемую в случае, если больше приложению делать нечего
(отсутствуют сообщения). Выполнение этой функции обратного вызова можно отменить передачей glutIdleFunc() аргумента NULL.
1.9.6 Запуск программы
После того, как все настройки выполнены, программы GLUT входят в цикл обработки сообщений функцией glutMainLoop(). void glutMainLoop (void);
Вводит программу в цикл обработки сообщений. Функции обратного вызова будут выполняться в случае наступления соответствующих событий.
Пример 1-2 показывает, как с помощью GLUT можно заставить работать программу, показанную в примере 1-1. Обратите внимание на реструктуризацию кода. Для увеличения эффективности операции, которые нужно выполнить только один раз
(установка цвета фона и координатной системы), теперь помещены в функцию init().
Операции по визуализации (и пересчету) сцены находятся в функции display(), которая зарегистрирована в качестве дисплейной функции обратного вызова.
Пример 1-2. Простая OpenGL – программа с использованием GLUT: hello.cpp
#include void init(void)
{
//Выбрать фоновый (очищающий) цвет glClearColor(0.0,0.0,0.0,0.0);
//Установить проекцию glMatrixMode(GL_PROJECTION); glLoadIdentity();
glOrtho(0.0,1.0,0.0,1.0,-1.0,1.0);
} void display(void)
{
//Очистить экран glClear(GL_COLOR_BUFFER_BIT);
//Нарисовать белый полигон (квадрат) с углами //в (0.25, 0.25, 0.0) и (0.75,
0.75, 0.0) glColor3f(1.0,1.0,1.0); glBegin(GL_POLYGON); glVertex3f(0.25,0.25,0.0); glVertex3f(0.75,0.25,0.0); glVertex3f(0.75,0.75,0.0); glVertex3f(0.25,0.75,0.0); glEnd();
//Не ждем. Начинаем выполнять буферизованные
//команды OpenGL glFlush();
}
//Установить начальные характеристики окна,
//открыть окно с заголовком «hello».
//Зарегистрировать дисплейную функцию обратного вызова
//Войти в главный цикл int main(int argc, char **argv)
{ glutInit(&argc,argv); glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB); glutInitWindowSize(250,250); glutInitWindowPosition(100,100); glutCreateWindow(“hello”); init(); glutDisplayFunc(display); glutMainLoop(); return 0;
}
1.9.7 Типы проектов
Поскольку данное пособие предназначено в основном для тех, кто программирует приложения для среды Microsoft Windows, требуется сделать несколько важных замечаний.
Существует два основным типа приложения для этой операционной системы: консольные и оконные.
Консольные приложения по своему строению практически не отличаются от программ
DOS запущенных под Windows (однако являются 32-разрядными приложениями защищенного режима). При их запуске система автоматически создает окно, в которое можно выводить текстовую информацию языковыми средствами C без привлечения функций API операционной системы. Тем не менее, само приложение может также создавать дополнительные окна, если разработчику это требуется. Именно так и происходит при создании консольных приложений с использованием GLUT и OpenGL.
Система при запуске создает консоль, а GLUT своим методом glutCreateWindow()
создает еще одно окно. В этом случае весь графический вывод OpenGL направляется в окно, созданное GLUT, а текстовый вывод функциями стандартной библиотеки C
(например, printf(“текстовая строка”) или cout<<”текстовая строка”) будет осуществляться в консольное окно. Это может быть весьма удобно, если помимо графического вывода программа выполняет некоторые вычисления над полученным изображением (определение минимальных и максимальных значений компонент цвета, использование обратного режима OpenGL и так далее). Стартовой точкой выполнения консольной программы является функция main(), в которую системой передаются те самые параметры командной строки (если они есть), которые позже должны быть переданы в glutInit().
В случаях, когда лишнее консольное окно приложению не нужно, лучше изначально создавать оконное приложение. При его запуске вся ответственность по созданию окон ложится на программиста и средства, которыми он пользуется, в нашем случае опять- таки на GLUT. И вот здесь может возникнуть потенциальная проблема, которая происходит из разницы между форматами передачи параметров командной строки в функцию main() и WinMain(). В первом случае параметры передаются в функцию в виде массива строк, тогда как во втором – в виде одной единственной строки, причем в этом случае количество параметров неизвестно. В свою очередь glutInit() ожидает параметры в формате main() с указанием их числа. Существует несколько вариантов решения этой проблемы. Если параметры вам нужны, следует создать (или позаимствовать) функцию разборки единой командной строки на составляющие с выяснением их количества (это довольно просто). Если же у вас нет нужды в параметрах командной строки можно передать в glutInit() подделку следующего вида: char* argv=""; int argc=0; glutInit(&argc,&argv);
Конечно, существуют и другие варианты.
1.10 Анимация
Одна из самых впечатляющих вещей, которую вы можете делать на компьютере – это рисование движущихся картинок. Являетесь ли вы инженером, пытающимся увидеть разрабатываемую деталь со всех сторон, пилотом, обучающимся летать на самолете с помощью симулятора или профессионалом в области создания компьютерных игр, вам должно быть абсолютно ясно, что анимация – это важная часть компьютерной графики.
В кинотеатре движение достигается за счет проектирования серии картинок на экран со скоростью 24 кадра в секунду. Каждый кадр выдвигается на позицию перед лампой, открывается шторка и кадр показывается на экране. Шторка моментально закрывается, пленка передвигается к следующему кадру, он отображается на экране и так далее.
Таким образом, когда вы просматриваете 24 кадра за одну секунду, ваш мозг объединяет их в непрерывную плавную анимацию. (Разрывы между кадрами были хорошо заметны в старых фильмах Чарли Чаплина, так как в то время фильмы снимались и показывались со скоростью 16 кадров в секунду.) Современные мониторы в среднем перерисовывают изображение от 60 до 76 раз в секунду (хотя скорость может достигать и 120 раз в секунду). Очевидно, что 60 кадров в секунду выглядят более гладко, чем 30, а 120 намного лучше, чем 60. Однако, частота обновления выше
120 кадров в секунду может находиться за гранью человеческого восприятия (это зависит, в основном, от индивидуальных особенностей).
Ключевая причина, по которой кинопроектор способен создать эффект движения заключается в том, что каждый кадр уже готов на момент его показа. Предположим, что вы пытаетесь воспроизвести анимацию, состоящую из миллиона кадров, с помощью программы вроде следующей:

Открыть
_
окно
(); for(i=0;i<1000000;i++)
{
Очистить
_
окно
();
Нарисовать
_
кадр
(i);
Подождать
_
пока
_
пройдет
_
одна
_24
я
_
часть
_
секунды
();
}
Если вы сложите время, которое требуется вашей программе, чтобы нарисовать кадр на экране и время, необходимое для очистки экрана, то выяснится, что программа выдает все более и более неприятные результаты по мере того, как это суммарное время приближается к 1/24 части секунды. Предположим, что рисование занимает практически всю 1/24 часть секунды. Тогда элементы изображения рисуемые первыми будут видны на экране практически постоянно, а те, что рисуются в конце, будут похожи на приведение, так как сразу после их отображения программа начнет стирать все изображение с экрана и, таким образом, большую часть времени наблюдатель будет видеть на месте этих элементов пустой фон. Проблема в том, что приведенная программа не воспроизводит готовые кадры, а рисует каждый из них и наблюдатель это видит.
Большинство реализаций OpenGL предоставляет двойную буферизацию – аппаратный или программный механизм, который обеспечивает наличие двух полноценных цветовых буферов. Один отображается на экране, пока второй отрисовывается. Когда рисование кадра окончено, два буфера переключаются: тот, что только что был на экране, теперь используется для рисования и наоборот. Этот процесс похож на отображение проектором в кинотеатре пленки всего с двумя кадрами: пока один проецируется на экран, художник стирает и перерисовывает другой – невидимый. До тех пор, пока художник достаточно быстр, наблюдатель не заметит разницы между подобной техникой и той, где все кадры уже готовы и просто показываются друг за другом. С двойной буферизацией каждый кадр показывается только после того, как он полностью готов. Наблюдатель никогда не увидит частично нарисованное изображение.
Модифицированная для использования двойной буферизации версия предыдущей программы может выглядеть следующим образом:
Открыть
_
окно
_
в
_
режиме
_
двойной
_
буферизации
(); for(i=0;i<1000000;i++)
{
Очистить
_
окно
();
Нарисовать
_
кадр
(i);
Переключить
_
буферы
();
}
1.11 Частота обновления экрана
В некоторых реализациях OpenGL, функция Переключить_буферы() помимо простого переключения буферов, ждет, пока закончится текущий период обновления экрана, чтобы предыдущий кадр был полностью отображен и следующий был нарисован с самого начала. Предположим, что ваша система обновляет экран 60 раз в секунду. Это означает, что наивысшая скорость смены кадров, которую вы можете достичь – 60 кадров в секунду (frames per second -- fps) и если все ваши кадры будут очищаться и перерисовываться менее чем за 1/60 долю секунды, ваша анимация будет на этой скорости гладкой.
Часто случается, что кадр слишком сложен, чтобы нарисовать его за 1/60 секунды, таким образом, каждый кадр показывается на экране более чем один раз. Если, например, рисование кадра занимает 1/45 секунды, вы получаете скорость 30 fps и ничем не занятое время в размере 1/30-1/45=1/90 секунды, то есть треть времени рисования кадра на каждый кадр.

Кроме того, частота обновления монитора постоянна, что может вызвать неожиданные последствия для быстродействия. Например, при частоте обновления монитора 60 раз в секунду, анимация может идти со скоростями 60 fps, 30, fps, 20 fps, 15 fps, 12 fps и так далее. Это означает, что если пишете приложение и постепенно добавляете детали, сначала каждая деталь, которую вы добавляете, не оказывает воздействия на общее быстродействие – анимация по-прежнему идет со скоростью 60 fps. Затем вы добавляете еще одну деталь, и, система уже не может рисовать кадр за 1/60 долю секунды, вследствие чего, неожиданно, быстродействие падает до 30 fps, поскольку не нельзя переключить буферы при первой возможности. То же происходит, если время рисования, затрачиваемое на каждый кадр, перешагивает порог в 1/30 долю секунды – быстродействие снижается до 20 fps.
Если сложность сцены близка к одному из волшебных чисел (1/60 секунды, 1/30 секунды, 1/20 секунды и так далее), то из-за некоторых вариаций некоторые кадры могут рисоваться чуть быстрее, а некоторые немного не укладываться в график. В этом случае скорость анимации непостоянна, что может вести к неприятному внешнему виду. В таких случаях, если не удается упростить медленные кадры, возможно, стоит добавить небольшую паузу в быстрые, чтобы сохранять скорость анимации постоянной.
Если различные кадры вашей анимации драматически отличаются друг от друга по сложности, может потребоваться более изощренный подход.
1.12 Движение = Перерисовка + Переключение
Структура реальных программ анимации не слишком отличается от описания, приведенного в заголовке этого раздела. Обычно намного легче перерисовать весь буфер, чем выяснять какие его части нужно изменить. Это особенно верно для таких приложений как симуляторы полетов, где даже небольшое изменение в ориентации самолета ведет к масштабным изменениям вида за окнами кабины.
В большинстве анимационных программ объекты сцены просто перерисовываются с учетом различных трансформаций – изменяется точка обзора или положение наблюдателя, или машина немного проезжает по дороге, или объект немного поворачивается. Если требуются обширные вычисления не связанные с рисованием, то они могут ощутимо понизить быстродействие в целом и скорость анимации в частности.
Имейте в виду однако, что часто пауза после вызова Переключить_буферы() может быть использована для таких операций.
В OpenGL нет функции Переключить_буферы(), так как данная возможность может не обеспечиваться отдельными аппаратными средствами и, в любом случае, она сильно зависит от оконной системы. Например, при использовании системы X Window (и работе без дополнительных библиотек) можно использовать следующую GLX функцию: void glXSwapBuffers (Display *dpy, Window window);
В аналогичном случае для системы Microsoft Windows функция будет выглядеть следующим образом:
BOOL SwapBuffers (HDC hdc);
При использовании GLUT следует вызывать функцию: void glutSwapBuffers (void);
Пример 1-3 иллюстрирует использование glutSwapBuffers() для рисования вращающегося квадрата, показанного на рисунке 1-3. Этот пример также показывает, как использовать GLUT для захвата пользовательского ввода и включения/выключения
функции фоновой обработки. В данном примере левая и правая кнопки мыши соответственно включают и выключают вращение.
Рисунок 1.3. Вращающийся квадрат в режиме двойной буферизации
Пример 1.3. Программа, использующая двойную буферизацию: double.cpp
#include
GLfloat spin=0.0; void init(void)
{ glClearColor(0.0,0.0,0.0,0.0); glShadeModel(GL_FLAT);
} void display(void)
{ glClear(GL_COLOR_BUFFER_BIT); glPushMatrix(); glRotatef(spin,0.0,0.0,1.0); glColor3f(1.0,1.0,1.0); glRectf(-25.0,-25.0,25.0,25.0); glPopMatrix(); glutSwapBuffers();
} void spinDisplay(void)
{ spin=spin+1.0; if(spin>360.0) spin=spin-360.0; glutPostRedisplay();
} void reshape(int w, int h)
{ glViewport(0,0,(GLsizei) w, (GLsizei) h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-50.0,50.0,-50.0,50.0,-1.0,1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity();
}
//При нажатии левой кнопки зарегистрировать

//функцию фоновой обработки (поворота)
//При нажатии правой – отменить регистрацию void mouse(int button,int state,int x,int y)
{ switch(button)
{ case GLUT_LEFT_BUTTON: if (state==GLUT_DOWN) glutIdleFunc(spinDisplay); break; case GLUT_RIGHT_BUTTON: if (state==GLUT_DOWN) glutIdleFunc(NULL); break;
}
}
//Запросить режим двойной буферизации
//Зарегистрировать функции обработки мышиного ввода int main(int argc, char **argv)
{ glutInit(&argc,argv); glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB); glutInitWindowSize(250,250); glutInitWindowPosition(100,100); glutCreateWindow("Двойная буферизация"); init(); glutDisplayFunc(display); glutReshapeFunc(reshape); glutMouseFunc(mouse); glutMainLoop(); return 0;
}
  1   2   3   4   5   6   7   8   9   ...   43