image

Введение


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

Развитие программных инструментов, реализующих трехмерную графику пришло, вне зависимости от того, какой из них вы выбираете, примерно к одинаковой концепции как математического, так и алгоритмического описания вышеупомянутых трансформаций. Идеологически и «чистые» графические API типа OpenGL, и крутые игровые движки типа Unity и Unreal, используют схожие механизмы описания преобразования трехмерной сцены. Не является исключением и OpenSceneGraph.

В этой статье мы сделаем обзор механизмов группировки и трансформации трехмерных объектов в OSG.

1. Матрица модели, матрица вида и матрица проекции


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

Матрица модели служит для описания расположения объекта в 3D-мире. Она осуществляет преобразование вершин из локальной системы координат объекта в мировую систему координат. К слову, все системы координат в OSG являются правовинтовыми.

Следующим шагом является преобразование мировых координат в пространство вида, выполняемое с помощью матрицы вида. Предположим, что мы имеем камеру, расположенную в начале отсчета мировой системы координат. Матрица, обратная матрице преобразования камеры фактически и используется как матрица вида. В правовинтовой системе координат OpenGL, по-умолчанию, всегда определяет камеру расположенной в точке (0, 0, 0) глобальной системы координат и направленной вдоль отрицательного направления оси Z.

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

Ve = V * modelViewMatrix

Следующей важной задачей является определить, как 3D-объекты будут проецироваться в плоскость экрана и вычислить так называемую пирамиду отсечения — область пространства, содержащую объекты, подлежащие отображению на экране. Матрица проекции используется для задания пирамиды отсечения, заданной в мировом пространстве шестью плоскостями: левой, правой, нижней, верхней, ближней и дальней. OpenGL предоставляет функцию gluPerapective(), позволяющую задать пирамиду отсечения и способ проецирования трехмерного мира на плоскость.

Полученная после вышеописанных преобразований система координат называется нормализованной системой координат устройства, имеет по каждой оси диапазон изменения координат от -1 до 1 и является левовинтовой. И, в качестве последнего шага, происходит проецирование полученных данных в порт отображения (вьюпорт) окна, определяемое прямоугольником клиентской области окна. После этого 3D-мир появляется на нашем 2D-экране. Окончательное значение экранных координат вершин Vs можно выразить следующим преобразованием

Vs = V * modelViewMatrix * projectionMatrix * windowMatrix

или

Vs = V * MVPW

где MVPW — эквивалентная матрица преобразования, равная произведению трех матриц: матрицы модель-вид, матрицы проекции и матрицы окна.

Vs в этой ситуации является трехмерным вектором, который определяет положение 2D-пикселя со значением глубины. Обратив операцию преобразования координат мы получим линию в трехмерном пространстве. Поэтому 2D-точку можно рассматривать как две точки — одну на ближней (Zs = 0), другую — на дальней плоскости отсечения (Zs = 1). Координаты этих точек в трехмерном пространстве

V0 = (Xs, Ys, 0) * invMVPW
V1 = (Xs, Ys, 1) * invMVPW

где invMVPW — матрица, обратная MVPW.

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

2. Групповые ноды


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

Класс osg::Group является производным от класса osg::Node, и соответственно наследуется и от класса osg::Referenced. osg::Group содержит список дочерних нод, где каждая дочерняя нода управляется умным указателем. Это гарантирует отсутствие утечек памяти при каскадном удалении ветки дерева сцены. Данный класс предоставляет разработчику ряд публичных методов
  1. addChild() — присоединяет узел в конец списка дочерних узлов. С другой стороны есть метод insertChild(), помещающий дочерний узел в конкретную позицию списка, которая задается целочисленным индексом или указателем на узел, передаваемыми в качестве параметра.
  2. removeChild() и removeChildren() — удаление одного узла или группы узлов.
  3. getChild() — получение указателя на ноду по её индексу в списке
  4. getNumChildren() — получение числа дочерних узлов, прикрепленных к данной группе.

Управление родительскими узлами


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

OSG позволяет узлам сцены иметь несколько родительских узлов (об этом мы поговорим когда-нибудь потом). Пока же мы рассмотрим методы, определенные в osg::Node, используемые для манипуляций над родительскими узлами:
  1. getParent() — возвращает указатель типа osg::Group, содержащий перечень родительских узлов.
  2. getNumParants() — возвращает число родительских узлов.
  3. getParentalNodePath() — возвращает все возможные пути к корневой ноде сцены от текущей ноды. Он возвращает список переменных типа osg::NodePath.

osg::NodePath представляет собой std::vector указателей на узлы сцены.



Например, для сцены, изображенной на рисунке следующий код

osg::NodePath &nodePath = child3->getParentalNodePaths()[0];
for (unsigned int i = 0; i < nodePath.size(); ++i)
{
	osg::Node *node = nodePath[i];
	// Что-нибудь делаем с нодой
}

вернет ноды Root, Child1, Child2.

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

3. Добавление нескольких моделей в дерево сцены


Проиллюстрируем механизм использования групп следующим примером

Полный текст примера group
main.h
#ifndef     MAIN_H
#define     MAIN_H

#include    <osg/Group>
#include    <osgDB/ReadFile>
#include    <osgViewer/Viewer>

#endif

main.cpp

#include    "main.h"

int main(int argc, char *argv[])
{
    (void) argc, (void) argv;

    osg::ref_ptr<osg::Node> model1 = osgDB::readNodeFile("../data/cessna.osg");
    osg::ref_ptr<osg::Node> model2 = osgDB::readNodeFile("../data/cow.osg");

    osg::ref_ptr<osg::Group> root = new osg::Group;
    root->addChild(model1.get());
    root->addChild(model2.get());

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

    return viewer.run();
}


Принципиально пример отличается от всех предыдущих тем, что мы загружаем две трехмерных модели, а для их добавления в сцену создаем групповую ноду root и добавляем в неё наши модельки как дочерние ноды

osg::ref_ptr<osg::Group> root = new osg::Group;
root->addChild(model1.get());
root->addChild(model2.get());



В итоге мы получаем сцену, состоящую из двух моделей — самолета и смешной зеркальной коровы. Кстати, зеркальная корова не будет зеркальной, если не скопировать её текстуру из OpenSceneGraph-Data/Images/reflect.rgb а каталог data/Images нашего проекта.

Класс osg::Group может принимать в качестве дочерних любые типы узлов, в том числе и узлы своего типа. Напротив, класс osg::Geode не содержит вообще каких-либо дочерних узлов — он является оконечным узлом, содержащим в себе геометрию объекта сцены. Этот факт удобен при выяснении вопроса является ли узел узлом типа osg::Group или другого типа производного от osg::Node. Рассмотрим маленький пример

osg::ref_ptr<osg::Group> model = dynamic_cast<osg::Group *>(osgDB::readNodeFile("../data/cessna.osg"));

Значение, возвращаемое функцией osgDB::readNodeFile() всегда имеет тип osg::Node*, но оно может быть преобразовано к своему наследнику osg::Group*. Если коневой узел модели Cessna это групповой узел, то преобразование будет успешным, в противном случае преобразование вернет NULL.

Можно выполнить так же такой фокус, работающий на большинстве компиляторов

// Загружаем модель в групповой узел
osg::ref_ptr<osg::Group> group = ...;
// Преобразуем его к узлу
osg::Node* node1 = dynamic_cast<osg::Node*>( group.get() );
// Преобразуем группу к узлу неявно
osg::Node* node2 = group.get();

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

osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("cessna.osg");
osg::Group* convModel1 = model->asGroup(); // Работает нормально
osg::Geode* convModel2 = model->asGeode(); // Вернет NULL.

4. Ноды трансформации


Узлы osg::Group не могут делать никаких преобразований, кроме возможности перехода к своим дочерним узлам. Для пространственного перемещения геометрии OSG предоставляет класс osg::Transform. Этот класс является наследником класса osg::Group, но и сам является абстрактным — на практике вместо него применяются его наследники, реализующие различные пространственные преобразования геометрии. При обходе графа сцены узел osg::Transform добавляет свое преобразование в текущую матрицу преобразования OpenGL. Это эквивалентно перемножению матриц преобразования OpenGL, выполняемое командой glMultMatrix()



Этот пример графа сцены можно транслировать в следующий кода на OpenGL

glPushMatrix();
	glMultMatrix( matrixOfTransform1 );
	renderGeode1(); 
	
	glPushMatrix();
		glMultMatrix( matrixOfTransform2 );
		renderGeode2();
	glPopMatrix();

glPopMatrix();

Можно сказать, что положение Geode1 задается в системе координат Transform1, а положение Geode2 задается в системе координат Transform2, смещенной относительно Transform1. При этом в OSG можно включить позиционирование в абсолютных координатах, что приведет к поведению объекта, эквивалентному результату команды glGlobalMatrix() OpenGL

transformNode->setReferenceFrame( osg::Transform::ABSOLUTE_RF );

Можно переключится обратно в режим позиционирования относительными координатами

transformNode->setReferenceFrame( osg::Transform::RELATIVE_RF );

5. Понятие о матрице преобразования координат


Тип osg::Matrix это базовый тип OSG не управляемый умными указателями. Он предоставляет интерфейс к операциями над матрицами размерности 4х4, описывающими преобразование координат, таких как перемещение, поворот, масштабирование и вычисление проекций. Матрица может быть задана явно

// Единичная матрица 4х4
osg::Matrix mat(1.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 0.0f, 1.0f ); 

Класс osg::Matrix предоставляет следующие публичные методы:

  1. postMult() и operator* () — умножение справа текущей матрицы на матрицу или вектор, переданные в качестве параметра. Метод preMult() выполняет умножение слева.
  2. makeTranslate(), makeRotate() и makeScale() — сбрасывают текущую матрицу и создают матрицу 4х4 описывающую перемещение, вращение и масштабирование. их статические версии translate(), rotate() и scale() могут быть использованы для создания матричного объекта со специфическими параметрами.
  3. invert() — вычисление матрицы обратной текущей. Его статическая версия inverse() принимает в качестве параметра матрицу и возвращает новую матрицу, обратную данной.

OSG понимает матрицы как матрицы строк, а векторы как строки, поэтому для применения к вектору матричного преобразования следует поступать так

osg::Matrix mat = …;
osg::Vec3 vec = …;
osg::Vec3 resultVec = vec * mat;

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

osg::Matrix mat1 = osg::Matrix::scale(sx, sy, sz);
osg::Matrix mat2 = osg::Matrix::translate(x, y, z);
osg::Matrix resultMat = mat1 * mat2;

Разработчик должен читать процесс трансформации слева направо. То есть, в описанном фрагменте кода сначала происходит масштабирование вектора, а затем его перемещение.

osg::Matrixf содержит элементы типа float.

6. Применение класса osg::MatrixTransform


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

Полный текст примера transform
main.h
#ifndef     MAIN_H
#define     MAIN_H

#include    <osg/MatrixTransform>
#include    <osgDB/ReadFile>
#include    <osgViewer/Viewer>

#endif

main.cpp

#include    "main.h"

int main(int argc, char *argv[])
{
    (void) argc; (void) argv;

    osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/cessna.osg");

    osg::ref_ptr<osg::MatrixTransform> transform1 = new osg::MatrixTransform;
    transform1->setMatrix(osg::Matrix::translate(-25.0, 0.0, 0.0));
    transform1->addChild(model.get());

    osg::ref_ptr<osg::MatrixTransform> transform2 = new osg::MatrixTransform;
    transform2->setMatrix(osg::Matrix::translate(25.0, 0.0, 0.0));
    transform2->addChild(model.get());

    osg::ref_ptr<osg::Group> root = new osg::Group;
    root->addChild(transform1.get());
    root->addChild(transform2.get());

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

    return viewer.run();
}


Пример, на самом деле довольно тривиален. Загружаем модель самолета из файла

osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/cessna.osg");

Создаем ноду трансформации

osg::ref_ptr<osg::MatrixTransform> transform1 = new osg::MatrixTransform;

Устанавливаем в качестве матрицы преобразования перемещение модели по оси X на 25 единиц влево

transform1->setMatrix(osg::Matrix::translate(-25.0, 0.0, 0.0));

Задаем для ноды трансформации нашу модель в качестве дочернего узла

transform1->addChild(model.get());

Аналогично поступаем и со второй трансформацией, но в качестве матрица задаем перемещение вправо на 25 единиц

osg::ref_ptr<osg::MatrixTransform> transform2 = new osg::MatrixTransform;
transform2->setMatrix(osg::Matrix::translate(25.0, 0.0, 0.0));
transform2->addChild(model.get());

Создаем корневую ноду и в качестве дочерних узлов для неё задаем трансформационные ноды transform1 и transform2

osg::ref_ptr<osg::Group> root = new osg::Group;
root->addChild(transform1.get());
root->addChild(transform2.get());

Создаем вьювер и в качестве данных сцены передаем ему корневую ноду

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

Запуск программы дает такую картинку



Структура графа сцены в этом примере такова



Нас не должен смущать тот факт, что ноды трансформации (Child 1.1 и Child 1.2) ссылаются на один и тот же дочерний объект модели самолета (Child 2). Это штатный механизм OSG, когда один дочерний узел графа сцены может иметь несколько родительских узлов. Таким образом нам не обязательно хранить в памяти два экземпляра модели, чтобы получить в сцене два одинаковых самолета. Такой механизм позволяет очень эффективно распределять память в приложении. Модель не будет удалена из памяти, пока на неё ссылается, как на дочернюю, хотя бы одна нода.

По своему действию класс osg::MatrixTransform эквивалентен командам OpenGL glMultMatrix() и glLoadMatrix(), реализует все виды пространственных преобразований, но сложен в использованию из-за необходимости вычислять матрицу преобразования.

Класс osg::PositionAttitudeTransform работает как функции OpenGL glTranslate(), glScale(), glRotate(). Он предоставляет публичные методы для преобразования дочерних узлов:

  1. setPosition() — переместить узел в данную точку пространства, задаваемую параметром osg::Vec3
  2. setScale() — масштабировать объект по осям координат. Коэффициенты масштабирования по соответствующим осям задаются параметром типа osg::Vec3
  3. setAttitude() — задать пространственную ориентацию объекта. В качестве параметра принимает кватернион преобразования поворота osg::Quat, конструктор которого имеет несколько перегрузок, позволяющих задавать кватернион как непосредственно (покомпонентно), так и, например, через углы Эйлера osg::Quat(xAngle, osg::X_AXIS, yAngle, osg::Y_AXIS, zAngle, osg::Z_AXIS) (углы задаются в радианах!)


7. Ноды-переключатели


Рассмотрим еще один класс — osg::Switch, позволяющий отображать или пропускать рендеринг узла сцены, в зависимости от некоего логического условия. Он является наследником класса osg::Group и прикрепляет к каждой своей дочерней ноде некоторое логическое значение. Он имеет несколько полезных публичных методов:
  1. Перегруженный addChild(), в качестве второго параметра принимающий логический ключ, указывающий отображать или нет данный узел.
  2. setValue() — установка ключа видимости/невидимости. Принимает индекс интересующей нас дочерней ноды и желаемое значение ключа. Соответственно getValue() позволяет получить текущее значение ключа по индексу интересующей нас ноды.
  3. setNewChildDefaultValue() — установка значения по-умолчанию для ключа видимости всех новых объектов, добавляемых в качестве дочерних.

Рассмотрим применение данного класса на примере.

Полный текст примера switch
main.h
#ifndef     MAIN_H
#define     MAIN_H

#include    <osg/Switch>
#include    <osgDB/ReadFile>
#include    <osgViewer/Viewer>

#endif


main.cpp
#include    "main.h"

int main(int argc, char *argv[])
{
    (void) argc; (void) argv;

    osg::ref_ptr<osg::Node> model1 = osgDB::readNodeFile("../data/cessna.osg");
    osg::ref_ptr<osg::Node> model2 = osgDB::readNodeFile("../data/cessnafire.osg");

    osg::ref_ptr<osg::Switch> root = new osg::Switch;
    root->addChild(model1.get(), false);
    root->addChild(model2.get(), true);

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

    return viewer.run();
}


Пример тривиален — мы загружаем две модели: обычную цессну и цессну с эффектом горящего двигателя

osg::ref_ptr<osg::Node> model1 = osgDB::readNodeFile("../data/cessna.osg");
osg::ref_ptr<osg::Node> model2 = osgDB::readNodeFile("../data/cessnafire.osg");

Однако, в качестве корневой ноды создаем osg::Switch, что позволяет нам, при добавлении в неё моделей в качестве дочерних узлов задать ключ видимости для каждой из них

osg::ref_ptr<osg::Switch> root = new osg::Switch;
root->addChild(model1.get(), false);
root->addChild(model2.get(), true);

То есть, model1 не будет рендерится, а model2 будет, что мы и пронаблюдаем, запустив программу



Поменяв местами значения ключей будем видеть противоположную картину

root->addChild(model1.get(), true);
root->addChild(model2.get(), false);



Взведя оба ключа, увидим две модели одновременно

root->addChild(model1.get(), true);
root->addChild(model2.get(), true);



Включать видимость и невидимость ноды, дочерней для osg::Switch можно прямо на ходу, используя метод setValue()

switchNode->setValue(0, false);
switchNode->setValue(0, true);
switchNode->setValue(1, true);
switchNode->setValue(1, false);

Заключение


В этом уроке мы рассмотрели все основные классы промежуточных узлов, используемых в OpenSceeneGraph. Таким образом мы уложили ещё один базовый кирпич в фундамент знаний об устройстве этого несомненно интересного графического движка. Рассмотренные в статье примеры, как и ранее, доступны в моем репозитории на Github. Продолжение следует...

Комментарии (15)


  1. BeardedBeaver
    18.11.2018 13:47

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


    1. oYASo
      18.11.2018 14:56

      Это же графический движок, его к чему угодно можно прикрутить.

      К слову, парни, которые пишут открытый Morrowind, в свое время перешли с Ogre3D на OSG и остались очень довольны.


      1. BeardedBeaver
        18.11.2018 15:51

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


        1. maisvendoo Автор
          18.11.2018 17:06

          Приходилось писать очень много не очень хорошего кода

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

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








        1. anikavoi
          18.11.2018 22:01
          +1

          Возможно, что для задачи «разрезать и поделить» будет полезна эта статья:
          vicrucann.github.io/tutorials/osg-intersectors-example

          И еще… По OSG удивительно мало статей и примеров в гугле, но на baidu.cn ищется намного больше по теме.


          1. maisvendoo Автор
            18.11.2018 22:38

            Да, Вика Рудакова как раз CAD-ми занимается и использует для этого OSG. Читал её материалы, в частности про интеграцию OSG и Qt.

            По OSG удивительно мало статей и примеров в гугле

            Материалов, даже не на русском, а на английском исчезающе мало. Единственная ниточка — книги Begiiner's Guide и Cookbook. Первая совсем для нубасов (как я) а вторая содержит продвинутые материалы типа osgEarth и osgOcean и прочее. Так что в своих копалках я опираюсь на эти источники, плюсп помог блок Александра Бобкова, который он, в плане OSG, к несчастью забросил


            1. BeardedBeaver
              19.11.2018 19:16

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


              1. maisvendoo Автор
                20.11.2018 00:14

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


      1. maisvendoo Автор
        18.11.2018 17:10

        парни, которые пишут открытый Morrowind, в свое время перешли с Ogre3D на OSG

        И главная причина, как я понял, в том, что под виндой огр использует DirectX. По мне так DX уже давно пора закапывать — OpenGL работает везде, а DX только на одной платформе. Плюс — грядет Vulkan…

        Хотя, тут не только DX

        • Система материалов
          В Ogre 2.1 система материалов была оптимизирована и усложнена, сотворение материала в OpenMW используется очень часто, поэтому требуется более простая и гибкая система материалов.
        • Tag points (значники) — Эта функция движка позволяет соединять разные части тела, этой функции нет в новой версии, и приходится сейчас использовать сложные обходные пути, что сильно усложняет задачу.
        • Ориентирование на OpenGL2 — OpenMW не делался под старое оборудование, на которое был рассчитан Morrowind, но отказываться от его поддержки мы не собираемся, это вызвало бы многочисленные проблемы у игроков.
        • Система шаблонов материалов. — В Morrowind есть так называемые .nif файлы, используемые для установки шаблонов. В Ogre3D эта система не поддерживается.
        • Масштабирование ширины NPC — NPC в Morrowind имеют свойство «масса», которое влияет на их ширину по его локальной оси X. OpenMW вынужден её игнорировать, т.к. система скелетов в Ogre3D не проводит одномерное масштабирование (только по 1-й оси).
        • Менеджер ресурсов — Ogre3D использует глобальный менеджер ресурсов, в то время как игре требуется по одному на каждый документ в OpenCS. Мало того, каждый ресурс должен иметь уникальный идентификатор, отдельные проблемы возникают, если случайно было создано два объекта с одинаковым именем. Команда Ogre3D работает над этим с 2013 года, и не похоже, что они решают эту проблему.


        1. oYASo
          19.11.2018 12:26

          Не, Ogre3D имеет рендеры и OpenGL, и DirectX. На винде можно использовать любой.


  1. anikavoi
    18.11.2018 22:05

    Интересно, автор доедет до osgUI и osgWidgets, или сломается, как все, в районе osgViewer? :)
    Буду надеяться на лучшее.

    ЗЫ: Если у кого-нибудь есть хоть какие-то примеры osgUI, кинте в личку, плс.


    1. maisvendoo Автор
      18.11.2018 22:40
      +2

      Думаю дойду, потому что меня самого это интересует. Но тут вилка — в корпоративном проекте у меня задача осваивать UI не стоит, а в личном проекте — имеется. Так что как выгорит. Собственно, не было бы желания разобраться, не стал бы писать. На сегодня готов материал в районе 35 уроков вчерне. Но для хабра он компонуется по нескольку уроков в статью


      1. anikavoi
        18.11.2018 22:56

        Дай Будда тебе терпения и здоровья, добрый человек :)


      1. oYASo
        19.11.2018 12:30

        Круто! Это будут, пожалуй, единственные уроки в рунете.

        Придется столкнуться с малым количеством комментариев, но это нынче нормальное явление на Хабре с техническими статьями. От этого они хуже становятся.


        1. maisvendoo Автор
          20.11.2018 01:29
          +1

          Придется столкнуться с малым количеством комментариев

          Я пишу статьи на хабр не для того, чтобы играть на хайпе, и не для рейтингов (хотя я был первым в рейтинге хабра в 15 году за магию тензорной алгебры. Было приятно, что уж говорить). Пишу потому, что узнал и нашел что-то интересное, и хочу этим походя поделится. А хабр для этого подходит очень хорошо. Мой принцип «научился — поделись этим, и пусть все об этом знают». Я за открытые исходники и доступную инфу, и мне пофиг на хайп и рейтинги.

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

          Читателям спасибо, плюсы и положительные отзывы тронули меня очень. Буду пытаться делится тем что узнал и далее. В меру сил