Главная » Хабрахабр » OpenSceneGraph: Основы работы с геометрией сцены

OpenSceneGraph: Основы работы с геометрией сцены

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

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

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

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

Класс osg::Geode представляет собой оконечный, так называемый "листовой" узел дерева сцены. Он не может иметь дочерних узлов, но при этом содержит всю необходимую информацию для рендеринга геометрии. Его имя — Geode есть сокращение от слов geometry node.

Класс osg::Drawable является чисто виртуальным классом. Геометрические данные, подлежащие обработке движком запоминаются в наборе объектов класса osg::Drawable, управляемых классом osg::Geode. Под drawable в OSG понимаются все элементы, которые могут быть отрисованы движком. От него наследуются ряд подклассов, представляющих собой трехмерные модели, изображения и текст, обрабатываемые конвейером OpenGL.

Класс osg::Geode предоставляет ряд методов для присоединения и отсоединения drawables:

  • Публичный метод addDrawable() — передает указатель на drawable элемент в экземпляр класса osg::Geode. Все эти элементы управляются посредством умных указателей osg::ref_ptr<>.
  • Публичный метод removeDrawable() и removeDrawables() удаляет объект из osg::Geode и уменьшает счетчик ссылок на него. Метод removeDrawable() принимает в качестве единственного параметра указатель на интересующий элемент, а метод removeDrawables() принимает два параметра: начальный индекс и число элементов, подлежащих удалению из массива объектов osg::Geode.
  • Метод getDrawable() возвращает указатель на элемент по передаваемому в качестве параметра индексу.
  • Метод getNumDrawables() возвращает общее число элементов, прикрепленных к osg::Geode. Например, для удаления всех элементов из osg::Geode можно использовать такой код

geode->removeDrawables(0, geode->getNumDrawables());

OSG предоставляет класс osg::ShapeDrawable, являющийся наследником класса osg::Drawable, и предназначенный для создания простейших трехмерных примитивов. Этот класс включает в себя объект osg::Shape, хранящий информацию о специфической геометрии и ещё параметрах. Генерация примитивов осуществляется с помощью метода setShape(), например

shapeDrawable->setShape(new osg::Box(osg::Vec3(1.0f, 0.0f, 0.0f), 10.0f, 10.0f, 5.0f));

создает прямоугольный параллелепипед с геометрическим центром в точке (1.0, 0.0, 0.0) c шириной и высотой 10 и глубиной 5 единиц. Класс osg::Vec3 определяет вектор в трехмерном пространстве (кроме того, представлены и классы osg::Vec2 и osg::Vec4 описывающие векторы соответствующей размерности).

Наиболее популярные примитивы представлены в OSG классами osg::Box, osg::Capsule, osg::Cone, osg::Cylinder и osg::Sphere.

Рассмотрим пример применения данного механизма.

main.h

#ifndef MAIN_H
#define MAIN_H #include <osg/ShapeDrawable>
#include <osg/Geode>
#include <osgViewer/Viewer> #endif // MAIN_H

main.cpp

#include "main.h" int main(int argc, char *argv[])
{ (void) argc; (void) argv; osg::ref_ptr<osg::ShapeDrawable> shape1 = new osg::ShapeDrawable; shape1->setShape(new osg::Box(osg::Vec3(-3.0f, 0.0f, 0.0f), 2.0f, 2.0f, 1.0f)); osg::ref_ptr<osg::ShapeDrawable> shape2 = new osg::ShapeDrawable; shape2->setShape(new osg::Cone(osg::Vec3(0.0f, 0.0f, 0.0f), 1.0f, 1.0f)); shape2->setColor(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f)); osg::ref_ptr<osg::ShapeDrawable> shape3 = new osg::ShapeDrawable; shape3->setShape(new osg::Sphere(osg::Vec3(3.0f, 0.0f, 0.0f), 1.0f)); shape3->setColor(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f)); osg::ref_ptr<osg::Geode> root = new osg::Geode; root->addDrawable(shape1.get()); root->addDrawable(shape2.get()); root->addDrawable(shape3.get()); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run();
}

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

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

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

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

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

Атрибут

Тип данных

Метод osg::Geometry

Эквивалентный вызов OpenGL

Положение

3-вектор

setVertexArray()

glVertexPointer()

Нормаль

3-вектор

setNormalArray()

glNormalPointer()

Цвет

4-вектор

setColorArray()

glColorPointer()

Вторичный цвет

4-вектор

setSecondaryColorArray()

glSecondaryColorPointerEXT()

Координаты тумана

float

setFogCoordArray()

glFogCoordPointerEXT()

Текстурные координаты

2- или 3-вектор

setTexCoordArray()

glTexCoordPointer()

Прочие атрибуты

Определен пользователем

setVertexArribArray()

glVertexAttribPointerARB()

В принципе, устанавливать свои атрибуты необходимо для каждой из вершин, что приводит к образованию нескольких массивов атрибутов одинакового размера — в противном случае несовпадение размеров массивов может привести к неопределенному поведению движка. 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 не проверяет, достаточно ли указанного числа вершин для построения заданной режимом геометрии, что может приводить к краху приложения! Первый и второй параметр задают первый индекс в массиве вершин и число вершин, из которых следует генерировать геометрию.

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

Полный исходный код примера quad

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

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

Однако, как видно из рисунка (смотрим на развертку куба на плоскость) некоторые вершины принадлежат более чем одной грани. Куб имеет восемь вершин. Если строить куб из 12-ти треугольных граней, то эти вершины будут повторятся, и вместо массива на 8 вершин мы получим массив на 36 вершин, большинство из которых на деле являются одной и той же вершиной!

Массивы индексов хранят индексы вершин примитивов, описывающих грани и другие элементы геометрии. В OSG существуют классы osg::DrawElementsUInt, osg::DrawElementsUByte и osg::DrawElementsUShort, которые используют в качестве данных массивы индексов вершин, призваны решить описанную проблему. При применении этих классов для куба достаточно хранить массив из восьми вершин, которые ассоциируются с гранями через массивы индексов.

Для добавления индексов может быть использован такой код Классы типа osg::DrawElements* устроены так же как и стандартный класс std::vector.

osg::ref_ptr<osg::DrawElementsUInt> de = new osg::DrawElementsUInt(GL_TRIANGLES);
de->push_back(0); de->push_back(1); de->push_back(2);
de->push_back(3); de->push_back(0); de->push_back(2);

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

Рассмотрим еще один показательный пример — октаэдр

Мы можем создать массив на 24 вершины для отображения всех восьми граней с помощью osg::DrawArrays. Интересен он потому, что содержит всего шесть вершин, но каждая вершина входит аж в четыре треугольных грани! Однако мы поступим иначе — вершины будем хранить в массиве из шести элементов, а грани генерируем используя класс osg::DrawElementsUInt.

Полный исходный текст примера octahedron

main.h

#ifndef MAIN_H
#define MAIN_H #include <osg/Geometry>
#include <osg/Geode>
#include <osgUtil/SmoothingVisitor>
#include <osgViewer/Viewer> #endif

main.cpp

#include "main.h" int main(int argc, char *argv[])
{ osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array(6); (*vertices)[0].set( 0.0f, 0.0f, 1.0f); (*vertices)[1].set(-0.5f, -0.5f, 0.0f); (*vertices)[2].set( 0.5f, -0.5f, 0.0f); (*vertices)[3].set( 0.5f, 0.5f, 0.0f); (*vertices)[4].set(-0.5f, 0.5f, 0.0f); (*vertices)[5].set( 0.0f, 0.0f, -1.0f); osg::ref_ptr<osg::DrawElementsUInt> indices = new osg::DrawElementsUInt(GL_TRIANGLES, 24); (*indices)[ 0] = 0; (*indices)[ 1] = 1; (*indices)[ 2] = 2; (*indices)[ 3] = 0; (*indices)[ 4] = 4; (*indices)[ 5] = 1; (*indices)[ 6] = 4; (*indices)[ 7] = 5; (*indices)[ 8] = 1; (*indices)[ 9] = 4; (*indices)[10] = 3; (*indices)[11] = 5; (*indices)[12] = 3; (*indices)[13] = 2; (*indices)[14] = 5; (*indices)[15] = 1; (*indices)[16] = 5; (*indices)[17] = 2; (*indices)[18] = 3; (*indices)[19] = 0; (*indices)[20] = 2; (*indices)[21] = 0; (*indices)[22] = 3; (*indices)[23] = 4; osg::ref_ptr<osg::Geometry> geom = new osg::Geometry; geom->setVertexArray(vertices.get()); geom->addPrimitiveSet(indices.get()); osgUtil::SmoothingVisitor::smooth(*geom); osg::ref_ptr<osg::Geode> root = new osg::Geode; root->addDrawable(geom.get()); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run();
}

Разберем этот код подробнее. Разумеется, первым делом, мы создаем массив из шести вершин

osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array(6);
(*vertices)[0].set( 0.0f, 0.0f, 1.0f);
(*vertices)[1].set(-0.5f, -0.5f, 0.0f);
(*vertices)[2].set( 0.5f, -0.5f, 0.0f);
(*vertices)[3].set( 0.5f, 0.5f, 0.0f);
(*vertices)[4].set(-0.5f, 0.5f, 0.0f);
(*vertices)[5].set( 0.0f, 0.0f, -1.0f);

Инициализируем каждую вершину непосредственно, обращаясь вектору её координат с использованием операции разыменования указателя и оператора operator[] (мы помним, что osg::Array аналогичен по своему устройству std::vector).

Теперь создаем грани в виде списка индексов вершин

osg::ref_ptr<osg::DrawElementsUInt> indices = new osg::DrawElementsUInt(GL_TRIANGLES, 24);
(*indices)[ 0] = 0; (*indices)[ 1] = 1; (*indices)[ 2] = 2; // Грань 0
(*indices)[ 3] = 0; (*indices)[ 4] = 4; (*indices)[ 5] = 1; // Грань 1
(*indices)[ 6] = 4; (*indices)[ 7] = 5; (*indices)[ 8] = 1; // Грань 2
(*indices)[ 9] = 4; (*indices)[10] = 3; (*indices)[11] = 5; // Грань 3
(*indices)[12] = 3; (*indices)[13] = 2; (*indices)[14] = 5; // Грань 4
(*indices)[15] = 1; (*indices)[16] = 5; (*indices)[17] = 2; // Грань 5
(*indices)[18] = 3; (*indices)[19] = 0; (*indices)[20] = 2; // Грань 6
(*indices)[21] = 0; (*indices)[22] = 3; (*indices)[23] = 4; // Грань 7

Грани будут треугольными, их будет 8, а значит список индексов должен содержать 24 элемента. Индексы граней идут в этом массиве последовательно: например грань 0 образована вершинами 0, 1 и 2; грань 1 — вершинами 0, 4 и 1; грань 2 — вершинами 4, 5 и 1 и так далее. Вершины перечисляются в порядке следования против часовой стрелки, если смотреть на лицевую сторону грани (смотрим рисунок выше).

Единственное чего мы не делали — автоматическая генерация сглаженных (усредненных) нормалей, которую мы выполняем в данном примере вызовом Дальнейшие шаги по созданию геометрии мы выполняли в предыдущих примерах.

osgUtil::SmoothingVisitor::smooth(*geom);

Действительно, если заданы вершины грани, то легко рассчитать нормаль к ней. В вершинах, в которых сходятся несколько граней рассчитывается некая усредненная нормаль — нормали сходящихся граней складываются и полученная сумма снова нормируется. Эти операции (а так же многое другое!) может выполнить сам движок с помощью классов из библиотеки osgUtil. Поэтому в нашем примере в файл *.pro мы добавим указание компоновщику собирать нашу программу и с этой библиотекой

octahedron.pro

CONFIG(debug, debug|release) { TARGET = $$join(TARGET,,,_d) . . . LIBS += -L$$OSG_LIB_DIRECTORY -losgUtild } else { . . . LIBS += -L$$OSG_LIB_DIRECTORY -losgUtil
}

В итоге мы получаем следующий результат

Чтобы понять как это работает, рассмотрим конвейер OpenGL

Он сохраняет данные о вершинах в памяти приложения, которая используется на стороне клиента. Механизм массивов вершин уменьшает число вызовов OpenGL. Как показано на схеме, OpenGL получает данные из буфера вершин на стороне клиента и упорядоченным образом, выполняет сборку примитивов. Конвейер OpenGL на серверной стороне получает доступ к различным массивам вершин. Класс osg::DrawArrays проходит по этим массивам непосредственно и отображает их. Так происходит обработка данных при использовании методов set*Array() класса osg::Geometry.

Массив индексов позволяет сформировать на стороне сервера кэш вершин. При использовании osg::DrawElements* обеспечивается понижение размерности массивов вершин и уменьшается число вершин, передаваемых в конвейер. Это существенно увеличивает общую производительность рендеринга. OpenGL читает данные о вершинах из кэша, вместо того, чтобы читать из из буфера вершин на стороне клиента.

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

К описанным техникам относятся:

  1. osgUtil::Simplifier — уменьшение числа треугольников в геометрии. Публичный метод simplify() используется для упрощения геометрии моделей.
  2. osgUtil::SmootingVisitor — вычисление нормалей. Метод smooth() может быть использован для генерации сглаженных нормалей для модели, вместо самостоятельного их расчета и явного задания через массив нормалей.
  3. osgUtil::TangentSpaceGenerator — генерация касательных базисных векторов для вершин модели. Запускается вызовом метода generate() и сохраняет результат, возвращаемый методами getTangentArray(), getNormalArray() и getBinormalArray(). Эти результаты могут быть использованы для различный атрибутов вершин при написании шейдеров на GLSL.
  4. osgUtil::Tesselator — выполняет тесселяцию полигональной сетки — разбиение сложных примитивов на последовательность простых (метод retesselatePolygons())
  5. osgUtil::TriStripVisitor — конвертирует геометрическую поверхность в набор полос треугольных граней, что позволяет выполнять рендеринг с эффективным расходом памяти. Метод stripify() конвертирует набор примитивов модели в геометрию на базе набора GL_TRIANGLE_STRIP.

Все методы принимают геометрию объекта в качестве параметра, передаваемого по ссылке osg::Geometry&, например так

osgUtil::TriStripVisitor tsv;
tsv.stripify(*geom);

где под geom понимается экземпляр геометрии, описываемый умным указателем.

Классы osg::Simplifier, osg::SmoothingVisitor и osg::TriStripVisitor могут работать непосредственно с узлами графа сцены, например

osgUtil::TriStripVisitor tsv;
node->accept(tsv);

Метод accept() обрабатывает все дочерние узлы, до тех пор, пока указанная операция не будет применена ко всем оконечным нодам этой части дерева сцены, хранящимся в нодах типа osg::Geode.

Попробуем на практике технику тесселяции.

Полный код примера tesselator

main.h

#ifndef MAIN_H
#define MAIN_H #include <osg/Geometry>
#include <osg/Geode>
#include <osgUtil/Tessellator>
#include <osgViewer/Viewer> #endif

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) ); // 0 vertices->push_back( osg::Vec3(2.0f, 0.0f, 0.0f) ); // 1 vertices->push_back( osg::Vec3(2.0f, 0.0f, 1.0f) ); // 2 vertices->push_back( osg::Vec3(1.0f, 0.0f, 1.0f) ); // 3 vertices->push_back( osg::Vec3(1.0f, 0.0f, 2.0f) ); // 4 vertices->push_back( osg::Vec3(2.0f, 0.0f, 2.0f) ); // 5 vertices->push_back( osg::Vec3(2.0f, 0.0f, 3.0f) ); // 6 vertices->push_back( osg::Vec3(0.0f, 0.0f, 3.0f) ); // 7 osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array; normals->push_back( osg::Vec3(0.0f, -1.0f, 0.0f) ); osg::ref_ptr<osg::Geometry> geom = new osg::Geometry; geom->setVertexArray(vertices.get()); geom->setNormalArray(normals.get()); geom->setNormalBinding(osg::Geometry::BIND_OVERALL); geom->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 8)); osg::ref_ptr<osg::Geode> root = new osg::Geode; root->addDrawable(geom.get()); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run();
}

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

Для исправления этой проблемы, построенную геометрию, перед передачей её во вьювер, следует подвергнуть тесселяции

osgUtil::Tessellator ts;
ts.retessellatePolygons(*geom);

после которой мы получим корректный результат

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

Класс osgUtil::Tessellator использует алгоритмы для трансформацию выпуклого многоугольника в серию невыпуклых — в нашем случае он трансформирует геометрию в GL_TRIANGLE_STRIP.

Через публичный метод setWindingType() можно определить различные правила обработки, такие как GLU_TESS_WINDING_ODD или GLU_TESS_WINDING_NONZERO, которые задают внутреннюю и внешнюю области сложного многоугольника. Этот класс может обрабатывать многоугольники с отверстиями и самопересекающиеся многоугольники.

В этой статье мы получили базовые представления от том, каким образом геометрия трехмерных объектов хранится и обрабатывается в движке OSG. Не стоит думать, что те простейшие и не слишком впечатляющие примеры, что рассмотрены в статье — предел возможностей движка. Просто данные примеры могут помочь разработчику понять механику OpenSceneGraph, а без этого понимания сложно представлять себе работу более сложных вещей.

0. Данная статья основана на переводе и переработке текста соответствующих глав книги OpenSceneGraph 3. Все примеры проверены мной лично, и их исходники доступны здесь. Beginner’s Guide. Продолжение следует...


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Два успеха частной космонавтики

На прошлой неделе произошло два достаточно важных события для частной космонавтики. Прежде всего, два пилота Virgin Galactic могут сверлить дырки в своих костюмах под значки астронавтов — поднявшийся 13 декабря до 82,7 км SpaceShipTwo оказался выше линии 50 миль, которая ...

Дайджест свежих материалов из мира фронтенда за последнюю неделю №343 (10 — 16 декабря 2018)

Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.     Медиа    |    Веб-разработка    |    CSS    |    Javascript    |    Браузеры Медиа • Подкаст «Frontend Weekend» #83 – Илья Климов о том, как и зачем был создан образовательный проект JavaScript.Ninja• Девшахта #61: TypeScript и его ...