Difference between revisions of "OpenGL Tutorial/ru"

From Lazarus wiki
Jump to navigationJump to search
m (Text replace - "delphi>" to "syntaxhighlight>")
Line 38: Line 38:
 
Для того чтобы использовать GLUT, сначала необходимо инициализировать его. Это делается с помощью функции <b>glutInit</b>. Эта функция может разобрать командную строку и установить параметры для главного окна, но она ожидает, что ввод будет в стиле C/C++. Вы можете написать собственную функцию, которая будет преобразовывать ParamCount и ParamStr в стиль C/C++.
 
Для того чтобы использовать GLUT, сначала необходимо инициализировать его. Это делается с помощью функции <b>glutInit</b>. Эта функция может разобрать командную строку и установить параметры для главного окна, но она ожидает, что ввод будет в стиле C/C++. Вы можете написать собственную функцию, которая будет преобразовывать ParamCount и ParamStr в стиль C/C++.
  
<delphi>
+
<syntaxhighlight>
 
  procedure glutInitPascal(ParseCmdLine: Boolean);  
 
  procedure glutInitPascal(ParseCmdLine: Boolean);  
 
  var
 
  var
Line 53: Line 53:
 
   glutInit(@CmdCount, @Cmd);
 
   glutInit(@CmdCount, @Cmd);
 
  end;
 
  end;
</delphi>
+
</syntaxhighlight>
  
 
По сути, вы создаете массив и заполняете его строками из ParamStr. Эта функция также имеет параметр, который указывает то, что передается glutInit -- вся команда или просто имя исполняемого файла.
 
По сути, вы создаете массив и заполняете его строками из ParamStr. Эта функция также имеет параметр, который указывает то, что передается glutInit -- вся команда или просто имя исполняемого файла.

Revision as of 14:48, 24 March 2012

Deutsch (de) English (en) español (es) français (fr) 日本語 (ja) 한국어 (ko) русский (ru) 中文(中国大陆)‎ (zh_CN)

Введение

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

Более подробную информацию о OpenGL можно узнать здесь.

GLUT

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

Подробнее о GLUT вы можете почитать здесь.

Многие ОС поставляются с GLUT, но если у вас её не оказалось, можно поискать её в Интернете с помощью Google.

Версию для Windows можно скачать на www.xmission.com.

Про GLUT unit можно почитать в статье OpenGL.

LCL

The Lazarus Component Library также может быть использована при работе с OpenGL. Lazarus включает в себя TOpenGLControl. Пакет LazOpenGLContext можно найти в lazarus/components/opengl/lazopenglcontext.lpk, примеры использования - в lazarus/examples/openglcontrol/openglcontrol_demo.lpi.

LCL / GLUT

Когда нужно использовать GLUT, а когда LCL?

  • GLUT лучше, если вы хотите сделать все сами.
  • LCL лучше для обычных приложений. Например, 3D-редактор должен иметь несколько окон OpenGL и прочие обыкновенные элементы: кнопки, выпадающие списки, окна, модальные окна и т. д.

GLUT требует специальные библиотеки, а LCL обычно работает "из коробки", но и исполняемый файл имеет больший размер.

Примеры

Первая программа GLUT

Для того чтобы использовать GLUT, сначала необходимо инициализировать его. Это делается с помощью функции glutInit. Эта функция может разобрать командную строку и установить параметры для главного окна, но она ожидает, что ввод будет в стиле C/C++. Вы можете написать собственную функцию, которая будет преобразовывать ParamCount и ParamStr в стиль C/C++.

 procedure glutInitPascal(ParseCmdLine: Boolean); 
 var
   Cmd: array of PChar;
   CmdCount, I: Integer;
 begin
   if ParseCmdLine then
     CmdCount := ParamCount + 1
   else
     CmdCount := 1;
   SetLength(Cmd, CmdCount);
   for I := 0 to CmdCount - 1 do
     Cmd[I] := PChar(ParamStr(I));
   glutInit(@CmdCount, @Cmd);
 end;

По сути, вы создаете массив и заполняете его строками из ParamStr. Эта функция также имеет параметр, который указывает то, что передается glutInit -- вся команда или просто имя исполняемого файла.

ToDo: probably glutInit(@argc, @argv); is enough.


Подробнее о glutInit: http://www.opengl.org/resources/libraries/glut/spec3/node10.html

Дальше нужно создать главное окно. Установите режим отображения для главного окна использованием glutInitDisplayMode. Она принимает только один параметр, который является комбинацией флагов. Обычно GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH -- это всё, что необходимо.

Подробнее о glutInitDisplayMode: http://www.opengl.org/resources/libraries/glut/spec3/node12.html

Положение и размер окна изменяются с помощью glutInitWindowPosition и glutInitWindowSize. Они принимают 2 параметра: X и Y координаты в первой функции, ширина и высота в последней. Вы можете использовать glutGet чтобы найти размер экрана и поместить центр окна.

ToDo: incorrect translate

Подробнее о glutInitWindowPosition, glutInitWindowSize и glutGet: http://www.opengl.org/resources/libraries/glut/spec3/node11.html http://www.opengl.org/documentation/specs/glut/spec3/node70.html

Наконец, окна должны быть созданы с использованием функции glutCreateWindow. Это создаст его с названием, переданным в параметре. Функция возвращает handle окна. Это можно использовать в других функциях, которые требуют handle.

Подробнее о glutCreateWindow: http://www.opengl.org/resources/libraries/glut/spec3/node16.html

Прежде чем программа войдёт в главный цикл, нужно установить некоторые функции для обратной связи. Это нужно для рисования окна, для изменения рзамеров и для получения нажатий клавиш на клавиатуре. Она устаналивается с помощью glutDisplayFunc, glutReshapeFunc и glutKeyboardFunc.

Подробнее: http://www.opengl.org/resources/libraries/glut/spec3/node45.html#SECTION00080000000000000000

Функция рисования может выглядеть следующим образом:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glutSwapBuffers;
end;

Она только очистит окно, зальёт его цветом фона и сбросит ZBuffer (не волнуйтесь о zbuffer... об этом позже).

Функция изменения размера может выглядеть так:

procedure ReSizeGLScene(Width, Height: Integer); cdecl;
begin
  if Height = 0 then
    Height := 1;

  glViewport(0, 0, Width, Height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluPerspective(45, Width / Height, 0.1, 1000);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
end;

С помощью этого кода вы говорите OpenGL, где в окне будет происходить отрисовка и задаете матрицу (матричные функции будут описаны позже).

Ввод с клавиатуры:

procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
begin
  if Key = 27 then
    Halt(0);
end;

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

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

Основная часть программы может выглядеть так:

const 
  AppWidth = 640; 
  AppHeight = 480; 

procedure InitializeGL; 
begin 
  glClearColor(0.18, 0.20, 0.66, 0); 
end; 

var 
  ScreenWidth, ScreenHeight: Integer; 
begin 
  glutInitPascal(True); 
  glutInitDisplayMode(GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH); 
  glutInitWindowSize(AppWidth, AppHeight); 
  ScreenWidth := glutGet(GLUT_SCREEN_WIDTH); 
  ScreenHeight := glutGet(GLUT_SCREEN_HEIGHT); 
  glutInitWindowPosition((ScreenWidth - AppWidth) div 2,
    (ScreenHeight - AppHeight) div 2); 
  glutCreateWindow('OpenGL Tutorial 1'); 

  InitializeGL; 

  glutDisplayFunc(@DrawGLScene); 
  glutReshapeFunc(@ReSizeGLScene); 
  glutKeyboardFunc(@GLKeyboard); 

  glutMainLoop; 
end.

Следующий урок добавит некоторый код, который будет рисовать простую фигуру.

Скачать исходный код или исполняемые файлы linux/windows с Lazarus CCR SourceForge.

Рисование простой фигуры

Примечание: Следующие детали описывают в основном только код OpenGL, поэтому они будут работать как с GLUT, так и с LCL. Вы можете уснать функции GLUT по префиксу 'glu'.

Мы добавим только несколько строк кода и сосредоточим внимание на объяснение некоторых функций OpenGL.

Поясним следующий код.

  .
  .
  .
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluPerspective(45, Width / Height, 0.1, 1000);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
end;

Использование функции glMatrixMode указывает, какую матрицу вы хотите установить. OpenGL работает с 3 матрицами: GL_MODELVIEW: это используется при перемещении вершин в пространстве модели. GL_PROJECTION: это используется для преобразования 3D-координат в 2D-координаты. GL_TEXTURE: это используется для изменения координат текстуры.

Послк того как вы выбрали матрицу, которую хотите изменить, вы можете вызвать функции, которые изменяют значения матриц. glLoadIdentity сбросит матрицу. Since almost all matrix functions multiply current matrix with a generated one, you sometimes need to clear matrix with this function.

Чтобы установить перспективу матрицы, используйте функцию gluPerspective. Четыре параметра задают поле зрения, соотношение сторон, передний и задний план. Это просто.

Теперь можно изменить матрицу... for this time, you just set it to identity.

Код рисования для первой фигуры:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glLoadIdentity;
  glTranslatef(0, 0, -5);

  glBegin(GL_TRIANGLES);
    glColor3f(1, 0, 0);
    glVertex3f(-1, -1, 0);

    glColor3f(0, 1, 0);
    glVertex3f(1, -1, 0);

    glColor3f(0, 0, 1);
    glVertex3f(0, 1, 0);
  glEnd;

  glutSwapBuffers;
end;

Мы использовали функцию glClear. она просто сбрасывает буферы. Следующие две функции пока пропустим.

glBegin обозначает начало рисования в блок. После нее можно начать ввод вершин. Параметр описывает, что мы будем рисовать: GL_POINTS: просто рисуем n точек. Нужно указать n вершин.

GL_LINES: каждая пара вершин задаст линию. Вершины 2n-1 и 2n задают одну линию. При использовании n вершин получится n/2 линий.

GL_LINE_STRIP: рисует отрезки из первой вершины через все в последнюю. Получится n-1 линий.

GL_LINE_LOOP: то же самое, но добавляется отрезок из последней вершины в первую. Получится n линий.

GL_TRIANGLES: каждые три точки зададут треугольник. Получится n/3 треугольников.

GL_TRIANGLE_STRIP: рисует связанные треугольники. Каждая вершина после первых двух будет задавать треугольник. При нечетном n вершины n, n+1 и n+2 зададут треугольник. При четном n - n+1, n и n+2. Получится n-2 треугольника.

GL_TRIANGLE_FAN: тоже рисует связанные треугольники. Каждая вершина после первых двух будет задавать треугольник. Вершины 1, n+1 и n+2 зададут треугольник. Получится n-2 треугольников.

GL_QUADS: каждые четыре вершины рисуют четырехугольник. Получится n/4 четырехугольников.

GL_QUAD_STRIP: рисует связанные четырехугольники. Каждая пара вершин после первой пары будет задавать четырехугольник. Вершины 2n-1, 2n, 2n+2 и 2n+1 задают четырехугольник n. n/2-1 четырехугольников получится. Обратите внимание, что порядок, в котором вершины используются для построения четырехугольника из ленты данных отличается от используемого с независимыми данными.

GL_POLYGON: рисует один выпуклый многоугольник. Вершины определяют этот полигон.

SimpleShapePic1.jpg

Нарисуем один треугольник с помощью GL_TRIANGLES. Функция glVertex3f задаёт положение вершины. Есть еще glVertex* функции. Единственное отличие состоит в количестве и типе параметров, которые они принимают. Например, glVertex2i принимает два параметра (x и y) типа integer. glVertex3f это обычно то, что вам нужно.

Перед glVertex можно установить цвет, материал, текстуру... Для простоты просто укажем цвет для каждой вершины. Цвет задаётся с помощью функции glColor3f. glColor также может принимать разные параметры, аналогично glVertex.

В коде мы можем увидеть, что Z имеет значение 0 для всех вершин. Поскольку передний план установлен как 0.1, треугольник не будет видно. Тут как раз нужны те две функции, которые мы пропустили. Вы уже знаете, что функция glLoadIdentity сбрасывает матрицу. glTranslatef перемещает треугольник на X, Y и Z, которые вы зададите. Если установить Z как -5 (отрицательный Z будет находиться дальше от камеры), то все вершины будут на 5 единиц дальше от камеры, и треугольник будет видно.

После завершения рисования вызовите функцию glEnd. Если хотите, то можете начать новое рисование с помощью glBegin.

Скачать исходный код, исполняемые файлы linux или windows с Lazarus CCR SourceForge.

Использование списков отображения

Иногда вам будет необходимо отобразить один и тот же объект на сцене несколько раз. OpenGL имеет возможность создавать списки отображения, которые также создают рисунок немного быстрее. Создать этот список очень легко. Просто задать вершины и заключить их между glNewList и glEndList.

const
  LIST_OBJECT = 1;

procedure CreateList;
begin
  glNewList(LIST_OBJECT, GL_COMPILE);
    glBegin(GL_TRIANGLE_FAN);
      glColor3f(1, 0, 0);
      glVertex3f(0, 0.5, 0);

      glColor3f(1, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);

      glColor3f(1, 1, 1);
      glVertex3f(0.5, -0.5, 0.5);

      glColor3f(0, 1, 1);
      glVertex3f(0.5, -0.5, -0.5);

      glColor3f(0, 0, 1);
      glVertex3f(-0.5, -0.5, -0.5);

      glColor3f(0, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);
    glEnd;

    glBegin(GL_QUADS);
      glColor3f(1, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);

      glColor3f(1, 1, 1);
      glVertex3f(0.5, -0.5, 0.5);

      glColor3f(0, 1, 1);
      glVertex3f(0.5, -0.5, -0.5);

      glColor3f(0, 0, 1);
      glVertex3f(-0.5, -0.5, -0.5);

      glColor3f(0, 1, 0);
      glVertex3f(-0.5, -0.5, 0.5);
    glEnd;
  glEndList;
end;

glNewList создаёт новый список, и все функции рисования будут записаны, пока не вызовется glEndList. Первый параметр функции glNewList задаёт ID списка. Каждый список определяется по его идентификатору. Если список с заданным ID уже существует, то он очистится перед записью. Если второй параметр GL_COMPILE, то все функции рисования просто записываются, если это GL_COMPILE_AND_EXECUTE, то они записываются и выполняются автоматически.

Функция glIsList поможет вам со списками. Она сообщает, существует ли список с таким ID. Еще одна полезная функция - это glGenLists. Он создает несколько пустых списков отображения. Вы задаёте количество нужных списков и получаете номер первого из них. Если вам нужно создать n списков и получить r ID, получатся списки: r, r+1, r+2,..., r+n-1

Все созданные списки должны быть удалены. Для этого при выходе из программы вы должны сделать следующее:

procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
begin
  if Key = 27 then
  begin
    glDeleteLists(LIST_OBJECT, 1);
    Halt(0);
  end;
end;

glDeleteLists принимает 2 параметра, ID списка отображения и количество списков для удаления. Если ID - r и количество списков для удаления n, удаляются следующие листы: r, r+1, r+2,..., r+n-1

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

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glLoadIdentity;
  glTranslatef(-2, 0, -5);
  glRotatef(40, 1, 0, 1);
  glCallList(LIST_OBJECT);

  glLoadIdentity;
  glTranslatef(1, -2, -10);
  glRotatef(62, 0, 1, 0);
  glCallList(LIST_OBJECT);

  glLoadIdentity;
  glTranslatef(-4, 0.5, -15);
  glRotatef(200, 1, 0, 0);
  glCallList(LIST_OBJECT);

  glutSwapBuffers;
end;
DisplayListsPic1.jpg

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

Иногда нужно рисовать несколько списков одновременно. Это возможно при использовании функции glCallLists. Он принимает количество списков, которые вы хотите нарисовать, тип массива, в котором содержатся ID и массив с идентиыикаторами списков. Тип может принимать одно из следующих значений:

GL_BYTE: список рассматривается как массив байтов, где каждый имеет значение в диапазоне от -128 до 127.

GL_UNSIGNED_BYTE: байты от 0 до 255.

GL_SHORT: список рассматривается как массив двухбайтовых целых чисел от -32768 до 32767.

GL_UNSIGNED_SHORT: short от 0 до 65535.

GL_INT: список рассматривается как массив integer.

GL_UNSIGNED_INT: список рассматривается как массив беззнаковых integer.

GL_FLOAT: список рассматривается как массив из четырехбайтовых вещественных чисел.

GL_2_BYTES: список рассматривается как массив беззнаковых байтов. Каждая пара байт определяет один идентификатор списка. Значение вычисляется как 256 раз первый байт плюс второй байт (беззнаковые значения).

GL_3_BYTES: список рассматривается как массив беззнаковых байтов. Каждая тройка байт определяет один идентификатор списка. Значение вычисляется как 65536 раз первый байт плюс 256 раз второй байт плюс третий байт (беззнаковые значения).

GL_4_BYTES: список рассматривается как массив беззнаковых байтов. Каждая четвёрка байт определяет один идентификатор списка. Значение вычисляется как 16777216 раз первый байт плюс 65536 раз второй байт плюс 256 раз третий байт плюс четвертый байт (беззнаковые значения).

Следующий пример создаст простую планетную систему. Он покажет, как сделать анимацию, но она будет зависеть от количества прорисовки кадров в секунду.

Скачать исходный код, исполняемые файлы linux или windows с Lazarus CCR SourceForge.

Анимация в полноэкраном режиме

Переход в полноэкранный режим в GLUT прост. Измените основную часть программы:

const
  FSMode = '800x600:32@75';

begin
  glutInitPascal(False);
  glutInitDisplayMode(GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH);
  glutGameModeString(FSMode);
  glutEnterGameMode;
  glutSetCursor(GLUT_CURSOR_NONE);

  InitializeGL;

  glutDisplayFunc(@DrawGLScene);
  glutReshapeFunc(@ReSizeGLScene);
  glutKeyboardFunc(@GLKeyboard);
  glutIdleFunc(@DrawGLScene);

  glutMainLoop;
end.

Так как нам не нужно, чтобы GLUT разбирал командную строку, то на этот раз на glutInitPascal стави параметр false. Также не нужен код для создания окна. Функцией glutEnterGameMode GLUT автоматически создаст окно на весь экран. Чтобы указать желаемый режим, через функцию glutGameModeStringпередаём нужные параметры экрана. Format of that string is:

[ширина "x" высота][":" цвет. палитра]["@" Гц]

В строке FSMode мы задали разрешение экрана 800x600, 32-битнкю цветовую палитру и частоту обновления экрана 75Гц. Можно пропустить один из элементов. Если вы пропустите разрешение, GLUT будет пытаться использовать текущее или самый низкое из тех, которые можно установить. То же для других параметров.

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

GLUT_CURSOR_RIGHT_ARROW
GLUT_CURSOR_LEFT_ARROW
GLUT_CURSOR_INFO
GLUT_CURSOR_DESTROY
GLUT_CURSOR_HELP
GLUT_CURSOR_CYCLE
GLUT_CURSOR_SPRAY
GLUT_CURSOR_WAIT
GLUT_CURSOR_TEXT
GLUT_CURSOR_CROSSHAIR
GLUT_CURSOR_UP_DOWN
GLUT_CURSOR_LEFT_RIGHT
GLUT_CURSOR_TOP_SIDE
GLUT_CURSOR_BOTTOM_SIDE
GLUT_CURSOR_LEFT_SIDE
GLUT_CURSOR_RIGHT_SIDE
GLUT_CURSOR_TOP_LEFT_CORNER
GLUT_CURSOR_TOP_RIGHT_CORNER
GLUT_CURSOR_BOTTOM_RIGHT_CORNER
GLUT_CURSOR_BOTTOM_LEFT_CORNER
GLUT_CURSOR_FULL_CROSSHAIR
GLUT_CURSOR_NONE
GLUT_CURSOR_INHERIT

glutIdleFunc определяет функцию для обратной связи, которая вызывается каждый раз, когда программа не имеет сообщений для обработки. Поскольку нам ннужно просто сделать новый кадр, установим на нее функцию DrawGLScene. Некоторые другие учебники показывают, что эта функция должна отправить сообщение для перерисовки, но тогда мы теряем 50-100 кадров, чем при использовании данного метода.

Теперь при выходе из программы нам нужно выйти из полноэкранного режима:

procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
begin
  if Key = 27 then
  begin
    glutLeaveGameMode;
    Halt(0);
  end;
end;

Как вы видите, нужно просто вызвать glutLeaveGameMode.

Теперь надо ввести некоторые новые матричные функции. Напишем их в функции ReSizeGLScene:

procedure ReSizeGLScene(Width, Height: Integer); cdecl;
begin
  .
  .
  .

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
  gluLookAt(0, 20, 25, 0, 0, 0, 0, 1, 0);
end;

gluLookAt создаст матрицу, определяющую, с какой точки вы смотрите на объекты. Первые три параметра задают позицию X, Y и Z камеры. Следующие три параметра задают точку X, Y и Z, в которую будет смотреть камера, и последние три параметра задают "верх" (в какую сторону "смотрит верх" камеры). Как правило, это положительная ось Y.

Теперь сделаем рисование. Поскольку вы установили матрицу с gluLookAt, которая должна использоваться другими объектами, просто использовать функцию glLoadIdentity для сброса матриц не получится. Вы будете сохранять состояние матрицы и восстановите его после того, как объекты нарисуются:

procedure DrawGLScene; cdecl;
var
  T: Single;
begin
  T := glutGet(GLUT_ELAPSED_TIME) / 1000;
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glPushMatrix;
    glRotatef(5 * T, 0, 1, 0);
    glColor3f(1, 1, 0);
    glutWireSphere(2, 20, 20);
  glPopMatrix;

  glPushMatrix;
    glRotatef(90 * T, 0, 1, 0);
    glTranslatef(5, 0, 0);
    glRotatef(40 * T, 0, 1, 0);
    glColor3f(1, 0, 0);
    glutWireSphere(0.6, 10, 10);
  glPopMatrix;

  glPushMatrix;
    glRotatef(60 * T, 0, 1, 0);
    glTranslatef(-3, 0, 9);
    glRotatef(50 * T, 0, 1, 0);
    glColor3f(0, 1, 0);
    glutWireSphere(1, 16, 16);

    glPushMatrix;
      glRotatef(360 * T, 0, 1, 0);
      glTranslatef(-1.7, 0, 0);
      glRotatef(50 * T, 0, 1, 0);
      glColor3f(0, 0, 1);
      glutWireSphere(0.4, 10, 10);
    glPopMatrix;

  glPopMatrix;

  glutSwapBuffers;
end;
FullScreenAnimationPic1.jpg

glPushMatrix и glPopMatrix используются для сохранения состояния матрицы и последующего восстановления. Как вы видите, мы сохраняем её состояние, потом изменяем ее, чтобы установить объект в нужное место и восстанавливаем старое состояние.

Зачем тут нужна переменная T? Она используется для задания скорости анимации. Каждое изменение, которое зависит от времени, умножается на T. Скорость анимации не будет зависеть от частоты кадров. Функция glutGet с параметром GLUT_ELAPSED_TIME возвращает время в миллисекундах, прошедшее от вызова функции glutInit. При делении на 1000 мы получаем время в секундах.

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

Скачать исходный код, исполняемые файлы linux или windows с Lazarus CCR SourceForge.

Освещение

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

Пока он будет иметь немного функций, он нам поможет получить время работы GLUT и дельта (время, прошедшее с предыдущего вызова) и для расчета количества кадров в секунду (frame per second, FPS).

unit utils;

{$mode objfpc}{$H+}

interface

uses
  glut;

function GetTotalTime: Single;
function GetDeltaTime: Single;
procedure FrameRendered(Count: Integer = 1);
function GetFPS: Single;

implementation

var
  OldTime: Integer = 0;
  FPSTime: Integer = 0;
  FPSCount: Integer = 0;

function GetTotalTime: Single;
begin
  Result := glutGet(GLUT_ELAPSED_TIME) / 1000;
end;

function GetDeltaTime: Single;
var
  NewTime: Integer;
begin
  NewTime := glutGet(GLUT_ELAPSED_TIME);
  Result := (NewTime - OldTime) / 1000;
  OldTime := NewTime;
end;

procedure FrameRendered(Count: Integer);
begin
  Inc(FPSCount, Count);
end;

function GetFPS: Single;
var
  NewTime: Integer;
begin
  NewTime := glutGet(GLUT_ELAPSED_TIME);

  Result := FPSCount / ((NewTime - FPSTime) / 1000);

  FPSTime := NewTime;
  FPSCount := 0;
end;

end.

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

Теперь повеселимся с освещением.

OpenGL поддерживает разные типы совещения: окружающее (ambient), диффузное (diffuse), точечное (point), пятно (spot), блеск (specular) и эмисионное (emissive) освещение.

Ambient - что-то вроде Солнца. Когда солнечные лучи через окно попадают в комнату, они рассеиваются и равномерно освещают всю комнату. Ambient освещает все вершины.

Diffuse может быть представлена как параллельные лучи света, идущие издалека. Ими осветятся только те вершины, на которые ориентирован источник света.

Point освещает всё вокруг себя. Это как огненный шар, который выпускает лучи света из себя и освещает все вершины, близкие к нему.

Spot - это как свет от фонарика. Это точечный источник света с малым радиусом конуса света. Все вершины, попадающие в него и близкие к источнику, освещаются.

Так же как и Diffuse, Specular зависит от направления источника света. Разница между ними в том, что specular это как отраженный свет. Освещение зависит от угла между источником света и камерой. С точки зрения смотрящего этот свет выглядит как зеркальное отражение.

Emissive мало отличается от других источиков света. Разница заключается лишь в том, что он освещает тот объект, из которого исходит, и не освещает соседние.

Для примера покажем diffuse light. Later on, some other lights may appear in tutorials :)

Давайте посмотрим, как включить свет в сцене:

const
  DiffuseLight: array[0..3] of GLfloat = (0.8, 0.8, 0.8, 1);

  glEnable(GL_LIGHTING);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, DiffuseLight);
  glEnable(GL_LIGHT0);

Параметры лампочки задаются функцией glLightfv. Он имеет три пааметра: первый задаёт номер лампочки, которую вы хотите изменить (OpenGL поддерживает до 8 лампочек), следующий сообщает OpenGL тип освещения, третий передает параметры лампочки. Но это еще не всё...

Подробнее о glLightfv: http://www.opengl.org//documentation/specs/man_pages/hardcopy/GL/html/gl/light.html

Чтобы видеть свет, нужно установить материал для вершин:

glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
LightPic1.jpg

Вы ожидали чего-то более сложного, не так ли? :) Этот код позволяет использовать функцию glColor, чтобы установить материал на вершины. Используя функцию glEnable и параметр GL_COLOR_MATERIAL, вы можете определить, какие свойства материала будет изменять glColor. glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE) задаёт OpenGL, что glColor changes ambient and diffuse material. Подробнее о материалах будет позже.

Еще одна вещь, которая важна при использовании освещения. Каждая вершина имеет связанную с ней нормаль. Нормаль используется для поиска направления вершины. Вы можете просто использовать стандартную функцию GLUT для рисования куба, так что можно не париться с нормалями. :)

После установки всех параметров ваш куб осветит лампочка :)

Часть текста скопирована из The OpenGL Light Bible

Скачать исходный код, исполняемые файлы linux или windows с Lazarus CCR SourceForge.

Растровые шрифты

Games and programs usually need to write some text on screen. GLUT provides several functions for drawing chars that are platform independent.

First, we'll show how to use default bitmap fonts. Almost all code additions will be made to utils.pas unit.

Since text will be drawn in 2D, we'll need to know width and height of viewport... so, we'll write two functions for that:

function glGetViewportWidth: Integer;
var
  Rect: array[0..3] of Integer;
begin
  glGetIntegerv(GL_VIEWPORT, @Rect);
  Result := Rect[2] - Rect[0];
end;

function glGetViewportHeight: Integer;
var
  Rect: array[0..3] of Integer;
begin
  glGetIntegerv(GL_VIEWPORT, @Rect);
  Result := Rect[3] - Rect[1];
end;

We just get left/right, top/bottom and calculate width/height by subtracting them.

There must be functions for entering and leaving 2D mode:

procedure glEnter2D;
begin
  glMatrixMode(GL_PROJECTION);
  glPushMatrix;
  glLoadIdentity;
  gluOrtho2D(0, glGetViewportWidth, 0, glGetViewportHeight);

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix;
  glLoadIdentity;

  glDisable(GL_DEPTH_TEST);
end;

procedure glLeave2D;
begin
  glMatrixMode(GL_PROJECTION);
  glPopMatrix;
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix;

  glEnable(GL_DEPTH_TEST);
end;

When entering 2D mode, we save current matrices and set 2D matrix using gluOrtho2D function. This way if we draw some thing on 100, 100 it will be drawn on exactly 100 pixels from left edge of window, and 100 pixels form bottom edge (positive Y is up). Also, we disable ZBuffer. This way text won't alter ZBuffer.

Leaving 2D mode just returns old matrices and enable ZBuffer.

Now, we can create function for text drawing:

procedure glWrite(X, Y: GLfloat; Font: Pointer; Text: String);
var
  I: Integer;
begin
  glRasterPos2f(X, Y);
  for I := 1 to Length(Text) do
    glutBitmapCharacter(Font, Integer(Text[I]));
end;

glutBitmapCharacter can draw only one character of selected font. First parameter is desired font (GLUT_BITMAP_9_BY_15, GLUT_BITMAP_8_BY_13, GLUT_BITMAP_TIMES_ROMAN_10, GLUT_BITMAP_TIMES_ROMAN_24, GLUT_BITMAP_HELVETICA_10, GLUT_BITMAP_HELVETICA_12 or GLUT_BITMAP_HELVETICA_18) and other one is character.

Character will be drawn at current raster position. To set desired raster position we call glRasterPos function. glRasterPos can handle different number and types of parameters just like glVertex function. Coordinate specified is transformed by model and projection matrix to get 2D coordinate where new raster position will be. Since we entered 2D mode, X and Y coordinates are actual 2D coordinates where drawing will occur.

This new functions will make text drawing very easy:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glLoadIdentity;
  glTranslatef(0, 0, -5);
  glRotatef(GetTotalTime * 10, 0, 0.5, 0.5);

  glColor3f(1, 0, 0);
  glutSolidCube(2);

  glEnter2D;

  glColor3f(0.2, 0.8 + 0.2 * Sin(GetTotalTime * 5), 0);
  glWrite(20, glGetViewportHeight - 20, GLUT_BITMAP_8_BY_13,
    Format('OpenGL Tutorial :: Bitmap Fonts :: FPS - %.2f FPS', [FPS]));

  glColor3f(1, 1, 1);
  glWrite(50, glGetViewportHeight - 60, GLUT_BITMAP_9_BY_15, 'GLUT_BITMAP_9_BY_15');
  glWrite(50, glGetViewportHeight - 90, GLUT_BITMAP_8_BY_13, 'GLUT_BITMAP_8_BY_13');
  glWrite(50, glGetViewportHeight - 120, GLUT_BITMAP_TIMES_ROMAN_10, 'GLUT_BITMAP_TIMES_ROMAN_10');
  glWrite(50, glGetViewportHeight - 150, GLUT_BITMAP_TIMES_ROMAN_24, 'GLUT_BITMAP_TIMES_ROMAN_24');
  glWrite(50, glGetViewportHeight - 180, GLUT_BITMAP_HELVETICA_10, 'GLUT_BITMAP_HELVETICA_10');
  glWrite(50, glGetViewportHeight - 210, GLUT_BITMAP_HELVETICA_12, 'GLUT_BITMAP_HELVETICA_12');
  glWrite(50, glGetViewportHeight - 240, GLUT_BITMAP_HELVETICA_18, 'GLUT_BITMAP_HELVETICA_18');

  glColor3f(0.5, 0.5, 1);
  glWrite(
    glGetViewportWidth - glutBitmapLength(GLUT_BITMAP_9_BY_15, LazText) - 5,
    10, GLUT_BITMAP_9_BY_15, LazText);

  glLeave2D;

  glutSwapBuffers;

  FrameRendered;
end;
BitmapFontsPic1.jpg

We draw red cube and rotate it, and some text to show how various bitmap fonts look like. glutBitmapLength function is used to find width of string so it could be aligned to right. Code can easily be altered to center text.

Note: See how cube looks without light.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Textures

It's time to use textures :)

This tutorial will show how to draw textured polygons and how to blend textures using multipass technic. Since OpenGL has no builtin mechanism for loading textures, we'll use external library: Vampyre Imaging Library. We'll use just OpenGL helper functions, but you may find this lib handy for some other things to.

Let's get started... we'll create display list for drawing textured rectangle:

procedure CreateList;
begin
  glNewList(LIST_OBJECT, GL_COMPILE);
    glBegin(GL_QUADS);
      glTexCoord2f(1, 0);
      glVertex3f( 2, 2, 0);
      glTexCoord2f(0, 0);
      glVertex3f(-2, 2, 0);
      glTexCoord2f(0, 1);
      glVertex3f(-2,-2, 0);
      glTexCoord2f(1, 1);
      glVertex3f( 2,-2, 0);
    glEnd;
  glEndList;
end;

Notice glTexCoord functions. They are used to specify which part of texture is assigned to vertex. Coordinates defined in this functions are from 0 to 1 (values greater than 1 are allowed but can generate different results). 0 is first pixel and 1 is last pixel. So, 0.5 will be right in the middle of texture.

Texture loading is extremely easy with Vampyre Imaging Library:

var
  Tex1, Tex2: GLuint;

procedure InitializeGL;
begin
  glClearColor(0, 0, 0, 0);
  Tex1 := LoadGLTextureFromFile('ashwood.bmp');
  Tex2 := LoadGLTextureFromFile('Flare.bmp');
  glEnable(GL_TEXTURE_2D);
end;

LoadGLTextureFromFile loads texture from file and returns it's ID. When texture is loaded it is allready setup for rendering. Last line just enables 2D textures.

To draw textured polygon you have to bind texture and setup texture coordinations (texture coordinations are set in display list in this tutorial):

  ...
  glLoadIdentity;
  glTranslatef(-5, 0, -15);
  glBindTexture(GL_TEXTURE_2D, Tex1);
  glCallList(LIST_OBJECT);
  ...

glBindTexture function is used to select texture. When you draw polygins they will have selected texture on them. It's that easy :)

So, using one texture is easy... but how to blend two textures. Basicly you draw polygon once with one texture, setup blending parameters, and draw polygon once more time with other texture. You can blend houndreds of textures this way. Let's see how code for this looks:

  ...
  glLoadIdentity;
  glTranslatef(5, 0, -15);
  glBindTexture(GL_TEXTURE_2D, Tex1);
  glCallList(LIST_OBJECT);

  glEnable(GL_BLEND);
  glBlendFunc(GL_ZERO, GL_SRC_COLOR);
  glLoadIdentity;
  glTranslatef(5, 0, -15);
  glBindTexture(GL_TEXTURE_2D, Tex2);
  glCallList(LIST_OBJECT);
  glDisable(GL_BLEND);
...

As you can see, polygon is drawn first time like we allready know. Before second drawing we enable blending by calling glEnable(GL_BLEND). Blending means that finall pixel color is calculated like this:

DrawingColor * SRCBLEND + BackgroundColor * DESTBLEND

SRCBLEND and DESTBLEND are defined using glBlendFunc function. In this tutorial we set SRCBLEND to GL_ZERO (zero) and DESTBLENT to GL_SRC_COLOR (DrawingColor) and finall color is then:

DrawingColor * 0 + BackgroundColor * DrawingColor
TexturesPic1.jpg

This means that background will get darker when you draw with dark colors... when you draw with white color, background color will not be changed. The result will look like this

Next time, we'll use extensions to show how to use singlepass multitexturing.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Multitexturing (extensions)

When youknow multipass multi texturing, singlepass is very easy. Texturing is separated in stages. First stage setup and draw first texture, second stage draws another one and so on. All you have to do is to setup texture stages and to render object.

Let's see how code looks like:

procedure InitializeGL;
begin
  Load_GL_ARB_multitexture;
  glClearColor(0, 0, 0, 0);
  Tex1 := LoadGLTextureFromFile('Lazarus.bmp');
  Tex2 := LoadGLTextureFromFile('Mask.bmp');
  glActiveTextureARB(GL_TEXTURE0_ARB);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, Tex1);
  glActiveTextureARB(GL_TEXTURE1_ARB);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, Tex2);
end;

First we need load OpenGL extension that will allow us to use multitexture functions. Load_GL_ARB_multitexture will try to load those extensions and will return TRUE if operation was successful.

To select texture stage you want to work on, use glActiveTextureARB function. It takes only one parameter that define which stage you want. After that all texture functions (enabling, disabling, binding, creating...) will affect that stage.

Since we setup every thing in initialization function, all we have to do is to draw object:

procedure DrawGLScene; cdecl;
begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  glLoadIdentity;
  glTranslatef(0, 0, -5);

  glBegin(GL_QUADS);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 0);
    glVertex3f(2.516, 2, 0);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 0);
    glVertex3f(-2.516, 2, 0);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 1);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 1);
    glVertex3f(-2.516,-2, 0);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1, 1);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 1);
    glVertex3f(2.516,-2, 0);
  glEnd;

  glutSwapBuffers;
end;
MultitexturePic1.jpg

As you can see, difference is only in defining texture coordinations. We now use glMultiTexCoord2fARB function that takes texture stage and texture coordinations. Every thing else is unchanged.

Today almost all graphic cards supports at least 2 texture stages. Using singlepass multitexturing is faster than multipass version since you draw objects only once. If hardware supports singlepass multitexturing (Load_GL_ARB_multitexture returns TRUE) use it.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Render to texture

This one will be short. OpenGL can capture current scene to texture so you can use it for texturing other objects (TV screen, mirror or some thing else). Well just render scene to texture and apply it to rotating plane.

First, we must create empty texture which we'll use to capture scene:

procedure SetupRenderTexture;
var
  Data: Pointer;
begin
  GetMem(Data, 256*256*3);
  glGenTextures(1, @RenderTexture);
  glBindTexture(GL_TEXTURE_2D, RenderTexture);
  glTexImage2D(GL_TEXTURE_2D, 0, 3, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, Data);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  FreeMem(Data);
end;

Buffer for 256*256 RGB image is created and it is used to setup 2D texture.

Main part is in drawing function:

procedure DrawGLScene; cdecl;
var
  TotalTime: Single;
begin
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glEnable(GL_LIGHTING);
  glDisable(GL_TEXTURE_2D);
  glViewport(0, 0, 256, 256);

  TotalTime := GetTotalTime;

  glLoadIdentity;
  glTranslatef(0, 0, -5);
  glRotatef(50 * TotalTime, 1, 0, 0);
  glRotatef(100 * TotalTime, 0, 1, 0);
  glRotatef(50 * TotalTime, 0, 0, 1);

  glColor3f(1, 1, 1);
  glutSolidCube(2);

  glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 256, 256, 0);

  glClearColor(0.18, 0.20, 0.66, 0);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glDisable(GL_LIGHTING);
  glEnable(GL_TEXTURE_2D);
  glViewport(0, 0, AppWidth, AppHeight);

  glLoadIdentity;
  glTranslatef(0, 0, -7);
  glRotatef(20 * TotalTime, 1, 0, 0);
  glRotatef(50 * TotalTime, 0, 1, 0);

  glBegin(GL_QUADS);
    glTexCoord2f(1, 0);
    glVertex3f(2, 2, 0);
    glTexCoord2f(0, 0);
    glVertex3f(-2, 2, 0);
    glTexCoord2f(0, 1);
    glVertex3f(-2,-2, 0);
    glTexCoord2f(1, 1);
    glVertex3f(2,-2, 0);
  glEnd;

  glutSwapBuffers;
end;
RenderToTexturePic1.jpg

First, everything is setup for scene that will be captured. Viewport is reduced to 256*256 so it will fit into texture and scene is drawn. glCopyTexImage2D is used to capture scene to currently selected texture.

When we have scene captured to texture, everything can be cleared again, viewport can be returned to original size and final scene is drawn using previous scene as texture.

P.S. Captured texture can be saved using SaveGLTextureToFile function from Vampyre Imaging Library.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.

Vertex array

OpenGL is capable of rendering primitives using data that is stored in buffers insted of calling glVertex. Buffers can be used to define vertex and texture coordinates, and colors (index and RGBA), normals and edge flags.

In this tutorial well use only vertex and color buffers, and we'll show non-indexed and indexed drawing. Non-indexed mode draws buffers as streams. Indexed mode will draw buffer elements in order that is defined in index buffer. But enough talking... let's start coding.

First, let's define some types and constants:

type
  TVertex3f = record
    X, Y, Z: Single;
  end;

  TColor3f = record
   R, G, B: Single;
  end;

  VertexBuffer: array [0..5] of TVertex3f = (
    (X : 1; Y : 1; Z : 0),
    (X : -1; Y : 1; Z : 0),
    (X : -1; Y : -1; Z : 0),
    (X : 1; Y : 1; Z : 0),
    (X : -1; Y : -1; Z : 0),
    (X : 1; Y : -1; Z : 0)
  );
  ColorBuffer: array [0..5] of TColor3f = (
    (R : 1; G : 0; B : 1),
    (R : 0; G : 0; B : 1),
    (R : 0; G : 1; B : 0),
    (R : 1; G : 0; B : 1),
    (R : 0; G : 1; B : 0),
    (R : 1; G : 1; B : 0)
  );

We have two buffers. One for vertex coordinates and one for vertex colors. This 6 vertices defines 2 triangles that forms rectangle.

Drawing primitives using buffers is easy:

  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);
  glVertexPointer(3, GL_FLOAT, 0, @VertexBuffer[0]);
  glColorPointer(3, GL_FLOAT, 0, @ColorBuffer[0]);

  glDrawArrays(GL_TRIANGLES, 0, Length(VertexBuffer));

  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_COLOR_ARRAY);

First we enable buffers we want to use using glEnableClientState function. Than we can select buffers we want to use. Every buffer type has own function for selecting (glColorPointer, glEdgeFlagPointer, glIndexPointer, glNormalPointer, glTexCoordPointer, glVertexPointer). First parameter in those functions defines how many numbers every element contains. For example, let's take vertex buffer. If this parameter is 2 than OpenGL expects that every element in buffer contains x and y coordinate. If this parameter is, for example, 4, than every element should contains x, y, z and w coordinate. Next parameter defines what type of data element contains (GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT or GL_DOUBLE). Next one defines how many bytes are between each element. This way you can have buffer that contains vertex coordinates and some custom data. For arbitrary data type, this parameter can be calculated like this:

type
  TBufferData = record
    DataBefore: TDataBefore;
    Vertex: TVertex;
    DataAfter: TDataAfter;
  end;

Bytes between elements = SizeOf(TDataBefore) + SizeOf(TDataAfter)

Last parameter if pointer to the begginig of buffer.

When buffers are selected we can draw them using glDrawArrays functions. All enabled buffers are used to draw primitives. What kind of polygons are being generated is defined in first parameter (same as in glBegin function). Next two defines subset of buffer which is used for drawing (start and count).

When buffers are not needed you can disable them.

To demonstrate indexed mode, I made some simple mesh class that can load vertex, color and index data from external files:

type
  TMesh = class
  private
    FVertices: array of TVertex3f;
    FColors: array of TColor3f;
    FIndices: array of Integer;
    procedure FreeBuffers;
  public
    constructor Create;
    destructor Destroy; override;
    procedure LoadMesh(FileName: String);
    procedure DrawMesh;
  end;

FVertices will contain data about vertices, FColors data about color and FIndices data about indices when external file is loaded.

First we'll write some code that deals with creation and destruction of class:

procedure TMesh.FreeBuffers;
begin
  FVertices := nil;
  FColors := nil;
  FIndices := nil;
end;

constructor TMesh.Create;
begin
  FreeBuffers;
end;

destructor TMesh.Destroy;
begin
  FreeBuffers;
  inherited Destroy;
end;

File that will contain mesh data is simple text file. First row will contain number of vertices and indices separated by space character. After that row will come rows for every vertex and color. X, Y, Z, R, G and B all separated by space character. In the end, there will be rows for indices... every index number is written in its own row... so, for one triangle, data file will look like this:

3 3
-1 -1 0 1 1 1
1 -1 0 1 1 1
0 1 0 1 1 1
0
1
2

This means that there is 3 vertices and 3 indices defined in file. First vrtex is at -1, -1, 0 and has color 1, 1, 1 and so on. Indices defines that order in which vertices are drawn (in this case vertices are drawn in the same order as they are defined).

Code for loading this data will loke like this:

procedure TMesh.LoadMesh(FileName: String);
var
  MeshFile: TextFile;
  VertexCount, IndexCount: Integer;
  iV, iI: Integer;
begin
  FreeBuffers;

  AssignFile(MeshFile, FileName);
  Reset(MeshFile);

  ReadLn(MeshFile, VertexCount, IndexCount);

  SetLength(FVertices, VertexCount);
  SetLength(FColors, VertexCount);
  SetLength(FIndices, IndexCount);

  for iV := 0 to VertexCount - 1 do
    ReadLn(MeshFile,
      FVertices[iV].X, FVertices[iV].Y, FVertices[iV].Z,
      FColors[iV].R, FColors[iV].G, FColors[iV].B);

  for iI := 0 to IndexCount - 1 do
    ReadLn(MeshFile, FIndices[iI]);

  CloseFile(MeshFile);
end;

After loading data, we have everything for drawing:

procedure TMesh.DrawMesh;
begin
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);
  glVertexPointer(3, GL_FLOAT, 0, @FVertices[0]);
  glColorPointer(3, GL_FLOAT, 0, @FColors[0]);

  glDrawElements(GL_TRIANGLES, Length(FIndices), GL_UNSIGNED_INT, @FIndices[0]);

  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_COLOR_ARRAY);
end;

As you can see, allmost everything is the same as for non-indexed drawing, except function that actually draw polygons. In this case we use glDrawElements function. For this one we specify what kind of polygons we want, how many indices are in index buffer, type of data in index buffer and pointer to the beginning of index buffer.

VertexArrayPic1.jpg

Full source code comes with mesh data file that this class can use to generates rectangle that is identical with one that is drawn using non-indexed mode. Mesh data file looks like this:

4 6
1 1 0 1 0 1
-1 1 0 0 0 1
-1 -1 0 0 1 0
1 -1 0 1 1 0
0
1
2
0
2
3

As you can see, there is data for only 4 vertices and 6 indices. So, first triangle is defined by vertices 0, 1 and 2, and the seccond one by vertices 0, 2 and 3. By using indexed mode we don't have to duplicate vertices.

Download source code, linux executable or windows executable from Lazarus CCR SourceForge.


See also

External links

  • OpenGL Light Tutorial - An OpenGL tutorial outlining how to create and manipulate lights, as well as deal with polygon surface reflections using normals.