Difference between revisions of "OpenGL Tutorial/ru"

From Lazarus wiki
Jump to navigationJump to search
 
(33 intermediate revisions by 5 users not shown)
Line 9: Line 9:
 
==GLUT==
 
==GLUT==
  
GLUT - библиотека для использования OpenGL, ревлизующий простой оконный интерфейс API. GLUT делает изучение OpenGL более легким. Это кроссплатформенный API, так что вы можете написать программу OpenGL, которая будет работать на  большинстве операционных систем.
+
GLUT - библиотека для использования OpenGL, реализующий простой оконный интерфейс API. GLUT делает изучение OpenGL более легким. Это кроссплатформенный API, так что вы можете написать программу OpenGL, которая будет работать на  большинстве операционных систем.
  
 
Подробнее о GLUT вы можете почитать [http://www.opengl.org/resources/libraries/glut/ здесь].
 
Подробнее о GLUT вы можете почитать [http://www.opengl.org/resources/libraries/glut/ здесь].
Line 18: Line 18:
  
 
Про GLUT unit можно почитать в статье [[OpenGL]].
 
Про GLUT unit можно почитать в статье [[OpenGL]].
 +
 +
==GLFW==
 +
 +
См. http://www.glfw.org/
  
 
==LCL==
 
==LCL==
Line 23: Line 27:
 
The Lazarus Component Library также может быть использована при работе с OpenGL. Lazarus включает в себя TOpenGLControl. Пакет LazOpenGLContext можно найти в lazarus/components/opengl/lazopenglcontext.lpk, примеры использования - в lazarus/examples/openglcontrol/openglcontrol_demo.lpi.
 
The Lazarus Component Library также может быть использована при работе с OpenGL. Lazarus включает в себя TOpenGLControl. Пакет LazOpenGLContext можно найти в lazarus/components/opengl/lazopenglcontext.lpk, примеры использования - в lazarus/examples/openglcontrol/openglcontrol_demo.lpi.
  
==LCL / GLUT==
+
==LCL / GLFW / GLUT==
  
 
Когда нужно использовать GLUT, а когда LCL?
 
Когда нужно использовать GLUT, а когда LCL?
  
* GLUT лучше, если вы хотите сделать все сами.
+
* GLUT лучше применим, если вы хотите сделать все сами.
* LCL лучше для обычных приложений. Например, 3D-редактор должен иметь несколько окон OpenGL и прочие обыкновенные элементы: кнопки, выпадающие списки, окна, модальные окна и т. д.
+
* LCL лучше использовать для обычных приложений. Например, 3D-редактор должен иметь несколько окон OpenGL и прочие обыкновенные элементы: кнопки, выпадающие списки, окна, модальные окна и т. д.
  
GLUT требует специальные библиотеки, а LCL обычно работает "из коробки", но и исполняемый файл имеет больший размер.
+
Часть OpenGL почти такая же. Для GLUT нужны dll под windows, а LCL обычно работает "из коробки", но и исполняемый файл имеет больший размер.
  
 
=Примеры=
 
=Примеры=
  
==Первая программа GLUT==
+
==Ваша первая LCL-программа==
 +
 
 +
Использование LCL, как правило, является самым простым способом доступа к OpenGL с помощью Lazarus. Поскольку GLUT устарела, использование LCL - это хорошая идея для нового проекта OpenGL Lazarus. Большинство примеров кода, описанных ниже для GLUT, легко перевести в код LCL, хотя вам нужно будет найти эквиваленты для функций с префиксом 'glut', например, вместо "glutSwapBuffers" мы будем использовать LCL-свойство "SwapBuffers" для отображения нашего рендеринга. Единственная замечательная функция, которую обеспечивает GLUT и которую трудно сделать с LCL - это отображение текста на экране (см. Раздел «Растровые шрифты» GLUT ниже). Однако, поскольку это ваша первая LCL-программа, мы немного упростим ее, не отображая текст.
 +
 
 +
Lazarus поставляется с примером программы OpenGL, вы можете найти ее в папке ''Lazarus/Examples/openglcontrol''. Этот пример демонстрирует множество мощных функций для создания анимированного изображения OpenGL. Тем не менее, это также относительно сложная программа. Ниже приведен минимальный проект Lazarus, который имитирует некоторые функции, описанные в примерах GLUT, описанных ниже. Чтобы создать его, запустите Lazarus и выберите Project/NewProject, чтобы создать новое приложение. Выберите пункт меню Project/ProjectInspector, нажмите кнопку 'Add..' (Добавить ...), перейдите к 'New Requirement' (Новое требование) и пакету «LazOpenGLContext». Затем вставьте приведенный ниже код в ваш 'unit1.pas'. Затем щелкните форму и на вкладке событий инспектора объектов свяжите событие «OnCreate» с функцией «FormCreate». Теперь вы сможете запустить новое приложение, выбрав пункт меню Run/Run (Выполнить/запустить).
 +
 
 +
Код создает новую панель OpenGL, которая заполняет форму. OpenGL рисует простой треугольник на форме. Обратите внимание, что при запуске приложения вы можете изменить размер формы, и треугольник, чтобы заполнить форму, масштабируется пропорционально.
 +
 
 +
<syntaxhighlight lang=pascal>
 +
unit Unit1;
 +
 
 +
{$mode objfpc}{$H+}
 +
 
 +
interface
 +
 
 +
uses
 +
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, OpenGLContext, gl;
 +
 
 +
type
 +
  TForm1 = class(TForm)
 +
    procedure FormCreate(Sender: TObject);
 +
    procedure GLboxPaint(Sender: TObject);
 +
  private
 +
    GLBox: TOpenGLControl;
 +
  public
 +
  end;
 +
 
 +
var
 +
  Form1: TForm1;
 +
 
 +
implementation
 +
 
 +
{$R *.lfm}
 +
 
 +
procedure TForm1.GLboxPaint(Sender: TObject);
 +
begin
 +
  glClearColor(0.27, 0.53, 0.71, 1.0); // Задаем синий фон
 +
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
 +
  glLoadIdentity;
 +
  glBegin(GL_TRIANGLES);
 +
    glColor3f(1, 0, 0);
 +
    glVertex3f( 0.0, 1.0, 0.0);
 +
    glColor3f(0, 1, 0);
 +
    glVertex3f(-1.0,-1.0, 0.0);
 +
    glColor3f(0, 0, 1);
 +
    glVertex3f( 1.0,-1.0, 0.0);
 +
  glEnd;
 +
  GLbox.SwapBuffers;
 +
end;
 +
 
 +
procedure TForm1.FormCreate(Sender: TObject);
 +
begin
 +
  GLbox := TOpenGLControl.Create(Self);
 +
  GLbox.AutoResizeViewport := true;
 +
  GLBox.Parent            := Self;
 +
  GLBox.MultiSampling      := 4;
 +
  GLBox.Align              := alClient;
 +
  GLBox.OnPaint            := @GLboxPaint; // для "mode delphi" должно быть "GLBox.OnPaint := GLboxPaint"
 +
  GLBox.invalidate;
 +
end;
 +
 
 +
end.
 +
</syntaxhighlight>
 +
 
 +
==Ваша первая GLUT-программа ==
  
 
Для того чтобы использовать GLUT, сначала необходимо инициализировать его. Это делается с помощью функции <b>glutInit</b>. Эта функция может разобрать командную строку и установить параметры для главного окна, но она ожидает, что ввод будет в стиле C/C++. Вы можете написать собственную функцию, которая будет преобразовывать ParamCount и ParamStr в стиль C/C++.
 
Для того чтобы использовать GLUT, сначала необходимо инициализировать его. Это делается с помощью функции <b>glutInit</b>. Эта функция может разобрать командную строку и установить параметры для главного окна, но она ожидает, что ввод будет в стиле C/C++. Вы можете написать собственную функцию, которая будет преобразовывать ParamCount и ParamStr в стиль C/C++.
  
<delphi>
+
<syntaxhighlight lang=pascal>
 
  procedure glutInitPascal(ParseCmdLine: Boolean);  
 
  procedure glutInitPascal(ParseCmdLine: Boolean);  
 
  var
 
  var
Line 53: Line 121:
 
   glutInit(@CmdCount, @Cmd);
 
   glutInit(@CmdCount, @Cmd);
 
  end;
 
  end;
</delphi>
+
</syntaxhighlight>
  
 
По сути, вы создаете массив и заполняете его строками из ParamStr. Эта функция также имеет параметр, который указывает то, что передается glutInit -- вся команда или просто имя исполняемого файла.
 
По сути, вы создаете массив и заполняете его строками из ParamStr. Эта функция также имеет параметр, который указывает то, что передается glutInit -- вся команда или просто имя исполняемого файла.
  
ToDo: probably '''glutInit(@argc, @argv);''' is enough.
+
ToDo: вероятно достаточно '''glutInit(@argc, @argv);'''.
  
  
Line 63: Line 131:
  
 
Дальше нужно создать главное окно.
 
Дальше нужно создать главное окно.
Установите режим отображения для главного окна использованием <b>glutInitDisplayMode</b>. Она принимает только один параметр, который является комбинацией флагов. Обычно <b>GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH</b> -- это всё, что необходимо.
+
Установите режим отображения для главного окна использованием <code>glutInitDisplayMode</code>. Она принимает только один параметр, который является комбинацией флагов. Обычно <code>GLUT_DOUBLE or GLUT_RGB or GLUT_DEPTH</code> - это всё, что необходимо.
  
 
Подробнее о glutInitDisplayMode: http://www.opengl.org/resources/libraries/glut/spec3/node12.html  
 
Подробнее о glutInitDisplayMode: http://www.opengl.org/resources/libraries/glut/spec3/node12.html  
  
Положение и размер окна изменяются с помощью <b>glutInitWindowPosition</b> и <b>glutInitWindowSize</b>. Они принимают 2 параметра: X и Y координаты в первой функции, ширина и высота в последней. Вы можете использовать <b>glutGet</b> чтобы найти размер экрана и поместить центр окна.  
+
Положение и размер окна изменяются с помощью <code>glutInitWindowPosition</code> и <code>glutInitWindowSize</code>. Они принимают 2 параметра: X и Y координаты в первой функции, ширина и высота в последней. Вы можете использовать <code>glutGet</code>, чтобы найти размер экрана и поместить центр окна.  
  
ToDo: incorrect translate
+
ToDo: неверный перевод
  
Подробнее о glutInitWindowPosition, glutInitWindowSize и glutGet: http://www.opengl.org/resources/libraries/glut/spec3/node11.html  
+
Подробнее о <code>glutInitWindowPosition</code>, <code>glutInitWindowSize</code> и <code>glutGet</code>: http://www.opengl.org/resources/libraries/glut/spec3/node11.html  
 
http://www.opengl.org/documentation/specs/glut/spec3/node70.html  
 
http://www.opengl.org/documentation/specs/glut/spec3/node70.html  
  
Line 78: Line 146:
 
Подробнее о glutCreateWindow: http://www.opengl.org/resources/libraries/glut/spec3/node16.html  
 
Подробнее о glutCreateWindow: http://www.opengl.org/resources/libraries/glut/spec3/node16.html  
  
Прежде чем программа войдёт в главный цикл, нужно установить некоторые функции для обратной связи. Это нужно для рисования окна, для изменения рзамеров и для получения нажатий клавиш на клавиатуре. Она устаналивается с помощью <b>glutDisplayFunc</b>, <b>glutReshapeFunc</b> и <b>glutKeyboardFunc</b>.
+
Прежде чем программа войдёт в главный цикл, нужно установить некоторые функции для обратной связи. Это нужно для рисования окна, для изменения размеров и для получения нажатий клавиш на клавиатуре. Она устаналивается с помощью <code>glutDisplayFunc</code>, <code>glutReshapeFunc</code> и <code>glutKeyboardFunc</code>.
  
 
Подробнее: http://www.opengl.org/resources/libraries/glut/spec3/node45.html#SECTION00080000000000000000  
 
Подробнее: http://www.opengl.org/resources/libraries/glut/spec3/node45.html#SECTION00080000000000000000  
  
 
Функция рисования может выглядеть следующим образом:
 
Функция рисования может выглядеть следующим образом:
 
+
<syntaxhighlight lang=pascal>
 
  procedure DrawGLScene; cdecl;
 
  procedure DrawGLScene; cdecl;
 
  begin
 
  begin
Line 89: Line 157:
 
   glutSwapBuffers;
 
   glutSwapBuffers;
 
  end;
 
  end;
 +
</syntaxhighlight>
  
 
Она только очистит окно, зальёт его цветом фона и сбросит ZBuffer (не волнуйтесь о zbuffer... об этом позже).
 
Она только очистит окно, зальёт его цветом фона и сбросит ZBuffer (не волнуйтесь о zbuffer... об этом позже).
  
 
Функция изменения размера может выглядеть так:  
 
Функция изменения размера может выглядеть так:  
 
+
<syntaxhighlight lang=pascal>
 
  procedure ReSizeGLScene(Width, Height: Integer); cdecl;
 
  procedure ReSizeGLScene(Width, Height: Integer); cdecl;
 
  begin
 
  begin
Line 107: Line 176:
 
   glLoadIdentity;
 
   glLoadIdentity;
 
  end;
 
  end;
 +
</syntaxhighlight>
  
С помощью этого кода вы говорите OpenGL, where in the window it should draw and set matrices to the desired values (matrix functions will be explained later).
+
С помощью этого кода вы говорите OpenGL, где в окне будет происходить отрисовка и задаете матрицу (матричные функции будут описаны позже).
 
 
ToDo: translate
 
  
 
Ввод с клавиатуры:
 
Ввод с клавиатуры:
 
+
<syntaxhighlight lang=pascal>
 
  procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
 
  procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
 
  begin
 
  begin
Line 119: Line 187:
 
     Halt(0);
 
     Halt(0);
 
  end;
 
  end;
 
+
</syntaxhighlight>
 
Эта функция завершит работу программы при нажатии клавиши ESC. Это единственный способ остановить программу. Если вы закроете окно по-другому, оно исчезнет, но сама программа продолжит работать бесконечно долго.
 
Эта функция завершит работу программы при нажатии клавиши ESC. Это единственный способ остановить программу. Если вы закроете окно по-другому, оно исчезнет, но сама программа продолжит работать бесконечно долго.
  
Для запуска основного цикла вызовите <b>glutMainLoop</b>. Это запустит бесконечный цикл, в котором содержатся все ваши функции для обратной связи.
+
Для запуска основного цикла вызовите <code>glutMainLoop</code>. Это запустит бесконечный цикл, в котором содержатся все ваши функции для обратной связи.
  
 
Основная часть программы может выглядеть так:
 
Основная часть программы может выглядеть так:
 
+
<syntaxhighlight lang=pascal>
 
  const  
 
  const  
 
   AppWidth = 640;  
 
   AppWidth = 640;  
Line 155: Line 223:
 
   glutMainLoop;  
 
   glutMainLoop;  
 
  end.
 
  end.
 
+
</syntaxhighlight>
 
Следующий урок добавит некоторый код, который будет рисовать простую фигуру.
 
Следующий урок добавит некоторый код, который будет рисовать простую фигуру.
  
Line 162: Line 230:
 
==Рисование простой фигуры==
 
==Рисование простой фигуры==
  
'''Примечание:''' Следующие детали описывают в основном только код OpenGL, поэтому они будут работать как с GLUT, так и с LCL. Вы можете уснать функции GLUT по префиксу 'glu'.
+
{{Note| Следующие детали описывают в основном только код OpenGL, поэтому они будут работать как с GLUT, так и с LCL. Вы можете узнать функции GLUT по префиксу 'glu'.}}
  
 
Мы добавим только несколько строк кода и сосредоточим внимание на объяснение некоторых функций OpenGL.
 
Мы добавим только несколько строк кода и сосредоточим внимание на объяснение некоторых функций OpenGL.
  
 
Поясним следующий код.
 
Поясним следующий код.
   
+
<syntaxhighlight lang=pascal>
  .
+
  begin
  .
 
  .
 
 
   glMatrixMode(GL_PROJECTION);
 
   glMatrixMode(GL_PROJECTION);
 
   glLoadIdentity;
 
   glLoadIdentity;
Line 178: Line 244:
 
   glLoadIdentity;
 
   glLoadIdentity;
 
  end;
 
  end;
Использование функции <b>glMatrixMode</b> указывает, какую матрицу вы хотите установить. OpenGL работает с 3 матрицами:
+
</syntaxhighlight>
<b>GL_MODELVIEW</b>: это используется при перемещении вершин в пространстве модели.
+
Использование функции <code>glMatrixMode</code> указывает, какую матрицу вы хотите установить. OpenGL работает с 3 матрицами:
<b>GL_PROJECTION</b>: это используется для преобразования 3D-координат в 2D-координаты.
+
<code>GL_MODELVIEW</code>: это используется при перемещении вершин в пространстве модели.
<b>GL_TEXTURE</b>: это используется для изменения координат текстуры.
+
<code>GL_PROJECTION</code>: это используется для преобразования 3D-координат в 2D-координаты.
 +
<code>GL_TEXTURE</code>: это используется для изменения координат текстуры.
  
Послк того как вы выбрали матрицу, которую хотите изменить, вы можете вызвать функции, которые изменяют значения матриц. <b>glLoadIdentity</b> сбросит матрицу. Since almost all matrix functions multiply current matrix with a generated one, you sometimes need to clear matrix with this function.
+
После того, как вы выбрали матрицу, которую хотите изменить, вы можете вызвать функции, которые изменяют значения матриц. <code>glLoadIdentity</code> сбросит матрицу. Поскольку почти все матричные функции умножают текущую матрицу на сгенерированную, иногда необходимо очистить матрицу с помощью этой функции.
  
Чтобы установить перспективу матрицы, используйте функцию <b>gluPerspective</b>. Четыре параметра задают поле зрения, соотношение сторон, передний и задний план. Это просто.
+
Чтобы установить перспективу матрицы, используйте функцию <code>gluPerspective</code>. Четыре параметра задают поле зрения, соотношение сторон, передний и задний план. Это просто.
  
Теперь можно изменить матрицу... for this time, you just set it to identity.
+
Теперь вы измените матрицу модели ... на этот раз вы просто установите ее на идентичность.
  
 
Код рисования для первой фигуры:
 
Код рисования для первой фигуры:
 
+
<syntaxhighlight lang=pascal>
 
  procedure DrawGLScene; cdecl;
 
  procedure DrawGLScene; cdecl;
 
  begin
 
  begin
Line 211: Line 278:
 
   glutSwapBuffers;
 
   glutSwapBuffers;
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
Мы использовали функцию glClear. Она просто сбрасывает буферы. Следующие две функции пока пропустим.
  
Мы использовали функцию glClear. она просто сбрасывает буферы. Следующие две функции пока пропустим.
+
<code>glBegin</code> обозначает начало рисования в блок. После нее можно начать ввод вершин. Параметр описывает, что мы будем рисовать:
  
<b>glBegin</b> обозначает начало рисования в блок. После нее можно начать ввод вершин. Параметр описывает, что мы будем рисовать:
+
<code>GL_POINTS</code>: просто рисуем n точек. Нужно указать n вершин.
GL_POINTS: просто рисуем n точек. Нужно указать n вершин.
 
  
GL_LINES: каждая пара вершин задаст линию. Вершины 2n-1 и 2n задают одну линию. При использовании n вершин получится n/2 линий.
+
<code>GL_LINES</code>: каждая пара вершин задаст линию. Вершины 2n-1 и 2n задают одну линию. При использовании n вершин получится n/2 линий.
  
GL_LINE_STRIP: рисует отрезки из первой вершины через все в последнюю. Получится n-1 линий.
+
<code>GL_LINE_STRIP</code>: рисует отрезки из первой вершины через все в последнюю. Получится n-1 линий.
  
GL_LINE_LOOP: то же самое, но добавляется отрезок из последней вершины в первую. Получится n линий.
+
<code>GL_LINE_LOOP</code>: то же самое, но добавляется отрезок из последней вершины в первую. Получится n линий.
  
GL_TRIANGLES: каждые три точки зададут треугольник. Получится n/3 треугольников.
+
<code>GL_TRIANGLES</code>: каждые три точки зададут треугольник. Получится n/3 треугольников.
  
GL_TRIANGLE_STRIP: рисует связанные треугольники. Каждая вершина после первых двух будет задавать треугольник. При нечетном n вершины n, n+1 и n+2 зададут треугольник. При четном n - n+1, n и n+2. Получится n-2 треугольника.
+
<code>GL_TRIANGLE_STRIP</code>: рисует связанные треугольники. Каждая вершина после первых двух будет задавать треугольник. При нечетном n вершины n, n+1 и n+2 зададут треугольник. При четном n - n+1, n и n+2. Получится n-2 треугольника.
  
GL_TRIANGLE_FAN: тоже рисует связанные треугольники. Каждая вершина после первых двух будет задавать треугольник. Вершины 1, n+1 и n+2 зададут треугольник. Получится n-2 треугольников.
+
<code>GL_TRIANGLE_FAN</code>: тоже рисует связанные треугольники. Каждая вершина после первых двух будет задавать треугольник. Вершины 1, n+1 и n+2 зададут треугольник. Получится n-2 треугольников.
  
GL_QUADS: каждые четыре вершины рисуют четырехугольник. Получится n/4 четырехугольников.
+
<code>GL_QUADS</code>: каждые четыре вершины рисуют четырехугольник. Получится n/4 четырехугольников.
  
GL_QUAD_STRIP: рисует связанные четырехугольники. Каждая пара вершин после первой пары будет задавать четырехугольник. Вершины 2n-1, 2n, 2n+2 и 2n+1 задают четырехугольник n. n/2-1 четырехугольников получится. Обратите внимание, что порядок, в котором вершины используются для построения четырехугольника из ленты данных отличается от используемого с независимыми данными.
+
<code>GL_QUAD_STRIP</code>: рисует связанные четырехугольники. Каждая пара вершин после первой пары будет задавать четырехугольник. Вершины 2n-1, 2n, 2n+2 и 2n+1 задают четырехугольник n. n/2-1 четырехугольников получится. Обратите внимание, что порядок, в котором вершины используются для построения четырехугольника из ленты данных отличается от используемого с независимыми данными.
  
GL_POLYGON: рисует один выпуклый многоугольник. Вершины определяют этот полигон.
+
<code>GL_POLYGON</code>: рисует один выпуклый многоугольник. Вершины определяют этот полигон.
  
[[Image:SimpleShapePic1.jpg|thumb]] Нарисуем один треугольник с помощью GL_TRIANGLES. Функция <b>glVertex3f</b> задаёт положение вершины. Есть еще glVertex* функции. Единственное отличие состоит в количестве и типе параметров, которые они принимают. Например, glVertex2i принимает два параметра (x и y) типа integer. glVertex3f это обычно то, что вам нужно.
+
[[Image:SimpleShapePic1.jpg|thumb]] Нарисуем один треугольник с помощью <code>GL_TRIANGLES</code>. Функция <code>glVertex3f</code> задаёт положение вершины. Есть еще glVertex* функции. Единственное отличие состоит в количестве и типе параметров, которые они принимают. Например, <code>glVertex2i</code> принимает два параметра (x и y) типа integer. <code>glVertex3f</code> это обычно то, что вам нужно.
  
Перед glVertex можно установить цвет, материал, текстуру... Для простоты просто укажем цвет для каждой вершины. Цвет задаётся с помощью функции <b>glColor3f</b>. glColor также может принимать разные параметры, аналогично glVertex.
+
Перед <code>glVertex</code> можно установить цвет, материал, текстуру... Для простоты просто укажем цвет для каждой вершины. Цвет задаётся с помощью функции <code>glColor3f</code>. <code>glColor</code> также может принимать разные параметры, аналогично <code>glVertex</code>.
  
В коде мы можем увидеть, что Z имеет значение 0 для всех вершин. Поскольку передний план установлен как 0.1, треугольник не будет видно. Тут как раз нужны те две функции, которые мы пропустили. Вы уже знаете, что функция glLoadIdentity сбрасывает матрицу. <b>glTranslatef</b> перемещает треугольник на X, Y и Z, которые вы зададите. Если установить Z как -5 (отрицательный Z будет находиться дальше от камеры), то все вершины будут на 5 единиц дальше от камеры, и треугольник будет видно.
+
В коде мы можем увидеть, что Z имеет значение 0 для всех вершин. Поскольку передний план установлен как 0.1, треугольник не будет видно. Тут как раз нужны те две функции, которые мы пропустили. Вы уже знаете, что функция <code>glLoadIdentity</code> сбрасывает матрицу. <code>glTranslatef</code> перемещает треугольник на X, Y и Z, которые вы зададите. Если установить Z как -5 (отрицательный Z будет находиться дальше от камеры), то все вершины будут на 5 единиц дальше от камеры, и треугольник будет видно.
  
После завершения рисования вызовите функцию <b>glEnd</b>. Если хотите, то можете начать новое рисование с помощью glBegin.
+
После завершения рисования вызовите функцию <code>glEnd</code>. Если хотите, то можете начать новое рисование с помощью <code>glBegin</code>.
  
 
Скачать исходный код, исполняемые файлы linux или windows с [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
 
Скачать исходный код, исполняемые файлы linux или windows с [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
Line 247: Line 315:
 
==Использование списков отображения==
 
==Использование списков отображения==
  
Иногда вам будет необходимо отобразить один и тот же объект на сцене несколько раз. OpenGL имеет возможность создавать <b>списки отображения</b>, которые также создают рисунок немного быстрее. Создать этот список очень легко. Просто задать вершины и заключить их между <b>glNewList</b> и <b>glEndList</b>.
+
Иногда вам будет необходимо отобразить один и тот же объект на сцене несколько раз. OpenGL имеет возможность создавать '''списки отображения''', которые также создают рисунок немного быстрее. Создать этот список очень легко. Просто задать вершины и заключить их между <code>glNewList</code> и <code>glEndList</code>.
 
+
<syntaxhighlight lang=pascal>
 
  const
 
  const
 
   LIST_OBJECT = 1;
 
   LIST_OBJECT = 1;
Line 293: Line 361:
 
   glEndList;
 
   glEndList;
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
 +
<code>glNewList</code> создаёт новый список, и все функции рисования будут записаны, пока не вызовется <code>glEndList</code>.
 +
 +
Первый параметр функции <code>glNewList</code> задаёт ID списка. Каждый список определяется по его идентификатору. Если список с заданным ID уже существует, то он очистится перед записью. Если второй параметр <code>GL_COMPILE</code>, то все функции рисования просто записываются, если это <code>GL_COMPILE_AND_EXECUTE</code>, то они записываются и выполняются автоматически.
  
<b>glNewList</b> создаёт новый список, и все функции рисования будут записаны, пока не вызовется <b>glEndList</b>. Первый параметр функции glNewList задаёт ID списка. Каждый список определяется по его идентификатору. Если список с заданным ID уже существует, то он очистится перед записью. Если второй параметр GL_COMPILE, то все функции рисования просто записываются, если это GL_COMPILE_AND_EXECUTE, то они записываются и выполняются автоматически.
+
Функция <code>glIsList</code> поможет вам со списками. Она сообщает, существует ли список с таким ID.
  
Функция <b>glIsList</b> поможет вам со списками. Она сообщает, существует ли список с таким ID.
+
Еще одна полезная функция - это <code>glGenLists</code>. Он создает несколько пустых списков отображения. Вы задаёте количество нужных списков и получаете номер первого из них. Если вам нужно создать n списков и получить r ID, получатся списки: r, r+1, r+2,..., r+n-1
Еще одна полезная функция - это <b>glGenLists</b>. Он создает несколько пустых списков отображения. Вы задаёте количество нужных списков и получаете номер первого из них. Если вам нужно создать n списков и получить r ID, получатся списки: r, r+1, r+2,..., r+n-1
 
  
 
Все созданные списки должны быть удалены. Для этого при выходе из программы вы должны сделать следующее:
 
Все созданные списки должны быть удалены. Для этого при выходе из программы вы должны сделать следующее:
 
+
<syntaxhighlight lang=pascal>
 
  procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
 
  procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
 
  begin
 
  begin
Line 309: Line 381:
 
   end;
 
   end;
 
  end;
 
  end;
 +
</syntaxhighlight>
  
<b>glDeleteLists</b> принимает 2 параметра, ID списка отображения и количество списков для удаления. Если ID - r и количество списков для удаления n, удаляются следующие листы: r, r+1, r+2,..., r+n-1
+
<code>glDeleteLists</code> принимает 2 параметра, ID списка отображения и количество списков для удаления. Если ID - r и количество списков для удаления n, удаляются следующие листы: r, r+1, r+2,..., r+n-1
  
 
Теперь вы знаете, как создавать и удалять списки отображения. Попробуем их применить:
 
Теперь вы знаете, как создавать и удалять списки отображения. Попробуем их применить:
 
+
<syntaxhighlight lang=pascal>
 
  procedure DrawGLScene; cdecl;
 
  procedure DrawGLScene; cdecl;
 
  begin
 
  begin
Line 335: Line 408:
 
   glutSwapBuffers;
 
   glutSwapBuffers;
 
  end;
 
  end;
 +
</syntaxhighlight>
  
[[Image:DisplayListsPic1.jpg|thumb]] Используйте <b>glCallList</b> для рисования одного списка. Прежде чем делать список отображения, вы меняете модели матрицы и рисовали объект в разных местах.
+
[[Image:DisplayListsPic1.jpg|thumb]] Используйте <code>glCallList</code> для рисования одного списка. Прежде, чем делать список отображения, вы меняли модели матрицы и рисовали объект в разных местах.
Иногда нужно рисовать несколько списков одновременно. Это возможно при использовании функции <b>glCallLists</b>. Он принимает количество списков, которые вы хотите нарисовать, тип массива, в котором содержатся ID и массив с идентиыикаторами списков. Тип может принимать одно из следующих значений:
+
Иногда нужно рисовать несколько списков одновременно. Это возможно при использовании функции <code>glCallLists</code>. Она принимает количество списков, которые вы хотите нарисовать, тип массива, в котором содержатся ID и массив с идентификаторами списков. Тип может принимать одно из следующих значений:
  
GL_BYTE: список рассматривается как массив байтов, где каждый имеет значение в диапазоне от -128 до 127.
+
<code>GL_BYTE</code>: список обрабатывается как массив байтов со знаком, каждый в диапазоне от -128 до 127.
  
GL_UNSIGNED_BYTE: байты от 0 до 255.
+
<code>GL_UNSIGNED_BYTE</code>: список обрабатывается как массив байтов без знака, каждый в диапазоне от 0 до 255.
  
GL_SHORT: список рассматривается как массив двухбайтовых целых чисел от -32768 до 32767.
+
<code>GL_SHORT</code>: Список обрабатывается как массив двухбайтовых целых чисел со знаком, каждое из которых находится в диапазоне от -32768 до 32767.
  
GL_UNSIGNED_SHORT: short от 0 до 65535.
+
<code>GL_UNSIGNED_SHORT</code>: Список обрабатывается как массив двухбайтовых целых чисел без знака, каждое из которых находится в диапазоне от 0 до 65535.
  
GL_INT: список рассматривается как массив integer.
+
<code>GL_INT</code>: списки обрабатываются как массив четырехбайтовых целых чисел со знаком.
  
GL_UNSIGNED_INT: список рассматривается как массив беззнаковых integer.
+
<code>GL_UNSIGNED_INT</code>: Список обрабатывается как массив беззнаковых четырехбайтовых целых чисел.
  
GL_FLOAT: список рассматривается как массив из четырехбайтовых вещественных чисел.
+
<code>GL_FLOAT</code>: Список обрабатывается как массив четырехбайтовых значений с плавающей точкой.
  
GL_2_BYTES: список рассматривается как массив беззнаковых байтов. Каждая пара байт определяет один идентификатор списка. Значение вычисляется как 256 раз первый байт плюс второй байт (беззнаковые значения).
+
<code>GL_2_BYTES</code>: Список обрабатывается как массив байтов без знака. Каждая пара байтов определяет один идентификатор списка отображения. Значение пары вычисляется как 256-кратное значение без знака первого байта плюс значение без знака второго байта.
  
GL_3_BYTES: список рассматривается как массив беззнаковых байтов. Каждая тройка байт определяет один идентификатор списка. Значение вычисляется как 65536 раз первый байт плюс 256 раз второй байт плюс третий байт (беззнаковые значения).
+
<code>GL_3_BYTES</code>: Список обрабатывается как массив байтов без знака. Каждый триплет байтов определяет один идентификатор списка отображения. Значение триплета вычисляется как 65536, умноженное на значение без знака первого байта, плюс 256 раз без знака без знака второго байта, плюс значение без знака третьего байта.
  
GL_4_BYTES: список рассматривается как массив беззнаковых байтов. Каждая четвёрка байт определяет один идентификатор списка. Значение вычисляется как 16777216 раз первый байт плюс 65536 раз второй байт плюс 256 раз третий байт плюс четвертый байт (беззнаковые значения).
+
<code>GL_4_BYTES</code>: Список обрабатывается как массив байтов без знака. Каждая четверка байтов задает один идентификатор списка отображения. Значение четверки вычисляется как 16777216 раз значение без знака первого байта, плюс 65536 раз значение без знака второго байта, плюс 256 раз значение без знака третьего байта, плюс значение без знака четвертого байта.
  
Следующий пример создаст простую планетную систему. Он покажет, как сделать анимацию, но она будет зависеть от количества прорисовки кадров в секунду.
+
Это пока. Следующий урок покажет, как создать маленькую планетную систему. Мы поговорим о матрицах и о том, как сделать анимированную сцену, которая не зависит от количества кадров в секунду.
  
 
Скачать исходный код, исполняемые файлы linux или windows с [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
 
Скачать исходный код, исполняемые файлы linux или windows с [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
Line 366: Line 440:
  
 
Переход в полноэкранный режим в GLUT прост. Измените основную часть программы:
 
Переход в полноэкранный режим в GLUT прост. Измените основную часть программы:
 
+
<syntaxhighlight lang=pascal>
 
  const
 
  const
 
   FSMode = '800x600:32@75';
 
   FSMode = '800x600:32@75';
Line 386: Line 460:
 
   glutMainLoop;
 
   glutMainLoop;
 
  end.
 
  end.
 +
</syntaxhighlight>
  
Так как нам не нужно, чтобы GLUT разбирал командную строку, то на этот раз на glutInitPascal стави параметр false. Также не нужен код для создания окна. Функцией <b>glutEnterGameMode</b> GLUT автоматически создаст окно на весь экран. Чтобы указать желаемый режим, через функцию <b>glutGameModeString</b>передаём нужные параметры экрана.
+
Так как нам не нужно, чтобы GLUT разбирал командную строку, то на этот раз на glutInitPascal стави параметр false. Также не нужен код для создания окна. Функцией <code>glutEnterGameMode</code> GLUT автоматически создаст окно на весь экран. Чтобы указать желаемый режим, через функцию <code>glutGameModeString</code>передаём нужные параметры экрана.
 
Format of that string is:
 
Format of that string is:
  
 +
Так как в этот раз мы не хотим, чтобы GLUT анализировал командную строку, мы вызываем <code>glutInitPascal</code> с параметром False. Как видите, нет кода для создания окна. GLUT имеет <code>glutEnterGameMode</code>, который создает полноразмерное окно. Чтобы указать, какой полноэкранный режим вы хотите, вы вызываете функцию <code>glutGameModeString</code>, которая принимает строку, которая определяет режим, который вам нравится.
 +
 +
Формат этой строки:
 +
<syntaxhighlight lang=pascal>
 
  [ширина "x" высота][":" цвет. палитра]["@" Гц]
 
  [ширина "x" высота][":" цвет. палитра]["@" Гц]
 +
</syntaxhighlight>
  
 
В строке FSMode мы задали разрешение экрана 800x600, 32-битнкю цветовую палитру и частоту обновления экрана 75Гц. Можно пропустить один из элементов. Если вы пропустите разрешение, GLUT будет пытаться использовать текущее или самый низкое из тех, которые можно установить. То же для других параметров.
 
В строке FSMode мы задали разрешение экрана 800x600, 32-битнкю цветовую палитру и частоту обновления экрана 75Гц. Можно пропустить один из элементов. Если вы пропустите разрешение, GLUT будет пытаться использовать текущее или самый низкое из тех, которые можно установить. То же для других параметров.
  
Обычно в полноэкранном режиме курсор не отображается. Чтобы скрыть курсор, используйте функцию <b>glutSetCursor</b>. Она принимает один параметр, задающий, какой курсор вы хотите видеть:
+
В строке <code>FSMode</code> мы объявили, что полноэкранный режим должен быть 800x600, с 32-битной палитрой и обновлением 75 Гц. Можно пропустить одну из групп. Если вы опустите размер, GLUT попытается использовать текущий или первый наименьший, который может работать. Эта политика используется и для других параметров.
 +
 
 +
Обычно в полноэкранном режиме курсор не виден. Чтобы скрыть курсор, вы должны использовать функцию <code>glutSetCursor</code>. Она принимает только один параметр, который описывает курсор, который вы хотели бы видеть:
 +
<syntaxhighlight lang=pascal>
 
  GLUT_CURSOR_RIGHT_ARROW
 
  GLUT_CURSOR_RIGHT_ARROW
 
  GLUT_CURSOR_LEFT_ARROW
 
  GLUT_CURSOR_LEFT_ARROW
Line 418: Line 501:
 
  GLUT_CURSOR_NONE
 
  GLUT_CURSOR_NONE
 
  GLUT_CURSOR_INHERIT
 
  GLUT_CURSOR_INHERIT
 +
</syntaxhighlight>
  
<b>glutIdleFunc</b> определяет функцию для обратной связи, которая вызывается каждый раз, когда программа не имеет сообщений для обработки. Поскольку нам ннужно просто сделать новый кадр, установим на нее функцию DrawGLScene.
+
<code>glutIdleFunc</code> определяет функцию обратного вызова, которую вы захотите вызывать каждый раз, когда у вашей программы нет сообщений для обработки. Так как мы просто хотим рендерить новый кадр, если ничего не нужно делать, просто установите функцию ожидания в <code>DrawGLScene</code>. Некоторые другие учебные пособия показывают, что функция бездействия должна отправлять сообщение об обновлении вместо рисования, но в этом случае у меня на 50-100 кадров меньше, чем при использовании описанного мной метода.  
Некоторые другие учебники показывают, что эта функция должна отправить сообщение для перерисовки, но тогда мы теряем 50-100 кадров, чем при использовании данного метода.
 
 
 
Теперь при выходе из программы нам нужно выйти из полноэкранного режима:
 
  
 +
Теперь давайте посмотрим на завершение программы, где вам нужно выйти из полноэкранного режима:
 +
<syntaxhighlight lang=pascal>
 
  procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
 
  procedure GLKeyboard(Key: Byte; X, Y: Longint); cdecl;
 
  begin
 
  begin
Line 432: Line 515:
 
   end;
 
   end;
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
Как видите, все, что вам нужно сделать, это вызвать <code>glutLeaveGameMode</code>.
  
Как вы видите, нужно просто вызвать <b>glutLeaveGameMode</b>.
+
Теперь мы представим некоторые новые матричные функции. Во-первых, давайте изменим функцию <code>ReSizeGLScene</code>:
 
+
<syntaxhighlight lang=pascal>
Теперь надо ввести некоторые новые матричные функции. Напишем их в функции ReSizeGLScene:
 
 
 
 
  procedure ReSizeGLScene(Width, Height: Integer); cdecl;
 
  procedure ReSizeGLScene(Width, Height: Integer); cdecl;
 
  begin
 
  begin
Line 447: Line 530:
 
   gluLookAt(0, 20, 25, 0, 0, 0, 0, 1, 0);
 
   gluLookAt(0, 20, 25, 0, 0, 0, 0, 1, 0);
 
  end;
 
  end;
 +
</syntaxhighlight>
  
<b>gluLookAt</b> создаст матрицу, определяющую, с какой точки вы смотрите на объекты. Первые три параметра задают позицию X, Y и Z камеры. Следующие три параметра задают точку X, Y и Z, в которую будет смотреть камера, и последние три параметра задают "верх" (в какую сторону "смотрит верх" камеры). Как правило, это положительная ось Y.
+
<code>gluLookAt</code> создает матрицу, которая будет определять, откуда вы смотрите на объекты. Первые 3 параметра - это координаты X, Y и Z положения камеры. Следующие 3 параметра - это координаты X, Y и Z точки, на которую смотрит камера, а последние 3 параметра определяют вектор «вверх» (где «вверх» для камеры). Обычно up - это положительная ось y.  
 
 
Теперь сделаем рисование. Поскольку вы установили матрицу с gluLookAt, которая должна использоваться другими объектами, просто использовать функцию glLoadIdentity для сброса матриц не получится. Вы будете сохранять состояние матрицы и восстановите его после того, как объекты нарисуются:
 
  
 +
Хорошо, давайте сейчас порисуем. Поскольку вы устанавливаете матрицу с помощью <code>gluLookAt</code>, которая должна использоваться со всеми объектами, вы не можете просто использовать <code>glLoadIdentity</code> для сброса матрицы для следующего объекта ... вы сохраните предыдущее состояние матрицы и восстановите его после отрисовки объекта:
 +
<syntaxhighlight lang=pascal>
 
  procedure DrawGLScene; cdecl;
 
  procedure DrawGLScene; cdecl;
 
  var
 
  var
Line 492: Line 576:
 
   glutSwapBuffers;
 
   glutSwapBuffers;
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
[[Image:FullScreenAnimationPic1.jpg|thumb]]
  
[[Image:FullScreenAnimationPic1.jpg|thumb]] <b>glPushMatrix</b> и <b>glPopMatrix</b> используются для сохранения состояния матрицы и последующего восстановления. Как вы видите, мы сохраняем её состояние, потом изменяем ее, чтобы установить объект в нужное место и восстанавливаем старое состояние.
+
<code>glPushMatrix</code> и <code>glPopMatrix</code> используются для сохранения и восстановления состояния матрицы. Как видите, мы сохраняем состояние матрицы, затем меняем матрицу, чтобы нарисовать объект в нужном месте, а затем восстанавливаем старое состояние матрицы.  
  
Зачем тут нужна переменная <b>T</b>? Она используется для задания скорости анимации. Каждое изменение, которое зависит от времени, умножается на T. Скорость анимации не будет зависеть от частоты кадров.
+
Вы можете спросить, для чего нужна переменная <tt>T</tt>? Ну, она используется для определения скорости анимации. Каждое изменение, зависящее от времени, умножается на <tt>T</tt>. Таким образом, скорость анимации постоянна на каждой частоте кадров.  
Функция <b>glutGet</b> с параметром <b>GLUT_ELAPSED_TIME</b> возвращает время в миллисекундах, прошедшее от вызова функции glutInit. При делении на 1000 мы получаем время в секундах.
 
  
Функция <b>glRotatef</b> поворачивает матрицу. Первый параметр - это угол в градусах, следующие три параметра определяют ось, по которой будет сделано вращение.
+
Функция <code>glutGet</code> с параметром <code>GLUT_ELAPSED_TIME</code> возвращает время в миллисекундах из <code>glutInit</code>. Разделив ее значение на 1000, мы получим время в секундах.  
Так как мы умножили угол на T, объект будет повернут на заданный угол в течение одной секунды.
 
  
Скачать исходный код, исполняемые файлы linux или windows с [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
+
Функция <code>glRotatef</code> создает матрицу вращения. Первый параметр - это угол в градусах, а последние 3 параметра определяют ось, вокруг которой будет выполняться вращение. Поскольку вы умножили угол на <tt>T</tt>, объект будет повернут на этот угол ровно за 1 секунду.
 +
 
 +
Загрузите исходный код, исполняемый файл linux или исполняемый файл windows из [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
  
 
==Освещение==
 
==Освещение==
 +
Этот урок познакомит вас с освещением сцены. Вы сделаете вращающийся куб и один источник света, который добавит реалистичности сцене, но сначала давайте сделаем несколько вспомогательных модулей.
  
Этот раздел расскажет о том, как сделать на сцене освещение. Мы сделаем вращающийся куб и один источник света, что придаст реализма сцене. Но сперва сделаем вспомогательный unit.
+
На данный момент урок будет иметь только базовые функции, которые помогут нам получить текущее время и дельту (время, прошедшее от одного рендеринга до другого вызова рендеринга), и рассчитать количество кадров в секунду.
 
 
Пока он будет иметь немного функций, он нам поможет получить время работы GLUT и дельта (время, прошедшее с предыдущего вызова) и для расчета количества кадров в секунду (frame per second, FPS).
 
  
 +
<syntaxhighlight lang=pascal>
 
  unit utils;
 
  unit utils;
 
   
 
   
Line 562: Line 648:
 
   
 
   
 
  end.
 
  end.
 +
</syntaxhighlight>
  
Как вы видите, ничего сложного в этом блоке нет. Вы просто сохраняете время последнего вызова, и при следующем вызове возвращается разница. FrameRendered должен быть вызван каждый раз, когда вы рисуете сцену.
+
Как видите, в этом модуле нет ничего сложного. Время просто сохраняется между вызовами, а разница возвращается. FrameRendered должен вызываться каждый раз, когда вы рисуете сцену, чтобы функция могла рассчитывать FPS.  
  
Теперь повеселимся с освещением.
+
Теперь давайте повеселимся с освещением. В OpenGL есть несколько типов света: ambient (окружающий), diffuse (рассеянный), point(точечный), spot (пятно), specular (зеркальный) и emissive (излучение).  
  
OpenGL поддерживает разные типы совещения: окружающее (ambient), диффузное (diffuse), точечное (point), пятно (spot), блеск (specular) и эмисионное (emissive) освещение.
 
  
Ambient - что-то вроде Солнца. Когда солнечные лучи через окно попадают в комнату, они рассеиваются и равномерно освещают всю комнату. Ambient освещает все вершины.
+
Окружающий свет (ambient) - это что-то вроде Солнца. Когда солнечные лучи проходят через окно комнаты, они ударяются о стены и отражаются во всех направлениях, что в среднем осветляет всю комнату. Все вершины освещены окружающим светом.  
  
Diffuse может быть представлена как параллельные лучи света, идущие издалека. Ими осветятся только те вершины, на которые ориентирован источник света.
+
Рассеянный свет(diffuse) может быть представлен как параллельные световые лучи, исходящие издалека. Они будут освещать только вершины, ориентированные на источник света.  
  
Point освещает всё вокруг себя. Это как огненный шар, который выпускает лучи света из себя и освещает все вершины, близкие к нему.
+
Точечный свет(point) освещает все вокруг себя. Он похож на огненный шар, он посылает световые лучи вокруг себя и освещает вершины, которые ориентированы на источник света и достаточно близки.  
  
Spot - это как свет от фонарика. Это точечный источник света с малым радиусом конуса света. Все вершины, попадающие в него и близкие к источнику, освещаются.
+
Пятно (spot) - как свет от фонарика. Это просто точечный источник света с небольшим радиусом светового конуса. Все вершины, которые попадают внутрь конуса и находятся достаточно близко, подсвечиваются.  
  
Так же как и Diffuse, Specular зависит от направления источника света. Разница между ними в том, что specular это как отраженный свет. Освещение зависит от угла между источником света и камерой. С точки зрения смотрящего этот свет выглядит как зеркальное отражение.
+
Зеркальный свет (specular), как и рассеянный свет, является направленным типом света. Это происходит из одного конкретного направления. Разница между ними заключается в том, что зеркальный свет отражается от поверхности острым и равномерным образом. Рендеринг зеркального света зависит от угла между зрителем и источником света. С точки зрения зрителя зеркальный свет создает выделенную область на поверхности наблюдаемого объекта, известную как зеркальное выделение или зеркальное отражение.  
  
Emissive мало отличается от других источиков света. Разница заключается лишь в том, что он освещает тот объект, из которого исходит, и не освещает соседние.
+
Излучение (emissive) - свет немного отличается от любых других ранее объясненных компонентов света. Этот свет исходит от объекта, который вы рисуете, но не освещаете другие объекты поблизости.  
  
Для примера покажем diffuse light. Later on, some other lights may appear in tutorials :)
+
Для простоты мы будем использовать только рассеянный свет в этом уроке. Позже, некоторые другие источники света могут появиться в уроках :)  
  
Давайте посмотрим, как включить свет в сцене:
+
Давайте посмотрим, как включить освещение в сцене:
  
 +
<syntaxhighlight lang=pascal>
 
  const
 
  const
 
   DiffuseLight: array[0..3] of GLfloat = (0.8, 0.8, 0.8, 1);
 
   DiffuseLight: array[0..3] of GLfloat = (0.8, 0.8, 0.8, 1);
Line 591: Line 678:
 
   glLightfv(GL_LIGHT0, GL_DIFFUSE, DiffuseLight);
 
   glLightfv(GL_LIGHT0, GL_DIFFUSE, DiffuseLight);
 
   glEnable(GL_LIGHT0);
 
   glEnable(GL_LIGHT0);
 +
</syntaxhighlight>
  
Параметры лампочки задаются функцией <b>glLightfv</b>. Он имеет три пааметра: первый задаёт номер лампочки, которую вы хотите изменить (OpenGL поддерживает до 8 лампочек), следующий сообщает OpenGL тип освещения, третий передает параметры лампочки.
+
Как видите, мы включаем подсветку в OpenGL, чтобы свет влиял на сцену, которую вы рендерили. Параметры освещения задаются функцией <code>glLightfv</code>. Потребуется 3 параметра ... один для числа освещения, которое вы хотите изменить (OpenGL поддерживает до 8 источников света), другой сообщает OpenGL, какой параметр освещения изменить, а последний - новый параметр для освещения. В этом уроке вы установите только рассеянный свет для освещения. После этого вы можете включить освещение, и на сцене будет свет ... но ... это еще не все.
Но это еще не всё...
 
  
Подробнее о glLightfv: http://www.opengl.org//documentation/specs/man_pages/hardcopy/GL/html/gl/light.html
+
Подробнее о <code>glLightfv</code>: http://www.opengl.org//documentation/specs/man_pages/hardcopy/GL/html/gl/light.html
 
 
Чтобы видеть свет, нужно установить материал для вершин:
 
  
 +
Если вы хотите использовать источники света, вы не можете просто задать цвет для вершины ... вы также должны задать и материал для вершин. Давайте настроим материал для рисования:
 +
<syntaxhighlight lang=pascal>
 
  glEnable(GL_COLOR_MATERIAL);
 
  glEnable(GL_COLOR_MATERIAL);
 
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
 
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
 +
</syntaxhighlight>
 +
[[Image:LightPic1.jpg|thumb]]
  
[[Image:LightPic1.jpg|thumb]] Вы ожидали чего-то более сложного, не так ли? :) Этот код позволяет использовать функцию glColor, чтобы установить материал на вершины. Используя функцию glEnable и параметр GL_COLOR_MATERIAL, вы можете определить, какие свойства материала будет изменять glColor. <b>glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE)</b> задаёт OpenGL, что glColor ''changes ambient and diffuse material.'' Подробнее о материалах будет позже.
+
Вы ожидали чего-то более сложного? :) Ну, этот код позволит нам использовать функцию <code>glColor</code>, чтобы задать материал для вершин. Используя функцию <code>glEnable</code> и флаг <code>GL_COLOR_MATERIAL</code>, вы можете определить, какие свойства материала будут изменять <code>glColor</code>. <code>glColorMaterial (GL_FRONT, GL_AMBIENT_AND_DIFFUSE)</code> сообщает OpenGL, что <code>glColor</code> изменяет окружающий и рассеянный материал. Мы обсудим материалы больше в следующих уроках.
  
Еще одна вещь, которая важна при использовании освещения. Каждая вершина имеет связанную с ней нормаль. Нормаль используется для поиска направления вершины. Вы можете просто использовать стандартную функцию GLUT для рисования куба, так что можно не париться с нормалями. :)
+
Еще одна вещь, которая важна при использовании источников света ... каждая вершина должна иметь нормаль, связанную с ней. Нормаль используется для определения направления вершины, чтобы свет можно было правильно рассчитать. Вы будете использовать функцию GLUT, чтобы нарисовать куб, и он предоставит нам нормали, так что на этот раз мы просто пройдемся по нормали.  
  
После установки всех параметров ваш куб осветит лампочка :)
+
После всех этих настроек, свет будет сиять в вашем кубе :)
  
 
Часть текста скопирована из [http://www.falloutsoftware.com/tutorials/gl/gl8.htm The OpenGL Light Bible]
 
Часть текста скопирована из [http://www.falloutsoftware.com/tutorials/gl/gl8.htm The OpenGL Light Bible]
Line 614: Line 703:
 
==Растровые шрифты==
 
==Растровые шрифты==
  
Games and programs usually need to write some text on screen. GLUT provides several functions for drawing chars that are platform independent.
+
Игры и программы, как правило, должны писать текст на экране. GLUT предоставляет несколько функций, которые не зависят от платформы, для рисования символов.  
  
First, we'll show how to use default bitmap fonts. Almost all code additions will be made to utils.pas unit.
+
Сначала мы покажем, как использовать стандартные растровые шрифты. Почти все дополнения кода будут сделаны в модуле utils.pas.  
 
 
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:
 
  
 +
Поскольку текст будет отображаться в 2D, нам нужно знать ширину и высоту области просмотра ... поэтому мы напишем две функции для этого:
 +
<syntaxhighlight lang=pascal>
 
  function glGetViewportWidth: Integer;
 
  function glGetViewportWidth: Integer;
 
  var
 
  var
Line 635: Line 724:
 
   Result := Rect[3] - Rect[1];
 
   Result := Rect[3] - Rect[1];
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
Мы просто получаем left/right, top/bottom и вычисляем width/height, вычитая их.
  
We just get left/right, top/bottom and calculate width/height by subtracting them.
+
Должны быть функции для входа и выхода из режима 2D:
 
+
<syntaxhighlight lang=pascal>
There must be functions for entering and leaving 2D mode:
 
 
 
 
  procedure glEnter2D;
 
  procedure glEnter2D;
 
  begin
 
  begin
Line 663: Line 752:
 
   glEnable(GL_DEPTH_TEST);
 
   glEnable(GL_DEPTH_TEST);
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
При входе в режим 2D мы сохраняем текущие матрицы и устанавливаем 2D-матрицу с помощью функции <code>gluOrtho2D</code>. Таким образом, если мы нарисуем какую-либо штуку в координатах (100,100), она будет нарисована точно на 100 пикселей от левого края окна и на 100 пикселей от нижнего края формы (положительные значения по оси Y направлены вверх). Также мы отключаем ZBuffer. Таким образом, текст не изменит ZBuffer.
  
When entering 2D mode, we save current matrices and set 2D matrix using <b>gluOrtho2D</b> 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.
+
Выход из режима 2D просто возвращает старые матрицы и включает ZBuffer.  
 
 
Leaving 2D mode just returns old matrices and enable ZBuffer.
 
 
 
Now, we can create function for text drawing:
 
  
 +
Теперь мы можем создать функцию для рисования текста:
 +
<syntaxhighlight lang=pascal>
 
  procedure glWrite(X, Y: GLfloat; Font: Pointer; Text: String);
 
  procedure glWrite(X, Y: GLfloat; Font: Pointer; Text: String);
 
  var
 
  var
Line 678: Line 767:
 
     glutBitmapCharacter(Font, Integer(Text[I]));
 
     glutBitmapCharacter(Font, Integer(Text[I]));
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
<code>glutBitmapCharacter</code> может рисовать только один символ выбранного шрифта. Первый параметр  - желаемый шрифт (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 или GLUT_BITMAP_HELVETICA_18) и другие - являются символом.
  
<b>glutBitmapCharacter</b> 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.
+
Символ будет нарисован в текущей позиции растра. Чтобы установить желаемую позицию растра, мы вызываем функцию <code>glRasterPos</code>. <code>glRasterPos</code> может обрабатывать различные количества и типы параметров так же, как функция <code>glVertex</code>. Указанная координата преобразуется моделью и матрицей проекции, чтобы получить двухмерную координату, в которой будет находиться новая позиция растра. Поскольку мы вошли в режим 2D, координаты X и Y являются фактическими координатами 2D, в которых будет происходить рисование.
 
 
Character will be drawn at current raster position. To set desired raster position we call <b>glRasterPos</b> 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:
 
  
 +
Эти новые функции сделают рисование текста очень простым:
 +
<syntaxhighlight lang=pascal>
 
  procedure DrawGLScene; cdecl;
 
  procedure DrawGLScene; cdecl;
 
  begin
 
  begin
Line 722: Line 811:
 
   FrameRendered;
 
   FrameRendered;
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
 
[[Image:BitmapFontsPic1.jpg|thumb]]
 
[[Image:BitmapFontsPic1.jpg|thumb]]
We draw red cube and rotate it, and some text to show how various bitmap fonts look like.
 
<b>glutBitmapLength</b> 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.
+
Мы рисуем красный куб и поворачиваем его, а также немного текста, чтобы показать, как выглядят различные растровые шрифты. Функция <code>glutBitmapLength</code> используется для определения ширины строки, чтобы ее можно было выровнять вправо. Код может быть легко изменен для выравнивая текста по центру.
  
Download source code, linux executable or windows executable from [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
+
{{Note| Посмотрите, как куб выглядит без света.}}
  
==Textures==
+
Загрузите исходный код, исполняемый файл под Linux или Windows с [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
  
It's time to use textures :)
+
==Текстуры==
 +
Пришло время использовать текстуры :)  
  
This tutorial will show how to draw textured polygons and how to blend textures using multipass technic.
+
Этот урок покажет, как рисовать текстурированные полигоны и как смешивать текстуры, используя технику многопроходности. Поскольку в OpenGL нет встроенного механизма загрузки текстур, мы будем использовать внешнюю библиотеку: [http://imaginglib.sourceforge.net/ Vampyre Imaging Library]. Мы будем использовать только вспомогательные функции OpenGL, но вам может пригодиться эта библиотека для некоторых других целей.  
Since OpenGL has no builtin mechanism for loading textures, we'll use external library: [http://imaginglib.sourceforge.net/ 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:
 
  
 +
Давайте начнем ... мы создадим список отображения для рисования текстурированного прямоугольника:
 +
<syntaxhighlight lang=pascal>
 
  procedure CreateList;
 
  procedure CreateList;
 
  begin
 
  begin
Line 755: Line 843:
 
   glEndList;
 
   glEndList;
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
Обратите внимание на функции <code>glTexCoord</code>. Они используются, чтобы указать, какая часть текстуры назначена вершине. Координаты, определенные в этих функциях, имеют значения от 0 до 1 (допустимы значения больше 1, но они могут давать разные результаты). 0 - это первый пиксель, а 1 - последний. Таким образом, 0.5 будет прямо в середине текстуры.
  
Notice <b>glTexCoord</b> 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.
+
Загрузка текстур чрезвычайно проста с Vampyre Imaging Library:
 
+
<syntaxhighlight lang=pascal>
Texture loading is extremely easy with Vampyre Imaging Library:
 
 
 
 
  var
 
  var
 
   Tex1, Tex2: GLuint;
 
   Tex1, Tex2: GLuint;
Line 770: Line 858:
 
   glEnable(GL_TEXTURE_2D);
 
   glEnable(GL_TEXTURE_2D);
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
<code>LoadGLTextureFromFile</code> загружает текстуру из файла и возвращает ее ID. Когда текстура загружена, она уже настроена на рендеринг. Последняя строка просто включает 2D текстуры.
  
<b>LoadGLTextureFromFile</b> 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.
+
<syntaxhighlight lang=pascal>
 
 
To draw textured polygon you have to bind texture and setup texture coordinations (texture coordinations are set in display list in this tutorial):
 
 
 
 
   ...
 
   ...
 
   glLoadIdentity;
 
   glLoadIdentity;
Line 782: Line 869:
 
   glCallList(LIST_OBJECT);
 
   glCallList(LIST_OBJECT);
 
   ...
 
   ...
 +
</syntaxhighlight>
 +
Функция <code>glBindTexture</code> используется для выбора текстуры. Когда вы рисуете полигоны, на них будет выделена текстура. Это так просто :)
  
<b>glBindTexture</b> function is used to select texture. When you draw polygins they will have selected texture on them. It's that easy :)
+
Итак, использовать одну текстуру легко ... но как смешать две текстуры? Обычно вы рисуете полигон один раз с одной текстурой, настраиваете параметры смешивания и еще раз рисуете полигон с другой текстурой. Таким способом вы можете смешивать текстуры. Давайте посмотрим, как выглядит код для этого:
 
+
<syntaxhighlight lang=pascal>
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;
 
   glLoadIdentity;
Line 801: Line 888:
 
   glDisable(GL_BLEND);
 
   glDisable(GL_BLEND);
 
  ...
 
  ...
 
+
</syntaxhighlight>
As you can see, polygon is drawn first time like we allready know. Before second drawing we enable blending by calling <b>glEnable(GL_BLEND)</b>. Blending means that finall pixel color is calculated like this:
+
Как видите, полигон рисуется впервые, как мы уже знаем. Перед вторым рисованием мы включаем смешивание, вызывая <code>glEnable(GL_BLEND)</code>. Смешивание означает, что конечный цвет пикселя рассчитывается следующим образом:
 
+
<syntaxhighlight lang=pascal>
 
  DrawingColor * SRCBLEND + BackgroundColor * DESTBLEND
 
  DrawingColor * SRCBLEND + BackgroundColor * DESTBLEND
 +
</syntaxhighlight>
 +
<code>SRCBLEND</code> и <code>DESTBLEND</code> определяются с помощью функции <code>glBlendFunc</code>. В этом уроке мы устанавливаем для <code>SRCBLEND</code> значение <code>GL_ZERO</code> (ноль) и для <code>DESTBLENT</code> значение <code>GL_SRC_COLOR (DrawingColor)</code>, а затем окончательный цвет:
 +
<syntaxhighlight lang=pascal>
 +
DrawingColor * 0 + BackgroundColor * DrawingColor
 +
</syntaxhighlight>
  
SRCBLEND and DESTBLEND are defined using <b>glBlendFunc</b> 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
 
 
[[Image:TexturesPic1.jpg|thumb]]
 
[[Image:TexturesPic1.jpg|thumb]]
  
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 [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
+
Загрузите исходный код, исполняемый файл для linux или windows с [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 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:
 
  
 +
Давайте посмотрим, как выглядит код:
 +
<syntaxhighlight lang=pascal>
 
  procedure InitializeGL;
 
  procedure InitializeGL;
 
  begin
 
  begin
Line 836: Line 925:
 
   glBindTexture(GL_TEXTURE_2D, Tex2);
 
   glBindTexture(GL_TEXTURE_2D, Tex2);
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
Для начала нам нужно загрузить расширение OpenGL, которое позволит нам использовать мультитекстурные функции. <code>Load_GL_ARB_multitexture</code> попытается загрузить эти расширения и вернет TRUE, если операция прошла успешно.
  
First we need load OpenGL extension that will allow us to use multitexture functions. <b>Load_GL_ARB_multitexture</b> will try to load those extensions and will return TRUE if operation was successful.
+
Чтобы выбрать стадию текстуры, над которой вы хотите работать, используйте функцию <code>glActiveTextureARB</code>. Она принимает только один параметр, который определяет, какая стадия вам нужна. После этого все текстурные функции (включение, отключение, связывание, создание ...) будут влиять на этот этап.  
 
 
To select texture stage you want to work on, use <b>glActiveTextureARB</b> 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:
 
  
 +
Поскольку мы настраиваем все в функции инициализации, все, что нам нужно сделать, это нарисовать объект:
 +
<syntaxhighlight lang=pascal>
 
  procedure DrawGLScene; cdecl;
 
  procedure DrawGLScene; cdecl;
 
  begin
 
  begin
Line 867: Line 956:
 
   glutSwapBuffers;
 
   glutSwapBuffers;
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
 
[[Image:MultitexturePic1.jpg|thumb]]
 
[[Image:MultitexturePic1.jpg|thumb]]
As you can see, difference is only in defining texture coordinations. We now use <b>glMultiTexCoord2fARB</b> function that takes texture stage and texture coordinations. Every thing else is unchanged.
+
Как видите, разница только в определении текстурных координат. Теперь мы используем функцию <code>glMultiTexCoord2fARB</code>, которая принимает стадию текстуры и координаты текстуры. Все остальное без изменений.  
 
 
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 [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
+
Сегодня почти все графические карты поддерживают как минимум 2 стадии текстур. Использование однопроходного мультитекстурирования выполняется быстрее, чем многопроходная версия, поскольку объекты рисуются только один раз за раз. Если оборудование поддерживает однопроходное мультитекстурирование (<code>Load_GL_ARB_multitexture</code> возвращает TRUE), используйте его.  
  
==Render to texture==
+
Загрузите исходный код, исполняемый файл под linux или windows с [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
  
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.
+
==Рендеринг в текстуру==
 
+
Этот будет кратко. OpenGL может захватывать текущую сцену в текстуру, чтобы вы могли использовать ее для текстурирования других объектов (экран телевизора, зеркало или что-то еще). Итак, просто визуализируйте сцену в текстуру и примените ее к вращающейся плоскости.  
First, we must create empty texture which we'll use to capture scene:
 
  
 +
Сначала мы должны создать пустую текстуру, которую мы будем использовать для захвата сцены:
 +
<syntaxhighlight lang=pascal>
 
  procedure SetupRenderTexture;
 
  procedure SetupRenderTexture;
 
  var
 
  var
Line 892: Line 982:
 
   FreeMem(Data);
 
   FreeMem(Data);
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
Создается буфер для изображения размером 256 * 256 RGB, который используется для настройки 2D-текстуры.
  
Buffer for 256*256 RGB image is created and it is used to setup 2D texture.
+
Основная часть - в функции рисования:
 
+
<syntaxhighlight lang=pascal>
Main part is in drawing function:
 
 
 
 
  procedure DrawGLScene; cdecl;
 
  procedure DrawGLScene; cdecl;
 
  var
 
  var
Line 944: Line 1,034:
 
   glutSwapBuffers;
 
   glutSwapBuffers;
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
 
[[Image:RenderToTexturePic1.jpg|thumb]]
 
[[Image:RenderToTexturePic1.jpg|thumb]]
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. <b>glCopyTexImage2D</b> is used to capture scene to currently selected texture.
+
Во-первых, все настроено для сцены, которая будет захвачена. Окно просмотра уменьшено до 256*256, поэтому оно будет вписываться в текстуру, и сцена будет прорисована. <code>glCopyTexImage2D</code> используется для захвата сцены в текущую выбранную текстуру.  
 
 
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 <b>SaveGLTextureToFile</b> function from [http://imaginglib.sourceforge.net/ Vampyre Imaging Library].
+
Когда у нас есть сцена, захваченная в текстуру, все может быть очищено снова, область просмотра может быть возвращена к исходному размеру, а финальная сцена рисуется с использованием предыдущей сцены в качестве текстуры.  
  
Download source code, linux executable or windows executable from [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
+
{{Note|Захваченную текстуру можно сохранить с помощью функции <code>SaveGLTextureToFile</code> из [http://imaginglib.sourceforge.net/ Vampyre Imaging Library].}}
  
==Vertex array==
+
Загрузите исходный код, исполняемый файл для linux или windows с [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
  
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.
+
OpenGL способен отображать примитивы, используя данные, которые хранятся в буферах, вызванных вызовом <code>glVertex</code>. Буферы могут использоваться для определения координат вершин и текстур, а также цветов (индекс и RGBA), нормалей и флагов ребер.
  
First, let's define some types and constants:
+
В этом уроке будут использоваться только вершинные и цветовые буферы, и мы покажем неиндексированное и индексированное рисование. Неиндексированный режим рисует буферы в виде потоков. Индексированный режим будет рисовать элементы буфера в порядке, определенном в индексе буфера. Но хватит говорить ... давайте начнем кодить.
  
 +
Для начала давайте определим некоторые типы и константы:
 +
<syntaxhighlight lang=pascal>
 
  type
 
  type
 
   TVertex3f = record
 
   TVertex3f = record
Line 986: Line 1,078:
 
     (R : 1; G : 1; B : 0)
 
     (R : 1; G : 1; B : 0)
 
   );
 
   );
 +
</syntaxhighlight>
 +
У нас есть два буфера. Один для координат вершин и один для цветов вершин. Эти 6 вершин определяют 2х треугольников, которые образуют прямоугольник.
  
We have two buffers. One for vertex coordinates and one for vertex colors. This 6 vertices defines 2 triangles that forms rectangle.
+
Рисовать примитивы с помощью буферов легко:
 
+
<syntaxhighlight lang=pascal>
Drawing primitives using buffers is easy:
 
 
 
 
   glEnableClientState(GL_VERTEX_ARRAY);
 
   glEnableClientState(GL_VERTEX_ARRAY);
 
   glEnableClientState(GL_COLOR_ARRAY);
 
   glEnableClientState(GL_COLOR_ARRAY);
Line 1,000: Line 1,092:
 
   glDisableClientState(GL_VERTEX_ARRAY);
 
   glDisableClientState(GL_VERTEX_ARRAY);
 
   glDisableClientState(GL_COLOR_ARRAY);
 
   glDisableClientState(GL_COLOR_ARRAY);
 +
</syntaxhighlight>
 +
Сначала мы включаем буферы, которые мы хотим использовать, используя функцию <code>glEnableClientState</code>. Затем мы можем выбрать буферы, которые мы хотим использовать. Каждый тип буфера имеет собственную функцию для выбора (<code>glColorPointer</code>, <code>glEdgeFlagPointer</code>, <code>glIndexPointer</code>, <code>glNormalPointer</code>, <code>glTexCoordPointer</code>, <code>glVertexPointer</code>).
  
First we enable buffers we want to use using <b>glEnableClientState</b> function. Than we can select buffers we want to use. Every buffer type has own function for selecting (<b>glColorPointer</b>, <b>glEdgeFlagPointer</b>, <b>glIndexPointer</b>, <b>glNormalPointer</b>, <b>glTexCoordPointer</b>, <b>glVertexPointer</b>).
+
Первый параметр в этих функциях определяет, сколько чисел содержит каждый элемент. Например, давайте возьмем буфер вершин. Если этот параметр равен 2, то OpenGL ожидает, что каждый элемент в буфере содержит координаты x и y. Если этот параметр равен, например, 4, то каждый элемент должен содержать координаты x, y, z и w. Следующий параметр определяет, какой тип элемента данных содержит (GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT или GL_DOUBLE). Далее определяется количество байтов между каждым элементом. Таким образом, вы можете иметь буфер, который содержит координаты вершины и некоторые пользовательские данные.  
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:
 
  
 +
Для произвольного типа данных этот параметр может быть рассчитан следующим образом:
 +
<syntaxhighlight lang=pascal>
 
  type
 
  type
 
   TBufferData = record
 
   TBufferData = record
Line 1,012: Line 1,105:
 
     DataAfter: TDataAfter;
 
     DataAfter: TDataAfter;
 
   end;
 
   end;
 +
</syntaxhighlight>
 
   
 
   
  Bytes between elements = SizeOf(TDataBefore) + SizeOf(TDataAfter)
+
  Байты между элементами = SizeOf(TDataBefore) + SizeOf(TDataAfter)
 
 
Last parameter if pointer to the begginig of buffer.
 
  
When buffers are selected we can draw them using <b>glDrawArrays</b> 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.
+
Когда буферы выбраны, мы можем нарисовать их, используя функции <code>glDrawArrays</code>. Все включенные буферы используются для рисования примитивов. Тип создаваемых полигонов определяется в первом параметре (так же, как в функции <code>glBegin</code>). Следующие два определяют подмножество буфера, который используется для рисования (start и count).  
  
To demonstrate indexed mode, I made some simple mesh class that can load vertex, color and index data from external files:
+
Когда буферы не нужны, вы можете отключить их.
  
 +
Чтобы продемонстрировать индексированный режим, я создал простой класс сетки, который может загружать данные вершин, цветов и индексов из внешних файлов:
 +
<syntaxhighlight lang=pascal>
 
  type
 
  type
 
   TMesh = class
 
   TMesh = class
Line 1,036: Line 1,130:
 
     procedure DrawMesh;
 
     procedure DrawMesh;
 
   end;
 
   end;
 +
</syntaxhighlight>
 +
Поле FVertices будет содержать данные о вершинах, поле FColors - данные о цвете, и поле FIndices - данные об индексах при загрузке внешнего файла.
  
FVertices will contain data about vertices, FColors data about color and FIndices data about indices when external file is loaded.
+
Сначала мы напишем некоторый код, который занимается созданием и уничтожением класса:
 
+
<syntaxhighlight lang=pascal>
First we'll write some code that deals with creation and destruction of class:
 
 
 
 
  procedure TMesh.FreeBuffers;
 
  procedure TMesh.FreeBuffers;
 
  begin
 
  begin
Line 1,058: Line 1,152:
 
   inherited Destroy;
 
   inherited Destroy;
 
  end;
 
  end;
 +
</syntaxhighlight>
  
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:
+
Файл, который будет содержать данные сетки, является простым текстовым файлом. Первая строка будет содержать количество вершин и индексов, разделенных пробелом. После этого ряда появятся строки для каждой вершины и цвета. X, Y, Z, R, G и B все разделены пробелом. В конце будут строки для индексов ... каждый номер индекса будет записан в своей собственной строке ... поэтому для одного треугольника файл данных будет выглядеть примерно так:
 
+
<syntaxhighlight lang=pascal>
 
  3 3
 
  3 3
 
  -1 -1 0 1 1 1
 
  -1 -1 0 1 1 1
Line 1,068: Line 1,163:
 
  1
 
  1
 
  2
 
  2
 +
</syntaxhighlight>
 +
Это означает, что в файле определены 3 вершины и 3 индекса. Первая вершина имеет значения -1, -1, 0 и цвет, имеющий значения 1, 1, 1 и т.д. Индексы определяют тот порядок, в котором рисуются вершины (в этом случае вершины рисуются в том же порядке, в котором они определены).
  
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).
+
Код для загрузки этих данных будет выглядеть так:
 
+
<syntaxhighlight lang=pascal>
Code for loading this data will loke like this:
 
 
 
 
  procedure TMesh.LoadMesh(FileName: String);
 
  procedure TMesh.LoadMesh(FileName: String);
 
  var
 
  var
Line 1,100: Line 1,195:
 
   CloseFile(MeshFile);
 
   CloseFile(MeshFile);
 
  end;
 
  end;
 +
</syntaxhighlight>
  
After loading data, we have everything for drawing:
+
После загрузки данных у нас есть все для рисования:
 
+
<syntaxhighlight lang=pascal>
 
  procedure TMesh.DrawMesh;
 
  procedure TMesh.DrawMesh;
 
  begin
 
  begin
Line 1,115: Line 1,211:
 
   glDisableClientState(GL_COLOR_ARRAY);
 
   glDisableClientState(GL_COLOR_ARRAY);
 
  end;
 
  end;
 +
</syntaxhighlight>
 +
Как видите, почти все то же самое, что и для неиндексированного рисования, за исключением функции, которая фактически рисует многоугольники. В этом случае мы используем функцию <code>glDrawElements</code>. Для этого мы указываем, какой тип полигонов мы хотим нарисовать, сколько индексов находится в буфере индекса, тип данных в буфере индекса и указатель на начало буфера индекса.
  
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 <b>glDrawElements</b> 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.
+
[[Image:VertexArrayPic1.jpg|thumb]]Полный исходный код поставляется с файлом данных сетки, который этот класс может использовать для создания прямоугольника, идентичного прямоугольнику, нарисованному в неиндексированном режиме. Файл данных сетки выглядит так:
 
+
<syntaxhighlight lang=pascal>
[[Image:VertexArrayPic1.jpg|thumb]]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
 
  4 6
 
  1 1 0 1 0 1
 
  1 1 0 1 0 1
Line 1,131: Line 1,227:
 
  2
 
  2
 
  3
 
  3
 +
</syntaxhighlight>
 +
 +
Как видите, есть данные только для 4 вершин и 6 индексов. Итак, первый треугольник определяется вершинами 0, 1 и 2, а второй - вершинами 0, 2 и 3. Используя индексированный режим, нам не нужно дублировать вершины.
 +
 +
Загрузите исходный код, исполняемый файл для linux или windows с [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
 +
 +
=Современный OpenGL с использованием MacOS=
 +
 +
Отличная особенность Lazarus - «написав однажды, компилируй где угодно», где код должен работать как на MacOS, так и на Linux или Windows. Однако для поддержки всех трех из этих систем вам необходимо решить, поддерживать ли только устаревший OpenGL или использовать профиль «OpenGL Core».
 +
 +
Для пользователей Linux и Windows последние версии OpenGL представляют собой расширенный набор старых версий. Поэтому пользователь Linux может смешивать и сопоставлять старый код OpenGL с современными шейдерами. Но только '''не''' для случая с пользователями Macintosh MacOS (OSX).
 +
 +
MacOS обеспечивает две формы поддержки: унаследованный режим поддерживает все функции OpenGL до 2.1 (и GLSL 1.2). Кроме того, пользователь может выбрать современную '''core'''-версию OpenGL (тогда как большинство пользователей Linux и Windows имеют доступ к «совместимым» версиям OpenGL).
  
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.
+
[https://prideout.net/modern-opengl-prezo/ '''Сore'''-версия OpenGL] удаляет многие устаревшие унаследованные функции. Это означает, что все приведенные выше учебники будут компилироваться только в устаревшем режиме MacOS. Режим ядра не имеет конвейера с фиксированными функциями, и поэтому разработчик должен написать свои собственные шейдеры. Кроме того, режим Core удаляет некоторые базовые примитивы, такие как [https://www.khronos.org/opengl/wiki/Legacy_OpenGL <code>GL_QUADS</code>], поэтому квадрат необходимо заменить двумя треугольниками (это действительно имеет смысл: все вершины треугольника копланарны (расположены в одной плоскости), но это не обязательно имеет место для четырехугольника, точно так же, как каждая ножка треноги всегда будет касаться земли, в то время как 4х-ногая табуретка может колебаться, если одна нога короче других).  
  
Download source code, linux executable or windows executable from [http://sourceforge.net/project/showfiles.php?group_id=92177&package_id=199145 Lazarus CCR SourceForge].
+
Преимущество базовой модели состоит в том, что она проста и, как правило, легко адаптируется к мобильным устройствам (которые используют аналогичную встроенную форму OpenGL). См. [Https://developer.apple.com/opengl/capabilities/ веб-сайт Apple], чтобы увидеть OpenGL, поддерживаемый вашей операционной системой. Еще одна проблема заключается в том, что основные режимы OpenGL поддерживаются только в наборе виджетов Cocoa, поэтому вы не можете использовать набор виджетов Carbon (который в настоящее время используется по умолчанию в Lazarus).  
  
 +
Три демонстрационных проекта доступны на [https://github.com/neurolabusc/OpenGLCoreTutorials Github OpenGLCoreTutorials] и будут компилироваться с использованием Lazarus 1.6.2 или более поздней версии для Linux, Windows или MacOS.
  
 
=See also=
 
=See also=
  
* [[Creating bindings for C libraries]]
+
* [[Creating_bindings_for_C_libraries/ru|Создание привязок для библиотек C]]
 
* [[OpenGL]]
 
* [[OpenGL]]
  
=External links=
+
=Внешние ссылки=
  
* [http://www.falloutsoftware.com/tutorials/gl/gl8.htm OpenGL Light Tutorial] - An OpenGL tutorial outlining how to create and manipulate lights, as well as deal with polygon surface reflections using normals.
+
* [http://www.falloutsoftware.com/tutorials/gl/gl8.htm OpenGL Light Tutorial] - Учебное пособие по OpenGL, в котором описывается, как создавать источники света и управлять ими, а также справляться с отражениями поверхности полигона с помощью нормалей.
 +
* [https://github.com/danginsburg/opengles3-book opengles3-book]
 +
* [https://github.com/zilongshanren/opengl-tutorials opengl-tutorials]
  
 +
{{AutoCategory}}
 +
[[Category:Russian (unfinished translation)]]
 +
[[Category:Graphics/ru]]
 +
[[Category:OpenGL/ru]]
 +
[[Category:Code/ru]]
 
[[Category:Example programs/ru]]
 
[[Category:Example programs/ru]]
 
[[Category:Tutorials/ru]]
 
[[Category:Tutorials/ru]]

Latest revision as of 14:28, 6 November 2019

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.

GLFW

См. http://www.glfw.org/

LCL

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

LCL / GLFW / GLUT

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

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

Часть OpenGL почти такая же. Для GLUT нужны dll под windows, а LCL обычно работает "из коробки", но и исполняемый файл имеет больший размер.

Примеры

Ваша первая LCL-программа

Использование LCL, как правило, является самым простым способом доступа к OpenGL с помощью Lazarus. Поскольку GLUT устарела, использование LCL - это хорошая идея для нового проекта OpenGL Lazarus. Большинство примеров кода, описанных ниже для GLUT, легко перевести в код LCL, хотя вам нужно будет найти эквиваленты для функций с префиксом 'glut', например, вместо "glutSwapBuffers" мы будем использовать LCL-свойство "SwapBuffers" для отображения нашего рендеринга. Единственная замечательная функция, которую обеспечивает GLUT и которую трудно сделать с LCL - это отображение текста на экране (см. Раздел «Растровые шрифты» GLUT ниже). Однако, поскольку это ваша первая LCL-программа, мы немного упростим ее, не отображая текст.

Lazarus поставляется с примером программы OpenGL, вы можете найти ее в папке Lazarus/Examples/openglcontrol. Этот пример демонстрирует множество мощных функций для создания анимированного изображения OpenGL. Тем не менее, это также относительно сложная программа. Ниже приведен минимальный проект Lazarus, который имитирует некоторые функции, описанные в примерах GLUT, описанных ниже. Чтобы создать его, запустите Lazarus и выберите Project/NewProject, чтобы создать новое приложение. Выберите пункт меню Project/ProjectInspector, нажмите кнопку 'Add..' (Добавить ...), перейдите к 'New Requirement' (Новое требование) и пакету «LazOpenGLContext». Затем вставьте приведенный ниже код в ваш 'unit1.pas'. Затем щелкните форму и на вкладке событий инспектора объектов свяжите событие «OnCreate» с функцией «FormCreate». Теперь вы сможете запустить новое приложение, выбрав пункт меню Run/Run (Выполнить/запустить).

Код создает новую панель OpenGL, которая заполняет форму. OpenGL рисует простой треугольник на форме. Обратите внимание, что при запуске приложения вы можете изменить размер формы, и треугольник, чтобы заполнить форму, масштабируется пропорционально.

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, OpenGLContext, gl;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure GLboxPaint(Sender: TObject);
  private
    GLBox: TOpenGLControl;
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

procedure TForm1.GLboxPaint(Sender: TObject);
begin
  glClearColor(0.27, 0.53, 0.71, 1.0); // Задаем синий фон
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glLoadIdentity;
  glBegin(GL_TRIANGLES);
    glColor3f(1, 0, 0);
    glVertex3f( 0.0, 1.0, 0.0);
    glColor3f(0, 1, 0);
    glVertex3f(-1.0,-1.0, 0.0);
    glColor3f(0, 0, 1);
    glVertex3f( 1.0,-1.0, 0.0);
  glEnd;
  GLbox.SwapBuffers;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  GLbox := TOpenGLControl.Create(Self);
  GLbox.AutoResizeViewport := true;
  GLBox.Parent             := Self;
  GLBox.MultiSampling      := 4;
  GLBox.Align              := alClient;
  GLBox.OnPaint            := @GLboxPaint; // для "mode delphi" должно быть "GLBox.OnPaint := GLboxPaint"
  GLBox.invalidate;
end;

end.

Ваша первая 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: вероятно достаточно glutInit(@argc, @argv);.


Подробнее о 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: неверный перевод

Подробнее о 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.

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

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

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

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

 begin 
   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 сбросит матрицу. Поскольку почти все матричные функции умножают текущую матрицу на сгенерированную, иногда необходимо очистить матрицу с помощью этой функции.

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

Теперь вы измените матрицу модели ... на этот раз вы просто установите ее на идентичность.

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

 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: Список обрабатывается как массив двухбайтовых целых чисел без знака, каждое из которых находится в диапазоне от 0 до 65535.

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

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

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:

Так как в этот раз мы не хотим, чтобы GLUT анализировал командную строку, мы вызываем glutInitPascal с параметром False. Как видите, нет кода для создания окна. GLUT имеет glutEnterGameMode, который создает полноразмерное окно. Чтобы указать, какой полноэкранный режим вы хотите, вы вызываете функцию glutGameModeString, которая принимает строку, которая определяет режим, который вам нравится.

Формат этой строки:

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

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

В строке 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 создает матрицу, которая будет определять, откуда вы смотрите на объекты. Первые 3 параметра - это координаты X, Y и Z положения камеры. Следующие 3 параметра - это координаты X, Y и Z точки, на которую смотрит камера, а последние 3 параметра определяют вектор «вверх» (где «вверх» для камеры). Обычно up - это положительная ось 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 создает матрицу вращения. Первый параметр - это угол в градусах, а последние 3 параметра определяют ось, вокруг которой будет выполняться вращение. Поскольку вы умножили угол на T, объект будет повернут на этот угол ровно за 1 секунду.

Загрузите исходный код, исполняемый файл linux или исполняемый файл windows из Lazarus CCR SourceForge.

Освещение

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

На данный момент урок будет иметь только базовые функции, которые помогут нам получить текущее время и дельту (время, прошедшее от одного рендеринга до другого вызова рендеринга), и рассчитать количество кадров в секунду.

 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 должен вызываться каждый раз, когда вы рисуете сцену, чтобы функция могла рассчитывать FPS.

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


Окружающий свет (ambient) - это что-то вроде Солнца. Когда солнечные лучи проходят через окно комнаты, они ударяются о стены и отражаются во всех направлениях, что в среднем осветляет всю комнату. Все вершины освещены окружающим светом.

Рассеянный свет(diffuse) может быть представлен как параллельные световые лучи, исходящие издалека. Они будут освещать только вершины, ориентированные на источник света.

Точечный свет(point) освещает все вокруг себя. Он похож на огненный шар, он посылает световые лучи вокруг себя и освещает вершины, которые ориентированы на источник света и достаточно близки.

Пятно (spot) - как свет от фонарика. Это просто точечный источник света с небольшим радиусом светового конуса. Все вершины, которые попадают внутрь конуса и находятся достаточно близко, подсвечиваются.

Зеркальный свет (specular), как и рассеянный свет, является направленным типом света. Это происходит из одного конкретного направления. Разница между ними заключается в том, что зеркальный свет отражается от поверхности острым и равномерным образом. Рендеринг зеркального света зависит от угла между зрителем и источником света. С точки зрения зрителя зеркальный свет создает выделенную область на поверхности наблюдаемого объекта, известную как зеркальное выделение или зеркальное отражение.

Излучение (emissive) - свет немного отличается от любых других ранее объясненных компонентов света. Этот свет исходит от объекта, который вы рисуете, но не освещаете другие объекты поблизости.

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

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

 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);

Как видите, мы включаем подсветку в OpenGL, чтобы свет влиял на сцену, которую вы рендерили. Параметры освещения задаются функцией glLightfv. Потребуется 3 параметра ... один для числа освещения, которое вы хотите изменить (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 изменяет окружающий и рассеянный материал. Мы обсудим материалы больше в следующих уроках.

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

После всех этих настроек, свет будет сиять в вашем кубе :)

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

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

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

Игры и программы, как правило, должны писать текст на экране. GLUT предоставляет несколько функций, которые не зависят от платформы, для рисования символов.

Сначала мы покажем, как использовать стандартные растровые шрифты. Почти все дополнения кода будут сделаны в модуле utils.pas.

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

 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;

Мы просто получаем left/right, top/bottom и вычисляем width/height, вычитая их.

Должны быть функции для входа и выхода из режима 2D:

 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;

При входе в режим 2D мы сохраняем текущие матрицы и устанавливаем 2D-матрицу с помощью функции gluOrtho2D. Таким образом, если мы нарисуем какую-либо штуку в координатах (100,100), она будет нарисована точно на 100 пикселей от левого края окна и на 100 пикселей от нижнего края формы (положительные значения по оси Y направлены вверх). Также мы отключаем ZBuffer. Таким образом, текст не изменит ZBuffer.

Выход из режима 2D просто возвращает старые матрицы и включает ZBuffer.

Теперь мы можем создать функцию для рисования текста:

 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 может рисовать только один символ выбранного шрифта. Первый параметр - желаемый шрифт (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 или GLUT_BITMAP_HELVETICA_18) и другие - являются символом.

Символ будет нарисован в текущей позиции растра. Чтобы установить желаемую позицию растра, мы вызываем функцию glRasterPos. glRasterPos может обрабатывать различные количества и типы параметров так же, как функция glVertex. Указанная координата преобразуется моделью и матрицей проекции, чтобы получить двухмерную координату, в которой будет находиться новая позиция растра. Поскольку мы вошли в режим 2D, координаты X и Y являются фактическими координатами 2D, в которых будет происходить рисование.

Эти новые функции сделают рисование текста очень простым:

 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

Мы рисуем красный куб и поворачиваем его, а также немного текста, чтобы показать, как выглядят различные растровые шрифты. Функция glutBitmapLength используется для определения ширины строки, чтобы ее можно было выровнять вправо. Код может быть легко изменен для выравнивая текста по центру.

Light bulb  Примечание: Посмотрите, как куб выглядит без света.

Загрузите исходный код, исполняемый файл под Linux или Windows с Lazarus CCR SourceForge.

Текстуры

Пришло время использовать текстуры :)

Этот урок покажет, как рисовать текстурированные полигоны и как смешивать текстуры, используя технику многопроходности. Поскольку в OpenGL нет встроенного механизма загрузки текстур, мы будем использовать внешнюю библиотеку: Vampyre Imaging Library. Мы будем использовать только вспомогательные функции OpenGL, но вам может пригодиться эта библиотека для некоторых других целей.

Давайте начнем ... мы создадим список отображения для рисования текстурированного прямоугольника:

 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;

Обратите внимание на функции glTexCoord. Они используются, чтобы указать, какая часть текстуры назначена вершине. Координаты, определенные в этих функциях, имеют значения от 0 до 1 (допустимы значения больше 1, но они могут давать разные результаты). 0 - это первый пиксель, а 1 - последний. Таким образом, 0.5 будет прямо в середине текстуры.

Загрузка текстур чрезвычайно проста с 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 загружает текстуру из файла и возвращает ее ID. Когда текстура загружена, она уже настроена на рендеринг. Последняя строка просто включает 2D текстуры.

Чтобы нарисовать текстурированный многоугольник, вы должны связать текстуру и установить координаты текстуры (координаты текстуры задаются в списке отображения в этом уроке):

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

Функция glBindTexture используется для выбора текстуры. Когда вы рисуете полигоны, на них будет выделена текстура. Это так просто :)

Итак, использовать одну текстуру легко ... но как смешать две текстуры? Обычно вы рисуете полигон один раз с одной текстурой, настраиваете параметры смешивания и еще раз рисуете полигон с другой текстурой. Таким способом вы можете смешивать текстуры. Давайте посмотрим, как выглядит код для этого:

   ...
   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);
 ...

Как видите, полигон рисуется впервые, как мы уже знаем. Перед вторым рисованием мы включаем смешивание, вызывая glEnable(GL_BLEND). Смешивание означает, что конечный цвет пикселя рассчитывается следующим образом:

 DrawingColor * SRCBLEND + BackgroundColor * DESTBLEND

SRCBLEND и DESTBLEND определяются с помощью функции glBlendFunc. В этом уроке мы устанавливаем для SRCBLEND значение GL_ZERO (ноль) и для DESTBLENT значение GL_SRC_COLOR (DrawingColor), а затем окончательный цвет:

 DrawingColor * 0 + BackgroundColor * DrawingColor
TexturesPic1.jpg

Это означает, что фон будет темнее, когда вы рисуете темными цветами ... когда вы рисуете белым цветом, цвет фона не изменится. Результат будет выглядеть следующим образом.

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

Загрузите исходный код, исполняемый файл для linux или windows с Lazarus CCR SourceForge.

Мультитекстурирование (расширения)

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

Давайте посмотрим, как выглядит код:

 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;

Для начала нам нужно загрузить расширение OpenGL, которое позволит нам использовать мультитекстурные функции. Load_GL_ARB_multitexture попытается загрузить эти расширения и вернет TRUE, если операция прошла успешно.

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

Поскольку мы настраиваем все в функции инициализации, все, что нам нужно сделать, это нарисовать объект:

 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

Как видите, разница только в определении текстурных координат. Теперь мы используем функцию glMultiTexCoord2fARB, которая принимает стадию текстуры и координаты текстуры. Все остальное без изменений.

Сегодня почти все графические карты поддерживают как минимум 2 стадии текстур. Использование однопроходного мультитекстурирования выполняется быстрее, чем многопроходная версия, поскольку объекты рисуются только один раз за раз. Если оборудование поддерживает однопроходное мультитекстурирование (Load_GL_ARB_multitexture возвращает TRUE), используйте его.

Загрузите исходный код, исполняемый файл под linux или windows с Lazarus CCR SourceForge.

Рендеринг в текстуру

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

Сначала мы должны создать пустую текстуру, которую мы будем использовать для захвата сцены:

 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;

Создается буфер для изображения размером 256 * 256 RGB, который используется для настройки 2D-текстуры.

Основная часть - в функции рисования:

 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

Во-первых, все настроено для сцены, которая будет захвачена. Окно просмотра уменьшено до 256*256, поэтому оно будет вписываться в текстуру, и сцена будет прорисована. glCopyTexImage2D используется для захвата сцены в текущую выбранную текстуру.

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

Light bulb  Примечание: Захваченную текстуру можно сохранить с помощью функции SaveGLTextureToFile из Vampyre Imaging Library.

Загрузите исходный код, исполняемый файл для linux или windows с Lazarus CCR SourceForge.

Массив вершин

OpenGL способен отображать примитивы, используя данные, которые хранятся в буферах, вызванных вызовом glVertex. Буферы могут использоваться для определения координат вершин и текстур, а также цветов (индекс и RGBA), нормалей и флагов ребер.

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

Для начала давайте определим некоторые типы и константы:

 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)
   );

У нас есть два буфера. Один для координат вершин и один для цветов вершин. Эти 6 вершин определяют 2х треугольников, которые образуют прямоугольник.

Рисовать примитивы с помощью буферов легко:

   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);

Сначала мы включаем буферы, которые мы хотим использовать, используя функцию glEnableClientState. Затем мы можем выбрать буферы, которые мы хотим использовать. Каждый тип буфера имеет собственную функцию для выбора (glColorPointer, glEdgeFlagPointer, glIndexPointer, glNormalPointer, glTexCoordPointer, glVertexPointer).

Первый параметр в этих функциях определяет, сколько чисел содержит каждый элемент. Например, давайте возьмем буфер вершин. Если этот параметр равен 2, то OpenGL ожидает, что каждый элемент в буфере содержит координаты x и y. Если этот параметр равен, например, 4, то каждый элемент должен содержать координаты x, y, z и w. Следующий параметр определяет, какой тип элемента данных содержит (GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT или GL_DOUBLE). Далее определяется количество байтов между каждым элементом. Таким образом, вы можете иметь буфер, который содержит координаты вершины и некоторые пользовательские данные.

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

 type
   TBufferData = record
     DataBefore: TDataBefore;
     Vertex: TVertex;
     DataAfter: TDataAfter;
   end;
Байты между элементами = SizeOf(TDataBefore) + SizeOf(TDataAfter)

Последний параметр - указатель на начало буфера.

Когда буферы выбраны, мы можем нарисовать их, используя функции glDrawArrays. Все включенные буферы используются для рисования примитивов. Тип создаваемых полигонов определяется в первом параметре (так же, как в функции glBegin). Следующие два определяют подмножество буфера, который используется для рисования (start и count).

Когда буферы не нужны, вы можете отключить их.

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

 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 будет содержать данные о вершинах, поле FColors - данные о цвете, и поле FIndices - данные об индексах при загрузке внешнего файла.

Сначала мы напишем некоторый код, который занимается созданием и уничтожением класса:

 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;

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

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

Это означает, что в файле определены 3 вершины и 3 индекса. Первая вершина имеет значения -1, -1, 0 и цвет, имеющий значения 1, 1, 1 и т.д. Индексы определяют тот порядок, в котором рисуются вершины (в этом случае вершины рисуются в том же порядке, в котором они определены).

Код для загрузки этих данных будет выглядеть так:

 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;

После загрузки данных у нас есть все для рисования:

 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;

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

VertexArrayPic1.jpg

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

 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

Как видите, есть данные только для 4 вершин и 6 индексов. Итак, первый треугольник определяется вершинами 0, 1 и 2, а второй - вершинами 0, 2 и 3. Используя индексированный режим, нам не нужно дублировать вершины.

Загрузите исходный код, исполняемый файл для linux или windows с Lazarus CCR SourceForge.

Современный OpenGL с использованием MacOS

Отличная особенность Lazarus - «написав однажды, компилируй где угодно», где код должен работать как на MacOS, так и на Linux или Windows. Однако для поддержки всех трех из этих систем вам необходимо решить, поддерживать ли только устаревший OpenGL или использовать профиль «OpenGL Core».

Для пользователей Linux и Windows последние версии OpenGL представляют собой расширенный набор старых версий. Поэтому пользователь Linux может смешивать и сопоставлять старый код OpenGL с современными шейдерами. Но только не для случая с пользователями Macintosh MacOS (OSX).

MacOS обеспечивает две формы поддержки: унаследованный режим поддерживает все функции OpenGL до 2.1 (и GLSL 1.2). Кроме того, пользователь может выбрать современную core-версию OpenGL (тогда как большинство пользователей Linux и Windows имеют доступ к «совместимым» версиям OpenGL).

Сore-версия OpenGL удаляет многие устаревшие унаследованные функции. Это означает, что все приведенные выше учебники будут компилироваться только в устаревшем режиме MacOS. Режим ядра не имеет конвейера с фиксированными функциями, и поэтому разработчик должен написать свои собственные шейдеры. Кроме того, режим Core удаляет некоторые базовые примитивы, такие как GL_QUADS, поэтому квадрат необходимо заменить двумя треугольниками (это действительно имеет смысл: все вершины треугольника копланарны (расположены в одной плоскости), но это не обязательно имеет место для четырехугольника, точно так же, как каждая ножка треноги всегда будет касаться земли, в то время как 4х-ногая табуретка может колебаться, если одна нога короче других).

Преимущество базовой модели состоит в том, что она проста и, как правило, легко адаптируется к мобильным устройствам (которые используют аналогичную встроенную форму OpenGL). См. веб-сайт Apple, чтобы увидеть OpenGL, поддерживаемый вашей операционной системой. Еще одна проблема заключается в том, что основные режимы OpenGL поддерживаются только в наборе виджетов Cocoa, поэтому вы не можете использовать набор виджетов Carbon (который в настоящее время используется по умолчанию в Lazarus).

Три демонстрационных проекта доступны на Github OpenGLCoreTutorials и будут компилироваться с использованием Lazarus 1.6.2 или более поздней версии для Linux, Windows или MacOS.

See also

Внешние ссылки

  • OpenGL Light Tutorial - Учебное пособие по OpenGL, в котором описывается, как создавать источники света и управлять ими, а также справляться с отражениями поверхности полигона с помощью нормалей.
  • opengles3-book
  • opengl-tutorials