16+
ComputerPrice
НА ГЛАВНУЮ СТАТЬИ НОВОСТИ О НАС




Яндекс цитирования


Версия для печати

Модуль поиска не установлен.

Delphi и OpenGl: братья на век

13.05.2003

Георгий Чухин <info@mipstudio.ru>

"Истина где-то рядом...."

Именно такими словами я начну свою статью про 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 и скачать исходник этой программы, чтобы лишний раз не перепечатывать все в компьютер.



статьи
статьи
 / 
новости
новости
 / 
контакты
контакты