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

Хранение данных геометрии: класс osg::Array

Класс osg::Array, как и многие другие из рассмотренных выше, является классом абстрактным, от которого наследуются несколько потомков, предназначенных для хранения данных, передаваемых в функции OpenGL. Идеология работы с данным классом подобна классу std::vector из стандартной библиотеки C++. Следующий код иллюстрирует добавление вектора в массив вершин методом push_back()

vertices->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));

Массивы OSG выделяются в куче и управляются посредством умных указателей. Однако это не касается элементов массивов, таких как osg::Vec3 или osg::Vec2, которые могут быть созданы и на стеке.

Класс osg::Geometry является высокоуровневой оберткой над функциями OpenGL, работающими с массивами вершин. Он является производным от класса osg::Drawable и может быть без проблем добавлен в список объектов osg::Geode. Этот класс принимает вышеописанные массивы в качестве входных данных и использует их для генерации геометрии средствами OpenGL.

Вершины и их атрибуты

Вершина является атомарной единицей примитивов геометрии. Она обладает рядом атрибутов, описывающих точку двух- или трехмерного пространства. К атрибутам относятся: положение, цвет, вектор-нормаль, текстурные координаты, координаты тумана и т.д. Вершина всегда должна иметь положение в пространстве, что касается других атрибутов, они могут присутствовать опционально. OpenGL поддерживает 16 базовых атрибутов вершины и может создавать разные массивы для их хранения. Все массивы атрибутов поддерживаются классом osg::Geometry и могут быть заданы методами вида set*Array().

Атрибуты вершин в OpenSceneGraph

Атрибут Тип данных метод osg::Geometry Эквивалентный вызов OpenGL
Положение 3D-вектор setVertexArray() glVertexPointer()
Нормаль 3D-вектор setNormalArray() glNormalPointer()
Цвет 4D-вектор setColorArray() glColorPointer()
Вторчный цвет 4D-вектор setSecondaryColorArray() glSecondaryColorPointerEXT()
Координаты тумана Float setFogCoordArray() glFogCoordPointerEXT()
Текстурные координаты 2D или 3D-вектор setTexCoordArray() glTexCoordPointer()
Прочие атрибуты Определен пользователем setVertexArribArray() glVertexAttribPointerARB()

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

geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);

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

geom->setColorBinding(osg::Geometry::BIND_OVERALL);

то он применяет один цвет ко всей геометрии. Аналогично могут быть настроены взаимосвязи между другими атрибутами путем вызова методов setNormalBinding(), setSecondaryColorBinding(), setFogCoordBinding() и setVertexAttribBinding().

Наборы примитивов геометрии

Следующим шагом после определения массивов атрибутов вершин является описание того, как данные вершины будут обработаны рендером. Виртуальный класс osg::PrimitiveSet используется для управления геометрическими примитивами, генерируемыми рендером из набора вершин. Класс osg::Geometry предоставляет несколько методов для работы с наборами примитивов геометрии:

  • addPrimitiveSet() - передает указатель на набор примитивов в объект osg::Geometry.
  • removePrimitiveSet() - удаление набора примитивов. В качетве параметров принимает начальный индекс наборов и число наборов, которое следует удалить.
  • getPrimitiveSet() - возвращает набор примитивов по индексу, переданному в качестве параметра.
  • getNumPrimitiveSets() - возвращает общее число наборов примитивов, связанных с данной геометрией.

Класс osg::PrimitiveSet является абстрактным и не инстанцируется, но от него наследуются несколько производных классов, инкапсулирующих наборы примитивов, которыми оперирует OpenGL, такие как osg::DrawArrays и osg::DrawElementsUInt.

Класс osg::DrawArrays использует несколько последовательных елементов массива вершин для конструирования геометрического примитива. Он может быть создан и прикреплен к геометрии вызовом метода

geom->addPrimitiveSet(new osg::DrawArrays(mode, first, count));

Первый параметр mode задает тип примитива, аналогичный соответсвующим типам примитивов OpenGL: GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS и GL_POLYGON.

Первый и второй параметр задают первый индекс в массиве вершин и число вершин, из которых следует генерировать геометрию. Причем OSG не проверяет, достаточно ли указанного числа вершин для построения заданной режимом геометрии, что может приводить к краху приложения!

Пример - рисуем цветной квадрат

Реализуем всё вышеописанное в виде простого примера

main.h

#ifndef     MAIN_H
#define     MAIN_H

#include    <osg/Geometry>
#include    <osg/Geode>
#include    <osgViewer/Viewer>

#endif // MAIN_H

main.cpp

#include    "main.h"

int main(int argc, char *argv[])
{
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    vertices->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));
    vertices->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));
    vertices->push_back(osg::Vec3(1.0f, 0.0f, 1.0f));
    vertices->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));

    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
    normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));

    osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
    colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
    colors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
    colors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
    colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));

    osg::ref_ptr<osg::Geometry> quad = new osg::Geometry;
    quad->setVertexArray(vertices.get());

    quad->setNormalArray(normals.get());
    quad->setNormalBinding(osg::Geometry::BIND_OVERALL);

    quad->setColorArray(colors.get());
    quad->setColorBinding(osg::Geometry::BIND_PER_VERTEX);

    quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));

    osg::ref_ptr<osg::Geode> root = new osg::Geode;
    root->addDrawable(quad.get());

    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());

    return viewer.run();
}

После компиляции и выполнения получим вот такую красоту

Данный пример нуждается в пояснении. Итак, первым делом мы создаем массив вершин квадрата, в котором хранятся их координаты

osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
vertices->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));
vertices->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));
vertices->push_back(osg::Vec3(1.0f, 0.0f, 1.0f));
vertices->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));

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

osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));

Зададим цвет для каждой из вершин

osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
colors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
colors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));

Теперь создаем объект геометрии, где будет хранится описание нашего квадрата, которое будет обработано рендером. Передаем в эту геометрию массив вершин

osg::ref_ptr<osg::Geometry> quad = new osg::Geometry;
quad->setVertexArray(vertices.get());

Передавая массив нормалей, сообщаем движку, что будет использована одна единственная нормаль для всех вершин, указанием метода связывания (“биндинга”) нормалей BIND_OVAERALL

quad->setNormalArray(normals.get());
quad->setNormalBinding(osg::Geometry::BIND_OVERALL);

Передавая цвета вершин, напротив, указываем, что каждой вершине будет соответствовать собственный цвет

quad->setColorArray(colors.get());
quad->setColorBinding(osg::Geometry::BIND_PER_VERTEX);

Теперь создаем набор примитивов для геометрии. Указываем, что из массива вершин следует генерировать квадратные (GL_QUADS) грани, взяв в качестве первой вершины вершину с индексом 0, а общее число вершин будет равно 4

quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));

Ну а передачу геометрии и запуск рендера пояснять, думаю, не стоит

osg::ref_ptr<osg::Geode> root = new osg::Geode;
root->addDrawable(quad.get());

osgViewer::Viewer viewer;
viewer.setSceneData(root.get());

return viewer.run();

Приведенный код эквивалентен следующей конструкции на чистом OpenGL

static const GLfloat vertices[][3] = {  };
glEnableClientState( GL_VERTEX_ARRAY );
glVertexPointer( 4, GL_FLOAT, 0, vertices );
glDrawArrays( GL_QUADS, 0, 4 );