Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

OpenGL для начинающих

01.01.2007

*************************************
************ Часть I ****************
*************************************
 
00 Введение
===========
 
Решив разобраться с OpenGL и просмотрев DRBK, я увидел печальную картину -
то что там есть никак не дает пинка в нужном направлении для написания программ,
использующих OpenGL. Ниже я постарался собрать в пучок результаты моих
исследований и получил этот текст и MyOpenGL.pas.
 
Я совсем не претендую на правильность терминов в описании и на то, как надо
писать OpenGL-программы. Но простую OpenGL-программу вы точно сможете написать,
использовав MyOpenGL.pas, после прочтения нижеследующего, как на VCL, так и на
чистом WinAPI.
 
Может кому пригодится для начала - написанное достаточно просто >:-)
 
Были использованы следующие источники
* Исходники Jan Horn (http://www.sulaco.co.za, http://home.global.co.za/~jhorn)
В частности Skyboxes и Гаусово размытие. Второе уже есть в DRKB без имени автора
Работа с графикой и мультимедиа > DerectX, OpenGL > OpenGL - радиальное размытие
(названия функций, основа программы и много другое взяты оттуда).
* Учебник по OpenGL (главы 1.3, 1.5) с www.ru-coding.com.
* MS SDK > OpenGL Programmer's Reference, поставляемый вместе с Delphi
(общая справка)
* модуль EasyGL.pas Данилова Андрея (http://dasoft.land.ru)
(своровал пару идей по оформлению VCL).
* Статья по рисованию вращающего сердечка на OpenGL (не помню где брал).
* Перевод Народного учебника по OpenGL от NeHe (http://nehe.gamedev.net).
Брать тут - http://pmg.org.ru/nehe/nehehtml.zip
(ну очень хороший учебник, хоть и на C; must see).
 
01 Контексты устройства и воспроизведения
=========================================
 
"DC (Device Context - контекст устройства). Это то, на чём мы рисуем,
и в Delphi контекст устройства представлен как TCanvas" (см. 1)

Получить его можно следующим образом

var
 DC: hDC;
...
 DC := GetDC(Form1.Hanle);
или
 DC := Form1.Canvas.Handle;

 
"Графическая система OpenGL, как и любое другое приложение Windows, также нуждается
в ссылке на окно, на котором будет осуществляться воспроизведение - специальной
ссылке на контекст воспроизведения - величина типа HGLRC (Handle openGL Rendering
Context, ссылка на контекст воспроизведения OpenGL). Для получения этого контекста
OpenGL нуждается в величине типа HDC (контекст воспроизведения) окна, на который
будет осуществляться вывод."
Создать его можно следующим образом

 

uses OpenGL;
...
var
  DC: hDC;
  RC: hGLRC;
...
  DC := GetDC(Form1.Handle);   // получаем контекст устройства
  SetDCPixelFormat(DC);        // устанавливаем формат точки (см. 2)
  RC := wglCreateContext(DC);  // создать новый контекст воспроизведения
  wglMakeCurrent(DC, RC);      // устанавливаем его текущим

 
Здесь SetDCPixelFormat(DC) самописная функция, код см. в MyOpenGL.pas.
 
Дополнительно
1. Работа с графикой и мультимедиа > GDI - графика в Delphi
2. Работа с графикой и мультимедиа > DerectX, OpenGL > Работа с OpenGL - Введение
3. Работа с графикой и мультимедиа > DerectX, OpenGL > Работа с OpenGL - Минимальная программа
 
02 Инициализация и завершение работы с OpenGL
=============================================
 
Поскольку часть операций при инициализации зависит от размеров окна и должна повторяться
при каждом изменении размеров окна, то эта часть вынесена в отдельную процедуру, которая
выполняется при инициализации и при изменении размеров окна.

 

{------------------------------------------------------------------}
{  Инициализация OpenGL                                            }
{------------------------------------------------------------------}
procedure glInit(Wnd: hWND);
const
  // Константы, задающие свойства материала фигур
  mat1_amb : array [0..2] of Single = (0.2, 0.2, 0.2);
  mat1_dif : array [0..2] of Single = (0.8, 0.8, 0.0);
  mat1_spec: array [0..2] of Single = (0.6, 0.6, 0.6);
  mat1_shininess = 10;
 
  // Константы для источника света
  light_pos  : array [0..3] of glFloat=(100.0, 100.0, 0.0, 1.0);
  light_amb  : array [0..3] of glFloat=(0.6, 0.6, 0.6, 1.0);
  light_dif  : array [0..3] of glFloat=(1.0, 1.0, 1.0, 1.0);
  light_spec : array [0..3] of glFloat=(1.0, 1.0, 1.0, 1.0);
  light_spot_direction : array [0..3] of glFloat=(1.0, 1.0, 1.0, 1.0);
 
  // Цвет тумана
  fogColor: array [0..3] of GLfloat = (0, 1.0, 0, 1.0);
var
  fogMode: GLint;
begin
  // Инициализация контекста воспроизведения OpenGL
  DC := GetDC(Wnd);            // Получить контекст устройства для окна
  SetDCPixelFormat(DC);        // Установить формата пикселов
  RC := wglCreateContext(DC);  // создать новый контекст воспроизведения
  wglMakeCurrent(DC, RC);      // Установить его текущим
 
  // Свойства материала для режима glEnable(GL_COLOR_MATERIAL)
  glMaterialfv(GL_FRONT, GL_AMBIENT,  @mat1_amb);
  glMaterialfv(GL_FRONT, GL_DIFFUSE,  @mat1_dif);
  glMaterialfv(GL_FRONT, GL_SPECULAR, @mat1_spec);
  glMaterialf (GL_FRONT, GL_SHININESS,mat1_shininess);
 
  // Свойства источника света GL_LIGHT1 для режима glEnable(GL_LIGHTING)
  // Стандартный источник одна команда:  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT1, GL_POSITION,@light_pos);
  glLightfv(GL_LIGHT1, GL_AMBIENT, @light_amb);    // направленность
  glLightfv(GL_LIGHT1, GL_DIFFUSE, @light_dif);    // рассеивание
  glLightfv(GL_LIGHT1, GL_SPECULAR, @light_spec);
  glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, @light_spot_direction);
  // разрешить источник света GL_LIGHT1
  glEnable(GL_LIGHT1);
 
  // Свойства тумана для режима glEnable(GL_FOG)
  fogMode := GL_EXP;                     // GL_EXP2, GL_LINEAR
  glFogi(GL_FOG_MODE, fogMode);          // установить режим
  glFogfv(GL_FOG_COLOR, @fogColor);      // цвет тумана
  glFogf(GL_FOG_DENSITY, 0.002);         // плотность  - 0.2%
  glHint(GL_FOG_HINT, GL_DONT_CARE);     // GL_NICEST, GL_FASTEST.
 
  glClearColor(0.0, 0.0, 0.2, 1.0);      // Установить цвет фона - синий
  glClearDepth(1.0);                     // Очистить буфер глубины
  glDepthFunc(GL_LESS);                  // Тип теста глубины
  glShadeModel(GL_SMOOTH);               // плавное цветовое сглаживание
 
 
  // glEnable(..) - включить режим, glDisable(..) - отключить
  glEnable(GL_LIGHTING);                 // разрешить осещенность
  //glEnable(GL_FOG);                    // разрешить тумана
  glEnable(GL_DEPTH_TEST);               // разрешить тест глубины, с использованием
                                         // функции определенной в glDepthFunc
  glEnable(GL_NORMALIZE);                // разрешить нормали (различение передней
                                         // и задней сторон плоских объектов)
  glEnable(GL_COLOR_MATERIAL);           // разрешить использование материалов на объектах
  glEnable(GL_TEXTURE_2D);               // разрешить наложение текстур
  //glEnable(GL_BLEND);                  // разрешить смешивание (напр. прозрачность)
                                         // не совместимо с glEnable(GL_DEPTH_TEST)
  glLoadTexture('1.bmp', Texture);       // загружаем текстуру
end;
 
 
{------------------------------------------------------------------}
{  Обработчик на изменение размеров окна                           }
{------------------------------------------------------------------}
procedure glOnResize(Width, Height: Integer);
begin
  if (Height = 0) then Height := 1;        // Предупреждаем деление на 0
  glViewport(0, 0, Width, Height);         // Устанавливаем область отображения
                                           // на все окно
  // Настройка матрицы проекции
  glMatrixMode(GL_PROJECTION);             // Выбираем матрицу проекции
  glLoadIdentity();                        // Устанавливаем ее единичной
  // Устанавливаем тип проеции - Ортогональный
  glOrtho(-Width div 2, Width div 2, -Height div 2, Height div 2, -800, 800); 
 
  // Настройка видовой матрицы
  glMatrixMode(GL_MODELVIEW);              // Выбираем матрицу проекции
  glLoadIdentity();                        // Устанавливаем ее единичной
end;

Инициализация OpenGL производится последовательным вызовом этих двух процедур -
сначала gLInit (в ее начале происходит создание контекста воспроизведения, упомянутого
в первой главе), а потом glOnResize.
Отмечу, что включение/выключение режимов можно делать когда угодно, а не только при
инициализации.
 

Завершение работы с OpenGL проводит функция glKill.

{------------------------------------------------------------------}
{  Завершение работы с OpenGL                                      }
{------------------------------------------------------------------}
procedure glKill(Wnd: hWND);
begin
  wglMakeCurrent(DC, 0);
  wglDeleteContext(RC);
  ReleaseDC(Wnd, DC);
end;

 
 
03 Создание и рисование примитивов
==================================
 
Ниже будет описан процесс отрисовки, использующий таймер.
 
Сначала подготавливаем объекты сцены процедурой сферу и треугольник, а
потом командой SwapBuffers(..) выводим сцену на экран.
Для улучшения понимания здесь порезанный вариант MyOpenGL.glDraw.

 

{------------------------------------------------------------------}
{  Создание объектов под отрисовку                                 }
{------------------------------------------------------------------}
procedure glDraw();
var
  Obj: GLUquadricObj;
begin
  // очистка Экрана и буфера глубины
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
 
  ...
 
  // Пример рисования разноцветного треугольника
  glBegin(GL_POLYGON);
    glColor(0.0, 0.5, 0.0, 0.0);
    glVertex3F(-50, -sqrt(3)*50, 0);        // 1-я вершина
    glColor(0.5, 0.0, 0.0, 0.0);
    glVertex3F(-100, -sqrt(3)*100, 0);      // 2-я вершина
    glColor(0.0, 0.0, 0.5, 0.0);
    glVertex3F(-125, -sqrt(3)*100, 0);      // 3-я вершина
  glEnd;
 
  ...
 
  // Пример рисования сферы
  glColor(1.0, 1.0, 1.0, 0.5);           // установить цвет объекта
  Obj := gluNewQuadric;
  gluSphere(Obj, 100, 25, 25);           // создать сферу  R = 100, детализация - 25х25
 
  ...
 
  // Выводим на экран подготовленную сцену
  SwapBuffers(DC);
end;

 
 
По таймеру выполняем

 

  glDraw();

 
gluNewQuadric используется для создания объемных фигур одной командой

 

var
  Obj : GLUquadricObj;
...
  Obj := gluNewQuadric;
  // Режимы отображения фигуры
  gluQuadricDrawStyle(Obj, GLU_FILL);            // GLU_POINT, GLU_LINE, GLU_SILHOUETTE  
  gluQuadricOrientation (Obj, GLU_INSIDE);       // GLU_OUTSIDE
  gluQuadricNormals (Obj, GLU_SMOOTH);           // - нормаль для каждой точки
                                                 // GLU_FLAT - для сегмента
                                                 // GLU_NONE - не строить нормалей 
  gluQuadricTexture(Obj, GL_TRUE);               // - разрешить наложение текущей текстуры
  // Создание фигуры 
  gluSphere(Obj, 100, 25, 25);                   // сфера  R = 100, детализация - 25х25
или
  gluCylinder(Obj, 10, 100, 150, 30, 1);         // цилиндр - R0 = 10, R1 = 100, H = 150, детализация - 30 плоскостей
или
  gluDisk(Obj, 10, 100, 30, 1);                  // Диск - R0 = 10, R1 = 100, детализация - 100
или  
  gluPartialDisk(Obj, 10, 100, 30, 100, 0, 120); // треть диска с параметрами как у верхнего
...
  //Освобождаем память
  gluDeleteQuadric(Obj);

 
 
Quadric-объекты являются надстройкой над конструкцией glBegin-glEnd.
Для glBegin определены следующие константы
(подробнее см. SDK OpenGL)
GL_POINTS - последовательность точек
GL_LINES - линии (пара точек; пара точек)
GL_LINE_STRIP - ломаная (аналог последовательности lineto)
GL_LINE_LOOP - замкнутая
GL_TRIANGLES - треугольник
GL_TRIANGLE_STRIP - объединенная группа треугольников
GL_TRIANGLE_FAN
GL_QUADS - четырехугольник
GL_QUAD_STRIP - объединенная группа четырехугольников
GL_POLYGON - плоский полигон
Так же стоит обратить внимание на glPolygonMode, позволяющий по различному отображать
строемую фигуру (см. код для Quadric)
 
04 Преобразования координат и проекции
======================================

 

Цитата

 
Q: Возникла такая ситуация. Есть несколько объектов. Hyжно повеpнyть
некотоpые из них. Функция glRotate() повоpачивает всю сценy. Сyществyет
ли функция, котоpая повоpачивает только некотоpые (не все) вершины?
 
 
"Для задания различных преобразований объектов сцены в OpenGL используются
операции над матрицами, при этом различают три типа матриц: видовая, проекций
и текстуры. Все они имеют размер 4x4"
 
В каждый момент можем работать только с одним из типов матрицы. Выбрать нужную
можно следующим образом

 

  // mode = 
  // GL_MODELVIEW  - выбрать видовую матрицу
  // GL_PROJECTION - выбрать матрицу проекций
  // GL_TEXTURE    - выбрать матрицу текстур  
  glMatrixMode(<mode>);

 
 
По умолчанию все матрицы нулевые, поэтому при инициализации их заполняем,
разумеется выбрав сначала тип матрицы.
Можно заполнить какой-то конкретной или просто единичной, как в MyOpenGL.glInit.

(Единичная - это та, у которой на диагонали единицы, а остальные нули).

// отличие glLoadMatrixf от glLoadMatrixg заключается в том, как будет

// восприниматься входной массив, определяющий матрицу

// так - <строка1>, <строка2>, <строка3>, <строка4>

// или так - <столбец1>, <столбец2>, <столбец3>, <столбец4>

glLoadMatrixf(<указатель на массив из 16 элементов типа double>);

или

glLoadMatrixg(<указатель на массив из 16 элементов типа double>);

или

glLoadIdentity();        // заполнение единичной

 
 
" Часто нужно сохранить содержимое текущей матрицы для дальнейшего использования,

для чего используют команды"

glPushMatrix();          // сохранить матрицу (в стеке) 

glPopMatrix();           // извлечь матрицу (из стека)

Вспоминаем, что такое стек )
Для GL_MODELVIEW-матриц глубина стека не менее 32, для других типов не менее 2.
 
Далее попробую объяснить для чего нужны различные матрицы и как их использовать.
Итак, задача такова, что надо создать трехмерную фигуру, изменить ее и потом
отобразить на плоскость окна. Реализация OpenGL такова, что при этом имеем четыре
перехода из одних координат в другие.
Координаты объекта (4D) => Видовые координаты (4D) => Усеченные координаты (4D) =>
Нормализованные координаты (3D) => Оконные координаты (2D)
(4D - это точка имеет 4 координаты, первые 3 - ее 3D координаты, последняя
дополнительная составляющая).
Переход от координат объекта к видовым осуществляется при использовании GL_MODELVIEW
матрицы. По видовым координатам вычисляются усеченные координаты, используя GL_PROJECTION
матрицу (название усеченные координаты идет от того, что для любой точки фигуры
(x1, x2, x3, x4), верно -1 <= x_i <= 1, то есть GL_PROJECTION матрица отображает фигуру
в 4D-куб c длиной стороны 2 и центром в (0, 0, 0, 0)). Нормализванные координаты
получаются из усеченных следующим преобразованием
(x1', x2', x3') = (x1/x4, x2/x4, x3/x4).
Размер окна, куда будет отображаться фигура заданная в нормализованных координатах,
определяется функцией glViewPort.
 

Теперь, думаю, код glOnResize достаточно понятен.

  // 1
  glViewport(0, 0, Width, Height);         // Устанавливаем область отображения
                                           // на все окно
  // 2
  // Настройка матрицы проекции
  glMatrixMode(GL_PROJECTION);             // Выбираем матрицу проекции
  glLoadIdentity();                        // Устанавливаем ее единичной
  // Устанавливаем тип проеции - Ортогональный
  glOrtho(-Width div 2, Width div 2, -Height div 2, Height div 2, -800, 800); 
 
  // 3
  // Настройка видовой матрицы
  glMatrixMode(GL_MODELVIEW);              // Выбираем матрицу проекции
  glLoadIdentity();                        // Устанавливаем ее единичной

 
Блоки 1, 2 и 3 можно менять местами. Единственное отличие, если поменять местами 2 и 3, то
что текущая матрица будет не GL_MODELVIEW, а GL_PROJECTION.
 
Примечание: GL_PROJECTION матрицу лучше выбирать так, чтобы
* GL_MODELVIEW была единичной;
* исходные размеры объекта были в пределах отрезка [-1; 1].

 

Цитата

 
A: Для того, чтобы пpименить pазные пpеобpазования к pазным объектам сцены, надо

делать так:

glMatrixMode(GL_MODELVIEW);

glLoadIdentity();

glRotate, glScale, glTranslate // ... --- в общем, всё что нyжно...

 
Затем pисyешь объекты...

После того, как наpисовал, yказываешь новое пpеобpазование:

glLoadIdentity(); glRotate, glScale, glTranslate // ...

 
Снова pисyешь объекты. И так повтоpяешь, сколько надо.
 
 

А теперь все это так, как было бы в коде (напр. в MyOpenGL.glDraw)

  // Указываем, что работать будем с видовой матрицей
  // Если она и так была текущей, то можно не выполнять 
  glMatrixMode(GL_MODELVIEW);
  // Сбрасываем до единичной
  // (Считаем, что GL_PROJECTION подобрана, как указано выше)
  glLoadIdenty(); 
  glTranslatef(100.0, 0.0, 0.0);  // Добавим к матрице перенос на 100 вдоль ОХ
  glPushMatrix;                   // сохраняем матрицу в стек - она нам еще понадобится
  ...
  // Рисуем какую-нить фигуру1
  ...
  glRotate (-90, 0.0 , 1.0, 0.0); // Поворот на -90 градусов вокруг оси OY
  glRotate(60, 1.0, 0.0, 0.0);    // Поворот на 60 градусов вокруг оси OX.   
  ...
  // Рисуем фигуру2
  ...
  glPopMatrix;                    // восстанавливаем матрицу из стека
  ...
  glTranslatef(0.0, 10.0, 0.0);   // двигаем на 10 по оси OX
  ...
  // Рисуем фигуру3
  ...

 
После этого имеем - фигура1 перенесена от центра на 100 по оси ОХ. Фигура2 имеет
матрицу - соответствующую следующим последовательным действиям: перенос на 100, поворот,
поворот. glPopMatrix - возвращает нас к исходной матрице, содержащей только перенос на 100,
потому GL_MODELVIEW-матрица у фигуры3 - это перенос на 100 + перенос на 10 по оси ОY.
Вообще говоря, последовательность неких действий - это домножение исходной матрицы на
матрицы, отвечающие этим действиям. Поскольку умножение матриц некоммуникативно (AB <> BA),
то и при смене чередовании элементов последовательности, получим другой результат.
 
Дополнительно - www.ru-coding.com/ogl_1_3.php
 
04а GL_PROJECTION
=================
 
Считаем, что GL_MODELVIEW матрица единичная.
 
Задание матрицы проекции, которое могло быть напр. в MyOpenGL.glOnResize
Перспективная проекция

 

  glFrustum( - Width div 2, Width div 2, - Height div 2, Height div 2, 100, 300);
  glTranslatef(0, 0, -200);

 
 
Ортогональная проекция

 

  glOrtho(- Width div 2, Width div 2, -Height div 2, Height div 2, -800, 800);

 
 
Примерный смысл для параметров команд glFrustum и glOrtho.
Первые четыре, расписанные как (left, top) и (right, bottom) определяют прямоугольник.
Лучше представить его как окно, через которое мы смотрим на мир (т. е. на группу объектов,
которые были получены при преобразовании их исходных координат их GL_MODELVIEW-матрицами).
Чем оно больше (больше значения параметров), тем больше объекты (по размерам) можем
видеть (по другому - как будто отходим назад от объектов). При симметричных координатах -
смотрим на центр.
Если провести через все углы линии ортогональные прямоугольнику, то высекаемый ими объем
будет тем, что мы видим в мире. Поскольку объекты в бесконечности никого не интересуют
(напр. не видны из-за малого размера при перспективной проекции), то логично ввести
плоскость, параллельную плоскости окна и если объект оказался за ней, то забыть о его
существовании - это сильно упрощает OpenGL расчеты. Логично ввести такую же плоскость и
для объектов сблизи (хотя бы из соображений симметрии ;-)), т. е. если объект ближе чем
она, то он не выводится. Пятый и шестой параметры - как раз и задают расположение второй
и первой плоскости: при перспективной проекции - как растояние от окна до требуемых
плоскостей (следствие: всегда пятый параметр меньше шестого и оба больше 0), при
ортогональной - расстояние от начала координат (поскольку при ортогональной проекции
нет искажении из-за того как далеко от нас находится центр, то можно представить его
где-либо перед нами и соответсвенно задать координаты плоскостей отсечения).
Если мы рисуем объекты вокруг центра координат, напр. так создаются Quadric-объекты, то
для перспективной проекции также надо добавить сдвиг, помещающий центр координат МЕЖДУ
плоскостями отсечения (этот же сдвиг можно сделать и в GL_MODELVIEW матрице).
Для ортогональной этого делать не надо - центр и так где-то перед нами.
Другой способ задавать перспективную проекцию - это воспользоваться gluPerspective.
Примечание: чем меньше расстояние между плоскостями отсечения, тем быстрее все считается.
 
05 Текструры
============
 
Текстуру сначала надо загрузить и получить ее идентификатор (хендл).
перед созданием объекта текстура выбирается по своему идентификатору
(делается текущей) и автоматом прилепляется к объекту (GLUquadricObj).

 

uses 
  OpenGL;
...
var
  Texture: glUint; 
...
// функция для установки текущей текстуры по идентификатору texture
// в openGL.pas ее почему то нет :(, потому импортируем
procedure glBindTexture(target: GLenum; texture: GLuint); stdcall; external opengl32;
...
// Где-то вначале программы, перед использованием glDraw
LoadTexture('1.bmp', Texture);
...
{------------------------------------------------------------------}
{  Создание объектов под отрисовку                                 }
{------------------------------------------------------------------} 
procedure glDraw();
var
  Obj : GLUquadricObj;
begin
  ...
  // 1-ый вариант
  glBindTexture(GL_TEXTURE_2D, Texture); // устанавливаем текущую текстуру Texture
  Obj:=gluNewQuadric;
  gluQuadricTexture(Obj, GL_TRUE);       // при создании объекта наложить текущую текстуру
  gluSphere(Obj, 100, 25, 25);           // создать сферу  R = 100, детализация - 25х25
  ...
или так
  ...
  // 2-ой вариант
  glBindTexture(GL_TEXTURE_2D, Texture); // устанавливаем текущую текстуру Texture
  // Рисуем фигуру, на которую наложится текстура при помощи glTexCoord2f
  glBegin(GL_QUADS);
    glNormal3f( 0.0, 0.0, 1.0);
    // 1-я точка текстуры + 1-я точка объекта
    glTexCoord2f(0.0, 0.0); 
    glVertex3f(-100.0, -100.0,  0.0);  
    // 3-я точка текстуры + 2-я точка объекта
    glTexCoord2f(1.0, 0.0); 
    glVertex3f( 100.0, -100.0,  0.0);
    // 3-я точка текстуры + 3-я точка объекта
    glTexCoord2f(1.0, 1.0); 
    glVertex3f( 100.0,  100.0,  0.0);
    // 1-я точка текстуры + 1-я точка объекта
    glTexCoord2f(0.0, 1.0); 
    glVertex3f(-100.0,  100.0,  0.0);
  glEnd();

При этом должен быть разрешен режим наложения текстур (по умолчанию - отключен)

 

glEnable(GL_TEXTURE_2D);

 
 
Примечание:
Функция MyOpenGL.glLoadTexture поддерживает загрузку только 8 и 24-битных изображений.
 
Дополнительно
www.ru-coding.com/ogl_1_5.php
 
06 Unit MyOpenGL.pas
====================

 

unit MyOpenGL;
 
interface
 
uses windows, OpenGL;
 
procedure glOnResize(Width, Height : Integer);
procedure glInit(Wnd: hWND);
procedure glKill(Wnd: hWND);
procedure glDraw();
procedure glLoadTexture(Filename: String; var Texture: GLuint);
 
implementation
 
procedure glBindTexture(target: GLenum; texture: GLuint); stdcall; external opengl32;
function  gluBuild2DMipmaps(Target: GLenum; Components, Width, Height: GLint; Format, atype: GLenum; Data: Pointer): GLint; stdcall; external glu32;
procedure glGenTextures(n: GLsizei; var textures: GLuint); stdcall; external opengl32;
procedure SetDCPixelFormat(DC: hDC); forward;
 
var
  DC: hDC;                                 // контекст устройства
  RC: hGLRC;                               // контекст воспроизведения
  Texture: GLuint;                         // хэндл текстуры
  Angle: Integer = 0;                      // угол поворота сферы
 
{------------------------------------------------------------------}
{  Обработчик на изменение размеров окна                           }
{------------------------------------------------------------------}
procedure glOnResize(Width, Height: Integer);
begin
  if (Height = 0) then Height := 1;        // Предупреждаем деление на 0
  glViewport(0, 0, Width, Height);         // Устанавливаем область отображения
                                           // на все окно
  // Настройка матрицы проекции
  glMatrixMode(GL_PROJECTION);             // Выбираем матрицу проекции
  glLoadIdentity();                        // Устанавливаем ее единичной
  // Устанавливаем тип проеции - Ортогональный
  glOrtho(-Width div 2, Width div 2, -Height div 2, Height div 2, -800, 800);
 
  // Настройка видовой матрицы
  glMatrixMode(GL_MODELVIEW);              // Выбираем матрицу проекции
  glLoadIdentity();                        // Устанавливаем ее единичной
end;
 
{------------------------------------------------------------------}
{  Инициализация OpenGL                                            }
{------------------------------------------------------------------}
procedure glInit(Wnd: hWND);
const
  // Константы, задающие свойства материала фигур
  mat1_amb : array [0..2] of Single = (0.2, 0.2, 0.2);
  mat1_dif : array [0..2] of Single = (0.8, 0.8, 0.0);
  mat1_spec: array [0..2] of Single = (0.6, 0.6, 0.6);
  mat1_shininess = 10;
 
  // Константы для источника света
  light_pos  : array [0..3] of glFloat = (100.0, 100.0, 0.0, 1.0);
  light_amb  : array [0..3] of glFloat = (0.6, 0.6, 0.6, 1.0);
  light_dif  : array [0..3] of glFloat = (1.0, 1.0, 1.0, 1.0);
  light_spec : array [0..3] of glFloat = (1.0, 1.0, 1.0, 1.0);
  light_spot_direction : array [0..3] of glFloat = (1.0, 1.0, 1.0, 1.0);
 
  // Цвет тумана
  fogColor: array [0..3] of GLfloat = (0, 1.0, 0, 1.0);
var
  fogMode: GLint;
begin
  // Инициализация контекста воспроизведения OpenGL
  DC := GetDC(Wnd);            // Получить контекст устройства для окна
  SetDCPixelFormat(DC);        // Установить формата пикселов
  RC := wglCreateContext(DC);  // создать новый контекст воспроизведения
  wglMakeCurrent(DC,RC);       // Установить его текущим
 
  // Свойства материала для режима glEnable(GL_COLOR_MATERIAL)
  glMaterialfv(GL_FRONT, GL_AMBIENT,  @mat1_amb);
  glMaterialfv(GL_FRONT, GL_DIFFUSE,  @mat1_dif);
  glMaterialfv(GL_FRONT, GL_SPECULAR, @mat1_spec);
  glMaterialf (GL_FRONT, GL_SHININESS,mat1_shininess);
 
  // Свойства источника света GL_LIGHT1 для режима glEnable(GL_LIGHTING)
  // Стандартный источник одна команда:  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT1, GL_POSITION,@light_pos);
  glLightfv(GL_LIGHT1, GL_AMBIENT, @light_amb);    // направленность
  glLightfv(GL_LIGHT1, GL_DIFFUSE, @light_dif);    // рассеивание
  glLightfv(GL_LIGHT1, GL_SPECULAR, @light_spec);
  glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, @light_spot_direction);
  // разрешить источник света GL_LIGHT1
  glEnable(GL_LIGHT1);
 
  // Свойства тумана для режима glEnable(GL_FOG)
  fogMode := GL_EXP;                     // GL_EXP2, GL_LINEAR
  glFogi(GL_FOG_MODE, fogMode);          // установить режим
  glFogfv(GL_FOG_COLOR, @fogColor);      // цвет тумана
  glFogf(GL_FOG_DENSITY, 0.002);         // плотность  - 0.2%
  glHint(GL_FOG_HINT, GL_DONT_CARE);     // GL_NICEST, GL_FASTEST.
 
  glClearColor(0.0, 0.0, 0.2, 1.0);      // Установить цвет фона - синий
  glClearDepth(1.0);                     // Очистить буфер глубины
  glDepthFunc(GL_LESS);                  // Тип теста глубины
  glShadeModel(GL_SMOOTH);               // плавное цветовое сглаживание
 
 
  // glEnable(..) - включить режим, glDisable(..) - отключить
  glEnable(GL_LIGHTING);                 // разрешить осещенность
  //glEnable(GL_FOG);                    // разрешить тумана
  glEnable(GL_DEPTH_TEST);               // разрешить тест глубины, с использованием
                                         // функции определенной в glDepthFunc
  glEnable(GL_NORMALIZE);                // разрешить нормали (различение передней
                                         // и задней сторон плоских объектов)
  glEnable(GL_COLOR_MATERIAL);           // разрешить использование материалов на объектах
  glEnable(GL_TEXTURE_2D);               // разрешить наложение текстур
  //glEnable(GL_BLEND);                  // разрешить смешивание (напр. прозрачность)
                                         // не совместимо с glEnable(GL_DEPTH_TEST)
  glLoadTexture('1.bmp', Texture);       // загружаем текстуру
end;
 
{------------------------------------------------------------------}
{  Завершение работы с OpenGL                                      }
{------------------------------------------------------------------}
procedure glKill(Wnd: hWND);
begin
  wglMakeCurrent(DC, 0);
  wglDeleteContext(RC);
  ReleaseDC(Wnd, DC);
end;
 
 
{------------------------------------------------------------------}
{  Создание объектов под отрисовку                                 }
{------------------------------------------------------------------}
procedure glDraw();
var
  Obj: GLUquadricObj;
begin
  // очистка Экрана и буфера глубины
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
 
  glMatrixMode(GL_MODELVIEW);            // Переключаемся на видовую матрицу
  glLoadIdentity;                        // Сбрасываем все преобразования
 
  // Пример рисования разноцветного треугольника
  glBegin(GL_POLYGON);
  glColor(0.0, 0.5, 0.0, 0.0);            // Цвет 1-ой вершины
  glVertex3F(-50, -sqrt(3)*50, 0);        // 1-я вершина
  glColor(0.5, 0.0, 0.0, 0.0);            // Цвет 2-ой вершины
  glVertex3F(-100, -sqrt(3)*100, 0);      // 2-я вершина
  glColor(0.0, 0.0, 0.5, 0.0);            // Цвет 3-ей вершины
  glVertex3F(-125, -sqrt(3)*100, 0);      // 3-я вершина
  glEnd;
 
  // Следующая фигура будет нарисована повернутой
  // Поворачиваем сферу так, чтобы северный полюс на текстуре
  // смотрел в нужную сторону
  glRotate(-90, 0.0, 1.0, 0.0);
  glRotate(60, 1.0, 0.0, 0.0);
  // здесь поворот сферы вокруг оси
  glRotate(-Angle, 0.0, 0.0, 1.0);
 
  // Пример рисования сферы
  glColor(1.0, 1.0, 1.0, 0.5);           // установить цвет объекта
  glBlendFunc(GL_SRC_ALPHA,GL_ONE);      // Функция смешивания для непрозрачности,
                                         // базирующаяся на значении альфы
                                         // Отрабатывает только при glEnable(GL_BLEND)
  glBindTexture(GL_TEXTURE_2D, Texture); // установить текущую текстуру Texture
  Obj:=gluNewQuadric;
  gluQuadricTexture(Obj, GL_TRUE);       // разрешить наложить текущую текстуру на
                                         // создаваемый объект
  gluSphere(Obj, 100, 25, 25);           // создать сферу  R = 100, детализация - 25х25
  gluDeleteQuadric(Obj);                 // Освобождаем память
 
  Angle := (Angle + 5) mod 360;
 
  // Выводим на экран подготовленную сцену
  SwapBuffers(DC);
end;
 
{------------------------------------------------------------------}
{  Выбор приемлемого формата точки для данного DC                  }
{------------------------------------------------------------------}
procedure SetDCPixelFormat(DC: hDC);
var
  pfd: TPixelFormatDescriptor;
  nPixelFormat: Integer;
begin
  FillChar(pfd, SizeOf(pfd), 0);
  with pfd do
  begin
    nSize      := sizeof(pfd);
    nVersion   := 1;
    dwFlags    := PFD_DRAW_TO_WINDOW or
                  PFD_SUPPORT_OPENGL or
                  PFD_DOUBLEBUFFER;
    iPixelType := PFD_TYPE_RGBA;
    cColorBits := 16;
    cDepthBits := 64;
    iLayerType := PFD_MAIN_PLANE;
   end;
 
  nPixelFormat := ChoosePixelFormat(DC, @pfd);
  SetPixelFormat(DC, nPixelFormat, @pfd);
end;
 
{------------------------------------------------------------------}
{  Загрузка 8 или 24-битного bmp-файла текстуры                    }
{------------------------------------------------------------------}
procedure glLoadTexture(Filename: String; var Texture: GLuint);
type BitmapHeader=
     record
     FH: BitmapFileHeader;
     IH: BitmapInfoHeader;
     end;
var
  ColorTable: array of TRGBQuad;
  Data: array of Byte;
  RGBData: array of TRGBTriple;
  Bmp: BitmapHeader;
  tmp, i, L: Cardinal;
  F: hFile;
begin
  F := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
  if F = INVALID_HANDLE_VALUE then
    begin
      MessageBox(0,PChar('File '+ FileName + ' not found'), 'glLoadTexture',0);
      exit;
    end;
 
  ReadFile(F, Bmp, sizeof(BitmapHeader),tmp, nil );
  L := Bmp.IH.biWidth * Bmp.IH.biHeight;
  SetLength(RGBData, L);
  case Bmp.IH.biBitCount of
    8:
      begin
        SetLength(ColorTable, round(exp(Bmp.IH.biBitCount*Ln(2))));
        ReadFile(F, ColorTable[0], SizeOf(TRGBQuad) * round(exp(Bmp.IH.biBitCount*Ln(2))), tmp, nil);
        SetLength(Data, L);
 
        ReadFile(F, Data[0], SizeOf(Byte) * L, tmp, nil);
        for i := 0 to L - 1 do
          begin
            RGBData[L - i - 1].rgbtRed   := ColorTable[Data[i]].rgbBlue;
            RGBData[L - i - 1].rgbtGreen := ColorTable[Data[i]].rgbGreen;
            RGBData[L - i - 1].rgbtBlue  := ColorTable[Data[i]].rgbRed;
          end;
       end;
    24:
      begin
        SetLength(Data, L * 3);
        ReadFile(F, Data[0], SizeOf(Byte) * L * 3, tmp, nil);
        for i := 0 to L - 1 do
          begin
            RGBData[L - i - 1].rgbtRed   := Data[3 * i];
            RGBData[L - i - 1].rgbtGreen := Data[3 * i + 1];
            RGBData[L - i - 1].rgbtBlue  := Data[3 * i + 2];
          end;
      end;
    else MessageBox(0, 'Format not support', 'glLoadTexture', 0);
    end; { end case}
  CloseHandle(F);
 
  // Далее код из BMP.pas от Jan Horn
  glGenTextures(1, Texture);
  glBindTexture(GL_TEXTURE_2D, Texture);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);  {Texture blends with object background}
  //  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);  {Texture does NOT blend with object background}
  {
    Select a filtering type. BiLinear filtering produces very good results with little performance impact
    GL_NEAREST               - Basic texture (grainy looking texture)
    GL_LINEAR                - BiLinear filtering
    GL_LINEAR_MIPMAP_NEAREST - Basic mipmapped texture
    GL_LINEAR_MIPMAP_LINEAR  - BiLinear Mipmapped texture
  }
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); { only first two can be used }
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); { all of the above can be used }
 
  gluBuild2DMipmaps(GL_TEXTURE_2D, 3, Bmp.IH.biWidth, Bmp.IH.biHeight, GL_RGB, GL_UNSIGNED_BYTE, RGBData);
  // Use when not wanting mipmaps to be built by openGL
  // glTexImage2D(GL_TEXTURE_2D, 0, 3, Bmp.IH.biWidth, Bmp.IH.biHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, RGBData);
end;
 
end.

 
 
 
07 Собранный пример VCL
=======================
 
На форме таймер, в проект включен MyOpenGL.pas. В той же папке лежит 8 или
24-битный bmp-фаил.

 

unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, MyOpenGL;
 
type
  TForm1 = class(TForm)
    Timer1: TTimer;
    procedure FormDestroy(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
var
  Form1: TForm1;
implementation
{$R *.dfm}
 
procedure TForm1.FormDestroy(Sender: TObject);
begin
  glKill(Form1.Handle);
end;
 
procedure TForm1.FormResize(Sender: TObject);
begin
  glOnResize(Form1.ClientWidth,Form1.ClientHeight);
end;
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  glDraw();
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  glInit(Form1.Handle);
  glOnResize(Form1.ClientWidth,Form1.ClientHeight);
end;
 
end.

 
 
08 Собранный пример WinApi
==========================
 
В той же папке лежит 8 или 24-битный bmp-файл.

 

program a;
 
uses
  windows,
  messages,
  MyOpenGL in 'MyOpenGL.pas';
 
var
  MainWnd: hWND;
 
function WindowProc(Wnd: HWND; Msg: Integer; wParam: wParam; lParam:lParam):lResult;stdcall;
begin
  case Msg of
    WM_DESTROY:
      begin
        glKill(MainWnd);
        postquitmessage(0);
        Result := 0;
        exit;
     end;
    WM_TIMER:
      begin
        glDraw();                       // Рисуем сцену
        Result := 0;
      end;
    WM_SIZE:
      begin
        // В lParam содержатся размеры клиентской области ;-)
        glOnResize(loWord(lParam), hiWord(lParam));
        Result := 0;
      end;
    else
      Result := DefWindowProc(Wnd, Msg, wParam, lParam);
  end;
end;
 
var
  Mesg: TMsg;
  wc : TWndClassEx;
begin
  wc.cbSize := sizeof(wc);
  wc.style := CS_HREDRAW or CS_VREDRAW;
  wc.lpfnWndProc := @WindowProc;
  wc.cbClsExtra := 0;
  wc.cbWndExtra := 0;
  wc.hInstance := hInstance;
  wc.hIcon := LoadIcon(0, IDI_WINLOGO);
  wc.hCursor := LoadCursor(0, IDC_ARROW);
  wc.hbrBackground := COLOR_BTNFACE+2;
  wc.lpszMenuName := nil;
  wc.lpszClassName := 'aWnd';
  RegisterClassEx(wc);
 
  // Создаем окно 400х400
  MainWnd := CreateWindowEx (0, 'aWnd', 'MyOpenGL', WS_OVERLAPPEDWINDOW or WS_VISIBLE, 100, 100, 400, 400, 0, 0, hInstance, nil);
 
  // Создаем контекст воспроизведения OpenGL и его параметры
  glInit(MainWnd);
  // Задаем матрицы OpenGL для размера окна 400х400
  glOnResize(400, 400);
 
  // Устанавливаем таймер 10мс для перерисовки сцены
  SetTimer(MainWnd, 1, 10, nil);
 
  while GetMessage(Mesg, 0, 0, 0) do
  begin
    TranslateMessage(Mesg);
    DispatchMessage(Mesg);
  end;
end.

*************************************
************ Часть II ***************
*************************************
 
В этой части будут только указаны исправления кода MyOpenGL.pas, которые
надеюсь без труда сделаете сами, или просто общие сведения.
 
09 Списки
=========
 
"Список - это некоторый набор команд OpenGL, который можно создать один раз,
например, перед началом работы программы, а потом им пользоваться вызывая его.
По логике он аналогичен процедуре (или функции, как хотите). Отличие его от
процедуры в том, что при формировании списка библиотека сохраняет его в
каком-то своем, оптимизированном формате и вызовы списка приводят к значительно
меньшим вычислительным затратам чем вызов функции, делающей тоже самое. Особенно
хорошо это заметно на картах, где операции со списками реализованы аппаратно."
(Взято с http://firststeps.ru/mfc/opengl/r.php?41)
 

Где-то до использования MyOpenGL.glDraw (напр. в MyOpenGL.glInit)

// Глобальная переменная для MyOpenGL.pas
var
  MyList: GLuint;
...
  // указываем, что в списке два набора команд
  MyList := glGenLists(2);
  // Создаем первый набор команд
  // Флаг GL_COMPILE указывает что его надо только создать
  // Использование GL_COMPILE_AND_EXECUTE сразу же еще и выполнило бы этот набор
  // В первом наборе будем рисовать треугольник
  glNewList(MyList, GL_COMPILE);
    glBegin(GL_POLYGON);
      glVertex3F(0.0, 0.0, 0.0);
      glVertex3F(100.0, 0.0, 0.0);
      glVertex3F(50.0, sqrt(3)*50, 0.0);
    glEnd;
  // Указываем что завершили создание набора команд
  glEndList();
 
  // Создаем второй набор команд
  // Во втором наборе к матрице GL_MODELVIEW будет добавляться параллельный
  // перенос на 50 по оси OX и на 1 по оси OZ (это чтобы поверх остального)
  // Поставить переменные значения в перенос нельзя, так как это будет уже 
  // другая матрица. Хотя может я и ошибаюсь.
  glNewList(MyList + 1, GL_COMPILE);
    glTranslatef(50, sqrt(3) * 12.5, 1);
  // Указываем что завершили создание набора команд
  glEndList();

 
 
MyOpenGL.glDraw можно переписать так. В итоге получим, что рисуется одна
неподвижная фигура (зеленая), а другая вращающаяся, выполненая как и первая,

и перенесенная в центр первой и на уровень выше первой (красная).

procedure glDraw();
begin
  // очистка Экрана и буфера глубины
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
 
  // Сбрасываем GL_MODELVIEW до единичной
  glLoadIdentity();
 
  // Устанавливаем зеленый цвет для фигур 
  glColor(0.0, 1.0, 0.0, 1);
  // Создаем фигуру, используя набор команд MyList
  glCallList(MyList);
 
  // Добавляем значение к углу поворота
  Angle := (Angle + 5) mod 360;
  // Вызываем второй набор команд, который хранится по MyList + 1
  // В результате к GL_MODELVIEW добавляется перенос
  glCallList(MyList + 1);
  // Добавим к GL_MODELVIEW-матрице еще и поворот на угол Angle
  glRotate(Angle, 0.0, 0.0, 1.0);
  // Устанавливаем красный цвет для фигур 
  glColor(1.0, 0.0, 0.0, 1);
  // Создаем фигуру, используя набор команд MyList
  // Поскольку GL_MODELVIEW-матрица у нее другая, то и распалагается
  // она уже иначе, чем первая фигура
  glCallList(MyList);
 
  // Выводим на экран подготовленную сцену
  SwapBuffers(DC);
end;

 
Ну и в MyOpenGL.glKill стоит добавить

 

  glDeleteLists(MyList, 2); // удаляем все элементы списка :-)

 
 
Примечание: устоявшееся понятие "список отображениия" - список, в котором все
наборы команд - это рисование фигуры (шаблон). См. Народный учебник, Глава 12.
 
10 Собранный пример WinAPI без таймера для отрисовки
====================================================
 
Взято у Jan Horn (http://www.sulaco.co.za, http://home.global.co.za/~jhorn)
Как обычно, в папке проекта есть MYOpenGL.pas и 1.bmp :-)
 
Идеи таковы:
* пока приложение не обработает всю свою очередь сообщений, перерисовка
выполняться не будет;
* Если нажали кнопку, то в массиве "Статус кнопок" для соответсвующего
элемента выстанавливается значение True, которое скинется после
обработки этого нажатия в ProcessKey. Такая схема позволяет запретить

накопление нажатий.

program a;
 
uses
  windows,
  messages,
  MyOpenGL in 'MyOpenGL.pas';
 
var
  MainWnd: hWND;
  FPSCount: Integer;                // Число FPS
  keys : array [0..255] of Boolean; // Статусы кнопок
 
function IntToStr(Num: Integer) : String;
begin
  Str(Num, result);
end;
 
// Обработка кнопок
procedure ProcessKeys;
begin
  if (keys[VK_UP]) then
  begin
    // Что-то делаем
    //...
    // Сбрасываем нажатие этой кнопки
    keys[VK_UP] := False;
  end;
  if Keys[Ord('H')] then
  begin
    // Что-то делаем
    //...
    // Сбрасываем нажатие этой кнопки
    Keys[Ord('H')] := FALSE;
  end;
end;
 
function WindowProc(Wnd: HWND; Msg: Integer; wParam: wParam; lParam:lParam):lResult;stdcall;
begin
  case Msg of
    WM_DESTROY:
      begin
        glKill(MainWnd);
        postquitmessage(0);
        Result := 0;
        exit;
     end;
    WM_KEYDOWN:
      begin
        keys[wParam] := True;
        Result := 0;
      end;
    WM_KEYUP:
      begin 
        keys[wParam] := False;
        Result := 0;
      end;
    WM_TIMER:
      begin
        FPSCount := Round(FPSCount * 1000/{FPS-интервал - у нас 1с}1000);
        SetWindowText(MainWnd, PChar('FPS: ' + IntToStr(FPSCount)));
        FPSCount := 0;
      end;
    WM_SIZE:
      begin
        glOnResize(loWord(lParam), hiWord(lParam));
        Result := 0;
      end;
    else
      Result := DefWindowProc(Wnd, Msg, wParam, lParam);
  end;
end;
 
var
  Mesg: TMsg;
  wc : TWndClassEx;
  finished: Boolean = False; // Флаг завершения работы OpenGL
begin
  wc.cbSize := sizeof(wc);
  wc.style := CS_HREDRAW or CS_VREDRAW;
  wc.lpfnWndProc := @WindowProc;
  wc.cbClsExtra := 0;
  wc.cbWndExtra := 0;
  wc.hInstance := hInstance;
  wc.hIcon := LoadIcon(0, IDI_WINLOGO);
  wc.hCursor := LoadCursor(0, IDC_ARROW);
  wc.hbrBackground := COLOR_BTNFACE + 2;
  wc.lpszMenuName := nil;
  wc.lpszClassName := 'aWnd';
  RegisterClassEx(wc);
 
  // Создаем окно 400х400
  MainWnd := CreateWindowEx (0, 'aWnd', 'MyOpenGL', WS_OVERLAPPEDWINDOW or WS_VISIBLE, 100, 100, 400, 400, 0, 0, hInstance, nil);
 
  // Создаем контекст воспроизведения OpenGL и его параметры
  glInit(MainWnd);
  // Задаем матрицы OpenGL для размера окна 400х400
  glOnResize(400, 400);
 
  // Устанавливаем таймер на 1с для отображения FPS в заголовке окна
  SetTimer(MainWnd, 1, 1000, nil);
 
  while not finished do
  begin
    // Если очередь сообщений для окна пуста, то проводим
    // расчет и отрисовку сцены, иначе обрабатываем
    // очередь сообщений
    if (PeekMessage(Mesg, 0, 0, 0, PM_REMOVE)) then
    begin
      // Если было получено WM_QUIT, то выход
      if (Mesg.message = WM_QUIT) then
        finished := True
      else
      begin
        TranslateMessage(Mesg);
        DispatchMessage(Mesg);
      end;
    end
    else
    begin
      // Увеличиваем счетчик отрисованных кадров
      Inc(FPSCount);
      // Рисуем-с кадр
      glDraw();
 
      // Если была до этого нажата ESC, то сразу выходим
      if (keys[VK_ESCAPE]) then
        finished := True
      else
        ProcessKeys;
    end;
  end;
 
end.

   
 
 
11 Разное
=========
 

а. Получение текста последней ошибки OpenGL

MessageBox(0, gluErrorString(glGetError), 'OpenGL error', MB_OK);

 
 
б. Восстановление состояния режима
Лампа GL_LIGHT0 будет включена или выключена в зависимости от ее состоянием
перед началом блока
 

 

glPushAttrib(GL_LIGHT0);

...

glEnable(GL_LIGH0);

...

glPopAttrib();

 
 
12 Вывод текста в OpenGL
========================
 
Код получен объединением кода Jan Horn, NeHe (уроки 13 и 14) и DRKB
Работа с графикой и мультимедиа > DerectX, OpenGL > OpenGL: Каким обpазом
выбиpать pазмеp шpифта.
 
Итак, можем выводить текст как растровое изображение и как векторное (буквы
это 3D объекты). Идея такова, что для алфавита подготавливается список
(см 09), а при вводе текст разбивается на буквы и потом последовательно для
каждой буквы печатаемой строки выполняется соответствующий набор команд из
списка.
 
Ниже код, который реализует как растровый, так и как векторный вывод.

(необходимо лишь закоментировать указанные строки).

var
  CharList: GLuint;                              // Вот этот список
  gmf: array [0..255] of GLYPHMETRICSFLOAT;      // массив требуется для векторного
...
{----------------------------------------------------------------}
{  Создание списка букв                                          }
{----------------------------------------------------------------}
procedure glBuildFont;
var
  Font: hFont;
begin
  // Список для 256 букв алфавита
  CharList := glGenLists(256);
  // Создаем шрифт, подробнее см. SDK
  Font := CreateFont(-28, 0, 0, 0, FW_BOLD, 0, 0, 0, RUSSIAN_CHARSET,
          OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY,
          FF_DONTCARE or DEFAULT_PITCH, 'Comic Sans MS');
  // Устанавливаем шрифт для DC
  SelectObject(DC, Font);
 
  // Создаем команды списка.
  // Выбираем один из двух вариантов
 
  // Если используем wglUseFontBitmaps то - это растровое изображение
  if not wglUseFontBitmaps(DC, 0, 256, CharList) then
или
  // Если используем wglUseFontOutlines то - это векторное изображение    
  if not wglUseFontOutlines(DC, 0, 255, CharList, 0, 0.2, WGL_FONT_POLYGONS, @gmf) then
 
 
     MessageBox(0, 'Font not create', 'glBuildFont', MB_OK);
 
  // Удаляем шрифт, так как соответствующий список уже создан
  DeleteObject(Font);
end;
 
{----------------------------------------------------------------}
{  Вывод строки на экран                                         }
{----------------------------------------------------------------}
procedure glPrint(X, Y: Integer; text: String);
begin
  // Расчитывается позиция на экране. Требуется только для растрового
  // вывода. То есть при векторном выводе X и Y gLPrint игнорируются, 
  // поэтому перед выводом надо будет воспользоваться glTranslatef 
  glRasterPos2i(X, Y);
  glPushAttrib(GL_LIST_BIT);
  // Указыывем, что первая буква алфавита рисуется 1-ым набором команд
  glListBase(1);
  // Выводим текст
  glCallLists(length(text), GL_UNSIGNED_BYTE, PChar(text));
  glPopAttrib();
end;
...
// Где-то перед использованием вывода текста, напр. в MyOpenGL.glInit
// после создания контеста воспроизведения RC
  glBuildFont;
...
// Когда работа с текстом будет уже не нужна, то очистим список
// Напр. в MyOpenGL.glKill перед освобождением RC 
  glDeleteLists(CharList, 256);

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

 

  // Устанавливаем цвет букв
  glColor3f(1.0, 0.0, 1.0);
  // Добавляем текст на сцену
  glPrint(-150, 0, 'Даст ист фантастиш');

 
Вывод векторного шрифта несколько сложнее, поскольку команды списка создают
3D объекты с определенными размерами (все буквы маленького размераа типа 0.01)
(для растровых шрифтов велична букв определяется только первым параметром
функции CreateFont в glBuildFont) и поэтому, если у матрица проекций работает
не с такими размерами (у меня она работает с размерами как напр. 100), то надо
увеличить текст glScale. Так же надо подобрать режимы, чтобы текст смотрелся
по-настоящему 3D.

 

// Подбираем нужный режим

glEnable(GL_LIGHT0);                   // Включаем стандартную лампу

glDisable(GL_LIGHT1);                  // Отключаем пользовательскую лампу

glDisable(GL_NORMALIZE);               // Отключаем режим нормалей

glLoadIdentity;                        // Загружаем в GL_MODELVIEW единичную матрицу

glTranslatef(-150, 0.0, 0.0);          // Добавим перенос от центра

glScale(100, 100, 1.0);                // Растягиваем текст на 100 по ОХ и OY

glRotate(45, 1.0, 1.0, 0.0);            // Добавим поворот

glColor(1.0, 0.0, 1.0);                // Установим цвет букв

glPrint(0, 0, 'Даст ист фантастиш');   // Добавим текст на сцену

// Возвращаем все как было

glDisable(GL_LIGHT0);                  // Выключаем стандартную лампу

glEnable(GL_NORMALIZE);                // Включаем режим нормалей

glEnable(GL_LIGHT1);                   // Включаем пользовательскую лампу

 
 
13 Компонент для работы с OpenGL
================================
 
Напр. на http://glscene.org - GL_Scene - версии для Delphi5-7.
Умеет много чего, в частности импорт 3DS файлов.
 
14 Загрузка объектов под отрисовку из файла
===========================================
 
Почти всегда хочется, чтобы в функции создании сцены (у меня это MyOpenGL.glDraw)
объекты под отрисовку загружались с файла. Обычно реализуют свой формат хранения
фигур и функции для работы с ним. Я ленивый, потому предлагаю свой способ -
хранить не объекты, а набор gl-комманд ему соответствующий, который будет

загружаться в список. Например рассмотрим такой фаил.

2

glNewList

glBegin 4

glVertex3f 0 0 0

glVertex3f 100,4 0 0

glVertex3f 50 100 0

glEnd

glEndList

glNewList

glRotate 10 0 1 0

glEndList

 
2 - это то что в списке 2 набора команд. Соответвенно в самой программе это
будет использоваться так (на примере MyOpenGL)
Примечания:
1. дробная часть указывается через запятую!

2. Для glBegin значения констант смотреть в OpenGL.pas

uses MyGLU;
...
var
  List: GLuint;
  Count: Integer;
...
// Где-то до использования создания сцены, напр. в MyOpenGL.glInit
  Count := gluLoadList('1.gls', List);    // моя функция загрузки скрипта из файла из MyGLU.pas
...
// Где-то в функции создания сцены, напр. в MyOpenGL.glDraw
  for i := 0 to Count - 1 do
  begin
    glCallList(List + i);             // Вызываем i-ый набор команд; нумерация с нулевого 
  end;

 
 
По-моему замечательная идея ;-) Возможно у нее уже есть реализация.
А теперь черновой код MyGLU.pas (поддерживаются не все функции gl, да и контроль ошибок
минимальный). Дописывать я его уже наверно не буду; если вам понравилась идея, то
доработать его думаю труда не составит - все прозрачно.

 

unit MyGLU;
 
interface
 
uses windows, OpenGL, SysUtils;
 
// Функция создает список, используя команды из текстового файла
// Возвращаемое значение - число элементов созданного списка  
function gluLoadList(FileName: String; var List: GLuint): Integer;
 
implementation
 
var
  Param: array [1..10] of Extended;
 
// Получение из входной строки S подслов, которые конвертируется в Float
// и помещаются в соответствующий Param[i]. S начинает обрабатываться
// с S[Pos].
function GetParam(S: String; Pos: Integer): Integer;
var
  i, j: Integer;
  Value: String;
begin
  for i := 1 to 10 do Param[i] := 0;
  j := 1;
  Value := '';
  for i := Pos + 1 to Length(S) do
    if (S[i] = ' ')  then
    begin
      Param[j] := StrToFloat(Value);
      if j = 10 then
      begin
        Result := 10;
        MessageBox(0, PChar('Too many params!'), 'GetParam', MB_OK);
        exit;
      end
      else j := j + 1;
      Value := '';
    end
    else Value := Value + S[i];
  if Value <> '' then Param[j] := StrToFloat(Value);
  Result := j;
end;
 
function gluLoadList(FileName: String; var List: GLuint): Integer;
var
  F: TextFile;
  S, Command: String;
  Count, CurrList, i: Integer;
  Obj: GLUquadricObj;
begin
  if not FileExists(FileName) then
    begin
    MessageBox(0, PChar('File '+FileName + ' not found'), 'glLoadList', MB_OK);
    end;
 
  AssignFile(F, FileName);
  Reset(F);
  readln(F, S);
  Count := StrToInt(S);
  CurrList := 0;              // сдвиг; иначе номер набора команд.
  List := glGenLists(Count);
 
  Obj := gluNewQuadric;
 
     // читаем из файла, если видим знакомую команду, то выполняем
     while not EOF(F) do
     begin
       Command := '';
       readln(F, S);
       for i := 1 to Length(S) do
         if S[i] = ' ' then break
           else Command := Command + S[i];
 
       GetParam(S, i);
 
       Command := UpperCase(Command);
 
       if Command = 'GLNEWLIST' then
         glNewList(List + CurrList, GL_COMPILE);
       if Command = 'GLENDLIST' then
       begin
         glEndList;
         CurrList := CurrList + 1;
       end;
 
       if Command = 'GLBEGIN' then
          glBegin(round(Param[1]));
       if Command = 'GLEND' then
          glEnd;
 
       if Command = 'GLROTATE' then
         glRotate(Param[1], Param[2], Param[3], Param[4]);
       if Command = 'GLSCALE' then
         glScale(Param[1], Param[2], Param[3]);
       if Command = 'GLTRANSLATEF' then
         glTranslatef(Param[1], Param[2], Param[3]);
 
 
       if Command = 'GLVERTEX3F' then
         glVertex3f(Param[1], Param[2], Param[3]);
       if Command = 'GLCOLOR' then
         glColor(Param[1], Param[2], Param[3], Param[4]);
 
       if Command = 'GLLOADIDENTITY' then
         glLoadIdentity;
       if Command = 'GLPUSHMATRIX' then
         glPushMatrix;
       if Command = 'GLPOPMATRIX' then
         glPopMatrix;
 
 
       if Command = 'GLUSPHERE' then
         gluSphere(Obj, Param[1], round(Param[2]), round(Param[3]));
       if Command = 'GLUCYLINDER' then
         gluCylinder(Obj, Param[1], Param[2], Param[3], round(Param[4]), round(Param[5]));
       if Command = 'GLUDISK' then
         gluDisk(Obj, Param[1], Param[2], round(Param[3]), round(Param[4]));
 
     end;
  glEndList;
 
  gluDeleteQuadric(Obj);
 
  CloseFile(F);
  Result := Count;
end;
 
end.

 
 
15 Bmp - формат
===============
 
Вообщем то к OpenGL это никакого отношения не имеет, но загрузку текстур все-таки
зачастую приходится производить из него, так что полезно представлять что это такое.
Описание самое простейшее, многое выкинуто, но для общего понимания и загрузки
текстур из bmp-файлов - этого будет достаточно.
 
В начале каждого bmp-файла идет служебная информация - общая информация о файле
TBitmapFileHeader и потом общая информация о bmp-изображении - TBitmapInfoHeader.

Определения типов из windows.pas (комментарии мои)

type
  tagBITMAPFILEHEADER = packed record
    bfType: Word;                         // тип файла - для Bmp = BM ~ $42$4D
    bfSize: DWORD;                        // размер файла в байтах
    bfReserved1: Word;                    // зарезервировано
    bfReserved2: Word;                    // зарезервировано
    bfOffBits: DWORD;                     // номер байта; со следующего начнется "изображение"
  end;
  TBitmapFileHeader = tagBITMAPFILEHEADER;
 
  tagBITMAPINFOHEADER = packed record
    biSize: DWORD;                        // размер структуры BitmapInfoHeader, байт
    biWidth: Longint;                     // ширина изображения
    biHeight: Longint;                    // высота изображения; отрицательна, если изображение
                                          // надо перевернуть
    biPlanes: Word;                       // число плоскостей изображения, всегда 1
    biBitCount: Word;                     // число бит на точку; ОЧЕНЬ ВАЖНЫЙ ПАРАМЕТР
    biCompression: DWORD;                 // Флаг компрессии - обычно BI_RGB (=0) 
    biSizeImage: DWORD;                   // Размер изображения в байтах
    biXPelsPerMeter: Longint;             // разрешение точек на метр по OX И по OY
    biYPelsPerMeter: Longint;
    biClrUsed: DWORD;                     // используемое количество цветов
    biClrImportant: DWORD;                // Обычно = biClrUsed
  end;
  TBitmapInfoHeader = tagBITMAPINFOHEADER;
  end;
   
Код поясняющий это

 

// Для простоты определим тип, состоящий из двух нужных нам
type 
  TBitmapHeader = record
    FH: TBitmapFileHeader;
    IH: TBitmapInfoHeader;
  end;
...
var
  Bmp: TBitmapHeader;
  tmp: Cardinal;
  F: hFile;
...
  // Для доступа к файлу, определяемого FileName, я использую WinAPI-функцию
  // Примерный смысл параметров, думаю, ясен
  F := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
  // Читаем данные из файла F, данные помещаются в Bmp, количество байт которых
  // хотим прочитать - это третий параметр, количество прочитанных байт будет
  // помещено в tmp
  ReadFile(F, Bmp, sizeof(TBitmapHeader), tmp, nil);
  ...
  //
  // Ширина изображения для примера
  MessageBox(0, PChar(IntToStr(Bmp.IH.biWidth)), 'Width', MB_OK);
  ...
  // Закрываем файл
  CloseHandle(F);

 
После этого заголовка (в байтах это Bmp.FH.bmOffBits) хранится изображение.
Следует различать две группы bmp-изображений 1, 4, 8-битные и 16, 24, 32-битные.
Для первой группы сначала идет палитра - то есть перечисение цветов в формате
Red-Green-Blue-Alpha (TRGBQuad), содержащая biClrUsed-цветов (обычно это
равно 2 ^ biBitCount), потом идут точки изображения - каждая точка пишется в
соответсвующее количество бит (1, 4, 8) - это просто номер цвета из палитры.
Для второй группы палитры нет, а сразу идут точки - для 24 и 32-битных
изображений - в формате RGB и RGBA (по байту на составляющую, что и дает
соответствующую битность); для 16-битных несколько хитрее, потому опущу описание.
 
При загрузке текстуры передаются - ширина, высота изображения и массив RGB точек.
Для 24- и 32- битных bmp файлов - массив получается элементарно. Для 1, 4 и 8-
битных необходимо этот массив формировать на основе палитры.
 
Примечания
* Точки изображения пробегаются с нижнего левого угла.
* Цвета в BMp-файле зранятся в последовательности BGR, потому при загрузке в
тестуру надо менять R и B цвета местами.
* На самом деле, есть еще такое понятие как байтовое выравнивание в точках,
при ширине изображения кратной 4 (для текстур это разумеется верно) об этом
можно не беспокоится.
 
Пример загрузки 8- и 24-битных изображений можно посмотреть в
MyOpenGL.glLoadTexture.

 

Взято из https://forum.sources.ru

Автор: Zoobastik