Представляю вашему вниманию кое-что из раннего…

Создание Win32 приложения

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

1) Создание начального проекта
С помощью Visual Studio это сделать очень просто:
1. Выбирите в меню File(Файл) пункт New (новый).
2. Среди всего перечня возможных проектов нам потребутся Win32 приложение. В поле Project Name (название проекта) введите Skel (Skeleton – Скелет. Этим мы будем называть приложение на основе которого будем строить все остальные. Это костяк.) На рис.01 изображен диалог установок.
img01
Рис.01 Создание Win32 приложения
Не забудьте указать в поле Location (местонахождение) расположение проекта. Лучше всего отведите для этого отдельную директорию.

img02

3. После нажатия кнопки Ок (применить) появиться диалоговое окно с выбором типа нашего проекта. Рис.02. Выбирите Simple Win32 application (простое Win32 приложение).
img02_2

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

Теперь щелкните Finish (завершить) и на этом работу с менеджером проектов можно считать завершенной.

2) создание главного окна
Если вы запустите сейчас откомпилируете наше приложение (с помощью команды Build – клавиша F7), а потом запустите его (с помощью команды Execute Program – клавиши Ctrl_F5), то ничего не произойдет. Это потому, что наше приложение сейчас пустое, но мы это быстро исправим.
Панелька, расположеная слева, содержит информацию о нашем проекте, об используемых в нем файлах. Перейдите ко кладке FileList (список файлов) и вы увидите дерево иерархий файлов нашего проекта. Найдите там файл Skel.cpp и откройте его. Рис.03. В этом файле находится функция WinMain, которая вызывается при запуске нашего приложения. Как видите она пуста (return 0 – выход из функции с возвращением 0), именно поэтому при запуске нашего приложения ничего не просходило. Теперь все, что мы напишем туда и будет выполняться, так что вперед. Наша задача построить обычное «виндовское» окно.

Рис.03 Список фалов проекта и открытый главный файл – Skel.cpp

img03

Для создания окна нам потребуется:
1. Добавим две литерные переменные – название класса нашего приложения и заголовок окна
Char *szClassName = «OpenGL Application»;
Char *szWndName = «OpenGL»;
2. Теперь нам нужно добавить еще одну переменную, характеризующую класс нашего окна.
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = szAppName;
Среди различных свойств, которые мы зададим этой переменной, мы должны указать и функцию, которую Windows будет использовать для отправки нам сообщений.
Wc.lpfnWndProc = (WNDPROC) MsgProc;
Т.е. технология работы нашего приложения будет следующая: создаем окно и указываем функцию обработки сообщений окна, а потом обрабатываем различные получаемые сообщения (сообщение на перерисовку, на нажатие клавиши и т.д.)
Когда же мы заполним все необходимые нам параметры нашего окна, то класс нашего окна нужно зарегистрировать с помощью функции
RegisterClass(&wc);
3. Всё, класс с таким именем зарегистрирован, теперь можно и создать наше долгожданное окошко с помощью функции
CreateWindow( <название класса>, <заголовок окна>,
<стиль>, , ,
<ширина>, <высота>,
<родитель>, <меню>,
<ссылка на окно>, <параметр> );

Эта функция возвратит нам код окна. Он нам пригодится, так что сохраним его в отдельной переменной типа HWND
HWND hWnd;

hWnd = CreateWindow(szAppName, szAppName,
WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,
CW_USEDEFAULT, CW_USEDEFAULT,
640, 480,
NULL, NULL,
hInstance,
NULL );

4. Окно создали, осталось установить на него фокус
SetFocus (hWnd);
А затем показать его
Showindow (hWnd, True)
Здесь-то нам и пригодилась переменная кода нашего окна.
5. Теперь мы готовы принимать и обрабатывать Windows сообщения:
BOOL bGotMsg;
MSG msg;

PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE );
while( WM_QUIT != msg.message )
{
bGotMsg = PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE );

if( bGotMsg )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
InvalidateRect( hWnd, NULL, FALSE );
}
}

return (INT)msg.wParam;

? последнее – добавим в нашу программу новую функцию
INT MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam );
Переменная Msg будет означать тип сообщения из-за которого вызывается данная функция. Вообще возможных сообщений очень большой перечень, но нам в работе потребуется только самая малость.
WM_CREATE – создание
WM_DESTROY – уничтожение
WM_PAINT – перерисовка
WM_RESIZE – изменение размеров
Чтобы проверить действительность вызывания windows этих сообщений давайте сделаем так, чтобы при их вызове выдавалось сообщение
MessageBox( NULL, «WM_FormCreate», «Message», MB_OK);
?так тело нашей функции должно выглядеть следующим образом:

switch( uMsg )
{
case WM_CREATE:
MessageBox( NULL, «WM_FormCreate», «Message», MB_OK);
return 0;

case WM_CLOSE:
MessageBox( NULL, «WM_FormCreate», «Message», MB_OK);
DestroyWindow( hWnd );
PostQuitMessage(0);
return 0;
}

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

Вот это я думаю все. Запустите приложение и перед вами должно появится обычное окно, причем это окно не должно иметь области рисования (как будто внутри пусто), т.к. мы её не задали и она нам и не понадобиться. В ex01 находится исходный вариант этой работы.

Подключение OpenGL

1) Чтобы подключить функции библиотеки OpenGL в начале программы впишите использования заголовка с прототипами этих функций
#include
Теперь подключите саму библиотеку
#pragma comment (lib, «opengl32.lib»)

2) OpenGL – библиотека рисования двухмерной и трехмерной графики, состоящая из большого количества команд. Естественно, что OpenGL’у требуется устройство, на которое будет производиться вывод. Это может быть и принтер, и рабочий стол, но мы будем использовать обычное виндовское окно (как его строить мы уже знаем). ?так, добавим новую переменную для хранения контекста устройства (в данном случае нашего окна)
HDC hDC;
Теперь занесем в неё контекст нашего окна (это лучше всего разместить после вызова функции по созданию окна)
HDC = GetDC(hWnd);
Двигаемся дальше. Очень важный момент – установка формата пикселей, т.е. мы должны установить связь между видеокартой и нашим устройством для вывода, а также задать параметрa работы OpenGL для данного устройства.
Добавим новую функцию
BOOL SetDCPixelFormat(HDC hDC);

Разместим в ней новую переменную, в которой укажем необходимые для нашей работы параметры:
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // размер структуры
1, // версия
PFD_DRAW_TO_WINDOW | // рисование в окне
PFD_SUPPORT_OPENGL | // поддержка OpenGL
PFD_DOUBLEBUFFER, // режим двойной буферизации
PFD_TYPE_RGBA, // режим RGBA цвета
GetDeviceCaps(hDC, BITSPIXEL), // зададим размер буфера цвета
0,0,0,0,0,0, // не используем
0,0, // не используем
0,0,0,0,0, // не используем
16, // размер буфера глубины
0, // не используем
0, // не используем
PFD_MAIN_PLANE, // вывод на главную плоскость
0, // не используем
0,0,0
};

Смысл в том, что мы зададим определенные параметры работы, чтобы настроить режим работы как нам хочется, а затем вызовем специальную функцию,
Int nPixelFormat = ChoosePixelFormat( hDC, &pfd);
которая попытается выбрать соответсвующий формат пикселей, согласуя с возможностями видео карты и учитывая наши пожелания.
Выбранный формат пикселей установим
SetPixelFormat(hDC, nPixelFormat, &pfd);

! ?спользуя следующий вызов
DescribePixelFormat(hDC, nPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
Мы получим в нашей переменной pfd флаги dwFlags, содержащие информацию о состоянии системы:
Если присутствует флаг PFD_NEED_PALETTE, то приложение запущено в режиме 256 цветов. В этом случае выдать сообщение о некорректном режиме и выйти из программы. (Какой смысл продолжать работу, если все будет не красиво)
Если присутствует флаг PFD_GENERIC_FORMAT, то приложение будет работать с поддержкой ускорителя, а иначе программная эмуляция.

После того как мы задали формат пикселей, нам нужно создать контекст воспроизведения для работы с OpenGL. Подобно тому, что контекст устройства хранит настройки GDI, так и контекст воспроизведения хранит настройки OpenGL.
Добавим новую переменную, хранящую значение контекста воспроизведения
HGLRC hGLRC;
Теперь можно создать контекст. (функция wglCreateContext возвратит его значение)
hGLRC = wglCreateContext( hDC );
Когда мы активизируем контекст воспроизведения, то сможем использовать команды OpenGL для рисования.
wglMakeCurrent( hDC, hGLRC );
Т.е. схема нашего приложения будет следующая:

Устройство
Монитор Видео карта
Контекст
воспроизведения приложение

Что же касается рисования, то как вы помните при установке формата пикселей, мы указали режим двойной буферизации. Это значит, что рисовать будем на вне экранном буфере, а затем поменяем его с экранным и получим тем самым плавную перерисовку. Т.е добавим в нашу функцию обработки сообщений новое – WM_PAINT (сообщение на перерисовку). В начале рисования нем обязательное следует очистить буфер кадра, чтобы стереть все предыдущее нарисованное:
GlClear ( GL_COLOR_BUFFER_BIT );
Можете здесь же вставить команду OpenGL задающую цвет фона, которым заполняется буфер после очистки.
GlClearColor( 0.5f, 0.5f, 0.5f, 1.0f), где все 4 параметра – RGBA состовляющие цвета ( я для фона задал серый цвет)
Хотя вызов этой функции не обязателен при рисовании каждого кадра, поэтому поместите её где-нибудь после активации контекста воспроизведения.
В конце рисования – смена буферов
SwapBuffers( hDC );
3) При уничтожении приложения (сообщение WM_DESTROY или WM_CLOSE) нам нужно деактивировать контекст воспроизведения, удалить его и освободить контекст устройства:
wglMakeCurrent( 0, 0 );
wglDeleteContext( hGLRC ) ;
ReleaseDC(hWnd, hDC);
Если у вас появилось окно с серым фоном, то я вас поздравляю – вы построили простое OpenGL приложение. В ex.02 находится этот проект.

Сбор информации о видео карте

OpenGL в основном рассчитан на использование акселератора (сейчас практически на всех компьютерах он имеется). Давайте научимся собирать информацию о видео карте и узнавать её возможности. Современные видео карты имеет довольно обширный перечень различных расширений, с которыми мы будем разбираться по ходу этой книги. Каждое расширение вносит дополнительную функциональность, поддерживаемую аппаратно, т.е. на использование своей видео карты. Ведь возможно у вас мощная видео карта, а вы даже не можете воспользоваться её возможностями.
После того как мы установили контекст воспроизведения, мы можем получить информацию с помощью функции glGetString(<параметр>), где в качестве параметра можно задать
Параметр Возвращаемая строка
GL_VENDOR Фирма-изготовитель видео карты
GL_RENDERER
Название видео-карты
GL_VERSION Версия
GL_EXTENSIONS Перечень расширений, поддерживаемых видео картой

Вот к примеру, поместим следующий код по выводу информации о видео карте после установки контекста воспроизведения:
Const Char Renderer = (const unsigned char*) glGetString(GL_RENDERER);
MessageBox( NULL, (char*) Renderer, «RENDERER», MB_OK );
В ex.03 находится наш проект скелета OpenGL, только после создания окна появляются диалоги с информацией о видео карте.

LOG – файлы

Очень удобно было бы выводить всю информации, а также различные замечания в отдельный файл, для того чтобы потом спокойно все просмотреть. Также часто возникает большая необходимость в слежке за данными, которые можно выводить также в этот файл для проведения отладки приложения.
Пусть этот файл будет log.txt, тогда нам потребуется 3 функции: для создания файла и подготовке к выводу в него; непосредственно для вывода; для закрытия файла при уничтожении окна.
1) Процедура подготовки LOG файла.
Добавьте новую переменную — FILE* logfile;
Она будет хранить контекст нашего файла. Теперь добавьте функцию
INT InitLOG()
{
//даже если этот файл уже присутсвует, то
//мы его сотрем
if((logfile=fopen(«Log.txt», «wb»))==NULL)
return false;

//закрываем файл – создание успешно завершено
fclose(logfile);
return true;
}
Мы открыли файл и отчистили его. Теперь будем только добавлять в него информацию.
2) Функция добавление в файл
printLOG(char* text, …)
{
va_list arg_list;

//Initialize variable argument list
va_start(arg_list, text);

//Open the log file for append
if((logfile = fopen(«Log.txt», «a+»))==NULL)
return false;

//Write the text and a newline
vfprintf(logfile, text, arg_list);
putc(‘\n’, logfile);

//Close the file
fclose(logfile);
va_end(arg_list);

return true;
}
Здесь мы открываем в наш файл в режиме добавления. Функция устроена так, что вы можете форматировать задаваемую строчку. Вот пример использования этой функции:
Output( “Renderer: %s”, glGetString(GL_RENDERER) );
Видите, функция вывода организована очень удобно.
3) Добавим процедуру, которую будем вызывать при уничтожении окна:
FreeLOG(void)
{
if(logfile)
fclose(logfile);

return true;
}
Файл обязательно нужно закрывать, чтобы потом его можно было просматривать в других программах.
В ex.04 находится наш проект с использованием LOG файла.

Подключение расширений

Как говорилось выше, каждое расширение вносит дополнительную функциональность в использовании нашей видео карты. Естественно, что каждое расширение вносит новые типы и новые функции. Чтобы узнать какие именно, нам нужно узнать их прототипы. Для этого подключим соответствующий модуль с описанием прототипов большинства из расширений.
#include
Новые функции загружаются с помощью вызова wglGetProcAddress(<имя функции>). Расширение может добавлять одну функцию, множество, а может и вообще не добавлять ни одной.

Вертикальная синхронизация

В качестве примера на использование расширений возьму расширение по настройке вертикальной синхронизации – WGL_EXT_SWAP_CONTROL. Смысл в том, что смена буферов синхронизируется с видео кадровым периодом, т.е. со временем, требуемым монитору для вывода полного кадра видео данных. Если нам нужно посчитать максимальное количество FPS (кадров в секунду), то вертикальную синхронизацию нужно убрать. Кстати, это можно сделать в большинстве случаев из настроек драйверов.
Если посмотреть в модуль описания прототипов этого расширения, то там можно найти следующее
typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval);
typedef int (WINAPI * PFNWGLGETSWAPINTERVALEXTPROC) (void);
Отсюда видно, что нам предоставляют прототипы для двух функций:
1) Вводим PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalExt;
Теперь загружаем нашу функцию
wglSwapIntervalExt = (WINAPI * PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress(?wglSwapIntervalEXT’);
Вызываем функция wglSwapIntervalExt(int interval), где переменной interval мы задаём значение
0 – вертикальная синхронизация выключена
1 – вертикальная синхронизация включена
2) Функция wglGetSwapInterwalExt, которая возвратит текущее значение интервала, загружаем аналогично с предыдущей.
В ex.05 содержится проект реализующий использование этого расширения. В нем на экране крутится коробка. Пока мы не будем вдаваться во все тонкости рисования, поэтому я подключил вспомогательную библиотеку
#include
? с помощью вызова функции DrawRotateBox() стал рисовать вращающийся кубик. Причем эту функцию нужно поместить в нашу процедуру Render, предназначенную для рисования.

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

Полноэкранные/ экранные режимы

Очень часто нам потребуется не просто оконное приложение, а развертывание окна на весь экран с возможной смены разрешения экрана (как делается во всех играх). Так что давайте научимся менять разрешение экрана, а также переходить из экранного режима в полноэкранный и наоборот.
1) При создании приложения мы должны все возможные режимы занести в отдельный массив, чтобы потом знать, какие режимы поддерживает монитор. Введите новые переменные:
Int nummodes; // количество поддерживаемых режимов
DEVMODE devmodes; // массив характеристик каждого из них
Введем функцию по перечислению возможных режимов
Void EnumDisplayModes()
{
DEVMODE devmode;

// подсчитаем количество режимов
// чтобы знать, сколько места отделять под массив
Nummodes=0;
While( EnumDisplaySettings(NULL, nummodes, &devmode))
Nummodes++;

Devmodes = new DEVMODE[nummodes];

// заносим в наш массив характеристики каждого
// режима
Nummodes=0;
While( EnumDisplaySettings(NULL, nummodes, &devmodes[nummodes]))
Nummodes++;
}
Теперь добавим функцию перехода окна в экранный режим
Void SetWindow()
{
// меняем разрешение
ChangeDisplaySettings(NULL, 0);

// выставляем положение и размеры нашего окна
Rect wrect;
}

OpenGL в среде Windows. Для начинающих
Метки:    

Одно мнение о “OpenGL в среде Windows. Для начинающих

  • Суббота Март 21, 2009 на 11:41
    Постоянная ссылка

    Спасибо, хотябы один подробный материал, теперь разберусь быстрее…
    Сейчас как раз планирую игрушку написать — надо попробовать использовать инструментарий GL

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *