5.2 Визуализация 2D примитивов в OpenGL. Основы.

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

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

Важные темы, рассматриваемые в данной части главы:
  1. Работа с 2D примитивами в OpenGL и С#.
  2. Первая OpenGL визуализация 2D примитивов.
  3. Визуализация буквы с помощью OpenGL 2D примитивов.

Работа с 2D примитивами

Начнем с того, как происходит рисование в компьютерной графике.

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

Другое определение полигона: полигон – это некая область в пространстве, которая ограничивается одной замкнутой ломаной линией, причем каждый из отрезков данной ломаной линии задается с помощью двух вершин на его концах. Но несложно догадаться, что описанный таким образом полигон может оказаться очень сложным геометрическим объектом.

Поэтому нельзя, чтобы ребра полигонов пересекались, а также он обязательно должен быть выпуклым.
Если соблюдать эти два условия, нет никаких ограничений на то, из скольких точек стоит полигон.
Для рисования примитивов в OpenGL мы будем использовать различные режимы визуализации, но перед тем как перейти к их изучению, сначала рассмотрим, как происходит указание вершин в OpenGl.

Для указания вершин используется специальная команда: glVertex*()

А имеет следующий формат:
/*http://esate.ru, Anvi*/

void glVertex {234} {ifd}(TYPE coords); 


Здесь 2, 3, 4 – это количество координат, которыми вы будете задавать вершину. В OpenGL точки на самом деле задаются 4 координатами: x, y, z, r. Таким образом, положение точки описывается как x/r, y/r, z/r. Когда r не указывается, оно принимается равным единице. Если указываются точки в двухмерной системе координат, z считается равным нулю.

i, f, d – это возможный тип принимаемых значений. К примеру, если написать команду glVertex3f (,,) то скобках должны последовать три переменные типа float. Для ddouble, для I – int.

Вызов функции glVertex может иметь результат только между вызовами функций glBegin() и glEnd(), о которых мы сейчас и поговорим.

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

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

Значения параметров, которые может принимать glBegin:

GL_POINTS. На месте каждой указанной с помощью команды glVertex вершины рисуется точка.

GL_LINES. Каждые 2 вершины объединяются в отрезок.

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

GL_LINE_LOOP. То же, что и GL_LINE_STRIP, но от последней точки будет идти отрезок к начальной точке.

GL_TRIANGLES. Каждые три вершины объединятся в треугольник.

GL_TRIANGLE_STRIP. Треугольники рисуются таким образом, что последняя грань треугольника является начальной гранью следующего (смотрите рисунок).

GL_TRIANGLE_FAN. То же, что и GL_TRIANGLE_STRIP, только изменен порядок следования вершин (смотрите рисунок).

GL_QUADS. Каждые четыре вершины объединяются в квадрат.

GL_QUAD_STRIP. Вершины объединяются в квадраты, причем последняя грань квадрата является первой гранью следующего (смотрите рисунок).

GL_POLYGON. Рисуется полигон на основе вершин. Вершин не должно быть менее 3 и должны отсутствовать самопересечения, а также должны сохраняться условия выпуклости.

Первая OpenGL визуализация 2D примитивов

Теперь попробуем нарисовать какой-либо рисунок. Для этого создайте приложение, основываясь на опыте, полученном в главе 4.4 (то есть подключите необходимые ссылки к библиотекам Tao, подлечите необходимые пространства имен, расположите элементы на окне, а также назначьте необходимые обработчики событий и инициализируйте необходимые элементы).

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

Перейдя к свойствам формы, измените название окна на «Рисование 2D примитивов».

Теперь рассмотрим код функции Form1_Load. Он будет выглядеть следующим образом:

/*http://esate.ru, Anvi*/

private void Form1_Load(object sender, EventArgs e) 
{ 

// инициализация Glut 
Glut.glutInit(); 
Glut.glutInitDisplayMode(Glut.GLUT_RGB | Glut.GLUT_SINGLE); 

// очистка окна 
Gl.glClearColor(255, 255, 255, 1); 

// установка порта вывода в соответствии с размерами элемента anT 
Gl.glViewport(0, 0, AnT.Width, AnT.Height); 

// настройка проекции 
Gl.glMatrixMode(Gl.GL_PROJECTION); 
Gl.glLoadIdentity(); 

  // теперь необходимо корректно настроить 2D ортогональную проекцию 
  // в зависимости от того, какая сторона больше 
  // мы немного варьируем то, как будет сконфигурированный настройки проекции 
  if ((float)AnT.Width <= (float)AnT.Height) 
  { 
    Glu.gluOrtho2D(0.0, 30.0 * (float)AnT.Height / (float)AnT.Width, 0.0, 30.0);
  } 
  else 
  { 
    Glu.gluOrtho2D(0.0, 30.0 * (float)AnT.Width / (float)AnT.Height, 0.0, 30.0);
  } 

Gl.glMatrixMode(Gl.GL_MODELVIEW); 
Gl.glLoadIdentity(); 

} 


Как видно из кода, за настройку 2D ортогональной проекции отвечает функция gluOrtho2D, реализация которой предоставлена библиотекой Glu. Эта функция, как бы помещает начало координат в самый левый нижний квадрат, а камера (наблюдатель) в таком случае находиться на оси Z, и таким образом мы визуализируем графику в 2D режиме.

По сути, мы должны передать в качестве параметров координаты области видимости окном проекции: left , right, bottom и top. В нашем случае мы передаем параметры таким образом, чтобы исключить искажения, связанные с тем, что область вывода не квадратная, поэтому параметр top рассчитывается как произведение 30 и отношения высоты элемента AnT (в котором будет идти визуализация) к его ширине.

В том же случае, если высота больше ширины, то наоборот: отношением ширины к высоте.

Теперь рассмотрим код функции button1_Click.

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

После этого мы перерисовываем содержимое элемента AnT.

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

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

/*http://esate.ru, Anvi*/

private void button1_Click(object sender, EventArgs e) 
{ 

  // очищаем буфер цвета 
  Gl.glClear(Gl.GL_COLOR_BUFFER_BIT); 

  // очищаем текущую матрицу 
  Gl.glLoadIdentity(); 
  // устанавливаем текущий цвет - красный 
  Gl.glColor3f(255, 0, 0); 


  // активируем режим рисования линий, на основе 
  // последовательного соединения всех вершин в отрезки 
  Gl.glBegin(Gl.GL_LINE_STRIP); 

  // первая вершина будет находиться в начале координат 
  Gl.glVertex2d(0, 0); 

  // теперь в зависимости от того, как была определена проекция 
  if ((float)AnT.Width <= (float)AnT.Height) 
  {
    // рисуем вторую вершину в противоположенном углу 
    Gl.glVertex2d(30.0f * (float)AnT.Height / (float)AnT.Width, 30);
  } 
  else 
  { 
    // рисуем вторую вершину в противоположенном углу 
    Gl.glVertex2d(30.0f * (float)AnT.Width / (float)AnT.Height, 30);
  } 


  // завершаем режим рисования 
  Gl.glEnd(); 


  // дожидаемся конца визуализации кадра 
  Gl.glFlush(); 

  // посылаем сигнал перерисовки элемента AnT. 
  AnT.Invalidate();

} 


Теперь, если откомпилировать проект и запустить приложение (F5), а затем нажать на кнопку «визуализировать», мы увидим следующий результат (рис. 1).
Уроки OpenGL + C#: Пример визаулизации линии Рисунок 1. Пример визаулизации линии.

Визуализация буквы с помощью OpenGL 2D примитивов

Теперь заменим код:

/*http://esate.ru, Anvi*/

Gl.glBegin(Gl.GL_LINE_STRIP); 

// первая вершина будет находиться в начале координат 
Gl.glVertex2d(0, 0); 

// теперь в зависимости от того, как была определена проекция 
if ((float)AnT.Width <= (float)AnT.Height) 
{ 
  // рисуем вторую вершину в противоположенном углу 
  Gl.glVertex2d(30.0f * (float)AnT.Height / (float)AnT.Width, 30);
} 
else 
{
  // рисуем вторую вершину в противоположенном углу 
  Gl.glVertex2d(30.0f * (float)AnT.Width / (float)AnT.Height, 30);
} 

// завершаем режим рисования 
Gl.glEnd();



На код, который будет рисовать букву А.

Сейчас все заключается в том, чтобы правильно указать координаты вершин. Буква будет рисовать с помощью 2 кривых (первая кривая выделена красным цветом, вторая кривая - синим).

То, как выглядит буква и координаты соответствующих вершин, вы можете видеть на рисунке 2.
Уроки OpenGL + C#: 2D координаты точек и последовательность их соединений Рисунок 2. 2D координаты точек и последовательность их соединений.
Код рисования буквы А (буква будет рисоваться двумя линиями).

/*http://esate.ru, Anvi*/

// активируем режим рисования линий на основе 
// последовательного соединения всех вершин в отрезки 
Gl.glBegin(Gl.GL_LINE_LOOP); 
// первая вершина будет находиться в начале координат 

Gl.glVertex2d(8, 7); 
Gl.glVertex2d(15, 27); 
Gl.glVertex2d(17, 27); 
Gl.glVertex2d(23, 7); 
Gl.glVertex2d(21, 7); 
Gl.glVertex2d(19, 14); 
Gl.glVertex2d(12.5, 14); 
Gl.glVertex2d(10, 7); 

// завершаем режим рисования 
Gl.glEnd(); 

// вторая линия 

Gl.glBegin(Gl.GL_LINE_LOOP); 

Gl.glVertex2d(18.5, 16); 
Gl.glVertex2d(16, 25); 
Gl.glVertex2d(13.2, 16); 

// завершаем режим рисования 
Gl.glEnd(); 


Пример нарисованной в программе буквы – на рисунке 3.
Уроки OpenGL + C#: Результат работы функции, визуализирующей 2D кривые Рисунок 3. Результат работы функции, визуализирующей 2D кривые.
Мы познакомились с основными принципами рисования примитивов в OpenGL.

В следующей части главы мы визуализируем треугольный полигон с разложенным по нему цветовым спектром, причем в окне программы будут иметься ползунки, с помощью которых можно будет управлять коэффициентами, влияющими на разложение спектра.
Добавить комментарий
Расширенный режим добавления комментариев доступен на форуме: загрузка изображений, цитирование, форматирование текста, и т.д.
Ваше имя:
Текст сообщения:
Комментарии (6):
Nickname
Nickname,  
Добрый день!
Очень полезный материал, спасибо большое. Единственное, что сильно режет глаз, это слово "будите" вместо "будете". Этой заметкой ни в коем случае не хотел обидеть автора :)
Anvi
Anvi,  
Привет, спасибо за отзыв :).

Да, грамотность страдает.
OpenGL + C# - это последний раздел, который еще не закончен только начат редактором, но проверка идет и весь этот ужас вычистим :D
bolevik
bolevik,  
Спасибо! Иду по шагам по инструкции. Понятно и получается.
Anna
Anna,  
Никак не могу понять, зачем умножать на 30?
Gl.glVertex2d(30.0f * (float)AnT.Height / (float)AnT.Width, 30);
noname
noname,  
Цитата
Anna написал:
Никак не могу понять, зачем умножать на 30?
Gl.glVertex2d(30.0f * (float)AnT.Height / (float)AnT.Width, 30);
Если вопрос еще актуален:
Код
Glu.gluOrtho2D(0.0, 30.0 * (float)AnT.Height / (float)AnT.Width, 0.0, 30.0);
В статье область отсечения настраивается таким образом, чтобы координатная область от 0 до 30 по обеим осям совпадала с размерами объекта AnT, в котором будет идти визуализация (т.е. чтобы визуализация была пропорциональна размерам области AnT).

Линия рисуется из координаты 0, 0 в координату 30, 30.
но мы не попадем в точку правого верхнего угла, если не скорректируем координату с учетом проекции, поэтому мы умножаем на (float)AnT.Height / (float)AnT.Width или на (float)AnT.Width / (float)AnT.Height

Другими словами, если область визуализации будет размера ~600х300, то если не корректировать координаты на отношение (float)AnT.Width / (float)AnT.Height, то сцену растянет:



С коррекцией:





kovalexius
kovalexius,  
Тут написано, установить матрицу проекции. Но если я например не хочу пользоваться матрицами в вершинном шейдере? Мой шейдер просто их пропускает и вершины ни на что не умножает, а прокидывает как есть.
Автор забыл указать, что установка матрицы проекции - вещь необязательная. К тому же не стоит пользоваться glut'ом и glu - это всего лишь навсего надстройки, не имеющие отношение к самому OpenGL. особенно glut - он тянет либку за собой, между прочим.
^