Взявшись за написание OpenGL программы, вы думаете, что написав такую программу можно запустить её на любом компьютере под управлением любой операционной системы? Да, к самому коду OpenGL это вполне применимо, но необходимо также учитывать, что у разных операционных систем свои особенности, а это значит что для конкретной операционной системы нужно произвести начальные настройки OpenGL, свойственные только этой операционной системе. У разных операционных систем свои способы вывода графической информации на устройства и свои реализации OpenGL. Данный материал относится к конкретной операционной системе - Windows. Материал разделён на две части: программирование с использованием Visual С++ и программирование с использованием Delphi. В конце каждой части приводятся примеры, которые можно переписать себе. Visual C++ под Windows. Самым главным этапом в инициализации OpenGL под Windows является заполнение информации о формате пикселей. Таким образом вы извещаете операционную систему о том, какую поверхность для вывода OpenGL информации конкретно вы хотите. Здесь указывается такая информация как глубина цвета, различные флаги поверхности (двойная или одинарная буферизация, ...), и т.д. Вся эта информация представляется в специальной структуре, которая называется PIXELFORMATDESCRIPTOR. Вот эта структура: typedef struct tagPIXELFORMATDESCRIPTOR { WORD nSize; WORD nVersion; DWORD dwFlags; BYTE iPixelType; BYTE cColorBits; BYTE cRedBits; BYTE cRedShift; BYTE cGreenBits; BYTE cGreenShift; BYTE cBlueBits; BYTE cBlueShift; BYTE cAlphaBits; BYTE cAlphaShift; BYTE cAccumBits; BYTE cAccumRedBits; BYTE cAccumGreenBits; BYTE cAccumBlueBits; BYTE cAccumAlphaBits; BYTE cDepthBits; BYTE cStencilBits; BYTE cAuxBuffers; BYTE iLayerType; BYTE bReserved; DWORD dwLayerMask; DWORD dwVisibleMask; DWORD dwDamageMask; } PIXELFORMATDESCRIPTOR; Теперь рассмотрим все поля структуры поподробнее: nSize Определяет размер этой структуры данных. Это значение должно быть установлено как sizeof(PIXELFORMATDESCRIPTOR). nVersion Определяет версию текущей структуры данных. Это значение должно быть установлено в 1 dwFlags Множество битовых флагов, которые определяют свойства буфера пикселей. Вы можете комбинировать следующие флаги: - PFD_DRAW_TO_WINDOW
Буфер можно рисовать в окне или на поверхности устройства. - PFD_DRAW_TO_BITMAP
Буфер можно рисовать в битовый массив в памяти. - PFD_SUPPORT_GDI
Буфер поддерживает рисование GDI. В текущей реализации этот флаг нельзя использовать вместе с PFD_DOUBLEBUFFER. - PFD_SUPPORT_OPENGL
Буфер поддерживает рисование OpenGL. - PFD_GENERIC_ACCELERATED
Формат пикселей поддерживается драйвером устройства который ускоряет программную реализацию. Если этот не установлен и флаг PFD_GENERIC_FORMAT установлен, тогда формат пикселей поддерживается только программной реализацией. - PFD_GENERIC_FORMAT
Формат пикселей поддерживается программной реализацией GDI. Если этот флаг не установлен, то формат пикселей поддерживается драйвером устройства или аппаратно. - PFD_NEED_PALETTE
Для управления палитрой устройства используются RGBA пиксели. Логическая палитра используется, чтобы достичь самых лучших результатов для этого типа пикселов. Цвета в палитре должны быть определены соответственно в полях: cRedBits, cRedShift, cGreenBits, cGreenShift, cBluebits, и cBlueShift. Палитра должна быть создана и инициализирована в контексте устройства до вызова функции wglMakeCurrent. - PFD_NEED_SYSTEM_PALETTE
Определяется для оборудования, которое поддерживает только одну аппаратную палитру (только в режиме 256 цветов). Для использования ускорения в таких устройствах палитра должна быть установлена в фиксированном порядке (например 3-3-2) в режиме RGBA или должна совпадать с логической палитрой в режиме индексации цветов. Когда этот флаг установлен, вы должны вызвать функцию SetSystemPaletteUse в вашей программе для принудительного отображения один к одному логической и системной палитры. Типично этот флаг сброшен, когда ваше OpenGL оборудование поддерживает несколько аппаратных палитр и драйвер устройства может распределять резервные аппаратные палитры для OpenGL. Также этот флаг не установлен для основного формата пикселей. - PFD_DOUBLEBUFFER
Если этот флаг установлен, то используется двойная буферизация. Как уже было сказано, этот флаг нельзя использовать совместно с флагом PFD_SUPPORT_GDI. - PFD_STEREO Буфер
стереоскопический. В этом режиме используются два буфера (левый и правый) для получения стерео изображения. Этот флаг не поддерживается в текущей основной реализации. - PFD_SWAP_LAYER_BUFFERS
Показывает, может ли устройство менять местами отдельные уровневые плоскости с форматами пикселей, которые включают верхние и нижние плоскости при двойной буферизации. Иначе все уровневые плоскости рассматриваются как одна группа. Если этот флаг установлен, то поддерживается функция: wglSwapLayerBuffers iPixelType Этот флаг определяет тип пикселей. Возможны следующие значения: - PFD_TYPE_RGBA
RGBA пиксели. Каждый пиксель определяется четырьмя компонентами в таком порядке: красный, зелёный, синий, альфа. - PFD_TYPE_COLORINDEX
Индексированный пиксели. Цвет каждого пикселя определяется индексом в специальной таблице цветов (палитре). cColorBits Определяет число битовых плоскостей в каждом буфере цвета. Для режима RGBA определяет размер буфера цвета, исключая альфа-плоскости. В режиме индексации цветов определяет буфера индексов. cRedBits Определяет количество битовых плоскостей красного цвета в каждом RGBA буфере. cRedShift Определяет сдвиг для битовых плоскостей красного цвета в каждом RGBA буфере. cGreenBits Определяет количество битовых плоскостей зелёного цвета в каждом RGBA буфере. cGreenShift Определяет сдвиг для битовых плоскостей зелёного цвета в каждом RGBA буфере. cBlueBits Определяет количество битовых плоскостей синего цвета в каждом RGBA буфере. cBlueShift Определяет сдвиг для битовых плоскостей синего цвета в каждом RGBA буфере. cAlphaBits Определяет количество битовых плоскостей альфа в каждом RGBA буфере. Альфа плоскости не поддерживаются. cAlphaShift Определяет сдвиг для битовых плоскостей альфа в каждом RGBA буфере. Альфа плоскости не поддерживаются. cAccumBits Определяет общее число битовых плоскостей в буфере аккумулятора. cAccumRedBits Определяет число битовых плоскостей красного цвета в буфере аккумулятора. cAccumGreenBits Определяет число битовых плоскостей зелёного цвета в буфере аккумулятора. cAccumBlueBits Определяет число битовых плоскостей синего цвета в буфере аккумулятора. cAccumAlphaBits Определяет число битовых плоскостей альфа в буфере аккумулятора. cDepthBits Определяет размер буфера глубины (ось Z). cStencilBits Определяет размер буфера трафарета. cAuxBuffers Определяет число вспомогательных буферов. Вспомогательные буферы не поддерживаются. iLayerType Принимает одно из следующих значений: PFD_MAIN_PLANE, PFD_OVERLAY_PLANE, PFD_UNDERLAY_PLANE. bReserved Определяет число плоскостей переднего и заднего плана. Биты 0..3 определяют до 15 плоскостей переднего плана, а биты 4..7 - до 15 плоскостей заднего плана. dwLayerMask Игнорируется. Раньше этот флаг использовался, но не долго. dwVisibleMask Определяет цвет или индекс прозрачности задней плоскости. dwDamageMask Игнорируется. Раньше этот флаг использовался, но не долго. Итак, после того как наша структура заполнена, мы приступаем к самой инициализации. Для начала нам нужно передать на рассмотрение системе, выбранный нами формат пикселей. После того как система просмотрит его, она выберет наиболее совпадающий формат с тем, который поддерживается в контексте устройства. Вот эта функция: int ChoosePixelFormat(HDC hdc, CONST PIXELFORMATDESCRIPTOR *ppfd); Как я уже сказал, функция просматривает в контексте устройства - hdc наиболее подходящий формат пикселей и выбирает его. ppfd - это указатель на нашу структуру. В случае успешного завершения функция возвращает индекс формата пикселей (значение начиная с 1) или возвращает 0 - в случае ошибки. Как только система выбрала формат пикселей, нам нужно установить его в контексте устройства. Для этого существует следующая функция: BOOL SetPixelFormat(HDC hdc, int iPixelFormat, CONST PIXELFORMATDESCRIPTOR *ppfd); Параметры функции: hdc - контекст устройства, для которого устанавливается данный формат пикселей, iPixelFormat - индекс, который получен при помощи предыдущей функции, ppfd - указатель на структуру PIXELFORMATDESCRIPTOR, которая содержит логическую спецификацию формата пикселей и эта структура никак не действует на функцию SetPixelFormat . В случае успеха функция возвратит TRUE, в случае неудачи - FALSE. Существуют ещё две функции, они используются реже чем две предыдущие. Обе предоставляют информацию о формате пикселей для контекста устройства. int GetPixelFormat(HDC hdc); Функция возвращает текущий формат пикселей для контекста устройства hdc, т.е. возвращает значение больше 0, если произошла ошибка функция возвратит 0. int DescribePixelFormat(HDC hdc, int iPixelFormat, UINT nBytes, LPPIXELFORMATDESCRIPTOR ppfd); Эта функция позволяет получить информацию о формате пикселей, заданного индексом iPixelFormat для контекста устройства hdc. Полученную информацию функция записывает в ppfd - это структура типа PIXELFORMATDESCRIPTOR, из которой вы уже сможете смотреть конкретную информацию о формате пикселей. Ещё один параметр - nBytes, это размер структуры в байтах, на которую ссылается ppfd, его можно определить например так: sizeof(PIXELFORMATDESCRIPTOR); При успешном завершении функция возвращает максимальный доступный индекс формата пикселей. Если возникает ошибка, функция возвратит 0. Чтобы вам стало понятнее, как на практике всё это применять, я написал небольшую программку, которая демонстрирует инициализацию OpenGL в Windows. Посмотрите на следующий код, он демонстрирует заполнение структуры PIXELFORMATDESCRIPTOR , запрашивает информацию в системе и устанавливает для контекста устройства формат пикселей. В данном примере инициализация OpenGL производится в конструкторе главного окна, т.е. сначала создаётся главное окно программы и сразу за ним устанавливается OpenGL. CMainWin::CMainWin() { // Тут создаётся главное окно программы Create(NULL, "OpenGL MFC Sample"); PIXELFORMATDESCRIPTOR pfd; memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; pfd.cRedBits = 0; pfd.cRedShift = 0; pfd.cGreenBits = 0; pfd.cGreenShift = 0; pfd.cBlueBits = 0; pfd.cBlueShift = 0; pfd.cAlphaBits = 0; pfd.cAlphaShift = 0; pfd.cAccumBits = 0; pfd.cAccumRedBits = 0; pfd.cAccumGreenBits = 0; pfd.cAccumBlueBits = 0; pfd.cAccumAlphaBits = 0; pfd.cDepthBits = 32; pfd.cStencilBits = 0; pfd.cAuxBuffers = 0; pfd.iLayerType = PFD_MAIN_PLANE; pfd.bReserved = 0; pfd.dwLayerMask = 0; pfd.dwVisibleMask = 0; pfd.dwDamageMask = 0; // Создаём новый контекст устройства m_pDC = new CClientDC(this); HDC hdc = m_pDC->GetSafeHdc(); // Выбираем подходящий формат пикселей int pixelFormat = ChoosePixelFormat(hdc, &pfd); if (pixelFormat == 0) { MessageBox("Error in Pixel format choosing!"); return; } // Устанавливаем теперь этот формат пикселей BOOL bresult = SetPixelFormat(hdc, pixelFormat, &pfd); if (!bresult) { MessageBox("Error in Pixel format registering!"); return; } // Создаём новый контекст воспроизведения для OpenGL rc = wglCreateContext(hdc); if (!rc) { MessageBox("Error in OpenGL context create!"); return; } // Делаем созданный контекст текущим. wglMakeCurrent(hdc, rc); // Дальше идут уже команды OpenGL. glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glViewport(0, 0, 400, 300); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(-1, 1, -1, 1); glMatrixMode(GL_MODELVIEW); } Вы наверно заметили в приведённом выше примере, что после того как устанавливается формат пикселей, дальше идут две функции wglCreateContex и wglMakeCurrent . Эти функции служат для создания контекста воспроизведения OpenGL. Что это такое и как его использовать, читайте дальше. Контекст воспроизведения OpenGL чем то напоминает контекст устройства Windows, но не является тем же самым. Для того чтобы рисовать в окне Windows, вы должны сначала запросить у системы контекст устройства (т.е. окна), который связан с конкретным устройством, на котором осуществляется рисование (т.е. окно, но может быть и не окном). Далее нужно создать контекст воспроизведения OpenGL, который необходимо связать с контекстом устройства в Windows, а потом выбрать его, т.е. сделать текущим. Зачем делать его текущим? Дело в том, что можно создать много контекстов воспроизведения, каждый из них будет связан со своим устройством отображения, но для того чтобы работать в OpenGL надо выбрать только один (текущий) контекст. Посмотрите на рисунок и вам станет понятнее: src="http://gamecreating.3dn.ru/pic/opengl_context.jpg" width=400> На рисунке синим цветом показан текущий поток, т.е. путь по которому осуществляется визуализация. Этот путь можно описать следующим образом: команды, вырабатываемые конвейером OpenGL поступают в контекст воспроизведения OpenGL, который в понятном (для контекста устройства Windows) виде предоставляет фрагменты, которые надо нарисовать на поверхности окна или устройства. Дальше Windows сама распоряжается что и как делать. В результате чего получается изображение. В процессе выполнения OpenGL программы вы можете переключать текущий поток на другие контексты воспроизведения. Таким образом используя несколько контекстов воспроизведения можно строить мощные комплексы воспроизведения. Например можно организовать сеть воспроизведения, где один компьютер будет вырабатывать команды OpenGL (такой компьютер называется клиентом) и будет переключать эти потоки команд между несколькими компьютерами (серверами), которые будут визуализировать поступающую информацию. Переходим теперь к практической части. То есть к программированию под Windows. В распоряжения программиста предоставлено несколько команд для управления контекстами воспроизведения. Сначала необходимо создать контекст воспроизведения, делает это функция: HGLRC wglCreateContext(HDC hdc); Эта функция создаёт новый контекст воспроизведения OpenGL, который подходит для рисования на устройстве, определённом дескриптором hdc. В случае успешного завершения функция возвращает дескриптор созданного контекста воспроизведения, в случае неудачи возвращает значение NULL. Следующая функция позволяет выбрать текущий контекст воспроизведения для потока воспроизведения. Если контекстов создано несколько, то надо выбрать один из них. Функция выглядит таким образом: BOOL wglMakeCurrent(HDC hdc, HGLRC hglrc); Контекст устройства задаётся параметром hdc, контекст воспроизведения OpenGL - параметром hglrc. Если функция выполняется успешно, то она возвратит TRUE, в противном случае возвратит FALSE. Есть ещё две функции, которые носят информационный характер. Первая из них: HGLRC wglGetCurrentContext(VOID); Она возвращает дескриптор текущего контекста воспроизведения или NULL, если такового не существует. Другая функция позволяет получить контекст устройства, который ассоциирован с текущим контекстом воспроизведения: HDC wglGetCurrentDC(VOID); В случае успешного завершения (если в потоке существует контекст воспроизведения) функция возвратит контекст устройства, связанного с ним или NULL, в противном случае. И ещё одна функция, которую в основном вызывают в конце работы с OpenGL: BOOL wglDeleteContext(HGLRC hglrc); Эта функция удаляет контекст воспроизведения. Необходимо также позаботиться об удалении контекста устройства Windows, связанного с этим контекстом воспроизведения, это делается Windows функцией: BOOL DeleteDC(HDC hdc); В случае успешного завершения функция wglDeleteContext возвращает TRUE, в противном же случае - FALSE. Ну и на последок маленький кусочек кода, который вызывается при завершении программы, поможет вам побольше разобраться в практической части. afx_msg void CMainWin::OnDestroy() { HDC hdc = m_pDC->GetSafeHdc(); wglMakeCurrent(hdc, rc); wglDeleteContext(rc); CFrameWnd::OnDestroy(); } В конце этого раздела приведу пару примеров, которые вы можете скачать в виде ZIP файлов. href="http://www.vbstreets.ru/lavision/default.asp?include=download.asp&id=opengl_mfc.zip">OpenGL_MFC.zip - Инициализация OpenGL в Windows средствами MFC href="http://www.vbstreets.ru/lavision/default.asp?include=download.asp&id=opengl_win32.zip">OpenGL_Win32.zip - Инициализация OpenGL в Windows средствами Win32, т.е. без MFC. Delphi под Windows. Программирование OpenGL на Delphi под Windows сильно напоминает то, что было описано выше. Всё то же надо проделать, всё те же шаги для инициализации OpenGL. Только в Delphi это всё выглядит иначе, правильно язык то другой - pascal! Итак приведу основные шаги, которые надо проделать для Delphi. - Во первых надо
нужен специальный файл, который называется OpenGL.pas. Его можно скачать на моей страничке. Автор этого файла: Mike Lischke, public@lischke-online.de. Файл представляет собой ничто иное как портированную версию OpenGL для Delphi. Необходимо подключить этот файл к проекту. - В форме, где вы
собираетесь использовать OpenGL необходимо в метод обработки события TForm.FormCreate(...) вставить команды по инициализации OpenGL, например как показано на примере: procedure TMain.FormCreate(Sender: TObject); var PixelFmt: Integer; pfd: TPixelFormatDescriptor; begin InitOpenGL; DC := GetDC(Handle); FillChar(pfd, SizeOf(pfd), 0); with pfd do begin nSize := sizeof(pfd); nVersion := 1; dwFlags := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER; iPixelType:= PFD_TYPE_RGBA; cColorBits:= 24; cDepthBits:= 32; iLayerType:= PFD_MAIN_PLANE; end; PixelFmt := ChoosePixelFormat(DC, @pfd); SetPixelFormat(DC, PixelFmt, @pfd); hrc := wglCreateContext(DC); wglMakeCurrent(DC, hrc); glClearColor(0.0, 0.0, 0.0, 1.0); glViewport(0, 0, 400, 300); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(-1, 1, -1, 1); glMatrixMode(GL_MODELVIEW); end; Как видно из примера, сначала необходимо выполнить функцию: InitOpenGL - эта функция из файла OpenGL.pas и она позволяет произвести предварительную инициализацию OpenGL. Далее идёт заполнение структуры PIXELFORMATDESCRIPTOR , а далее уже всё по знакомой схеме, описанной выше. - Для рисования
в окне OpenGL необходимо в метод TForm.FormPaint(...) вставить соответствующий код. - Ну и наконец при
завершении приложения необходимо написать следующее: procedure TMain.FormDestroy(Sender: TObject); begin wglMakeCurrent(0, 0); wglDeleteContext(hrc); ReleaseDC(Handle, DC); end; Заключение: Теперь, когда вы наконец то завершили инициализацию OpenGL под Windows, можно спокойно начинать программировать на OpenGL. Хотя если вы не только будете рисовать в окне, а захотите работать например с клавиатурой или мышкой, то вам опять OpenGL не поможет и надо будет использовать функции Windows, но это тема уже другой статьи. Скажу лишь несколько слов о том, что существуют специальные библиотеки, например GLUT, которые берут управление инициализацией, окнами, устройствами ввода/вывода на себя и не зависят от конкретной операционной системы, но это тоже тема другой статьи. |