Введение в OpenGLНачало|Основные команды-рисуем примитивы| Операции с матрицами|Свет и материалы.|Quadric-объекты |
![]() |
-->Начало: о чём это всё и зачем нужно.На сегодняшний день существуют две достаточно продвинутые библиотеки для работы с 3D-графикой - OpenGL и Direct3D. После появления Direct3D 8 господство OpenGL несколько пошатнулось, однако с её использованием до сих пор пишутся разные продвинутые игрухи, например, Quake III. Сейчас я попытаюсь рассказать что-нибудь про OpenGL. Данное руководство не подлежит осуждению по поводу малых подробностей - это всего лишь введение, не претендующее на доскональность документации. Цель этого руководства - дать читателю хоть какое-то понятие о библиотеки в короткий срок. OpenGL - обыкновенная DLL библоитека, находящаяся по адресу % Systemroot%/system32/opengl32.dll, а также надстройка к ней % Systemroot%/system32/glu32.dll. Существуют, конечно, не Microsoft-овские версии OpenGL, опережающие стандартную по некоторым скоростным характеристикам, но я их сейчас обсуждать не буду. Напомню, что существуют два способа загрузки Dll-библиотеки - явная загрузка через функцию LoadLibrary (заголовочный файл Windows.pas), позволяющий контролировать загрузку библоитеки и загружать её по пожеланию пользователя, и загрузка вместе с программой путем дописывания (в Delphi) ключевого слова external после прототипа неодходимой функции. Выглядит это так:
Однако, экспортировать так все необходимые функции довольно утомительно, поэтому все нормальные люди пользуются стандартными заголовочными файлами, к сожалению, до сих не свободными от ошибок. Существуют несколько популярных заголовочних файлов - gl, glut, glaux, все они переведены на Delphi. Я буду использовать модуль OpenGL.pas (GL.pas). Файл DGLUT.pas можно стырить здесь. Файл GLUT особенно удобно использовать при программировании на C/C++, на Delphi менее, более того, на Delphi 7 он даже не компилится (очередной баг Delphi 7). Однако, пора увидеть OpenGL в действии. -->Подключаем OpenGLКак правило, программисты игр и прочих программ, использующих OpenGL, отказываются от использования VCL. Однако нашей целью является освоение OpenGL, следовательно, попользуемся VCL. OpenGL в целом достаточно неплохо уживается со стандартными компонентами, однако ставить кнопки и другие контролы, а также дочерние окна, поверх окна OpenGL не рекомендую - иногда не пашет. Итак, создайте новое приложение и добавьте в раздел uses модуль OpenGL. В раздел private формы добавьте поля "DC:HDC;" и "hrc : HGLRC;". HDC - это тип контекста устройства в Windows. Он имеется у любого устройства вывода. В данном случае окно выводит информацию на экран. В VCL контекстом устройства НDС является хэндл Canvas.Handle. Тип HGLRC - контекст рендеринга OpenGL - внутренний тип библиотеки OpenGL. Это указатель на структуру, содержащую необходимые данные для данного устройства. Создайте новую функцию для установки формата пиксела - SetDCPixelFormat. Код её будет выглядеть так(MainForm - имя формы):
Можно заполнять структуру и вручную, но вышеуказанный метод работает почти на любой машине, тогда как вручную заполненная структура может на заработать. В некоторых отдельных случаях надо вручную устанавливать значания некоторых полей, например, "pfd.cStencilBits:=64;"(чтобы проэнэйблить буфер трафарета). В конструкторе Create(); окна необходимо инициализировать OpenGL:
Это обязано работать. Можно считать, что мы умеем подключать OpenGL. Осталось научиться рисовать. Процедура "glViewport (x,y: GLint; width, height: GLsizei);" задаёт область воспроизведения, однако она изменяет только вывод на экран (как матрица проекции). Для того, чтобы воспроизведение производилось не на всё окно, необходимо вызвать процедуру "glScissor (x,y: GLint; width, height: GLsizei);", задающую область вырезки. Перед её использованием необходимо включить проверку выхода за область вырезки : "glEnable(GL_SCISSOR_TEST);". Отключается он, соответственно, с помощью glDisable. В качестве параметров необходимо задать область воспроизведения. Если параметры в процедуре glViewPort оставить прежними, то мы будем видеть только ту часть прежнего изображения, которая попадает в область вырезки. -->Основные команды - рисуем примитивыНачну с того, что OpenGL - это вам не GDI, и перед рисованием не желательно, а обязательно надо вызывать функцию BeginPaint, а по завершении рисования EndPaint, иначе на экране будут появлятся цветные куски в самых разнообразных местах и надо указать система контроллировать их появление. Перед рисованием очистим (это не всегда нужно делать) буфер цветов. Для этого выполним команду glClear с флагом GL_COLOR_BUFFER_BIT. Цвет фона мы задавали процедурой glClearColor, требующую в качастве параметров четыре числа от 0 до 1 типа GLсlampf=Single(числа с плавающей запятой)-R,G,B,A. Цвета задаются значениями от 0 до 1. Весьма полезно очистить буфер глубины или Z-буфер(если трёхмерное изображение меняется от прорисовки к прорисовке). Это делается добавлением в процедуру glClear флага GL_DEPTH_BUFFER_BIT. Для отображения примитивов не в порядке выполнения процедур внутри FormPaint-а, а прорисовывать более близкие объекты позднее более отдалённых, необходимо запустить процедуру glEnable c параметром GL_DEPTH_TEST - включить тест глубины. В конце рисования (поскольку был запрошен режим двойной буферизации) надо поменять местами главный и вторичный буферы: "SwapBuffers(DC);". В OpenGL существуют две процедуры-скобки - glBegin(...) и glEnd(), между которыми следует выполнять рисование. В качестве параметра в процедуру glBegin передаётся переменная, характеризующая тип изображаемых объектов.
Процедура glVertex3f рисует точку в пространстве с тремя заданными координатами. Аналогично, функция glVertex2f рисует точку на плоскости и требует два параметра. Цифра в командах OpenGL характеризует количество аргументов, а буква на конце - их тип. Например, f - вещественное число, i - целое, ub - байт(UNSIGNED_BYTE). Как правило, в OpenGL существуют несколько аналогичных процедур с разными типами параметров. Из них предпочтительнее те, у которых агрументы вещественны, поскольку сама библиотека хранит их в вещественном виде. Процедура glColor3f требует в качестве параметра три вещественных числа - R, G, B - в диапазоне от 0 до 1 - тип GLclampf. (Кстати, получить теущий цвет можно с помощью команды glGetFloatv (GL_CURRENT_COLOR, @ColorArray). ColorArray - массив из 4-х вещественных чисел (RGBA)). Левый нижний угол (на плоскости, без масштабирования) имеет координату (-1, -1), правый верхний, соответственно, (1, 1). Если вы заметили, точки рисуются квадратными, чтобы их сгладить, необходимо включить режим сглаживания точек : "glEnable(GL_POINT_SMOOTH);". Проверить, проэнейблен ли какой-нибудь режим, необходимо вызвать функцию glIsEnabled(<"режим">). Сейчас несколько поподробнее об агрументах команды glBegin(mode : Integer). Вот возможно неполный список возможных параметров:
Из употреблённых команд осталась ещё нерассмотренной команда glFrustum, задающая перспективу. Просто рисование мысленно происходит внутри комнаты и всё выходящее за её пределы отсекается. glFrustum задаёт доступную облать по осям x, y и z (наименьшее возможное значение, затем - наибольшее). Ось X направлена слева направо, Y - снизу вверх, ось Z - от нас. -->Матрицы OpenGLПрочитав предыдущий пример, читатель может подумать, что поворот, растяжение, сдвиг и прочие операции воспроизвести достаточно затруднительно. Но это не так. В OpenGL это достигается при помощи матриц. Рассмотрим некоторую точку в пространстве с точки зрения библиотеки (например, вершину какой-нибудь фигуры). Реально изображаемый трёхмерный объект будет получен путём умножения вектора на так называемую матрицу модели (GL_MODELVIEW). Далее он будет спроектирован на плоскость экрана путём умножения на матрицу проекции. Теперь опишу некоторые полезные команды, связанные с матрицами.
Сейчас я приведу пример "человеческой"(рекомендуемой) последовательности команд в OnResize или (похуже, но тоже возможно) перед прорисовкой в OnPaint, правильно реагирующую на изменение размеров окна. Выглядеть она будет так:
Это некий стандарт вывода изображения в окно с переменными размерами. То, что он будет работать, и так понятно, но почему это оптимальная последовательность... Ну хотя бы потому, что здесь мы везде грузим единичные матрицы - что может выполняться быстрее? Теперь немного о командах, изменяющих матрицу проекции. Матрица проекции формирует вывод на экран и определяется местоположением "глаза" и перспективой. Задание перспективы происходит при помощи команды glFrustum(...), описанной выше, команды "gluPerspective(fovy, aspect, zNear, zFar : GLDouble);"(угол видимости к оси Y, угол видимости к оси X, ближняя плоскость отсечения по оси Z, дальняя плоскость отсечения по оси Z), "gluOrtho(left, right, bottom, top, zNear, zFar);"("плоская" проекция, не видимость, свойственная глазу, а проекция на плоскость по стандартным правилам начертальной геометрии), "gluOrtho2D(left, right, bottom, top);"(то же, что и "gluOrtho(...)", но zNear = -1, zFar = 1 и работает несколько побыстрее). Местоположение глаза определяется командой "gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz : Double);". Здесь eye* - соответствующая координата глаза, center* - соответствующая координата центра экрана, up* - соответствующая координата вектора поворота. Как пользоваться первыми двумя параметрами, интуитивно понятно, но как быть с вектором up? На самом деле OpenGL выбирает максимальную (по модулю) из координат вектора и делит оставшиеся на неё. Поэтому вариант (0,0,0) не запашет. Далее происходит поворот по каждой из осей из расчёта "1" = 180o. Иллюстрация к команде gluLookAt и матрицам GL_MODELVIEW и GL_PROJECTION можно качнуть здесь. -->Свет и материалыЧтобы разрешить работу со светом, необходимо выполнить команду "glEnable(GL_LIGHTING);". Чтобы выключить, соответственно, "glDisable(GL_LIGHTING);" Далее нужно разрешить работу с каждым используемым источником по отдельности: "glEnable(GL_LIGHT0);". Чтобы узнать максимально возможное число источников света, небходимо вызвать "glGetIntegerv(GL_MAX_LIGHTS, @<целое число>);". Для каждого многоугольника при этом необходимо прописать нормаль - "glNormal3f(x, y, z : GLfloat);". Можно запретить рисование передней или задней сторон(с одной стороны полигон прозрачен): glEnable(GL_CULL_FACE); glCullFace(GL_FRONT);//(или, наоборот, GL_BACK). Свойства источника можно задавать функцией glLightfv(light, pname : GLEnum; params : PGLfloat);. Параметр light - индекс источника. Параметр pname может принимать значения GL_POSITION, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_SPOT_CUTOFF, GL_SPOT_DIRECTION, GL_SPOT_EXPONENT и GL_SPECULAR. GL_POSITION задаёт координаты источника, читаемые из массива "array [0..3] of GLfloat;", первые три ячейки которого задают координаты источника, а последний параметр равен 1.0. Замечу также, что при преобразованиях системы координат источник остаётся на месте! Параметры GL_AMBIENT, GL_DIFFUSE и GL_SPECULAR задают свойства окружающей среды. Теперь поподробнее о свойствах источника, то есть о флажках. Итак, массив по адресу params должен содержать:
Ещё существует замечательная функция glLightModelfv(pname : GLenum, params : PGLtype). Параметр pname может принимать следующие значения :
Теперь о свойствах материала. Свойства материала задаются с помощью команды "glMaterialfv(face, pname: GLenum; params: PGLfloat);". Перед использованием этой процедуры необходимо включить режим "GL_COLOR_MATERIAL" : "glEnable(GL_COLOR_MATERIAL);". Команда "glMaterialfv" устроена аналогично команде "glLightfv". Параметр face может принимать значения GL_FRONT, GL_BACK и GL_FRONT_AND_BACK, в зависимости от того, у какой стороне полигонов мы хотим изменить свойства материала. Параметр face может принимать следующие значения (для справки, запись "четыре вещественных числа" означает указатель на массив из четырёх вещественных чисел типа array [0..3] of GLfloat;):
В качестве примера к свойствам материала я предложу вам проект - набор шариков с разными свойствами. Понаблюдать за перемещением источника света можно тут. -->Quadric-объектыВ библиотеке glu находятся некоторые полезные функции, без которых, в принципе, обойтись можно. Одним из нововведений являются Quadric-объекты. Для использования их, необходимо объявить переменную типа GLUquadricObj внутри объекта окна или глобально. Объект необходимо создать (выделить память) и удалить (освободить память). Это можно делать в FormPaint-е соответственно перед и после рисования, можно в "FormCreate" и "FormDestroy". Создание Quadric-объекта производится функцией "gluNewQuadric();", а его удаление функцией "gluDeleteQuadric(state: GLUquadricObj);". Функция "gluNewQuadric" возвращает созданный объект, а процедура "gluDeleteQuadric" принимает в качестве параметра подлежащий удалению объект. Для обработки ошибок при работе с Quadric-объектами предназначена команда gluQuadricCallback(quadObject: GLUquadricObj; which: GLenum; callback: Pointer). Первый параметр - Quadric-объект. Второй равен константе GLU_TESS_ERROR. Третий параметр callback - процедура со стандартным вызовом (допишите ключевое слово StdCall; после объявления процедуры), в которой у вас будет происходить обработка ошибки, например, уведомление пользователя об ошибке. Данная процедура не может быть объявлена внутри класса. Процедура "gluQuadricDrawStyle (quadObject: GLUquadricObj; drawStyle: GLenum);" задаёт режим отображения Quadric-объекта. Первый параметр - объект. Второй может быть равен GLU_POINT,{рисовать только точки} GLU_LINE,{рисовать только линии (рёбра)} GLU_FILL,{рисовать всё (залить грани)} GLU_SILHOUETTE{рисовать только контур (силуэт)}. Направление нормалей задаётся процедурой gluQuadricOrientation(quadObject: GLUquadricObj; orientation: GLenum);". Первый параметр - объект, второй - ориентация нормалей, может быть либо "GLU_INSIDE" (внутрь) или "GLU_OUTSIDE" (наружу). О том, как их(нормали) строить, говорит функция "gluQuadricNormals(quadObject: GLUquadricObj; normals: GLenum);", где первый параметр - сам объект, а второй равен "GLU_NONE"(не строить), "GLU_FLAT"(перпендикулярно плоскости грани), "GLU_SMOOTH"(строить под углом, таким образом, чтобы сгладить объект). В качестве иллюстрации я предложу вам достаточно наглядный проект-иллюстрацию книге. Понажимайте на клавиши "1", "2", "3", "4", "up", "down", "left" и "right". Скачать его можно здесь. |