Opengl инициализация или как написать приложение с нуля


4.4 Инициализация OpenGL в C#. Тестирование визуализации 3D сферы.

Необходимые знания:

Инициализация OpenGL в C#. Подключение библиотеки Tao и визуализация графики с ее помощью

Скопируйте файлы из директории
C:\Programm Files\TaoFramework\lib
в папку
C:\Windows\System32

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

Создание проекта и подключение библиотеки Tao OpenGL в С#

Сначала создайте новый проект, в качестве шаблона установив приложение Windows Forms. Назовите его Tao-OpenGL-Initialization-Test.

Дождитесь пока MS Visual Studio закончит генерацию кода шаблона. Теперь перейдите к окну Solution Explorer (Обозреватель решений). Здесь нас интересует узел Links, который отображает связи с библиотеками, необходимыми для работы нашего приложения (рис. 1).
Рисунок 1. Узел «ссылки», необходимый для подключения, который необходимо выбрать для подключения библиотек.
Назовите главное окно «Тестирование инициализации OpenGL в С# .NET». (Свойства окна -> параметр Text).

Щелкните по этому узлу правой клавишей мыши, после чего в открывшемся контекстном меню выберите «Добавить ссылку» (“Add Link”), как показано на рисунке 2.
Рисунок 2. Процесс добавления новой ссылки.
В открывшемся окне «Добавить ссылку» перейдите к закладке «Обзор». После этого перейдите к директории, в которую была установлена библиотека Tao Framework. (По умолчанию – «C:\Program Files\Tao Framework»).

Нам потребуется папка bin – в ней хранятся необходимые нам библиотеки. Перейдите в папку bin и выберите 3 библиотеки, как показано на рисунке 3:

  1. Tao.OpenGL.dll — отвечает за реализация библиотеки OpenGL.
  2. Tao.FreeGlut.dll — отвечает за реализацию функций библиотеки Glut. Мы будем ее использовать для инициализации рендера , а так же для различных других целей.
  3. Tao.Platform.Windows.dll — отвечает за поддержку элементов непосредственно для визуализации на платформе Windows.

Рисунок 3. Процесс добавления библиотек Tao.OpenGL, Tao.FreeGLUT и Tao.Platform.Windows.
На рисунке 4 мы видим все добавившиеся библиотеки в узле «Ссылки» (Links).
Рисунок 4. Добавление элемента на панель инструментов — в последствии мы разместим добавляемый элемент на форме и в нем будет реализовываться визуализация.
Теперь перейдите к исходному коду окна. Для работы с нашими библиотеками нам необходимо подключить соответствующие пространства имен:

Инициализация OpenGL в C# .NET

Теперь нам необходимо инициализировать работу OpenGl.

Сначала мы в конструкторе класса должны инициализировать работу элемента AnT:

Снова перейдите к конструктору и сделайте двойной щелчок левой клавишей мыши на форме – создастся функция обработчик события загрузки формы.

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

На окне мы создали 2 кнопки. Обработчик кнопки выйти будет выглядеть следующим образом:

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

Откомпилируйте и запустите приложение.

Результат работы приложения показан на рисунке 9. Если вы правильно набрали все исходные коды и выполнили все описанные действия, то вы увидите аналогичную визуализацию сферы после нажатия на кнопке «Визуализировать».
Рисунок 9. Приложение готово: OpenGL подключен и инициализирован. Пример визаулизации при нажатии на кнопку «Визуализировать».
Вот и все. Мы протестировали работоспособность библиотеки Tao, инициализировав библиотеку OpenGL в C# .NET.

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

Примечание

Если у вас возникли проблемы с запуском приложений, обратите внимание на следующие нюансы:

Если вы используете ОС x64, первым делом попробуйте изменить тип проекта на x86. (Указать в Visual Studio).

Сергей Солонько: «System.DllNotFoundException: Не удается загрузить DLL „freeglut.dll“: Не найден указанный модуль. (Исключение из HRESULT: 0x8007007E) — решается методом копирования из папки C:\Programm Files\TaoFramework\lib в папку C:\Windows\System32.»

Clandestin: «У меня в Windows 7 копирование в C:\Windows\System32 не помогло. Зато помогло копирование непосредственно в папку C:\Windows.»

KsenoByte: «Предлагаю всем решение проблемы с SimpleOpenGLControl в Visual Studio 2010.

Для того чтобы добавить в Панель Элементов (Toolbox) элемент SimpleOpenGLControl, необходимо следующее:

1. Нажимаем правой кнопкой по вкладке «Общие» в Панели Элементов и выбираем «Выбор Элементов» (за тавтологию нижайше извиняюсь).
Выбор элементов» /> Рисунок 1. Панель элементов -> Выбор элементов.
2. В открывшемся окне обнаруживаем отсутствие SimpleOpenGLControl.

3. Нажимаем кнопку «Обзор» и находим файл библиотеки C:\Program Files\TaoFramework\bin\Tao.Platform.Windows.dll.

5. Наслаждаемся результатом.»
Рисунок 2. Решение проблемы с SimpleOpenGLControl.

Opengl инициализация или как написать приложение с нуля

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

Итак, в проекте будет 3 файла: main.cpp; init.cpp; main.h.

main.h: обьявления функций, структур и глобальных переменных.

#ifndef _MAIN_H
#define _MAIN_H

// Хидеры, необходимые для работы программы
#include
#include
#include
#include
#include

// Обьявим глобальные переменные, ширину, высоту и глубину цвета экрана
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
#define SCREEN_DEPTH 16

// Глобальные параметры окна; будут доступны из других файлов:
extern HWND g_hWnd ;
extern RECT g_rRect ;
extern HDC g_hDC ;
extern HGLRC g_hRC ;
extern HINSTANCE g_hInstance ;

// Прототип главной функции программы — WinMain
int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hprev , PSTR cmdline , int ishow ) ;

// Прототип функции обработки сообщений
LRESULT CALLBACK WinProc ( HWND hwnd , UINT message , WPARAM wParam , LPARAM lParam ) ;

// Функция — главный цикл программы
WPARAM MainLoop ( ) ;

// Функция, создающая окно
HWND CreateMyWindow ( LPSTR strWindowName , int w >, int height , DWORD dwStyle , bool bFullScreen , HINSTANCE hInstance ) ;

// Функция, устанавливающая формат пиксела
bool bSetupPixelFormat ( HDC hdc ) ;

// Прототип функции, устанавливающей размеры окна OpenGL
void SizeOpenGLScreen ( int w >, int height ) ;

// Функция, инициализирующая OpenGL
void InitializeOpenGL ( int w >, int height ) ;

// Общая инициализация
void Init ( HWND hWnd ) ;

// Функция, которая собственно рисует сцену
void RenderScene ( ) ;

// Де-инициализация
void DeInit ( ) ;

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

init.cpp: основные функции программы; инициализация и деинициализация

// Включаем наш хидер
#include «main.h»

///////////////////////////////////////////////////////////////
//
// Функция, создающая наше окошко
//
///////////////////////////////////////////////////////////////
HWND CreateMyWindow ( LPSTR strWindowName , int w >, int height , DWORD dwStyle , bool bFullScreen , HINSTANCE hInstance )
<
HWND hWnd ; // дескриптор окна
WND >; // класс окна
memset ( & wnd >, 0 , sizeof ( WND >) ) ; // Резервируем память под класс окна
wnd >style = CS_HREDRAW | CS_VREDRAW ; // Стандартные параметра
wnd >lpfnWndProc = WinProc ; // Передаём указатель на функцию обработки сообщений
wnd >hInstance = hInstance ;
wnd >hIcon = LoadIcon ( NULL , >) ; // Иконка
wnd >hCursor = LoadCursor ( NULL , >) ; // Курсор
wnd >hbrBackground = ( HBRUSH ) ( COLOR_WINDOW + 1 ) ; // Окно будет белым
wnd >lpszClassName = «Wingman`s 3dLessons» ; // Имя класса
Register >( & wnd >) ; //регистрируем класс

dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN ; // Стиль окна

// Устанавливаем заданные размеры окна.
RECT rWindow ;
rWindow. left = 0 ; // Левая сторона — 0
rWindow. right = w >; // Правая сторона — 800
rWindow. top = 0 ; // Верх — 0
rWindow. bottom = height ; // Низ — 800

AdjustWindowRect ( & rWindow , dwStyle , false ) ; // Применяем заданные размеры

// Создаём окно
hWnd = CreateWindow ( «Wingman`s 3dLessons» , strWindowName , dwStyle , 0 , 0 ,
rWindow. right — rWindow. left , rWindow. bottom — rWindow. top ,
NULL , NULL , hInstance , NULL ) ;

if ( ! hWnd ) return NULL ; // Есди не получилось — умираем.
ShowWindow ( hWnd , SW_SHOWNORMAL ) ; // Показать окно
UpdateWindow ( hWnd ) ; // И нарисовать его
SetFocus ( hWnd ) ; // Фокусирует клавиатуру на наше окно

///////////////////////////////////////////////////////////////
//
// Функция, устанавливающая формат пиксела
//
///////////////////////////////////////////////////////////////
bool bSetupPixelFormat ( HDC hdc )
<
PIXELFORMATDESCRIPTOR pfd ; // Дескриптор формата пиксела
int pixelformat ;
pfd. nSize = sizeof ( PIXELFORMATDESCRIPTOR ) ; // Устанавливаем размер структуры
pfd. nVersion = 1 ; // Всегда ставим = 1
// Передаём нужные флаги OpenGL
pfd. dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER ;
pfd. dwLayerMask = PFD_MAIN_PLANE ; // Стандартная маска (один хрен игнорируется)
pfd. iPixelType = PFD_TYPE_RGBA ; // Нам нужны RGB and Alpha типа пикселей
pfd. cColorBits = SCREEN_DEPTH ; // Используем наши #define для цветовой глубины
pfd. cDepthBits = SCREEN_DEPTH ; // Это игнорируется для RGBA, но все равно передадим
pfd. cAccumBits = 0 ;
pfd. cStencilBits = 0 ;

// Ф-я ищет формат пиксела, наиболее подходящий заданным требованиям, выход при неудаче
if ( ( pixelformat = ChoosePixelFormat ( hdc , & pfd ) ) == FALSE )
<
MessageBox ( NULL , «ChoosePixelFormat failed» , «Error» , MB_OK ) ;
return FALSE ;
>

// Сделаем наше окно OpenGL размером с главное окно программы. При желании можно сделать
// вьюпорт меньше окна.
glViewport ( 0 , 0 , w >, height ) ;

glMatrixMode ( GL_PROJECTION ) ; // Выберем матрицу проекции
glLoad >( ) ; // И сбросим её

// Вычислим перспективу нашего окна
// Параметры:
// (угол взгляда, отношение ширины и высоты,
// Ближайшее расстояние обьекта до камеры, при котором он виден,
// и дальнейшее расстояние, при котороом происходит отрисовка
gluPerspective ( 45.0f , ( GLfloat ) w >/ ( GLfloat ) height , .5f , 150.0f ) ;

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

glMatrixMode ( GL_MODELVIEW ) ; // Выберем матрицу моделей
glLoad >( ) ; // И сбросим её
>

void InitializeOpenGL ( int w >, int height )
<
g_hDC = GetDC ( g_hWnd ) ; // Устанавливаем глобальный дескриптор окна

if ( ! bSetupPixelFormat ( g_hDC ) ) // Устанавливаем формат пиксела
PostQuitMessage ( 0 ) ; // И выходим при ошибке

g_hRC = wglCreateContext ( g_hDC ) ; // Контекст рендеринга для hdc
wglMakeCurrent ( g_hDC , g_hRC ) ; // Делаем контекст текущим
glEnable ( GL_TEXTURE_2D ) ; // Включаем текстуры
glEnable ( GL_DEPTH_TEST ) ; // И тест глубины

// И устанавливаем размер вьюпорта:
SizeOpenGLScreen ( w >, height ) ;
>

///////////////////////////////////////////////////////////////
//
// Ф-я де-Инициализирует OpenGL
//
///////////////////////////////////////////////////////////////
void DeInit ( )
<
if ( g_hRC )
<
wglMakeCurrent ( NULL , NULL ) ; // Освобождает память, занятую для рендера
wglDeleteContext ( g_hRC ) ; // Удаляет контекст рендеринга OpenGL
>

if ( g_hDC )
ReleaseDC ( g_hWnd , g_hDC ) ; // Убирает HDC из памяти

Unregister >( «Wingman`s 3dLessons» , g_hInstance ) ; // Освобождаем класс окна
PostQuitMessage ( 0 ) ; // Выходим
>

// Создает окно с помощью нашей функции, в которую передаём:
// Имя, Ширину, Высоту, любые флаги для окна, хотим ли мы фулскрин, и hInstance
hWnd = CreateMyWindow ( «Wingman`s 3dLessons» , SCREEN_W >, SCREEN_HEIGHT , 0 , false , hInstance ) ;

// Выходим при ошибке
if ( hWnd == NULL ) return TRUE ;

// Инициализируем OpenGL
Init ( hWnd ) ;

// Запускаем игровой цикл
return MainLoop ( ) ;
>

Ну и теперь последний файл:
main.cpp: Главный файл, в котором и выполняется программа

// Включаем необходимые библиотеки:
// OpelGL
#pragma comment(lib, «opengl32.lib»)
#pragma comment(lib, «glu32.lib»)
#pragma comment(lib, «glaux.lib»)

// Наш главный хидер:
#include «main.h»

// Необходимые дескрипторы:
HWND g_hWnd ;
RECT g_rRect ;
HDC g_hDC ;
HGLRC g_hRC ;
HINSTANCE g_hInstance ;

glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ) ;
glLoad >( ) ;

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

void Init ( HWND hWnd )
<
g_hWnd = hWnd ;
GetClientRect ( g_hWnd , & g_rRect ) ;
InitializeOpenGL ( g_rRect. right , g_rRect. bottom ) ;
>

WPARAM MainLoop ( )
<
MSG msg ;

return ( msg. wParam ) ;
>

LRESULT CALLBACK WinProc ( HWND hWnd , UINT uMsg , WPARAM wParam , LPARAM lParam )
<
LONG lRet = 0 ;
PAINTSTRUCT ps ;

switch ( uMsg )
<
case WM_SIZE : // Если изменён размер окна

SizeOpenGLScreen ( LOWORD ( lParam ) , HIWORD ( lParam ) ) ; // LoWord=W >
GetClientRect ( hWnd , & g_rRect ) ; // получаем window rectangle
break ;

case WM_PAINT : // Если нужно перерисовать сцену
BeginPaint ( hWnd , & ps ) ; // Иниц. paint struct
EndPaint ( hWnd , & ps ) ; // EndPaint, подчищаем
break ;

case WM_KEYDOWN : // Это сообщение означает, что нажата клавиша на клавиатуре.
// Сама клавиша передаётся в параметре wParam
switch ( wParam )
<
case VK_ESCAPE : // Если нажат ESCAPE
PostQuitMessage ( 0 ) ; // Выходим
break ;
>
break ;

case WM_CLOSE : // Если окно было закрыто
PostQuitMessage ( 0 ) ; // Выходим
break ;

default : // Return по умолчанию
lRet = DefWindowProc ( hWnd , uMsg , wParam , lParam ) ;
break ;
>

Этот урок совсем простой: была описана инициализация программы и OpenGL.
В программе ничего не делается — отрисовывается чёрный экран, и ожидается нажатие
клавиши ESCAPE для выхода из программы.

Инициализация OpenGL в Windows

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

Данный материал относится
к конкретной операционной системе — Windows. Материал разделён на две
части: программирование с использованием Visual С++ и программирование
с использованием Delphi. В конце каждой части приводятся примеры, которые
можно переписать себе.

Visual C++ под Windows.

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

Вот эта структура:

typedef
struct tagPIXELFORMATDESCRIPTOR <

Теперь рассмотрим
все поля структуры поподробнее:

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.

//
Тут создаётся главное окно программы

Opengl инициализация или как написать приложение с нуля

Урок 1. Инициализация в Windows

Добро пожаловать в мои уроки по OpenGL . Я обычный программист, который любит OpenGL . Первый раз я услышал об OpenGL , когда 3 DFx выпустила драйвера для OpenGL для видеокарты Voodoo 1. Тут же я понял, что OpenGL это то, чему я должен научиться. В это время было довольно сложно найти нужную информацию по OpenGL в книгах или Интернет. Я потратил много времени, чтобы сделать рабочий код и часто спрашивал про OpenGL по электронной почте или на каналах IRC . Я увидел, что те люди, которые знали OpenGL , считали себя элитой, и не хотели делиться своими знаниями. Что очень неприятно!

Этот сайт был создан для того, чтобы помочь людям, которые интересуются OpenGL. В других уроках я попытаюсь объяснить в деталях, на сколько это будет возможно, что делают отдельные строки кода. Я попытаюсь сделать код как можно проще (не используя MFC)! Так, чтобы даже абсолютный новичок в Visual C ++ и OpenGL был способен, прочитав код, понять то, что происходит. Этот сайт всего лишь один из многих предлагающих материал для изучения по OpenGL. Если Вы опытный программист OpenGL, то сайт может показаться слишком простым. Но если Вы только начинаете, я надеюсь, что этот сайт многое может Вам предложить!

Илон Маск рекомендует:  Дочерние селекторы в CSS

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

Я начинаю этот урок непосредственного с кода, который разбит на секции, каждая из которых будет подробно комментироваться. Первое, что Вы должны сделать — это создать проект в Visual C++. Если это для Вас затруднительно, то Вам стоит для начала изучить C++, а уже затем переходить на OpenGL. При написании был использован компилятор M icroSoft Visual Studio 2005. Некоторые версии VC++ требуют замены bool на BOOL, true на TRUE и false на FALSE.

После создания нового Win32 приложения (НЕ КОНСОЛЬНОГО) в Visual C++, Вам надо будет добавить для сборки проекта библиотеки OpenGL. В меню Project/setting, выберите закладку LINK. В строке «Object/Library Modules» добавьте «OpenGL32.lib GLu32.lib GLaux.lib». Затем нажмите OK. Теперь все готово для создания программы на OpenGL.

Примечание #1: Во многих компиляторах константа CDS_FULLSCREEN — не определенна. Если получено сообщение об ошибке связанное с CDS_FULLSCREEN, то Вы должны добавить следующую строчку в начале кода Вашей программы: #define CDS_FULLSCREEN 4.

Примечание #2: Когда писался первый урок, библиотека GLAUX была такой, какой ей и следовало оставаться. Со временем её перестали поддерживать. До сих пор во многих уроках на этом сайте используется как раз прежний вариант библиотеки. Если Ваш компилятор не поддерживает GLAUX или Вы не желаете ее использовать, скачайте GLAUX REPLACEMENT CODE отсюда.

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

#include // Заголовочные файлы для Windows

#include // Заголовочные файлы для библиотеки OpenGL32

#include // Заголовочные файлы для библиотеки GLu32

#include // Заголовочные файлы для библиотеки GLaux

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

Первые строки устанавливают Контекст Рендеринга (Rendering Context). Каждая OpenGL программа связывается с Контекстом Рендеринга, который в свою очередь вызывает Контекст Устройства (Device Context). Контекст Рендеринга OpenGL определен как hRC. Для того чтобы рисовать в окне, Вам необходимо создать Контекст Устройства Windows, который определен как hDC. DC соединяет окно с GDI (Graphics Device Interface). RC соединяет OpenGL с DC.

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

HGLRC hRC=NULL; // Постоянный контекст рендеринга

HDC hDC=NULL; // Приватный контекст устройства GDI

HWND hWnd=NULL; // Здесь будет хранится дескриптор окна

HINSTANCE hInstance; // Здесь будет хранится дескриптор приложения

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

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

Смысл переменной fullscreen вполне очевиден. Если наша программа запущена в полноэкранном режиме, fullscreen имеет значение true, если же она запущенна в оконном режиме, то fullscreen будет false. Очень важно сделать эти переменные глобальными, так чтобы все другие функции знали — приложение запущенно в полноэкранном режиме или нет.

bool keys[256]; // Массив, используемый для операций с клавиатурой

bool active=true; // Флаг активности окна, установленный в true по умолчанию

bool fullscreen=true; // Флаг режима окна, установленный в полноэкранный по умолчанию

Теперь мы должны описать прототип функции WndProc(). Причиной, по которой мы должны сделать это, является то, что функция CreateGLWindow() вызывает функцию WndProc(), но WndProc() описывается после CreateGLWindow(). А в языке C (как и в C++), для вызова функции необходимо ее определить или описать прототип этой функции. Именно так, мы и поступаем в следующей строке. И теперь CreateGLWindow() может вызывать WndProc().

LRESULT CALLBACK WndProc ( HWND , UINT , WPARAM , LPARAM ); // Прототип функции WndProc

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

GLvoid ReSizeGLScene ( GLsizei width , GLsizei height ) // Изменить размер и инициализировать окно GL

if( height == 0 ) // Предотвращение деления на ноль

glViewport( 0, 0, width, height ); // Сброс текущей области вывода

Следующие строчки настраивают экран для перспективного вида. Предметы с увеличением расстояния становятся меньше. Это придаёт реалистичность сцене. Охват перспективы — 45 градусов, угол поворота оси рассчитывается на основе ширины и высоты окна. Значения 0.1f, 100.0f — отправная и конечная точки для того, чтобы определить какая будет глубина у экрана.

glMatrixMode(GL_PROJECTION) сообщает о том, что следующие две команды будут воздействовать на матрицу проекции. Матрица проекции отвечает за добавление в нашу сцену перспективного вида. glLoadIdentity() – это функция работает подобно сбросу. Она восстанавливает выбранную матрицу в первоначальное состояние. Раз матрица проекции сброшена, необходимо вычислить перспективу для сцены. После вызова glLoadIdentity() мы инициализируем перспективный вид нашей сцены. glMatrixMode(GL_MODELVIEW) сообщает, что любые новые трансформации будут воздействовать на матрицу вида модели. Матрица вида модели — то «место», где сохранена информация о наших объектах. Позже мы сбрасываем матрицу вида модели. Не волнуйтесь, если что-то из выше изложенного Вы не понимаете, я буду обучать всему этому в дальнейших уроках. Только запомните, что НАДО сделать, если Вы хотите получить красивую перспективную сцену.

glMatrixMode( GL_PROJECTION ); // Выбор матрицы проекций

glLoadIdentity(); // Сброс матрицы проекции

// Вычисление соотношения геометрических размеров для окна

gluPerspective( 45.0f, (GLfloat)width/(GLfloat)height, 0.1f, 100.0f );

glMatrixMode( GL_MODELVIEW ); // Выбор матрицы вида модели

glLoadIdentity(); // Сброс матрицы вида модели

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

int InitGL( GLvoid ) // Все установки касаемо OpenGL происходят здесь

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

glShadeModel( GL_SMOOTH ); // Разрешить плавное цветовое сглаживание

Следующая строка устанавливает цвет, которым будет очищен экран. Для тех, кто не знает, как устроены цвета, я постараюсь кратко объяснять. Все значения могут быть в диапазоне от 0.0f до 1.0f, при этом 0.0 самый темный, а 1.0 самый светлый. Первый аргумент в glClearColor — это интенсивность Красного (Red), второй – Зеленного (Green), третий – Синего (Blue). Наибольшее значение – 1.0f, является самым ярким значением данного цвета. Последнее число — для Альфа (Прозрачность) значения. Когда начинается очистка экрана, я никогда не волнуюсь о четвертом числе. Пока оно будет 0.0f. Как его использовать, я объясню в другом уроке.

Вы можете получить различные цвета, смешивая три компоненты цвета. Поэтому, если Вы вызвали glClearColor(0.0f,0.0f,1.0f,0.0f) Вы произведете очистку экрана, с последующим закрашиванием его в ярко-синий цвет. Если Вы вызвали glClearColor(0.5f,0.0f,0.0f,0.0f) экран будет заполнен умеренно красным цветом. Не очень ярким (1.0f) и не темным (0.0f), а именно умеренно красным. Для того чтобы сделать белый фон, Вы должны установить все цвета в (1.0f). Черный — все компоненты цвета равны 0.0f.

glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Очистка экрана в черный цвет

Следующие три строки создают Буфер Глубины. Думайте о буфере глубины как о слоях на экране. Буфер глубины указывает, как далеко объекты находятся от экрана. Мы не будем реально использовать буфер глубины в этой программе, но любая программа с OpenGL, которая рисует на экране в 3D, будет его использовать. Он позволяет сортировать объекты для отрисовки, поэтому квадрат, расположенный под кругом не будет изображен поверх него. Буфер глубины очень важная часть OpenGL.

glClearDepth( 1.0f ); // Разрешить очистку буфера глубины

glEnable( GL_DEPTH_TEST ); // Разрешить тест глубины

glDepthFunc( GL_LEQUAL ); // Тип теста глубины

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

glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST ); // Улучшение в вычислении перспективы

И, в конце концов, мы возвращаем true. Если мы хотим узнать, прошла ли инициализация как положено, мы должны проверить возвращаемое значение на true и false. Вы можете добавить код, который будет возвращать FALS E , если произошла какая-нибудь ошибка. Но сейчас это не должно Вас беспокоить.

return true; // Инициализация прошла успешно

В следующей секции содержится весь код для рисования. Все, что Вы планируете для отрисовки на экране, будет содержатся в этой секции кода. В каждом уроке код будет добавляться в эту секцию программы. Если Вы уже понимаете OpenGL, Вы можете попробовать добавить в код простейшие формы на OpenGL, ниже вызова glLoadIdentity() и перед возвращением значения (return true). Если Вы новичок в OpenGL, подождите до следующего моего урока. Сейчас все что мы сделаем, это очистка экрана цветом, который мы определили выше, очистка буфера глубины и сброс сцены.

Возвращение значения true говорит нашей программе о том, что в этой секции не возникло никаких проблем. Если, по каким-либо причинам, Вы хотите остановить выполнение программы добавьте строчку return false, где-нибудь перед return true, таким образом мы сообщим программе, что в секции кода, выполняющего рисование, произошла какая-то ошибка. Тогда произойдёт выход из программы.

int DrawGLScene( GLvoid ) // Здесь будет происходить вся прорисовка

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); // Очистить экран и буфер глубины

glLoadIdentity(); // Сбросить текущую матрицу

return true; // Прорисовка прошла успешно

Следующая секция кода вызывается только перед выходом из программы. Задача KillGLWindow() — освободить Контекст Рендеринга (hRC), Контекст Устройства (hDC) и, наконец, дескриптор окна (hWnd). Я добавил много проверок на наличие ошибок. Если программа неспособна удалить какую-нибудь часть из контекстов окна, появится окно (Message Box) с соответствующим сообщением об ошибке. Чем их больше будет создано (Message Box-ов), тем проще будет найти ошибку.


GLvoid KillGLWindow ( GLvoid ) // Корректное разрушение окна

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

if( fullscreen ) // Мы в полноэкранном режиме?

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

ChangeDisplaySettings( NULL, 0 ); // Если да, то переключаемся обратно в оконный режим

ShowCursor( true ); // Показать курсор мышки

Код приведённый ниже проверяет, существует ли Контекст Рендеринга (hRC). Если его нет, то программа переходит на секцию кода, расположенную ниже и проверяющие существует ли Контекст Устройства (hDC).

if( hRC ) // Существует ли Контекст Рендеринга?

Если он существует, код ниже проверит, возможно ли освободить его (отсоединить RC от DC). С помощью сообщений можно отследить ошибки. Мы просим программу освободить Контекст Рендеринга (с помощью вызова wglMakeCurrent(NULL,NULL)), затем проверяем, благополучно ли завершена эта операция или нет.

if( !wglMakeCurrent( NULL, NULL ) ) // Возможно ли освободить RC и DC?

Если невозможно уничтожить контексты RC и DC, выскочит сообщение об ошибке, это позволит понять, что контексты не уничтожены. NULL в функции MessageBox() означает, что у сообщения не будет родительского окна. Текст справа от NULL — текст, который будет содержать сообщение. «SHUTDOWN ERROR» — текст, который будет содержаться в заголовке окна-сообщения. Следом мы видим MB_OK, это означает, что окно-сообщение будет с одной кнопкой помеченной «ОК». MB_ICONINFORMATION создаёт иконку в области окна-сообщения (что заставляет обратить на себя внимание).

MessageBox( NULL, «Release Of DC And RC Failed.», «SHUTDOWN ERROR», MB_OK | MB_ICONINFORMATION );

Потом мы пытаемся удалить Контекст Рендеринга. Если это не возможно выскочит соответствующее сообщение.

if( !wglDeleteContext( hRC ) ) // Возможно ли удалить RC?

Если невозможно удалить Контекст Рендеринга код, приведённый ниже, выведет окно-сообщение позволяющее понять, что его удаление — невозможно. hRC будет установлено в NULL.

MessageBox( NULL, «Release Rendering Context Failed.», «SHUTDOWN ERROR», MB_OK | MB_ICONINFORMATION );

hRC = NULL; // Установить RC в NULL

Теперь мы проверим, имеет ли программа Контекст Устройства, и если это так, то мы попробуем его уничтожить. Если это невозможно окно-сообщение выведет соответствующее сообщение и hDC будет установлен в NULL.

if( hDC && !ReleaseDC( hWnd, hDC ) ) // Возможно ли уничтожить DC?

MessageBox( NULL, «Release Device Context Failed.», «SHUTDOWN ERROR», MB_OK | MB_ICONINFORMATION );

hDC=NULL; // Установить DC в NULL

Теперь проверим, есть ли дескриптор окна, а если есть, мы попробуем уничтожить окно, используя DestroyWindow(hWnd). Если это невозможно окно-сообщение выведет соответствующее сообщение и hWnd будет установлен в NULL.

if(hWnd && !DestroyWindow(hWnd)) // Возможно ли уничтожить окно?

MessageBox( NULL, «Could Not Release hWnd.», «SHUTDOWN ERROR», MB_OK | MB_ICONINFORMATION );

hWnd = NULL; // Установить hWnd в NULL

Последнее, что нам необходимо сделать так это разрегистрировать (операция, обратная регистрации) класс окна. Это позволяет нам корректным образом уничтожить окно и открыть другое без получения сообщения об ошибке «Windows Class already registered» (Класс окна уже зарегистрирован).

if( !UnregisterClass( «OpenGL», hInstance ) ) // Возможно ли разрегистрировать класс

MessageBox( NULL, «Could Not Unregister Class.», «SHUTDOWN ERROR», MB_OK | MB_ICONINFORMATION);

hInstance = NULL; // Установить hInstance в NULL

Следующая секция кода создаёт наше OpenGL окно. Я провёл много времени, пытаясь решить, создавать ли полноэкранное приложение, что требует немного кода. Или же позволить, выбирать удобный для пользователя режим окна, что потребует больше кода. Я решил, что удобный для пользователя режим окна с большим количеством кода лучший выбор. Я всё время получал такие вопросы по электронной почте, как: «Как я могу создать окно, вместо того, чтобы использовать полноэкранный режим?», «Как можно изменить заголовок окна?», «Как можно изменить разрешение экрана или формат пикселей окна?». Следующий далее код решает все эти вопросы. Поэтому лучше понять этот материал, и тогда написание программ на OpenGL покажется гораздо легче!

Как Вы можете видеть, функция возвращает BOOL (true или false), всего она получает 5 аргументов: Заголовок Окна, Ширину Окна, Высоту Окна, Число Битов (16/24/32), и, наконец, флаг режима (true для полноэкранного или false для оконного). Мы возвращаем логическую переменную, которая говорит о том, возможно ли создать окно.

BOOL CreateGLWindow( LPCWSTR title, int width, int height, int bits, bool fullscreenflag )

Когда мы просим Windows найти формат пикселей, соответствующий тому, который мы хотим, номер режима, который нашёл Windows, будет храниться в переменной PixelFormat.

GLuint PixelFormat; // Хранит результат после поиска

Переменная wc будет использоваться для хранения структуры класса нашего окна. Структура класса содержит информацию о нашем окне. Изменяя различные поля класса, мы можем изменить вид окна и его поведение. Каждому окну соответствует определённый класс. Перед созданием окна Вы должны ЗАРЕГИСТРИРОВАТЬ класс для окна.

WNDCLASS wc; // Структура класса окна

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

DWORD dwExStyle; // Расширенный стиль окна

DWORD dwStyle; // Обычный стиль окна

Следующие пять строчек кода устанавливают значения координат левого верхнего и правого нижнего угла прямоугольника (окна). Мы будем использовать эти значения для выравнивания нашего окна так, чтобы область, в которой мы рисуем, имела такое разрешение, какое мы хотим. Обычно, если мы создаём окно 1024х768, границы окна занимают некоторую часть нашего разрешения.

RECT WindowRect; // Grabs Rectangle Upper Left / Lower Right Values

WindowRect.left=(long)0; // Установить левую составляющую в 0

WindowRect.right=(long)width; // Установить правую составляющую в Width

WindowRect.top=(long)0; // Установить верхнюю составляющую в 0

WindowRect.bottom=(long)height; // Установить нижнюю составляющую в Height

В следующей строчке кода мы присваиваем глобальной переменной fullscreen значение переменной fullscreenflag.

fullscreen=fullscreenflag; // Устанавливаем значение глобальной переменной fullscreen

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

Стили CS_HREDRAW и CS_VREDRAW принуждают перерисовать окно всякий раз, когда оно перемещается. CS_OWNDC создает скрытый DC для окна. Это означает, что DC не используется совместно несколькими приложениями. WndProc — процедура, которая перехватывает сообщения для программы. Дополнительной информации для нашего окна нет, поэтому заполняем два этих (cbClsExtra и cbWndExtra) поля нулями. Затем мы устанавливаем дескриптор приложения. hIcon установлен равным нулю, это означает, что мы не хотим ICON в окне, и для мыши используем стандартный указатель. Фоновый цвет не имеет значения (мы установим его в GL). Мы не хотим меню в этом окне, поэтому мы используем установку его в NULL, и имя класса – это любое имя которое Вы хотите. Для простоты я использую «OpenGL».

hInstance = GetModuleHandle(NULL); // Считаем дескриптор нашего приложения

wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // Перерисуем при перемещении и создаём скрытый DC

wc.lpfnWndProc = (WNDPROC) WndProc; // Процедура обработки сообщений

wc.cbClsExtra = 0; // Нет дополнительной информации для окна

wc.cbWndExtra = 0; // Нет дополнительной информации для окна

wc.hInstance = hInstance; // Устанавливаем дескриптор

wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // Загружаем иконку по умолчанию

wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Загружаем указатель мышки

wc.hbrBackground = NULL; // Фон не требуется для GL

wc.lpszMenuName = NULL; // Меню в окне не будет

wc.lpsz ; // Устанавливаем имя классу

Теперь надо зарегистрировать класс. Если возникнет какая-либо проблема или ошибка, выскочит соответствующее окно-сообщение. Кликнув «ОК» в нём мы выйдем из программы.

if( !RegisterClass( &wc ) ) // Пытаемся зарегистрировать класс окна

MessageBox( NULL, «Failed To Register The Window Class.», «ERROR», MB_OK | MB_ICONEXCLAMATION );

return false; // Выход и возвращение функцией значения false

Теперь проверим, желаем ли мы запустить программу в полноэкранном режиме или в оконном режиме. Если выбран полноэкранный режим, мы пробуем установить его.

if( fullscreen ) // Полноэкранный режим?

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

DEVMODE dmScreenSettings; // Режим устройства

dmScreenSettings.dmSize=sizeof( dmScreenSettings ); // Размер структуры Devmode

dmScreenSettings.dmPelsW >Ширина экрана

dmScreenSettings.dmPelsHeight = height; // Высота экрана

dmScreenSettings.dmBitsPerPel = bits; // Глубина цвета

dmScreenSettings.dmFields= DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;// Режим Пикселя

В коде, приведённом выше, мы очищаем память для хранения наших видео настроек. Мы устанавливаем ширину, высоту и глубину цвета, которые мы хотим иметь. В коде ниже мы пытаемся подать запрос на установление полноэкранного режима. Вся информация о ширине, высоте и глубине цвета хранится в dmScreenSettings. В строчке ниже функция ChangeDisplaySettings пробует переключить экран в режим, настройки которого хранятся в dmScreenSettings. Я использую параметр CDS_FULLSCREEN, когда переключаю режим потому, что он позволяет скрыть панель управления Windows (в том числе кнопку «Пуск») внизу экрана, к тому же это позволяет нам избежать перемещения и изменения размеров рабочего стола, когда мы переключаемся в полноэкранный режим и обратно.

// Пытаемся установить выбранный режим и получить результат. Примечание : CDS_FULLSCREEN убирает панель управления .

if( ChangeDisplaySettings( &dmScreenSettings, CDS_FULLSCREEN ) != DISP_CHANGE_SUCCESSFUL )

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

// Если переключение в полноэкранный режим невозможно, будет предложено два варианта: оконный режим или выход.

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

if( MessageBox( NULL, «The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?»,

«NeHe GL», MB_YESNO | MB_ICONEXCLAMATION) == IDYES )

Если пользователь решил, использовать оконный режим — переменной fullscreen присвоится значение false и выполнение программы продолжится.

fullscreen = false; // Выбор оконного режима (fullscreen = false)

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

// Выскакивающее окно, сообщающее пользователю о закрытие окна.

MessageBox( NULL, «Program Will Now Close.», «ERROR», MB_OK | MB_ICONSTOP );

return false; // Выход и возвращение функцией false

Поскольку код выше мог вызвать ошибку инициализации полноэкранного режима, и пользователь мог решить запускать программу всё же в оконном режиме, мы проверяем ещё раз значение переменной fullscreen (true или false) перед тем, как мы установим режим экрана/окна.

if(fullscreen) // Мы остались в полноэкранном режиме?

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

И, наконец, мы запретим использования указателя мышки. Если программа не является интерактивной, правилом хорошего тона является сокрытия указателя мышки в полноэкранном режиме. Но это решать Вам.

dwExStyle = WS_EX_APPWINDOW; // Расширенный стиль окна

dwStyle = WS_POPUP; // Обычный стиль окна

ShowCursor( false ); // Скрыть указатель мышки

Если мы используем оконный режим, вместо полноэкранного режима, мы добавим WS_EX_WINDOWEDGE в расширенный стиль. Это придаст окну более объёмный вид. Для обычного стиля зададим параметр WS_OVERLAPPEDWINDOW вместо WS_POPUP. WS_OVERLAPPEDWINDOW создаёт окно с заголовком, границы для регулировки размера, оконное меню и кнопки для сворачивания/разворачивания.

dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // Расширенный стиль окна

dwStyle = WS_OVERLAPPEDWINDOW; // Обычный стиль окна

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

AdjustWindowRectEx( &WindowRect, dwStyle, false, dwExStyle ); // Подбирает окну подходящие размеры

В следующей секции кода мы приступим к созданию нашего окна и проверке создано ли оно должным образом. Мы передаём CreateWindowEx() все параметры, в которых она нуждается:

· определённый ранее расширенный стиль;

· имя класса (которое должно быть тем самым, что Вы использовали, когда регистрировали класс окна);

· обычный стиль окна;

· X левого угла окна;

· Y левого угла окна;


· родительское окно (у нас его нет);

· дескриптор меню (и меню у нас тоже нет);

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

if( !( hWnd = CreateWindowEx( dwExStyle, // Расширенный стиль для окна

_T(«OpenGL»), // Имя класса

title, // Заголовок окна

WS_CLIPSIBLINGS | // Требуемый стиль для окна

WS_CLIPCHILDREN | // Требуемый стиль для окна

dwStyle, // Выбираемые стили для окна

0, 0, // Позиция окна

WindowRect.right-WindowRect.left, // Вычисление подходящей ширины

WindowRect.bottom-WindowRect.top, // Вычисление подходящей высоты

NULL, // Нет родительского

hInstance, // Дескриптор приложения

NULL ) ) ) // Не передаём ничего до WM_CREATE (. )

Теперь проверим, создано ли наше окно должным образом. Если наше окно было создано, hWnd будет хранить дескриптор окна. Если же окно не было создано — код ниже вызовет всплывающее сообщение об ошибке и программа завершится.

KillGLWindow(); // Восстановить экран

MessageBox( NULL, «Window Creation Error.», «ERROR», MB_OK | MB_ICONEXCLAMATION );

return false; // Вернуть false

Следующая секция кода описывает Формат Пикселей (Pixel Format). Мы выбираем формат, который поддерживает OpenGL и двойной буфер, в соответствии с RGBA (Red-красный, Green-зелёный, Blue-синий, Alpha Channel-альфа канал (канал прозрачности)). Мы пытаемся найти формат пикселя, который соответствует такому количеству бит, выделенных на глубину цвета, какое заранее будет заданно. И, наконец, мы установим 32 битный Z-буфер (буфер глубины). Остальные параметры или не используются, или не важны (кроме буфера трафарета и (медленного) буфера накопления).

static PIXELFORMATDESCRIPTOR pfd= // pfd сообщает Windows каким будет вывод на экран каждого пикселя

sizeof(PIXELFORMATDESCRIPTOR), // Размер дескриптора данного формата пикселей

1, // Номер версии

PFD_DRAW_TO_WINDOW | // Формат для Окна

PFD_SUPPORT_OPENGL | // Формат для OpenGL

PFD_DOUBLEBUFFER, // Формат для двойного буфера

PFD_TYPE_RGBA, // Требуется RGBA формат

bits, // Выбирается бит глубины цвета

0, 0, 0, 0, 0, 0, // Игнорирование цветовых битов

0, // Нет буфера прозрачности

0, // Сдвиговый бит игнорируется

0, // Нет буфера накопления

0, 0, 0, 0, // Биты накопления игнорируются

32, // 32 битный Z-буфер (буфер глубины)

0, // Нет буфера трафарета

0, // Нет вспомогательных буферов

PFD_MAIN_PLANE, // Главный слой рисования

0, 0, 0 // Маски слоя игнорируются

Если во время создания окна не возникло ни одной ошибки, мы попытаемся получить Контекст Устройства (DC) OpenGL. Если мы не можем получить DC, на экран выскочит сообщение об ошибке и программа завершит работу (функция возвратит false).

if( !( hDC = GetDC( hWnd ) ) ) // Можем ли мы получить Контекст Устройства?

KillGLWindow(); // Восстановить экран

MessageBox( NULL, «Can’t Create A GL Device Context.», «ERROR», MB_OK | MB_ICONEXCLAMATION );

return false; // Вернуть false

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

if( !( PixelFormat = ChoosePixelFormat( hDC, &pfd ) ) ) // Найден ли подходящий формат пикселя?

KillGLWindow(); // Восстановить экран

MessageBox( NULL, «Can’t Find A Suitable PixelFormat.», «ERROR», MB_OK | MB_ICONEXCLAMATION );

return false; // Вернуть false

Если Windows нашёл соответствующий формат, мы попытаемся установить его. Если же он не может быть установлен, на экран выскочит сообщение об ошибке и программа завершит работу (функция возвратит false).

if( !SetPixelFormat( hDC, PixelFormat, &pfd ) ) // Возможно ли установить Формат Пикселя?

KillGLWindow(); // Восстановить экран

MessageBox( NULL, «Can’t Set The PixelFormat.», «ERROR», MB_OK | MB_ICONEXCLAMATION );

return false; // Вернуть false

Если формат был корректно установлен, мы попытаемся получить Контекст Рендеринга. Если это не возможно, на экран выскочит сообщение об ошибке и программа завершит работу (функция возвратит false).

if( !( hRC = wglCreateContext( hDC ) ) ) // Возможно ли установить Контекст Рендеринга?

KillGLWindow(); // Восстановить экран

MessageBox( NULL, «Can’t Create A GL Rendering Context.», «ERROR», MB_OK | MB_ICONEXCLAMATION);

return false; // Вернуть false

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

if( !wglMakeCurrent( hDC, hRC ) ) // Попробовать активировать Контекст Рендеринга

KillGLWindow(); // Восстановить экран

MessageBox( NULL, «Can’t Activate The GL Rendering Context.», «ERROR», MB_OK | MB_ICONEXCLAMATION );

return false; // Вернуть false

Если всё прошло гладко, и наше OpenGL окно было создано, мы покажем окно, установим его на передний план (присвоив более высокий приоритет) и затем установим фокус для этого окна. Потом мы вызовем ReSizeGLScene, передавая ширину и высоту экрана для настройки перспективы для нашего OpenGL экрана.

ShowWindow( hWnd, SW_SHOW ); // Показать окно

SetForegroundWindow( hWnd ); // Слегка повысим приоритет

SetFocus( hWnd ); // Установить фокус клавиатуры на наше окно

ReSizeGLScene( width, height ); // Настроим перспективу для нашего OpenGL экрана.

Наконец мы переходим к выполнению функции InitGL(), где мы можем настроить освещение, текстуры и что-нибудь еще, если это необходимо. Вы можете сделать дополнительные проверки на ошибки в InitGL(), и передать true (всё OK) или false (что-то не так). Например: если Вы загружаете текстуры в InitGL() и возникают ошибки, Вы можете захотеть, чтобы программа остановилась. Если InitGL() возвращает главной функции false, строчки кода ниже среагируют на это сообщением об ошибке и программа завершится.

if( !InitGL() ) // Инициализация только что созданного окна

KillGLWindow(); // Восстановить экран

MessageBox( NULL, _T(«Initialization Failed.»), _T(«ERROR»), MB_OK | MB_ICONEXCLAMATION );

return false; // Вернуть false

Если программа дошла до этого момента, логично предположить, что создание окна закончилось успехом. Мы возвращаем true в WinMain(), что сообщает о том, что не возникло никаких ошибок. То есть мы не выходим из программы, а благополучно продолжаем работу.

return true; // Всё в порядке!

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

LRESULT CALLBACK WndProc ( HWND hWnd , // Дескриптор нужного окна

UINT uMsg, // Сообщение для этого окна

WPARAM wParam, // Дополнительная информация

LPARAM lParam) // Дополнительная информация

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

switch (uMsg) // Проверка сообщения для окна

Если uMsg — WM_ACTIVATE проверяем, активно ли еще наше окно. Если оно минимизировано, переменная active будет равна false. Если окно активно, переменная active будет равна true.

case WM_ACTIVATE: // Проверка сообщения активности окна

if( !HIWORD( wParam ) ) // Проверить состояние минимизации

active = true; // Программа активна

active = false; // Программа теперь не активна

return 0; // Возвращаемся в цикл обработки сообщений

Если возникло сообщение WM_SYSCOMMAND (системная команда) мы будем сравнивать состояния по параметру wParam. Если wParam — SC_SCREENSAVE или SC_MONITORPOWER, то или запускается скринсейвер (программа сохранения экрана) или монитор пытается перейти в режим сбережения энергии. Возвращая ноль мы предотвращаем наступлению обоих этих событий.

case WM_SYSCOMMAND: // Перехватываем системную команду

switch ( wParam ) // Останавливаем системный вызов

case SC_SCREENSAVE: // Пытается ли запустится скринсейвер?

case SC_MONITORPOWER: // Пытается ли монитор перейти в режим сбережения энергии?

return 0; // Предотвращаем это

Если uMsg — WM_CLOSE окно будет закрыто. Мы отправляем сообщение о выходе, чтобы прервать выполнение главного цикла. Переменная done будет установлена в true, главный цикл в WinMain() будет остановлен и программа будет закрыта.

case WM_CLOSE: // Мы получили сообщение о закрытие?

PostQuitMessage( 0 ); // Отправить сообщение о выходе

return 0; // Вернуться назад

Если произошло нажатие кнопки (на клавиатуре) мы можем узнать какая клавиша это была считав wParam. Тогда я делаю, чтобы эта ячейка в массиве keys[ ] содержала true. Таким образом, я могу считать этот массив позже и найти какая клавиша была нажата. Это позволяет отследить нажатия сразу несколько клавиш одновременно.

case WM_KEYDOWN: // Была ли нажата кнопка?

keys[wParam] = true; // Если так, мы присваиваем этой ячейке true

return 0; // Возвращаемся

Если кнопка была отпущена мы можем узнать какая клавиша это была считав wParam. Тогда мы делаем, чтобы эта ячейка в массиве keys[ ] была равна false. Таким образом, когда Вы считываете эту ячейку, Вы будете знать нажата ли она до сих пор или была отпущена. Другие кнопки на клавиатуре могут быть представлены в диапазоне 0-255. Когда мы нажимаем кнопку со скан-кодом 40, например, keys[40] вернёт true. Когда я её отпущу, она вернёт false. Вот так мы используем ячейки для хранения нажатых клавиш.

case WM_KEYUP: // Была ли отпущена клавиша?


keys[wParam] = false; // Если так, мы присваиваем этой ячейке false

return 0; // Возвращаемся

Всякий раз, когда изменяются размеры нашего окна uMsg в конечном счёте будет иметь значение WM_SIZE. Мы считываем LOWORD и HIWORD (младшее и старшее слова) переменной lParam для того, чтобы узнать новые высоту и ширину окна. Мы передаём эти аргументы функции ReSizeGLScene(). OpenGL сцена перерисуется с новой шириной и высотой.

case WM_SIZE: // Изменены размеры OpenGL окна

ReSizeGLScene( LOWORD(lParam), HIWORD(lParam) ); // Младшее слово=W >

return 0; // Возвращаемся

Любое сообщение, которое мы не проверили, будет передано в качестве фактического параметра функции DefWindowProc для того, чтобы Windows могла его обработать.

// пересылаем все необработанные сообщения DefWindowProc

return DefWindowProc( hWnd, uMsg, wParam, lParam );

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

int WINAPI WinMain( HINSTANCE hInstance, // Дескриптор приложения

HINSTANCE hPrevInstance, // Дескриптор родительского приложения

LPSTR lpCmdLine, // Параметры командной строки

int nCmdShow ) // Состояние отображения окна

Мы инициализируем две переменные. Переменная msg будет использоваться для того, чтобы проверить существует ли какое-нибудь ожидающее обработки сообщение. Переменная done при старте будет равна false. Это означает, что наша программа не закончила своего выполнения. Пока done равно false программа будет продолжать выполнение. Как только done изменит значение с f a lse на true — наша программа закончит выполнение.

MSG msg; // Структура для хранения сообщения Windows

BOOL done = false; // Логическая переменная для выхода из цикла

Эта секция кода является полностью дополнительной. Она вызовет всплывающее окно, которое спросит, хотите ли Вы запустить приложение в полноэкранном режиме. Если пользователь кликнет на кнопке «NO», переменная изменится с true (по умолчанию) на false и программа запустится в оконном режиме.

// Спрашивает пользователя, какой режим экрана он предпочитает

if( MessageBox( NULL, «Хотите ли Вы запустить приложение в полноэкранном режиме?», «Запустить в полноэкранном режиме?», MB_YESNO | MB_ICONQUESTION) == IDNO )

fullscreen = false; // Оконный режим

Тут мы задаем, как будет создано окно. Мы передаём заголовок, ширину, высоту, глубину цвета и true (полноэкранный режим) или false (оконный режим) функции CreateGLWindow. Вот так! Я вполне доволен такой простотой кода. Если окно не будет создано по какой бы то ни было причине, CreateGLWindow вернёт false и наша программа немедленно завершиться (return 0).

// Создать наше OpenGL окно

if( !CreateGLWindow( «NeHe OpenGL окно «, 1024, 768, 32, fullscreen ) )

return 0; // Выйти, если окно не может быть создано

Здесь стартует наш цикл. Пока done равно false цикл будет повторяться.

while( !done ) // Цикл продолжается, пока done не равно true

Первым делом мы должны проверить стоит ли в очереди какое-нибудь сообщение. Используя PeekMessage() мы можем сделать это без остановки выполнения нашей программы. Многие программы используют GetMessage(). Это работает не плохо, но при вызове GetMessage() Ваша программа ничего не делает, пока не получит сообщение о перерисовке или ещё какое-либо другое сообщение.

if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) // Есть ли в очереди какое-нибудь сообщение?

В следующей секции кода мы проверяем, есть ли в очереди сообщение о выходе. Если текущее сообщение WM_QUIT, это повод для вызова PostQuitMessage( 0 ), установки переменной done в true и завершения программы.

if( msg.message == WM_QUIT ) // Мы поучили сообщение о выходе?

done = true; // Если так , done=true

else // Если нет, обрабатывает сообщения

Если сообщение в очереди не сообщение о выходе, мы преобразуем сообщение, затем отсылаем его так, чтобы WndProc() или Windows могли работать с ним.

TranslateMessage( &msg ); // Переводим сообщение

DispatchMessage( &msg ); // Отсылаем сообщение

else // Если нет сообщений

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

if( active ) // Активна ли программа?

if(keys[VK_ESCAPE]) // Было ли нажата клавиша ESC?

done = true; // ESC говорит об останове выполнения программы

else // Не время для выхода, обновим экран.

Если программа активна и не нажата ESC мы визуализируем сцену и меняем буфер (используя двойную буферизацию мы исключаем мерцание при анимации). Используя двойную буферизацию мы рисуем всё на «скрытом экране» (второй буфер) так, чтобы мы не могли видеть этого. Когда мы меняем буфер, экран (первый буфер), который мы видим, становится скрытым, а скрытый — становится видимым. Таким образом, мы не видим саму прорисовку сцены, а только результат визуализации.

DrawGLScene(); // Рисуем сцену

SwapBuffers( hDC ); // Меняем буфер (двойная буферизация)

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

if( keys[VK_F1] ) // Была ли нажата F1?

keys[VK_F1] = false; // Если так, меняем значение ячейки массива на false

KillGLWindow(); // Разрушаем текущее окно

fullscreen = !fullscreen; // Переключаем режим

// Пересоздаём наше OpenGL окно

if( !CreateGLWindow( _T(«NeHe OpenGL структура»), 1024, 768, 32, fullscreen ) )

return 0; // Выходим, если это невозможно

Если переменная done больше не false, программа завершается. Мы корректно разрушаем наше OpenGL окно, чтобы всё было освобождено и выходим из программы.

KillGLWindow(); // Разрушаем окно

return ( msg.wParam ); // Выходим из программы

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

Библиотека GLUT. Урок 1. Инициализация.

Добро пожаловать в изучение OpenGl и Glut. Необходимые знания — геометрия пространства, базовые понятия графических примитивов — точка, ребро, пиксель, плоскость, полигон или фейс и т.д. и т.п. OpenGl является основным 3D набором инструментов разработчика, который пишет продвинутые графические приложения для работы с 3D графикой, разрабатывает игры под MacOS или Линукс ( напомню, что под Windows есть свой API — DirectX, тоже богатый на возможности, но поддержка OpenGl в ней также реализована).
Среда разработки — MS Visual Studio Ultimate 2012.
В этом разделе мы собираемся создать функцию main нашей программы. Функция main будет состоять из необходимой инициализации и цикл обработки событий.
Первый блок функции main будет инициализировать процедуры GLUT и создаст окно.
После GLUT входит в цикл обработки событий, получив контроль над приложением. GLUT будет ждать каждое следующее событие, проверяя, есть ли функция для его обработки.
Поэтому, прежде чем GLUT вступит в цикл обработки событий мы должны инициализировать те функции GLUT, которые мы хотим вызвать для обработки вызываемых событий.
Каждый раз для вызова функции GLUT, её( функцию) — нужно зарегистрировать.

Скелет нашей функции main.

void glutInit ( int * argc, char ** argv ) ;

параметры:
* int argc — количество аргументов
* char** argv — их описание в виде указателя на строку

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

void glutInitWindowPosition ( int x, int y ) ;

Параметры:
х — число пикселей от левой части экрана.
у — количество пикселей от верхней части экрана.

Далее мы выбираем размер окна. Для этого мы используем функцию glutInitWindowSize.

void glutInitWindowSize ( int w >int height ) ;

width — ширина окна;
height — высота окна;

Затем вы должны определить режим отображения с помощью функции glutInitDisplayMode.

void glutInitDisplayMode ( unsigned int mode ) ;

mode — определяет режим отображения;

Вы можете использовать режим , чтобы определить цвет, а также количество и тип буферов.
Предопределенных констант для определения цвета модели являются:
GLUT_RGBA или GLUT_RGB — выбирает окно RGBA.
GLUT_INDEX — выбирает режим индексированного цвета.
Режим отображения также позволяет выбрать одно-или двухместных буфера окна. Предопределенные константы для этого являются:
GLUT_SINGLE — режим одинарной буферизации.
GLUT_DOUBLE — режим двойной буферизации, — РЕЖИМ, ПОДХОДЯЩИЙ ДЛЯ АНИМАЦИИ.
Также существуют специализированные режимы буфера:
GLUT_ACCUM -буфер накопления.
GLUT_STENCIL — Буфер трафарета.
GLUT_DEPTH -буфер глубины.
Итак, предположим, вы хотите создать окно в цветовом пространстве RGB , с двойной буферизацией, с использованием буфера глубины. Все, что вам нужно сделать, это прописать соответствующие константы для того, чтобы создать необходимый режим.

glutInitDisplayMode ( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH ) ;

После этих шагов, окно может быть создано с помощью glutCreateWindow.

int glutCreateWindow ( char * title ) ;

title — имя создаваемого окна;

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

Название этой функции задаётся программистом. Однако теперь вы должны сказать GLUT что он должен использовать функции, которую мы только что написали для рисования. Это называется регистрации обратного вызова. GLUT будет вызывать функцию всякий раз, когда вы выберете её для рендеринга( отрисовнки ).
Так что давайте говорить GLUT, что функция renderScene должны использоваться всякий раз, когда окно требуется окрасить. GLUT имеет функцию, которая принимает в качестве параметра имя функции для использования при необходимости перерисовки.

void glutDisplayFunc ( void ( * funcName ) ( void ) ) ;

Пропись главного цикла программы. Бесконечный цикл, отрабатывающий все функции событий, зарегестрированные программистом.

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

void glutMainLoop ( void ) ; //главный цикл

Итоговый текст программы выглядит вот так:

Пробуем скомпилировать

Результат работы программы: окно размером 400*400 пикселей с заголовком «Урок 1» и белым треугольником внутри окна

Инициализация OpenGL окна в Win32

Привет. Это самая первая статься по написанию своего движка на OpenGL в C++ Visual Sudio.

Первым делом создайте новый проект и обзовите его, например OpenGL Engine.

Зайдите в свойства проекта и в вкладке Свойства конфигурации -> Общее измените Набор символов на Использовать многобайтовую кодировку. Откройте вкладку Компоновщик -> Ввод в строке Дополнительные зависимости укажите opengl32.lib;glu32.lib;%(AdditionalDependencies)

С свойствами проекта закончали. Теперь перейдем непосредственно к написанию кода движка OpenGL.

Сначала создайте файл resource.h и напишите в нем:

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

Создайте файл init.cpp. Он будет создавать приложение.

Сначала подключим необходимые хидеры в нём:

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

Объявим прототипы необходимых функций:

А затем напишем главную функцию, которая и будет создавать наше окно Win32:

Далее нам нужна функция, которая будет обрабатывать события окна:

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

Эта функция нужна для корректного выключения окна:

Ну и конечно же функция, которая отображает (рендерит) сцену каждый кадр:

Можете запускать. У вас должен появится вот такой вот зеленый экран:

H OpenGL: пишем библиотеку. Инициализация и создание окна в черновиках

Для чего нам нужна эта библиотека? Попробуйте открыть CodeBlocks и создать OpenGL проект. Сколько строк? Под Windows — около 100 строк, а все они только поворачивают и рисуют треугольник! А чтобы все это сократить нам и нужна библиотека.

Ну-с приступим. Вот несколько советов:

  1. Не забывайте про документацию и комментарии кода
  2. Сделайте GIT/SVN репозиторий для библиотеки и контроля ее версий.
  3. Не надо стразу пытаться написать кучу функций и макросов — начинайте с минимума.


Как я выше и написал — начнем с минимума. А именно с создания окна и включения OpenGL.

Делаем пространство имен для данных окна.

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

Далее сделаем макрос:

Данный макрос проверяет условие b. Также он может быть применен к переменным: значение false или 1 или NULL — все неверно!

Теперь сделаем функцию OK. У нее аргументы: нужно проверять создание окна или нет, нужно проверять запуск OpenGL или нет.

Окей, готово. Перейдем к самым важным вещам: создание окна и инициализация OpenGL.

Тут мы инициализируем данные окна и увеличиваем на размеры рамки, а на выходе получаем окно, у нас нет меню(мы его инициализировали как NULL в классе окна). Проверяем что окно создалось и уходим.

Но можно заметить вызов RunOpenGL(), а эта функция не стандартная => нам надо ее сделать.
Это вроде самое простое.

Здесь же мы настраиваем формат пикселей, создаем контекст окна рисования и готово! Можно протестировать библиотеку! Во второй части будет полный построковый разбор кода и возможно перейдем к другим функциям. Простите, что в коде присваивание не выравнено, я копировал со своей библиотеки, а там то раб, то пробел…

Opengl инициализация или как написать приложение с нуля

Для чего эта тема?
У многих создалась иллюзия сложности изучения «OpenGL», и не понимания простоты работы этой библиотеки для программиста.
И даже используя «движок» нужно понимать как это взаимодействует с ОС, что может/не может конкретные устройства.

В данной статье постараюсь выйти за рамки стандартных примеров — а именно постараюсь рассказать почему и за чем.
( тем более я давно это пообещал )
От читателей требуется хотя бы поверхностное знание любого ЯП.

Все дальнейшее посвящено библиотеке OpenGL ES 2.0 под Android, и последующим версиям.

Что такое библиотека OpenGL ES 2.0?
На базовом уровне, OpenGL ES 2.0 — это просто спецификация, то есть документ, описывающий набор функций и их точное поведение. Производители оборудования на основе этой спецификации создают реализации — библиотеки функций, соответствующих набору функций спецификации ( W: ).

OpenGL ориентируется на следующие две задачи:
Скрыть сложности адаптации различных 3D-ускорителей, и предоставить разработчику единый API.

Для программиста OpenGL представляет низкоуровневую библиотеку для доступа к GPU ( графическому процессору ).

Схема вариантов реализации библиотеки ( с точки зрения программиста + для сравнения DirectX ):

В Android на 99.99% используется вариант В.
То есть реализация OpenGL ES входит в состав драйвера,
в отличие от DirectX, которая скорее является прослойкой между приложением и драйвером.
Есть еще отдельные реализации OpenGL, например Mesa3D, но они в основном достаточно медленно развиваются и часто отстают на несколько поколений от решений производителей чипов.

Что лучше, DirectX или OpenGL?
Вопрос не корректный. Например если нужна мультиплатформенность — про DirectX можно забыть.
И на взгляд автора DirectX слишком «оброс» хвостами. ( но это очень субъективно )
+ Сравнивать не совсем корректно, так как DirectX кроме графики реализует много интерфейсов ( и вполне кошерных — включая звук, ввод, сеть и т. д. )

Что быстрее, DirectX или OpenGL?

Тоже не корректный вопрос, все зависит от опытности программиста.
Но опять на взгляд автора ( меня =) ) нестандартные возможности проще реализовывать на современных версиях OpenGL и тем более
для этого не требуются переходы на новые операционные системы ( в отличие от DirectX 10 ).
Времени на изучение тоже требуется на порядок меньше. + переносимость.

Теперь чуть-чуть про GPU:
В данный момент ( декабрь 2012г. ) в Android устройствах присутствуют два поколения GPU, Поддерживающие OpenGL ES 2.0 ( почти 95% ) и поддерживающие только версии 1.0 и 1.1.
Аппаратной обратной совместимости НЕТ.
Поэтому рассматривать версию стандарта меньше 2.0 на взгляд автора кроме как археологам не имеет смысла.
( стандарт версии 3.0 обратно совместим с 2.0 )

Структура конвейера OpenGL 1.x:

То есть часть блоков с «железной логикой» заменили на программируемые процессоры.
Вопрос: а для чего?

А все дело в том что аппаратно было реализовано достаточно мало функций, из-за этого создавались существенные ограничения в дальнейшем развитии и гибкость была равна нулю.
История ( немного ):
Первые попытки перенести расчеты с Cpu ( центрального процессора ) были реализованы в первом Geforce ( а не в Voodoo, как думают многие ), называлать технология T&L.
Она позволяла аппаратно просчитывать на GPU освещение и выполнять уже простейшие шейдеры.
Получилось «быстро», но не осталось даже минимальной гибкости. Есть аппаратно-реализованный метод освещения например,используем. Нет — и не будет.
Следующая веха — GeForce 3, который обладал уже вполне программируемой логикой, но процессорные блоки еще небыли универсальными.
То есть блоки делились на обрабатывающие вершины и фрагментные ( обрабатывающие пиксели ).
Одни могли быть перегружены, другие простаивали.
В чем смысл наращивания процессоров ( вычислительных блоков ) у GPU?
Все дело в том что графические просчеты почти линейно маштабируется, то есть увеличение процессоров например со 100 до 200 дает почти 100% прирост производительности, так как в компьютерной графике текущий расчет обычно не зависит от предыдущего — то есть легко запаралелить.
Но и существуют некоторые ограничения, о которых будет написано ниже.
Теперь про сам OpenGL ES:

Что может OpenGL ES?
Основным принципом работы OpenGL является получение наборов векторных графических примитивов в виде точек, линий и многоугольников с последующей математической обработкой полученных данных и построением растровой картинки на экране и/или в памяти. Векторные трансформации и растеризация выполняются графическим конвейером (graphics pipeline), который по сути представляет собой дискретный автомат. Абсолютное большинство команд OpenGL попадают в одну из двух групп: либо они добавляют графические примитивы на вход в конвейер, либо конфигурируют конвейер на различное исполнение трансформаций.
Ключевая особенность — CPU и GPU работают не синхронно, то есть CPU не дожидается окончания исполнения команд от GPU, а продолжает работать ( если не было дополнительных указаний ).
Есть стек команд ( инструкций ) OpenGL.
( стек бывает двух типов, fifo и lifo. FIFO — акроним «First In, First Out» (англ. ). Принцип «первым пришёл — первым ушёл», LIFO — акроним «Last In, First Out» (англ.), обозначающий принцип «последним пришёл — первым ушёл». В OpenGL используется fifo ( очередь )).

Урок первый END.

Представьте себе конвейер производства дед.мороза =)

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

Это и есть машина конечных состояний. Проще объяснить не могу. Кто не понял — тут

Продолжение как чуть высплюсь.
( следующая тема — что скрывается за GLSurfaceView, чем это плохо и что такое EGL. )
OpenGL 2.0+ под Android (Пост Leopotam #18544726)

Все статьи в PDF, благодаря who-e, oges2.zip ( 2,76 МБ )
. Надеюсь будет полезно. Спасибо who-e.

В теме нет куратора. Если в теме есть пользователь, желающий стать Куратором и соответствующий Требованиям для кандидатов, он может подать заявку в теме Хочу стать куратором (предварительно изучив шапку темы и все материалы для кураторов).
До назначения куратора, по вопросам наполнения шапки, обращайтесь к модераторам раздела через кнопку под сообщениями, на которые необходимо добавить ссылки.

Сообщение отредактировал vaalf — 23.11.17, 12:12

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

Сообщение отредактировал Leopotam — 10.01.13, 22:58

Теперь пример простейшей инициализации OpenGL ES 2.0 под Andro >
В приложении, методе OnCreate:

requestWindowFeature(Window.FEATURE_NO_TITLE); // Убираем заголовок
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN); // Устанавливаем полноэкранный режим

// Далее устанавливаем версию OpenGL ES, равную 2
glSurfaceView.setEGLContextClientVersion(2);

renderer = new nRender();

glSurfaceView.setRenderer(renderer); // устанавливаем нашу реализацию GLSurfaceView.Renderer для обработки событий

glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // режим смены кадров
// RENDERMODE_CONTINUOUSLY — автоматическая смена кадров,
// RENDERMODE_WHEN_DIRTY — по требованию ( glSurfaceView.requestRender(); )

>catch(RuntimeException e)<> // выводим окошко «увы, выше устройство слишком. «

далее создаем класс nRender

package com.example.ogl1;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import java.util.Random;

public class nRender implements GLSurfaceView.Renderer
<
public nRender() <
>

public void onDrawFrame(GL10 glUnused) < // Отрисовка кадра

Random rnd = new Random();

// Задаем случайный цвет и сводим с ума эпилептиков =)
// Цвет задается в формате RGBA, от 0.0f до 1.0f.
GLES20.glClearColor(((float)rnd.nextInt(2)/2.0f), ((float)rnd.nextInt(2)/2.0f), ((float)rnd.nextInt(2)/2.0f), 1.0f);

GLES20.glClear( GLES20.GL_COLOR_BUFFER_BIT ); // Очищаем буффер цвета
>

public void onSurfaceChanged(GL10 glUnused, int width, int height) < // изменение поверхности, например изменение размера

GLES20.glViewport(0, 0, width, height);
// Устанавливаем положение и размер вьюпорта
// вьюпорт устанавливаеться относительно поверхности ( OpenGLSurface ), в данном случае на весь экран.
// замечу, что GLES20.glClear очищает всю поверхность, все зависимости от установки Viewport.
>

public void onSurfaceCreated(GL10 glUnused, EGLConfig config) < // вызываеться при создании поверхности

Далее пробуем запустить, и получаем мечту эпилептика.

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

Сообщение отредактировал usnavii — 17.01.13, 11:55

OpenGL по своему принципу является конечным автоматом.

Представьте себе конвейер производства дед.мороза =)

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

Это и есть машина конечных состояний. Проще объяснить не могу. Кто не понял — тут

Продолжение как чуть высплюсь.
( следующая тема — что скрывается за GLSurfaceView, чем это плохо и что такое EGL. )

Сообщение отредактировал usnavii — 10.01.13, 23:26

Конечный автомат — это система, содержащая в себе несколько состояний, причем можно четко определить, в каком из них она находится. Это если по-умному и непонятному.
Если ближе к отображению графики, например, выводу модели Деда Мороза на экран:
*) существует единая лента-конвейер по выводу изображения на экран, на вход которого подаются модели Деда Мороза, а на выходе получаем 2d картинку, готовую для отображения на мониторе. Конвейер выполняет обработку данных последовательно, без пропусков. Модели представляют собой набор вершин и треугольные грани (см. P.S.1), связывающие 3 соседние вершины.
*) У главного пульта конвейера есть всего, допустим, 5 тумблеров-переключателей (P.S.2), каждый из которых может находиться в состоянии вкл / выкл (см P.S.3):
-) тумблер «отрисовывать сглаженно или с острыми кромками».
-) тумблер «отрисовывать с текстурой или просто сплошным цветом». Под этим тумблером есть фото-рамка для вставки желаемой текстуры.
-) тумблер «отрисовывать все грани или только лицевые» (например, для прозрачных объектов).
-) тумблер «очищать только буфер глубины или буфер глубины и буфер цвета (изображения)».
-) еще какой-то, с красной кнопкой.
*) Стартовые значения переключателей на пульте устанавливаются в момент инициализации OpenGL и сохраняются в специальной области, называемой контекстом. Да, OpenGL может в одном приложении выводить изображение в 2 окна, каждый из которых будет иметь свой независимый контекст и свой независимый конвейер.
*) Конвейер работает все время существования окна приложения, используя для отрисовки Дедов Морозов текущие настройки на пульте. Нет необходимости перед каждой отрисовкой повторно устанавливать выключатели в старое положение.
*) Эльфы, сидящие внутри блока обработки входящей информации через конвейер, не сразу выдают результат на экран, а только после выполнения нужных операций по растеризации-отрисовке внутри себя, распараллеливая между собой. Чем совершеннее графический процессор, тем больше эльфов, способных работать параллельно для отрисовки точек, сидит внутри. При этом конвейер приостанавливается до обработки всей модели. Если данных будет очень много, то эльфы, сидящие внутри, не будут успевать перерабатывать такой объем и блокировка конвейера вызовет простой уже центрального процессора, ожидающего, когда конвейер освободится — отсюда мы видим падение fps-ов при большом потоке информации (перекрывающем филлрейт конвейера) и слабом графическом процессоре.

Пример.
Конвейер запущен, Деды Морозы поехали по ленте. И через 5 штук мы вспоминаем, что нам не нужны гламурно-сглаженные, но только рубленные топором, только хардкорные модели на выходе. Подходим к пульту и перещелкиваем первый тумблер (см. выше). Все, все последующие модели будут отрисовываться, словно резчик по дереву был пьян и забыл про шлифовку и прочие доработки. Нам ничего не нужно перещелкивать на пульте перед каждой последующей моделью.
Прибежала Снегурочка, наблюдающая все это непотребство на экране и сказала, что неплохо бы раскрасить Деда Мороза, потому что гламурное розовое нечто с неаккуратными краями смотрится на экране неканонично, хотя ей нравится. Берем у нее фотографию-картинку настоящего Деда Мороза, подсовываем в рамку под тумблером текстурирования и перещелкиваем его. Все, все последующие Деды Морозы будут выглядеть с наложенной картинкой-текстурой и с несглаженными гранями. Нам ничего не нужно перещелкивать на пульте перед каждой последующей моделью.
Снегурочка пришла еще через 10 моделей, уже немного нетрезвая, и заявила, что пусть лучше Дед Мороз будет гламурно ровненьким, но должен иметь сходство с фотографией. ок, перещелкиваем тумблер сглаживания и идем дальше праздновать НГ. На выходе получим текстурированного и сглаженного Деда Мороза для всех последующих моделей. Нам ничего не нужно перещелкивать на пульте перед каждой последующей моделью.

P.S.1. На самом деле OpenGL поддерживает не только треугольные грани, но для унификации всего процесса лучше использовать именно их.
P.S.2. Их не 5, а гораздо больше — в этом смысл конечного автомата OpenGL — много настроек, каждая из которых отвечает за что-то свое и за изменением которых нужно внимательно следить, чтобы не получить Злого Санту, например.
P.S.3. Состояний может быть больше двух, если настройка подразумевает несколько вариантов значений.

Сообщение отредактировал Leopotam — 11.01.13, 10:36

OpenGL: Основы.

OpenGL является одним из ведущих и популярных графических API, разработанный SGI. OpenGL разрабатывался как многоплатформенный, открытый и быстрый графический API. Многие графические пакеты используют OpenGL для вывода трёхмерной графики. Многие известные игры, такие как Quake, Serious Sam и наш отечественный ИЛ-2 Штурмовик, также написаны под OpenGL.

Введение

Почти все современные графические акселераторы могут аппаратно поддерживать OpenGL, что позволяет выводить графику в реальном времени.

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

OpenGL представляет собой набор функций, при помощи которых можно выводить 2-х и 3-х мерные графические примитивы, управлять ими, их свойствами, способами вывода и пр.

Заголовочные файлы, которые могут понадобиться для работы с OpenGL, обычно находятся в папке GL, расположенной в стандартной папке Include.

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

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

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

В OpenGL нет встроенных функций для инициализации OpenGL. Это связано с тем, что OpenGL является независимым от платформы графическим API. Инициализацию обеспечивает операционная среда.

Существует несколько функций по работе с OpenGL, которые представляет Windows API.

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

Обычно в трёхмерных играх каждый видимый кадр строится заново. То есть, в каждом кадре полностью перерисовывается вся видимая графика. Построение кадра может происходить следующим образом. Сначала весь кадр очищается, а потом последовательно выводятся трёхмерные графические объекты. Такое действие происходит несколько раз в секунду. Количество этих перерисовок в секунду называют FPS (frames per second).

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

Чтобы не было этого неприятного эффекта, пользуются следующим способом. Весь кадр полностью строят в невидимой части видеопамяти адаптера. Эту часть называют Back Buffer. После того, как все графические объекты выведены, вся эта невидимая часть копируется в видимую область видеопамяти, называемую Front Buffer. Если ваше приложение выводит графику на весь экран, а не в окно, то можно избежать копирования буфера, переключая указатель на видимую часть в видео памяти. Такой эффект называют Flip. Видно, что он работает быстрее, чем копирование буфера.

Мы будем строить пример оконного приложения.

Окно, в котором будет выводиться графика под OpenGL должно иметь два дополнительных стиля WS_CLIPSIBLINGS и WS_CLIPCHILDREN. Эти стили можно установить либо во время создания окна в функции CreateWindow(), либо функцией из Windows API — SetWindowLong(), если окно уже создано.

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

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

Это можно сделать при помощи объекта структуры PIXELFORMATDESCRIPTOR. Тут вы указываете, например, сколько бит необходимо выделить под каждое значение цвета, под буфер глубины и т.д.

Рассмотрим функции, работающие с PIXELFORMATDESCRIPTOR.

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

Итак, сначала создадим объект типа PIXELFORMATDESCRIPTOR, полностью заполненный нулевыми значениями.

Для работы с этим объектов всегда нужно выставлять поля nSize значением равным размеру всей структуры и nVersion (версия) — со значением равным 1.

Также необходимо выставить требуемые флаги в dwFlags. Поскольку мы собираемся выводить графические объекты в окно, то нужно выставить флаг PFD_DRAW_TO_WINDOW. Далее, говорим, что буфер кадра поддерживает вывод через OpenGL — PFD_SUPPORT_OPENGL, и, так как мы используем два буфера (Back и Front), устанавливаем флаг PFD_DOUBLEBUFFER. Итак:

В поле iPixelType выставим PFD_TYPE_RGBA. Это означает, что цвет для пикселя представляется в виде RGBA (цветовые компоненты: R — красный, G — зелёный, B — голубой и A — альфа). Кроме этого бывает ещё индексное представление, которое мы не будем рассматривать вообще.

Дальше выставляем желаемые параметры. Например: глубину цвета (cColorBits) — 32 бит.

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

Если формат был успешно подобран, нужно его выставить функцией SetPixelFormat().

Чтобы узнать, какие именно значения устанавливаются для формата, то есть получить описание полей структуры PIXELFORMATDESCRIPTOR, пользуются функцией DescribePixelFormat()

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

Если удалось установить пиксельный формат, то теперь нужно создать просчитывающий контекст (rendering context) OpenGL. Это делается вызовом функции wglCreateContext(). Далее, созданный контекст выставляется текущим функцией wglMakeCurrent().

Теперь вы можете пользоваться функциями OpenGL.

По окончанию работы с OpenGL, например, в конце работы приложения, нужно освободить занятые ресурсы: освободить контекст, вызвав wglMakeCurrent() с параметром ноль для идентификатора контекста OpenGL и разрушить этот контекст функцией wglDeleteContext().

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

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

Функцию CreateMainWindow() вызвать с true для параметра bIsOpenGL.

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