Войти
ПрограммированиеСтатьиГрафика

OpenGL: Основы. (3 стр)

Автор:

Вывод кадра

Мы уже говорили, что почти всегда в трёхмерных играх за одну секунду несколько раз полностью строится кадр. Другими словами, всегда, когда программа свободна от обработки сообщений системы, нужно вызывать некую функцию, назовём её DrawFrame(), которая будет каждый раз выводить все объекты в кадре.

Следуя нашему примеру построения приложения, видно, что эта функция должна вызываться из функции OnIdle().

// Функция, вызывающаяся при пустой очереди сообщений
void OnIdle()
{
  DrawFrame();
}

Какие общие действия должны произойти в функции DrawFrame()? Сначала, нужно очистить невидимый буфер (Back buffer), вывести все объекты в этот буфер и сделать невидимый буфер - видимым.

Для очистки используется функция glClear(). Этой функцией очищают несколько типов буферов, нас интересует тот, в котором хранится цветовое изображение (color buffer). Поэтому функцию glClear будем вызывать с параметром GL_COLOR_BUFFER_BIT.

Чтобы back buffer сделать видимым, в нашем случае это означает копирование содержимого этого буфера в видимую часть видеопамяти, используют функцию из Windows API - SwapBuffers().

Итак, что мы можем добавить в наш код. Поскольку функция DrawFrame() должна быть видна из main.cpp, включающей #include "OpenGL.h", то необходимо добавить объявление DrawFrame() в файл OpenGL.h.

Реализация функции DrawFrame() (добавить в OpenGL.cpp):

void DrawObjects()
{
}

void DrawFrame()
{
  glClear(GL_COLOR_BUFFER_BIT);
  DrawObjects();
  SwapBuffers(hDC);
}

Функцию DrawObjects()оставьте пока с пустым телом, после будем её использовать в наших примерах по выводу графических примитивов.

Графические примитивы

Существует несколько типов примитивов: точки, линейные сегменты и полигоны.

В функциях, отвечающих за вывод примитивов, нужно указывать, какие именно примитивы будут выводиться, указывая соответствующий режим. Например, для вывода точек, выставляется режим GL_POINTS, для вывода линий - GL_LINES, а для треугольников - GL_TRIANGLES. Помимо этого, есть специальные режимы, использующиеся для удобства и оптимизации. Например, GL_QUADS - выводятся четырёх вершинные примитивы, GL_TRIANGLE_STRIP - выводится цепочка треугольников, у которой каждый следующий треугольник имеет общее ребро с предыдущим и т.д.

Все примитивы описываются вершинами.

Вершины

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

Вывод примитивов последовательностью вершинных функций

Это самый старый способ вывода вершин, реализованный в OpenGL.

Примитив задаётся отдельными функциями для каждой вершины между функциональными скобками glBegin() и glEnd().

Режим, отвечающий за тип примитива, выставляется в функции glBegin(). Например:

glBegin(GL_TRIANGLES);

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

Функции glVertex* - задают координаты вершины, которые в свою очередь описывают форму геометрического объекта. При выполнении такой функции, другие данные вершины, такие как вектор нормали и цвет, берутся из установленных ранее "текущих" значений.

Пример. Задаём три (3) координаты типа GLfloat (f) для вершины:

glVertex3f(0.5f, 1.0f, 0.0f);

Пример:

void DrawObjects()
{
  glBegin(GL_TRIANGLES);
    glVertex3f(-0.5f, -0.5f, 0.0f);
    glVertex3f( 0.5f,  0.5f, 0.0f);
    glVertex3f(-0.5f,  0.5f, 0.0f);
  glEnd();
}

Эта функция выведет простейший полигон - треугольник, каждая вершина которого задаётся glVertex3f(). Ещё раз обратите внимание, что все функции glVertex* находятся между glBegin() и glEnd().

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

glColor* - задаёт цвет RGBA вершины вместе с условиями освещённости.

glNormal* - задаёт вектор нормали, соответствующий отдельной вершине.

glTexCoord* - задаёт текущие текстурные координаты для вершины.

Текущие значения для вершины выставляются до вызова glVertex*.

Пример:

void DrawObjects()
{
  glBegin(GL_TRIANGLES);
    glColor3ub(255,0,0); // красный
    glVertex3f(-0.5f, -0.5f, 0.0f);
    glVertex3f( 0.5f,  0.0f, 0.0f);
    glColor3ub(0,0,255); // синий
    glVertex3f(-0.5f , 0.5f, 0.0f);
  glEnd();
}

Эта функция рисует треугольник с двумя красными вершинами и одной синей.

Изображение

Вывод примитивов, используя массивы

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

Указание OpenGL на массивы происходит отдельными функциями, сам вывод тоже происходит отдельной функцией.

К примеру, выведем опять тот же треугольник.

Имеем массив вершин:

GLfloat pVerts[]= {-0.5f, -0.5f, 0.0f,

                    0.5f,  0.0f, 0.0f,
                  -0.5f , 0.5f, 0.0f};

здесь последовательно расположено по три координаты для трёх вершин.

Установка массива для вершинных координат происходит функцией glVertexPointer():

glVertexPointer(3, GL_FLOAT, 0, pVerts);

Первый параметр в этой функции говорит, сколько координат идёт на одну вершину, возможные значения - 2,3,4. Второй параметр отвечает за тип координат. Третий параметр равен шагу в байтах между данными с координатами для каждой вершины. 0 в нашем случае означает, что координаты лежат плотно друг за другом. Четвёртый параметр - собственно сам указатель на массив с координатами.

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

glEnableClientState(GL_VERTEX_ARRAY);

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

Включённое состояние остаётся включённым, до тех пор, пока вы его не выключите. То есть, если у вас последовательно идёт вывод нескольких объектов, содержащих свои, к примеру, вершинные массивы, достаточно одного включения состояние для всех объектов перед выводом первого объекта.

Если мы хотим, чтобы объект выводился, используя координаты из массива, но имел, например, один общий цвет для всех вершин, то не нужно строить массив с одинаковыми значениями цвета. Пользуйтесь уже известной вам функцией glColor*().

Рассмотрим теперь одну из функций, отвечающих за вывод примитива с использованием выставленных массивов.

glDrawArrays()

Выводит примитивы по данным в массивах.

glDrawArrays(mode, first, count);

mode - режим, отвечающий за тип примитива,
first - индекс вершины, с которой мы будем выводить объект,
count - количество вершин.

В нашем случае - для одного треугольника используем 3 вершины.

glDrawArrays(GL_TRIANGLES,0,3);

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

Пример:

void DrawObjects()
{
  static GLfloat pVerts[]= {-0.5f, -0.5f, 0.0f,
                          0.5f,  0.0f, 0.0f,
                         -0.5f , 0.5f, 0.0f};
  
  glEnableClientState(GL_VERTEX_ARRAY);
  glVertexPointer(3,GL_FLOAT,0,pVerts);
  
  glColor3ub(255,0,0); // красный

  glDrawArrays(GL_TRIANGLES,0,3);
}

Индексные примитивы

Позже, в OpenGL в версии 1.1 ввели дополнительный метод для вывода на экран. Этот метод позволяет работать с индексным заданием геометрических объектов для вывода на просчёт адаптера.

Что это означает? Почти все полигональные модели имеют треугольники с общими вершинами. И обычно, таких вершин большое количество. Ясно, что хранить и использовать копии одинаковых вершин расточительно.

Рассмотрим простейший пример.

Изображение

Имеем три соседних треугольника с общими вершинами. Для первого и второго треугольника - общие вершины 1 и 2, для второго и третьего - 2,3. Вершина 2 общая для всех треугольников.

Если бы мы не использовали индексный способ задания объекта, то нам понадобилось бы задать для вывода этих треугольников 9-ть вершин (по три для каждого треугольника).

Однако можно задать только 5-ть вершин, используя массивы для координат и прочих вершинных данных, а сами треугольники выводить, указывая индексы, соответствующие расположению вершинных данных в массивах.

То есть
для первого треугольника индексы будут - 0,1,2
для второго - 1,3,2
для третьего - 2,3,4

Рассмотрим одну из функций для работы с индексами.

glArrayElement()

glArrayElement( index );

Эта функция используется внутри пары glBegin() и glEnd(). Собственно ей мы и задаём индексы. А массивы с данными о самих вершинах устанавливаются ранее.

Пример:

void DrawObjects()
{
  static GLfloat pVerts[]= {-0.5f,  0.0f, 0.0f,
                          -0.25f,-0.4f, 0.0f,
                           0.0f,  0.0f, 0.0f,
                           0.25f,-0.4f, 0.0f,
                           0.5f,  0.0f, 0.0f};
  
  glEnableClientState(GL_VERTEX_ARRAY);
  glVertexPointer(3,GL_FLOAT,0,pVerts);

  glBegin(GL_TRIANGLES);
   glColor3ub(255,0,0); // красный
    glArrayElement(0);
    glArrayElement(1);
    glArrayElement(2);
   glColor3ub(0,255,0); // зеленый
    glArrayElement(1);
    glArrayElement(3);
    glArrayElement(2);
   glColor3ub(0,0,255); // синий
    glArrayElement(2);
    glArrayElement(3);
    glArrayElement(4);
  glEnd();
}

Этот пример выводит три цветных треугольника.

Однако индексы также можно задавать массивом и выводить объект одним вызовом без пары glBegin() и glEnd().

Это можно сделать функцией:

glDrawElements()

glDrawElements(mode, count, type, indices);

Здесь
mode - как всегда выставляет режим типа полигонов,
count - количество индексов, берущихся из массива,
type - тип элементов в индексном массиве, может принимать значения: GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT или GL_UNSIGNED_INT. Обычно под индексы используют unsigned short.
indices - указатель на массив с индексами. Заметьте, что этот указатель можно расценить, как указатель на элемент, с которого будет происходить расчёт объекта. То есть вы можете поставить указатель на любой элемент в массиве.

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

 
void DrawObjects()
{
  static GLfloat pVerts[]= {-0.5f,  0.0f, 0.0f,
                          -0.25f,-0.4f, 0.0f,
                           0.0f,  0.0f, 0.0f,
                           0.25f,-0.4f, 0.0f,
                           0.5f,  0.0f, 0.0f};

  static GLushort pInds[] =
  { 0,1,2 , 1,3,2 , 2,3,4 };
  
  glEnableClientState(GL_VERTEX_ARRAY);
  glVertexPointer(3,GL_FLOAT,0,pVerts);

  glDrawElements(GL_TRIANGLES, 9, GL_UNSIGNED_SHORT, pInds);
}

Пример выводит те же самые треугольники, поскольку цвет не выставляется, то все треугольники будут белыми.

Страницы: 1 2 3

#OpenGL, #основы

16 февраля 2002 (Обновление: 14 июня 2011)

Комментарии [92]