Урок 1. Простейшее приложение OpenGL4

11.08.2012

Этим циклом мы начинаем базовый курс уроков по OpenGL4. Один из лозунгов OpenGL звучиттак: Revolution throught Evolution. И действительно, текущая версия OpenGL поистине революционная: в ней есть всё, чтобы запустить любую 3d систему, от продвинутых игр, до солидных приложений, требующих обработки графики. В то же время OpenGL начиналась всего лишь как библиотека рендера в реальном времени нескольких геометрических фигур, так что от простой системы, до мощной и комплексной OpenGL прошла большой эволюционный путь. В этом уроке вы узнаете: как устанавливать среду разработки OpenGL как создавать окно; как инициализировать OpenGL и получать доступ к интерфейсу; как отобразить наше первое окно OpenGL

Установка среды разработки OpenGL

Для того, чтобы работать с OpenGL, необходимо иметь минимальные базовые навыки программирования под Windows. Более того, нужно быть неплохим программистом и хорошо знать C++, чтобы освоить OpenGL и уже иметь определенный опыт написания программ. Для разработки подойдет компилятор MicrosoftVisualStudio2010.

Установка комплекта OpenGL

OpenGLне имеет единого OpenGLSDK. Однако вы можете собрать сами свою мини-версию SDK для OpenGL. Чтобы начать работать с OpenGL вам потребуется ознакомится с тем, где находится справка по функциям OpenGL определенной версии, например OpenGL4.x, и такая справка находится на сайте OpenGL. А затем скачать несколько комплектов OpenGL библиотек, которые вы будете подключать по мере необходимости к своему проекту или проектам. Для того, чтобы подключить ту или иную библиотеку необходимо в вашем компиляторе:

  • Подключить путь к файлам include библиотеки
  • Подключить путь к файлам lib библиотеки
  • Добавить к проекту скомпилированные библиотеки

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

Архитектура нашей программы

Наше приложение OpenGL как и последующие приложения будет иметь следующую архитектуру: Для каждой отдельной операции мы будем использовать функцию. Большинство из этих функций будут вызываться из WinMain, некоторые из MsgProc. В нашем примере мы будем рассматривать отдельно каждую функцию и её содержимое

Главная функция приложения: WinMain

Каждое приложение Windows должно иметь функцию WinMain. Мы будем использовать эту функцию с той же целью, что и всегда – как главную функцию программы. Рассмотрим исходный текст нашей функции WinMain:

int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
UNREFERENCED_PARAMETER( hPrevInstance );
UNREFERENCED_PARAMETER( lpCmdLine );
 
if( FAILED( InitWindow( hInstance, nCmdShow ) ) )
        return 0;
if( FAILED( InitCompatibleContext() ) )
        return 0;
if( FAILED( InitContext() ) )
{
        CleanupDevice();
        return 0;
}
 
// Цикл обработки сообщений
MSG msg = {0};
while( WM_QUIT != msg.message )
{
        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else
        {
            Render();
        }
}
CleanupContext();
return ( int )msg.wParam;
}

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

Каждое приложение Windowsимеет цикл обработки сообщений. Обработка сообщений нужна для того чтобы отслеживать поступающие в окно сообщения от системы – клавиши, сворачивание и разворачивание окна и т.п. На самом деле отслеживание сообщений будет выполнятся в функции WndProc, однако цикл обработки сообщений – это совершенно необходимая надстройка и часть любого приложения Windowsи OpenGL. Из этого цикла выполняется функция Render, то есть периодически будет обновятся динамически меняющееся содержимое сцены. Вы можете взглянуть на содержимое Renderниже по тексту и получить уже на 75% представление о том, вообщем, как работает наше приложение, однако архитектура OpenGLимеет свою специфику и, чтобы правильно инициализировать OpenGLнужно немного изучить то , как происходит инициализация системы.

Архитектура подключения приложения к системе OpenGL

Инициализация OpenGLсильно отличается от любых других комплектов 3dсистем и имеет свою специфику. Чтобы начать работать с OpenGLнужно очень хорошо выучить эту специфику. Вся система делится на OpenGL-специфичные корневые функции и платформ-специфичные функции. OpenGL-специфичные операции выполняют всё что связано с рендер и рисованием, то есть система OpenGLфактически не знает о том, что происходит вокруг неё, в том числе, что есть еще какие другие окна, кроме того, в котором происходит рисование и т.п. Таким образом, система работает только с графикой. Все остальные уроки будут посвящены GL-специфичным функциям, так как нам нужно будет что-то рисовать и отображать. Но чтобы инициализировать OpenGLпонадобится выполнить платформ-специфичные функции. Теперь разберем последовательность инициализации OpenGL.

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

Инициализация

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

Создание OpenGLконтекста

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

Создание контекста является платформ-специфичной операцией, функция создания контекста косвенно встроена в OpenGL, и для создания контекста существует определенная методика.

Получение функций

Мы уже подошли вплотную к завершению инициализации OpenGL. Сама система OpenGLпредставляет из себя набор функций, но чтобы использовать функции к ним нужно подключиться. Подключение фукнций это что-то наподобие динамической линковки функций. В OpenGLнедостаточно сделать просто #inlude и затем написать имя библиотеки с функциям. Кроме #includeнужно еще и выполнить команду для подключения функций.

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

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

Использование функций

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

Инициализация окна:InitWindow

Итак, откройте исходный текст нашего первого приложения и найдите функцию InitWindow. Эта функция просто создает окно для нашей программы. Не забываем, что так как мы пользуемся функциями Windows, то мы должны сделать в начале программы соответствующий #include . Эта строка подключит необходимые API функции. Внутри функции InitWindow существует два основных вызова – это регистрация класса окна:

if( !RegisterClassEx( &wcex ) )        return E_FAIL;
g_hInst = hInstance;
RECT rc = { 0, 0, 640, 480 };
AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );
g_hWnd = CreateWindow( LWCaption,LWName , WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance,NULL );

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

Инициализация OpenGL:InitCompatibleContext

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

//------------------------------------------------------------------------------------
// Инициализация OpenGL совместимого контекста
//------------------------------------------------------------------------------------
HRESULT InitCompatibleContext()
{
      int iMajorVersion=0;
      int iMinorVersion=0;
 
      HDC hDC = GetDC(g_hWnd);
 
      // Устанавливаем параметры поверхности контекста
 
      PIXELFORMATDESCRIPTOR pfd;
      memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
      pfd.nSize         = sizeof(PIXELFORMATDESCRIPTOR);
      pfd.nVersion   = 1;
      pfd.dwFlags    = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
      pfd.iPixelType = PFD_TYPE_RGBA;     //тип пикселей
      pfd.cColorBits = 32;                //тип цвета
      pfd.cDepthBits = 24;                //тип z-буфера
      pfd.iLayerType = PFD_MAIN_PLANE;
 
      int iPixelFormat = ChoosePixelFormat(hDC, &pfd);
      if (iPixelFormat == 0) return false;
 
      if(!SetPixelFormat(hDC, iPixelFormat, &pfd)) return false;
 
      // Создаем совместимый контекст
 
      HGLRC hRCCompatible = wglCreateContext(hDC);
      wglMakeCurrent(hDC, hRCCompatible);
 
      bool bResult = true;
 
      if(glewInit() != GLEW_OK)
      {
      MessageBoxA(g_hWnd, "Couldn't initialize GLEW!", "Fatal Error", MB_ICONERROR);
      bResult = false;
      }
 
      wglMakeCurrent(NULL, NULL);
      wglDeleteContext(hRCCompatible);
 
      return bResult;
}

Установка совместимого OpenGLконтекста происходит в несколько шагов. Перед созданием контекста всегда устанавливается его формат. Таким образом, функцией SetPixelFormat устанавливается формат цвета пикселей, формат z-буфера а так же другие параметры. После этого функцией wglCreateContextсоздается контекст. Входящим параметров функции является hDC, то есть дескриптор окна. В нашем приложении мы создали окно предварительно функцией InitWindow, так что информация о дескрипторе окна имеется. Всегда после функции wglCreateContext должна следовать функция wglMakeCurrent.

Теперь настал ключевой момент инициализации, когда мы можем прилинковать все функции OpenGL к нашему приложению. Это делается очень простой командой glewInit. Однако обратите внимание, что если вы не создадите OpenGL контекст, вы не сможете запустить эту команду, так как без контекста OpenGL не будет иметь точку привязки графики к окну и к вашему приложению и не имея такой привязки, система OpenGL не даст доступ к функциям. Но мы создали контекст и теперь, после выполнения функции glewInit доступны все функции системы OpenGL и мы можем приступать к отображению графики. Теперь доступ к функциям OpenGL получен, и совместимый контекст больше не требуется, так что мы освободим его с помощью функции wglMakeCurrent cпараметром NULLа также функции wglDeleteContext и перейдем к следующему разделу в котором мы создадим основной контекст, то есть такой контекст, который будет работать с последними версиями OpenGL.

Инициализация OpenGL:InitContext

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

//------------------------------------------------------------------------------------
// Инициализация OpenGL основного контекста
//------------------------------------------------------------------------------------
HRESULT InitContext()
{
      int iMajorVersion=4;
      int iMinorVersion=0;
 
      HDC hDC = GetDC(g_hWnd);
      PIXELFORMATDESCRIPTOR pfd;
      ZeroMemory(&pfd,sizeof(pfd)); 
 
      bool bError=false;
 
      // Устанавливаем параметры поверхности контекста
      if(WGLEW_ARB_create_context && WGLEW_ARB_pixel_format)
      {
            const int iPixelFormatAttribList[] =
            {
                  WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
                  WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
                  WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
                  WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, //тип пикселей
                  WGL_COLOR_BITS_ARB, 32,                   //тип цвета
                  WGL_DEPTH_BITS_ARB, 24,                //тип z-буфера
                  WGL_STENCIL_BITS_ARB, 8,
                  0 // End of attributes list
            };
            int iContextAttribs[] =
            {
                  WGL_CONTEXT_MAJOR_VERSION_ARB, iMajorVersion,
                  WGL_CONTEXT_MINOR_VERSION_ARB, iMinorVersion,
                  WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
                  0 // End of attributes list
            };
 
            int iPixelFormat, iNumFormats;
            wglChoosePixelFormatARB(hDC, iPixelFormatAttribList, NULL, 1,
             &iPixelFormat, (UINT*)&iNumFormats);
 
            // PFD seems to be only redundant parameter now
            if(!SetPixelFormat(hDC, iPixelFormat, &pfd))return false; 
 
            // Создаем основной контекст
 
            hRC = wglCreateContextAttribsARB(hDC, 0, iContextAttribs);
            // If everything went OK
            if(hRC) wglMakeCurrent(hDC, hRC);
            else bError = true;
 
      }
      else bError = true;
      if(bError) {      // Generate error messages }
 
   return S_OK;
}

Итак, после выполнения функции glewInit в предыдущем разделе, повторим всю последовательность действий, но используя новые функции, и создадим окончательный контекст для версии OpenGL4.x, с которым далее и будем работать. Для создания основного контекста в нашем приложении сначала определим формат устройства в структуре iPixelFormatAttribList а также установим формат функцией wglChoosePixelFormatARB. Обратите внимание на токенARBв конце имени функции. Этот токен указывает на то, что эта функция принадлежит расширенным версиям OpenGL 4.x.

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

Отображение сцены: Render

Теперь нужно что-нибудь отобразить в нашем окне. Функция Render обычно вызывается в играх несколько десятков раз в секунду. Но нам не нужно заботится о том, чтобы устанавливать конкретные цифры. Функция OpenGLSwapBuffers сама вносит необходимую задержку, в соответствии с тем, на какой частоте работает видеоадаптер. Так что мы будем просто вызывать Render так часто, как получиться, а задержка выставиться автоматически. Рассмотрим функцию:

//------------------------------------------------------------------------------------
// Рендер
//------------------------------------------------------------------------------------
void Render()
{
    //
    // Очистка рендер-таргета
    // 
    HDC hDC = GetDC(g_hWnd);
 
    // Очистка поверхности цветом
    glClearColor(0.0f, 0.9f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT); void Render()
 
    // Отображение геометрии на рендер-таргете
    // Вывод содержимого рендер-таргета на экран
    SwapBuffers(hDC);
}

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

Очистка объектов:CleanupContext

Когда окно закрывается, нужно очистить все ранее инициализированные объекты и освободить контекст OpenGL. Для этого предназначена небольшая функция очистки, вызываемая из MsgProc. То есть, когда приходит сообщение о закрытии окна (а также когда приходят и другие сообщения) Windows вызывает MsgProc, и если окно закрывается, то мы делаем из MsgProc очистку объектов и завершение приложения.

//------------------------------------------------------------------------------------
// Очистка
//------------------------------------------------------------------------------------
void CleanupContext()
{
      HDC hDC = GetDC(g_hWnd);
 
      wglMakeCurrent(NULL, NULL);
      wglDeleteContext(hRC);
      ReleaseDC(g_hWnd, hDC);
 
      g_hWnd = NULL;
}

Цикл обработки сообщений

Перед нашей главной и глобальной функцией WinMain должна быть еще одна функция, MsgProc. Это так называемся CallBack функция, и Windows вызывает её даже самостоятельно, когда ей это потребуется. Что же касается наполнения функции MsgProc то в неё приходят различные сообщения Windows, и нужно на это как то реагировать. В дальнейшем внутрь неё вы конечно же вставите обработку клавиш «вперед, назад, влево вправо» а также обязательно «пробел» или «ctrl» для того, чтобы эффективно и быстро нажав на них уничтожать ваших ненавистных 3d – монстров из плазмомета.

LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    HDC hdc;
 
    switch( message )
    {
        case WM_PAINT:
            hdc = BeginPaint( hWnd, &ps );
            EndPaint( hWnd, &ps );
            break;
 
        case WM_DESTROY:
            PostQuitMessage( 0 );
            break;
 
        default:
            return DefWindowProc( hWnd, message, wParam, lParam );
    }
 
    return 0;
}

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

Обзор приложения

Теперь остается только скомпилировать и запустить приложение. Исходный код данного приложения а также скомпилированное приложение вы можете найти тут. Не забываем, что в системе должен быть установлен OpenGL версии не ниже 4.0. В текущих версиях Windows, например Windows 7, а также Windows 8 всё уже установлено, и наше приложение успешно запуститься на них и будет прекрасно функционировать.

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

Дополнение к уроку. Настройка среды разработки

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

  1. GL
  2. Article1
  3. ArticleN

В папке GLбудут хранится все подключаемые библиотеки, включая GLEW. В наших уроках, по мере подключения тех или иных библиотек ваша папка GL будет наполнятся ими. В исходных файлах примеров, а именно в папке LinkInfo к каждому примеру, вы найдете информацию о том, какие именно библиотеки должны быть в папке GL. В нашем первом примере в папке GL будет только библиотека GLEW. Для того, чтобы работать с файлами .cpp в MicrosoftVisual C++ нужно либо открыть существующий проект, либо создать новый. Если вы решите создать новый проект на основе существующих файлов C++, то необходимо воспользоваться следующей процедурой.

  1. Открыть MicrosoftVisual C++ 2010
  2. Выбрать пункт меню «Файл->Создать->Проект из существующего кода»

Далее нужно выбрать папку проекта, в этой папке уже должны быть будут .cpp файлы, назначить имя проекта, например, MyProject1 и далее нажать копку «Создать».

Visual C++ создаст проект, при этом в обозревателе решений будет ваш проект MyProject1. Внембудутследующиепапки:

  1. Header Files
  2. Resource Files
  3. Source Files
  4. Внешние зависимости

Прежде всего, убедитесь что в папке «SourceFiles» расположен ваш код .cpp. После этого нужно настроить собственно проект. Для этого нужно нажать в обозревателе решений на MyProject1, то есть перейти на проект, и затем выбрать в меню «Проект->Свойства».

После этого нужно нажать на «Свойства конфигурации».

Обратите внимание, что существует несколько конфигураций: Debug, Release и Все конфигурации. Перед любым дальнейшим изменеием выберите в выпадающем списке конфигурации «все конфигурации». Это нужно сделать обязательно, иначе опции, которые вы будете устанавливать применятся не ко всему, а к чему-то отдельному, что будет мешать. Итак, настроим наш проект.

  1. Во вкладке «Общее» выберите «Набор символов->Использовать набор символов Юникода»
  2. Во вкладке «Каталоги VC++» выберите «Каталоги включения», далее добавьте следующий элемент (нужно впечатать текст, который идет далее): ..\GL\glew\include\GL
  3. Во вкладке «Каталоги VC++» выберите «Каталоги библиотек», далее добавьте следующий элемент (нужно впечатать текст, который идет далее): ..\GL\glew\lib

Во вкладке «Компоновщик» выберите пункт «Ввод», затем добавьте следующий список:

glew32.lib
opengl32.lib

Итак, на этом конфигурация MiscrosoftVisual C++ завершена и можно редактировать код C++ вашего проекта. Чтобы вы поняли, для чего нужны были настройки, описанные выше, можно пару слов сказать о них. Самое главное, что мы сделали это прописали пути к файлам include а также пути к библиотекам lib. Однако все те библиотеки, которые есть в lib, либо часть из них, еще нужно подключить к проекту. Для этого и нужно было прописывать список файлов с расширением .lib– тем самым, мы подключили библиотеки к проекту.

Заключение

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

Заполнен: OpenGL4
Присвоен тэг:

avatar

Об Авторе ()

Планета назначения, JO - 8703 - IV, обозначается светлой точкой на объемном экране корабля. К тому времени, как Робоид нанялся на юпитерианский круговой рейс, ему следовало бы стать главным инженером, но после Суховодного пионерского его вышибли, внеся в черный список, и высадили в Луна-Сити за то, что вместо слежения за приборами он провел время, программируя синтезатор пищи корабля строжайше запрещенной корабельными инструкциями последовательностью синтеза пива.

Комментирование закрыто.

Наверх