Delphi и OpenGl: братья на век
Георгий Чухин
"Истина где-то рядом...."
Именно такими словами я начну свою статью про Delphi и конкретно -
про библиотеку OpenGL. Я решил использовать именно эту цитату, потому что
главным вопросом, который возникает перед человеком, плохо умеющим
программировать (конкретно игры или другие приложения, использующие трехмерную
графику), но очень хотящим научиться этому, является дилемма в выборе языка
программирования.
В последнее время определились два языка сравнительно часто
обновляющихся и развивающихся в этом направлении: это языки на базе "С" (си)
(С++, С builder, C# и так далее) и, как ни странно, Delphi, возможно, в этом
месте многие презрительно фыркнут, но окажутся в корне не правыми.
Так получилось, что за недолгую историю компьютерных игр основным,
если не единственным, языком программирования для этих целей являлся "С", однако
в настоящее время все стало плавно меняться в сторону разделения зон правления.
Поскольку речь в этой статье пойдет о Delphi, мы не будем углубляться в другие
среды разработки программных продуктов.
Когда человек пытается доказать кому-либо, что писать игру на "C"
правильнее, то основными аргументами являются: 1) меньший размер файлов (чем в
Delphi); 2) разнообразность библиотек и SDK; 3) общепринятость (дань традициям);
4) скорость обработки кода.
Конечно, таких факторов можно придумать великое множество, но, с
другой стороны, на любой приведенный здесь пункт, приверженец Delphi может гордо
ответить: 1) если писать на WinAPI функциях в Delphi, то размер и скорость
обращения к коду практически сравнивается с "С"; 2) библиотеки и справки под
Delphi можно найти тоже очень просто (благо, Интернет не подводит), а если "руки
прямые", то и самому можно, например, транслировать библиотеки с кода "С"; 3)
молодым везде у нас дорога... 4) на сегодняшний день средний компьютер стал
настолько мощным и производительным, что о скорости обработки программного кода
можно не говорить. Как видите, Delphi ничем не уступает "С", а, например, в
скорости разработки - даже обходит.
Немного теории...
Хотя эта статья рассчитана на человека, хоть немного разбирающегося
в Delphi, я все равно хочу пояснить одну деталь. Существует два типа
программирования на Delphi: можно писать с применением vcl, то есть с
использованием библиотек классов Delphi - это те самые кнопочки, формочки,
которые можно растянуть и поставить как душе угодно, естественно, это не
единственное достоинство vcl, но при этом нельзя забывать, что это увеличивает и
утяжеляет код. Но есть альтернативное решение, если писать код c
WINapi-функциями - это значительно сокращает код, увеличивает скорость работы
(хотя это заметно только в больших и сложных проектах). Разумеется, для
обработки трехмерной графики больше подходит второй способ, но он вызывает
больше вопросов и сложностей.
В нашем примере мы будем использовать именно WINapi-функции, потому
что рано или поздно при написании ресурсоемких проектов вам придется их
использовать, и чем раньше начать, тем лучше.
Много практики...
Шаг 1.
Смело заходим в Delphi и выбираем в разделе "файл" подменю
"создать", а там - "приложение для консоли". Естественный вопрос, который может
возникнуть: на Delphi какой версии писать приложение, благо выбор велик, уже
вышло семь версий "дельфина". Но почему-то для написания игр зачастую используют
Delphi 5, возможно, потому, что именно для пятой версии существует больше всего
библиотек.
Например, библиотека Glut, которая позволяет одной строчкой
рисовать сложные объекты - тетраэдры, сферы и много других, категорически
отказывалась запускаться под шестой "дельфи". Но, так или иначе, мы будем
пользоваться только стандартной библиотекой OpenGL, которая входит в состав
Windows, и, конкретно, ее проекцией в Delphi - OpenGl.pas.
Свой небольшой пример я буду писать под шестую "дельфи", но он
будет работать и под другими.
Шаг 2.
Когда перед вами появилось окно с несколькими строчками вроде:
program Project1; {$APPTYPE CONSOLE} uses
SysUtils; begin { TODO -oUser -cConsole Main : Insert code
here } end. Ну, или иного содержания в зависимости от версии программы,
можно начинать "кодинг". Я опишу программирование трехмерного объекта - куба,
ведь для любой красивой игры нужна 3d-графика, но начинать что-либо всегда
приходится с нуля, а мы и начнем с самых азов трехмерного программирования...
Шаг 3. Здесь будет написан сам код, а пояснения к каждой строчке будут
ограничены двумя галочками "//" или скобками {} (пояснение не является частью
кода и в финальной стадии ничему не служит).
program
window; uses Windows, Messages, //Библиотека, отвечающая за прорисовку
окна и объектов на нем OpenGL; //Библиотека OpenGL
const WND_TITLE
= 'www.mipstudio.ru'; //
заголовок окна FPS_TIMER = 1; // Таймер, чтобы считать FPS FPS_INTERVAL =
100; // Отсчет fps каждые 100 миллисекунд RAIN_TIMER = 2;
type
TGLCoord = Record // Координаты X, Y, Z : glFloat;
end; var h_Wnd : HWND; // Глобальный дескриптор окна h_DC : HDC; //
Глобальный контекст устройства h_RC : HGLRC; // OpenGL контекст
прорисовки keys : Array[0..255] of Boolean; // массив клавиш FPSCount :
Integer = 0; // Счетчик для FPS ElapsedTime : Integer; // Время, затраченное
на прорисовку кадра ElapsedTime2 : Integer;
xVert, yVert : glFloat; //
Переменные, отвечающие за поворот объекта на оси
{$R
*.RES} //////////////////////////////////////// { Процедура по заданию
координат объекта } ////////////////////////////////////////
procedure
DrawBox; begin glBegin(GL_QUADS); //Низ куба
glTexCoord2f(0, 0); glVertex3f(-1, -1, 1);
//На примере двух верхних
строк разберем все остальные, //первая половина строки - это координаты
текстуры, а //вторая - это координаты вершины по x, y, z, как
вы //понимаете, в кубе четыре вершины, значит, и строчек //с координатами
вершин тоже должно быть четыре. glcolor3f(1, 1, 1); // А эта строка отвечает
за цветовое оформление //стороны куба, соответственно Red, Green, Blue в
разных //сочетаниях и оттенках, чтобы стороны не сливались.
glTexCoord2f(0, 1); glVertex3f( 1, -1, 1); glTexCoord2f(1, 1);
glVertex3f( 1, -1, -1); glTexCoord2f(1, 0); glVertex3f(-1, -1,
-1); //Верх куба glcolor3f(1, 0, 0); glTexCoord2f(0, 0);
glVertex3f(-1, 1, 1); glTexCoord2f(0, 1); glVertex3f( 1, 1, 1);
glTexCoord2f(1, 1); glVertex3f( 1, 1, -1); glTexCoord2f(1, 0);
glVertex3f(-1, 1, -1); //Передняя грань glcolor3f(0, 1,
0); glTexCoord2f(0, 0); glVertex3f(-1, 1, -1); glTexCoord2f(0,
1); glVertex3f( 1, 1, -1); glTexCoord2f(1, 1); glVertex3f( 1,-1,
-1); glTexCoord2f(1, 0); glVertex3f(-1,-1, -1); //Задняя
грань glcolor3f(0, 0, 1); glTexCoord2f(0, 0); glVertex3f(-1, 1,
1); glTexCoord2f(0, 1); glVertex3f( 1, 1, 1); glTexCoord2f(1,
1); glVertex3f( 1,-1, 1); glTexCoord2f(1, 0); glVertex3f(-1,-1,
1); //Правый бок glcolor3f(1, 1, 0); glTexCoord2f(0, 0);
glVertex3f(-1, 1, 1); glTexCoord2f(0, 1); glVertex3f(-1, 1,-1);
glTexCoord2f(1, 1); glVertex3f(-1,-1,-1); glTexCoord2f(1, 0);
glVertex3f(-1,-1, 1); //Левый бок glcolor3f(0, 1, 1);
glTexCoord2f(0, 0); glVertex3f(1, 1, 1); glTexCoord2f(0, 1);
glVertex3f(1, 1,-1); glTexCoord2f(1, 1); glVertex3f(1,-1,-1);
glTexCoord2f(1, 0); glVertex3f(1,-1, 1);
glEnd; end; /////////////////////////////////////// { Функция для
прорисовки текущей
сцены} /////////////////////////////////////// procedure
glDraw(); begin glClear(GL_COLOR_BUFFER_BIT or GL_ DEPTH_BUFFER_BIT); //
Очистка экрана и буфера глубины glLoadIdentity(); // Сбрасываем установки
вида glTranslatef(0.0,0.0,-5); // Расположение объекта в
пространстве glRotatef(xVert, 1, 0, 0); // Угол поворота
объекта glRotatef(yVert, 0, 1, 0); DrawBox; // Вызов координат объекта и
прорисовка по ним end;
///////////////////////// { Инициализация
OpenGL} //////////////////////// procedure glInit(); var I, J :
Integer; begin glClearColor(0.0, 0.0, 0.0, 0.0); // Черный
фон glShadeModel(GL_SMOOTH); glClearDepth(1.0); // Установка буфера
глубины glEnable(GL_DEPTH_TEST); // Включаем буфер глубины xVert :=0; //
Задаем начальный-стартовый поворот объекта yVert
:=30; end;
/////////////////////////// { Изменение размеров
окна} /////////////////////////// procedure glResizeWnd(Width, Height :
Integer); begin if (Height = 0) then // чтобы не было деления на
ноль Height := 1; glViewport(0, 0, Width, Height); // Устанавливаем окно
вывода для OpenGL glMatrixMode(GL_PROJECTION); // Изменяем режим
матрицы glLoadIdentity(); // Сбрасываем установки
вида gluPerspective(45.0, Width/Height, 1.0, 100.0); // Установка
перспективы glMatrixMode(GL_MODELVIEW); // Возвращаем режим матрицы к
modelview glLoadIdentity(); // Сбрасываем установки
вида end;
//////////////////////////////////////// { Обработка
нажатия клавиш клавиатуры
} /////////////////////////////////////// procedure
ProcessKeys; begin if (keys[VK_UP]) then xVert := xVert - 0.2; // При
нажатии на соответствующие if (keys[VK_DOWN]) then xVert := xVert + 0.2;
// кнопки стрелок объект будет if (keys[VK_RIGHT]) then yVert := yVert -
0.2; // поворачиваться. if (keys[VK_LEFT]) then yVert := yVert +
0.2; end;
//////////////////////////////////////// { Оконная
функция для обработки
сообщений} //////////////////////////////////////// function WndProc(hWnd:
HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT;
stdcall; begin case (Msg) of WM_KEYDOWN: // Устанавливаем
параметр нажатия клавиши //(wparam) в значение true, чтобы мы могли
проверить нажатие клавиши begin keys[wParam] := True; Result
:= 0; end; WM_KEYUP: // Устанавливаем параметр нажатия
клавиши //(wparam) в значение false, чтобы мы могли проверить нажатие
клавиши begin keys[wParam] := False; Result :=
0; end; WM_SIZE: // Изменяем размер окна с новыми width и
height begin glResizeWnd(LOWORD(lParam),HIWORD(
lParam)); Result := 0; end; WM_TIMER : // Обработка
события таймер begin if wParam = FPS_TIMER then
begin FPSCount :=Round(FPSCount * 1000/FPS_INTERVAL); FPSCount
:= 0; Result := 0; end end; else Result :=
DefWindowProc(hWnd, Msg, wParam, lParam); // По умолчанию возвращаем //ничего не
случилось
end; end;
//////////////////////////////////////// { Корректно
уничтожает окно, созданное
нами} //////////////////////////////////////// procedure
glKillWnd(Fullscreen : Boolean); begin // Делаем текущий контекст
устройства нетекущим и освобождаем контекст // устройства, которое
использовалось как контекст вывода if (not wglMakeCurrent(h_DC, 0))
then MessageBox(0, 'Ошибка', 'Error', MB_OK or MB_ICONERROR);
// Пробуем удалить контекст вывода if (not wglDeleteContext(h_RC))
then begin MessageBox(0, 'Ошибка', 'Error', MB_OK or
MB_ICONERROR); h_RC := 0; end;
// Пробуем удалить
контекст устройства if ((h_DC = 1) and (ReleaseDC(h_Wnd, h_DC) <>
0)) then begin MessageBox(0, 'Ошибка', 'Error', MB_OK or
MB_ICONERROR); h_DC := 0; end;
// Пробуем уничтожить
окно if ((h_Wnd <> 0) and (not Destroy Window (h_Wnd))) then
begin MessageBox(0, 'Не могу уничтожить окно', 'Error', MB_OK or
MB_ICONERROR); h_Wnd := 0; end;
// Пробуем удалить регистрацию
класса окна if (not UnRegisterClass('OpenGL', hInstance))
then begin MessageBox(0, 'Не могу удалить регистрацию класса окна!',
'Error', MB_OK or MB_ICONERROR); hInstance := 0;
end; end; // Функции, описанные выше, служат для оповещения о
соответствующих ошибках. // Они очень важны и нельзя о них
забывать!
//////////////////////////////////////// { Создает окно и
сопоставляет ему OpenGL rendering context
} //////////////////////////////////////// function glCreateWnd(Width,
Height : Integer; Fullscreen : Boolean; PixelDepth : Integer) :
Boolean; var wndClass : TWndClass; // Класс Окно dwStyle : DWORD;
// Стиль окна dwExStyle : DWORD; // Расширенные стили окна
dmScreenSettings : DEVMODE; // Установки экрана (fullscreen, и т.д...)
PixelFormat : GLuint; // Установки для OpenGL визуализации h_Instance :
HINST; // Текущий экземпляр pfd : TPIXELFORMATDESCRIPTOR; // Установки для
OpenGL окна begin h_Instance := GetModuleHandle(nil); //Получаем
экземпляр для нашего окна ZeroMemory(@wndClass, SizeOf(wndClass)); //
Очищаем структуру класса окна
with wndClass do // Устанавливаем класс
Окно begin style := CS_HREDRAW or // Перерисовка окна в случае
изменения его ширины CS_VREDRAW or // Перерисовка окна в случае изменения его
высоты CS_OWNDC; // Уникальный device context для окна lpfnWndProc :=
@WndProc; // Оконная функция для обработки сообщений hInstance :=
h_Instance; hCursor := LoadCursor(0, IDC_ARROW); lpszClassName :=
'OpenGL'; end;
if (RegisterClass(wndClass) = 0) then // Пробуем
зарегистрировать новый оконный класс begin MessageBox(0, 'Невозможно
зарегистрировать класс окна!', 'Error', MB_OK or MB_ICONERROR); Result :=
False; Exit end;
// Если задано, переходим в полноэкранный
режим if Fullscreen then begin ZeroMemory(@dmScreenSettings,
SizeOf(dmScreenSettings)); with dmScreenSettings do begin //
Устанавливаем параметры экрана dmSize :=
SizeOf(dmScreenSettings); dmPelsWidth := Width; // Ширина экрана
dmPelsHeight := Height; // Высота экрана dmBitsPerPel := PixelDepth; //
Глубина цвета dmFields := DM_PELSWIDTH or DM_PELSHEIGHT or
DM_BITSPERPEL; end;
// Пробуем переключиться в полноэкранный
режим if (ChangeDisplaySettings(dmScreenSettings, CDS_FULLSCREEN) =
DISP_CHANGE_FAILED) then begin MessageBox(0, 'Невозможно
переключиться на полный экран', 'Error', MB_OK or MB_ICONERROR);
Fullscreen := False; end; end;
// Если мы в полноэкранном
режиме, то if (Fullscreen) then begin dwStyle := WS_POPUP or //
Создаем popup окно WS_CLIPCHILDREN or WS_CLIPSIBLINGS;
dwExStyle := WS_EX_APPWINDOW; // Максимальный приоритет окна
ShowCursor(False); // Убираем курсор с экрана (чтобы не мешал) end
else begin dwStyle := WS_OVERLAPPEDWINDOW or // Создаем
overlapping окно WS_CLIPCHILDREN or WS_CLIPSIBLINGS; :=
WS_EX_APPWINDOW or // Максимальный приоритет окна WS_EX_WINDOWEDGE; //
Граница с выступающим контуром end;
// Непосредственно создаем
окно h_Wnd := CreateWindowEx(dwExStyle, // расширенные стили окна
'OpenGL', // Имя класса WND_TITLE, // Название окна (заголовок) 0,
0, // Позиция окна Width, Height, // Размер окна 0, // Дескриптор
родительского окна 0, // Дескриптор меню h_Instance, // Дескриптор копии
приложения nil); // Ничего не передаем в WM_CREATE if h_Wnd = 0
then begin glKillWnd(Fullscreen); // Отменяем все установки,
сделанные нами MessageBox(0, 'Уничтожение окна невозможно', 'Error',
MB_OK or MB_ICONERROR); Result := False; Exit;
end;
// Пробуем получить контекст устройства h_DC :=
GetDC(h_Wnd); if (h_DC = 0) then begin
glKillWnd(Fullscreen); MessageBox(0, 'Ошибка при обращении к
устройству', 'Error', MB_OK or MB_ICONERROR); Result := False;
Exit; end;
// Установки для OpenGL with pfd do
begin nSize := SizeOf(TPIXELFORMATDESCRIPTOR); // Размер этого дескриптора
формата точек nVersion := 1; // Версия этой структуры данных dwFlags
:= PFD_DRAW_TO_WINDOW // Буфер, поддерживающий вывод в окно or
PFD_SUPPORT_OPENGL // Буфер, поддерживающий OpenGL рисование or
PFD_DOUBLEBUFFER; // Поддержка двойной буферизации iPixelType :=
PFD_TYPE_RGBA; // Формат цвета - RGBA cColorBits := PixelDepth; // OpenGL
глубина цвета cDepthBits := 16; // Глубина цвета буфера глубины
end;
// Пробуем найти формат точек, поддерживаемый устройством, что
лучше, чем присваивать свой //формат PixelFormat := ChoosePixelFormat(h_DC,
@pfd); if (PixelFormat = 0) then begin
glKillWnd(Fullscreen); MessageBox(0, 'Невозможно найти соответствующий
формат пиксела', 'Error', MB_OK or MB_ICONERROR); Result :=
False; Exit; end;
// Устанавливаем определенный
устройством формат точек (содержится в PixelFormat) if (not
SetPixelFormat(h_DC, PixelFormat, @pfd)) then begin
glKillWnd(Fullscreen); MessageBox(0, 'Невозможно установить
соответствующий формат пиксела', 'Error', MB_OK or MB_ICONERROR); Result
:= False; Exit; end; // Создаем контекст прорисовки OpenGL
(rendering context) h_RC := wglCreateContext(h_DC); if (h_RC = 0)
then begin glKillWnd(Fullscreen); MessageBox(0, 'Ошибка',
'Error', MB_OK or MB_ICONERROR); Result := False; Exit; end;
if (not wglMakeCurrent(h_DC, h_RC)) then begin
glKillWnd(Fullscreen); MessageBox(0, 'Не могу активировать контекст
OpenGL', 'Error', MB_OK or MB_ICONERROR); Result := False;
Exit; end;
// Инициализируем таймер для подсчета FPS
SetTimer(h_Wnd, FPS_TIMER, FPS_INTERVAL, nil);
// Устанавливаем окно
так, чтобы быть уверенными, что у него наибольший приоритет
ShowWindow(h_Wnd, SW_SHOW); SetForegroundWindow(h_Wnd);
SetFocus(h_Wnd);
// Пробуем изменить размер, чтобы убедиться, что все
работает нормально glResizeWnd(Width, Height); glInit();
Result := True; end;
//////////////////////////////////////// {
Главный цикл обработки сообщений приложения
} //////////////////////////////////////// function WinMain(hInstance :
HINST; hPrevInstance : HINST; lpCmdLine : PChar; nCmdShow : Integer) :
Integer; stdcall; var msg : TMsg; finished : Boolean;
DemoStart, LastTime : DWord; begin finished := False;
//
Выполняем инициализацию приложения (создаем окно): if not glCreateWnd(800,
600, true, 32) then //Объявление разрешения и количество бит begin
Result := 0; Exit; end;
// Главный цикл: while
not finished do begin if (PeekMessage(msg, 0, 0, 0, PM_REMOVE)) then
// Проверяем, было ли сообщение для этого окна begin if (msg.message
= WM_QUIT) then // Выходим, если получили WM_QUIT finished :=
True else begin // Иначе транслируем и диспетчируем полученное
сообщение TranslateMessage(msg); DispatchMessage(msg);
end; end else begin Inc(FPSCount); // Увеличиваем
счетчик FPS
LastTime :=ElapsedTime; ElapsedTime
:=GetTickCount() - DemoStart; // Считаем затраченное время ElapsedTime
:=(LastTime + ElapsedTime) DIV 2; // Усредняем с предыдущим временем для
гладкости вывода счетчика
glDraw(); // Рисуем сцену
SwapBuffers(h_DC); // Показываем сцену if (keys[VK_ESCAPE]) then //
Если пользователь нажал ESC, то выходим finished := True
else ProcessKeys; // Проверяем нажатие любых других клавиш
end; end; glKillWnd(FALSE); Result :=
msg.wParam; end;
begin WinMain( hInstance, hPrevInst, CmdLine,
CmdShow ); end. // Все, проект готов и отлично функционирует!!!
Шаг 4.
Как видите, не такой уж сложный и большой код, а по меркам
серьезного проекта - вообще смешно, но если представить, что только что, своими
руками, не рисуя ни в каком 3D графическом пакете, вы создали куб, даже более,
вы создали полностью самостоятельную, полноэкранную программу, рассчитывающую в
реальном времени координаты куба с использованием библиотеки OpenGl. Не правда
ли, после таких слов повышается самооценка? В дальнейшем, возможно с моей
помощью или своими стараниями, вы можете дополнить этот проект текстурами,
загружаемыми из любых форматов рисунков или, изменить объекты на сцене, в общем,
что вашей душе угодно.
Шаг 5. Завершение.
Хотелось бы подытожить: если после прочтения этой статьи у вас не
возникло желание сделать что-то такое самому или продолжить изучение Delphi в
связке с OpenGL, то вы многое потеряли, но те, кто-все таки заинтересовался,
можете зайти на мой сайт www.mipstudio.ru и скачать исходник этой программы, чтобы
лишний раз не перепечатывать все в компьютер.
Источник: http://www.comprice.ru/
|