26 January 2019

OpenSceneGraph: Основы работы с текстурами

ProgrammingWorking with 3D-graphicsGame developmentDevelopment for LinuxDevelopment for Windows
Tutorial
image

Введение


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

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

1. Представление данных растровых изображений. Класс osg::Image


Лучшим способом загрузки изображения с диска служит применение вызова osgDB::readImageFile(). Оно очень похож на уже набивший нам оскомину вызов osg::readNodeFile(). Если у нас есть битмэп с именем picture.bmp, то его загрузка будет выглядеть так

osg::ref_ptr<osg::Image> image = osgDB::readImageFile("picture.bmp");

Если изображение загружено корректно, то указатель будет валидным, в противном случае функция возвратит NULL. После загрузки мы можем получить информацию об изображении, используя следующие публичные методы

  1. t(), s() и r() — возвращают ширину, высоту и глубину изображения.
  2. data() — возвращает указатель типа unsigned char* на "сырые" данные изображения. Через данный указатель разработчик может непосредственно воздействовать на данные изображения. Получить представление о формате данных изображения можно, используя методы getPixalFormat() и getDataType(). Возвращаемые ими значения эквивалентны параметрам формата и типа функций OpenGL glTexImage*(). Например, если картинка имеет формат пикселя GL_RGB и тип данный GL_UNSIGNED_BYTE то используются три независимых элемента (беззнаковых байта) для представления RGB-компонент цвета



Можно создать новый объект изображения и выделить под него память

osg::ref_ptr<osg::Image> image = new osg::Image;
image->allocateImage(s, t, r, GL_RGB, GL_UNSIGNED_BYTE);
unsigned char *ptr = image->data();
// Далее выполняем с буфером данных изображения любые операции 

Здесь s, t, r — размеры изображения; GL_RGB задает формат пикселя, а GL_UNSIGNED_BYTE — тип данных для описания одной цветовой компоненты. Внутренний буфер данных нужного размера выделяется в памяти и автоматически уничтожается, если на данное изображение нет ни одной ссылки.

Система плагинов OSG поддерживает загрузку чуть ли не всех популярных форматов изображений: *.jpg, *.bmp, *.png, *.tif и так далее. Этот список нетрудно расширить, написав собственный плагин, но это тема для отдельной беседы.

2. Основы текстурирования


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

  1. Задать геометрическому объекту текстурные координаты вершин (в среде трехмерных дизайнеров это называется UV-разверткой).
  2. Создать объект атрибута текстуры для 1D, 2D, 3D или кубической текстуры.
  3. Задать одно или несколько изображения для атрибута текстуры.
  4. Прикрепить текстурный атрибут и режим к набору состояний, применяемому к отрисовываемому объекту.

OSG определяет класс osg::Texture, инкапсулирующий все виды текстур. От него наследуются подклассы osg::Texture1D, osg::Texture2D, osg::Texture3D и osg::TextureCubeMap, которые представляю различные техники текстурирования, принятые в OpenGL.

Наиболее употребимый метод класса osg::Texture это setImage(), задающий изображение, используемое в текстуре, например

osg::ref_ptr<osg::Image> image = osgDB::readImageFile("picture.bmp");
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
texture->setImage(image.get());

или, можно передать объект изображения непосредственно в конструктор класса текстуры

osg::ref_ptr<osg::Image> image = osgDB::readImageFile("picture.bmp");
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D(image.get());

Изображение можно получить обратно из объекта текстуры вызвав метод getImage().

Другим важным моментом является задание текстурных координат для каждой вершины в объекта osg::Geometry. Передача этих координат происходит через массив osg::Vec2Array и osg::Vec3Array вызовом метода setTexCoordArray().

После задания текстурных координат мы должны установить номер текстурного слота (юнит), так как OSG поддерживает наложение нескольких текстур на одну и ту же геометрию. При использовании одной текстуры номер юнита всегда равен 0. Например, следующий код иллюстрирует задание текстурных координат для юнита 0 геометрии

osf::ref_ptr<osg::Vec2Array> texcoord = new osg::Vec2Array;
texcoord->push_back( osg::Vec2(...) );
...
geom->setTexCoordArray(0, texcoord.get());

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

geom->getOrCreateStateSet()->setTextureAttributeAndModes(texture.get());

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

texture->setUnRefImageDataAfterApply( true );

3. Загружаем и применяем 2D-текстуру


Чаще всего используется техника 2D-текстурирования — накладывание двухмерного изображения (или изображений) на грани трехмерной поверхности. Рассмотрим простейший пример наложения одной текстуры на четырехугольный полигон

Пример texture
main.h

#ifndef		MAIN_H
#define		MAIN_H

#include    <osg/Texture2D>
#include    <osg/Geometry>
#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::Vec3Array> vertices = new osg::Vec3Array;
    vertices->push_back( osg::Vec3(-0.5f, 0.0f, -0.5f) );
    vertices->push_back( osg::Vec3( 0.5f, 0.0f, -0.5f) );
    vertices->push_back( osg::Vec3( 0.5f, 0.0f,  0.5f) );
    vertices->push_back( osg::Vec3(-0.5f, 0.0f,  0.5f) );

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

    osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
    texcoords->push_back( osg::Vec2(0.0f, 0.0f) );
    texcoords->push_back( osg::Vec2(0.0f, 1.0f) );
    texcoords->push_back( osg::Vec2(1.0f, 1.0f) );
    texcoords->push_back( osg::Vec2(1.0f, 0.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->setTexCoordArray(0, texcoords.get());
    quad->addPrimitiveSet( new osg::DrawArrays(GL_QUADS, 0, 4) );

    osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
    osg::ref_ptr<osg::Image> image = osgDB::readImageFile("../data/Images/lz.rgb");
    texture->setImage(image.get());

    osg::ref_ptr<osg::Geode> root = new osg::Geode;
    root->addDrawable(quad.get());
    root->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.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.5f, 0.0f, -0.5f) );
vertices->push_back( osg::Vec3( 0.5f, 0.0f, -0.5f) );
vertices->push_back( osg::Vec3( 0.5f, 0.0f,  0.5f) );
vertices->push_back( osg::Vec3(-0.5f, 0.0f,  0.5f) );

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

Создаем массив текстурных координат

osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
texcoords->push_back( osg::Vec2(0.0f, 0.0f) );
texcoords->push_back( osg::Vec2(0.0f, 1.0f) );
texcoords->push_back( osg::Vec2(1.0f, 1.0f) );
texcoords->push_back( osg::Vec2(1.0f, 0.0f) );

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

Далее создаем квадрат, присваивая геометрии массив вершин и массив нормалей

osg::ref_ptr<osg::Geometry> quad = new osg::Geometry;
quad->setVertexArray(vertices.get());
quad->setNormalArray(normals.get());
quad->setNormalBinding(osg::Geometry::BIND_OVERALL);
quad->setTexCoordArray(0, texcoords.get());
quad->addPrimitiveSet( new osg::DrawArrays(GL_QUADS, 0, 4) );

Создаем объект текстуры и загружаем изображение, используемое для нее

osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
osg::ref_ptr<osg::Image> image = osgDB::readImageFile("../data/Images/lz.rgb");
texture->setImage(image.get());

Создаем корневой узел сцены и помещаем туда созданную нами геометрию

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

и, наконец, применяем атрибут текстуры к узлу, в который помещена геометрия

root->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get());



Класс osg::Texture2D определяет, являются ли размеры изображения текстуры кратными степеням двойки (например 64х64 или 256х512) автоматически масштабируя неподходящие по размеру изображения, фактически применяя функцию gluScaleImage() OpenGL. Существует метод setResizeNonPowerOfTwoHint(), определяющий, нужно или нет изменять размер изображения. Некоторые видеокарты требуют кратность размера изображения степени двойки, в то время как класс osg::Texture2D поддерживает работу с произвольным размером текстуры.

Кое-что о режиме наложения текстур


Как мы уже говорили, текстурные координаты нормированы от 0 до 1. Точке (0, 0) соответствует левый верхний угол изображения, а точке (1, 1) — правый нижний. Что будет, если задать текстурные координаты больше единицы?

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

Этим поведением можно управлять через метод setWrap() класса osg::Texture. В качестве первого параметра метод принимает идентификатор оси, к которой следует применить режим наложения, передаваемый в качестве второго параметра, например

// Повторять текстуру по оси s
texture->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT ); 
// Повторять текстуру по оси r
texture->setWrap( osg::Texture::WRAP_R, osg::Texture::REPEAT ); 

Данный код явно указывает движку повторять текстуру по осям s и r, если значения текстурных координат превышают 1. Полный список режимом наложения текстур:

  1. REPEAT — повторять текстуру.
  2. MIRROR — повторять текстуру, отразив зеркально.
  3. CLAMP_TO_EDGE — координаты, выходящие за пределы от 0 до 1 привязываются к соответствующему краю текстуры.
  4. CLAMP_TO_BORDER — координаты, выходящие за пределы от 0 до 1 будут давать установленный пользователем цвет границы.

4. Рендеринг в текстуру


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

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

  1. Создать объект текстуры для рендеринга в неё.
  2. Отрендерить сцену в текстуру.
  3. Использовать полученную текстуру по назначению.

Мы должны создать пустой текстурный объект. OSG позволяет создать пустую текстуру заданного размера. Метод setTextureSize() позволяет задавать ширину и высоту текстуры, а так же ещё глубину в качестве дополнительного параметра (для 3D-текстур).

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

camera->attach( osg::Camera::COLOR_BUFFER, texture.get() );

К другим, доступным для рендеринга частям кадрового буфера, относятся буфер глубины DEPTH_BUFFER, буфер трафарета STENCIL_BUFFER дополнительные буферы цвета от COLOR_BUFFER0 до COLOR_BUFFER15. Наличие дополнительных буферов цвета и их количество определяется моделью видеокарты.

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

5. Пример реализации рендеринга в текстуру


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

Пример texrender
main.h

#ifndef		MAIN_H
#define		MAIN_H

#include    <osg/Camera>
#include    <osg/Texture2D>
#include    <osg/MatrixTransform>
#include    <osgDB/ReadFile>
#include    <osgGA/TrackballManipulator>
#include    <osgViewer/Viewer>

#endif

main.cpp

#include	"main.h"

//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
osg::Geometry *createQuad(const osg::Vec3 &pos, float w, float h)
{
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    vertices->push_back( pos + osg::Vec3( w / 2, 0.0f, -h / 2) );
    vertices->push_back( pos + osg::Vec3( w / 2, 0.0f,  h / 2) );
    vertices->push_back( pos + osg::Vec3(-w / 2, 0.0f,  h / 2) );
    vertices->push_back( pos + osg::Vec3(-w / 2, 0.0f, -h / 2) );

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

    osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
    texcoords->push_back( osg::Vec2(1.0f, 1.0f) );
    texcoords->push_back( osg::Vec2(1.0f, 0.0f) );
    texcoords->push_back( osg::Vec2(0.0f, 0.0f) );
    texcoords->push_back( osg::Vec2(0.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->setTexCoordArray(0, texcoords.get());
    quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));

    return quad.release();
}

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

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

    osg::ref_ptr<osg::MatrixTransform> transform1 = new osg::MatrixTransform;
    transform1->setMatrix(osg::Matrix::rotate(0.0, osg::Vec3(0.0f, 0.0f, 1.0f)));
    transform1->addChild(sub_model.get());

    osg::ref_ptr<osg::Geode> model = new osg::Geode;
    model->addChild(createQuad(osg::Vec3(0.0f, 0.0f, 0.0f), 2.0f, 2.0f));

    int tex_widht = 1024;
    int tex_height = 1024;

    osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
    texture->setTextureSize(tex_widht, tex_height);
    texture->setInternalFormat(GL_RGBA);
    texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR);
    texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR);

    model->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get());    

    osg::ref_ptr<osg::Camera> camera = new osg::Camera;
    camera->setViewport(0, 0, tex_widht, tex_height);
    camera->setClearColor(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
    camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    camera->setRenderOrder(osg::Camera::PRE_RENDER);
    camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
    camera->attach(osg::Camera::COLOR_BUFFER, texture.get());

    camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
    camera->addChild(transform1.get());

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

    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());
    viewer.setCameraManipulator(new osgGA::TrackballManipulator);
    viewer.setUpViewOnSingleScreen(0);

    camera->setProjectionMatrixAsPerspective(30.0, static_cast<double>(tex_widht) / static_cast<double>(tex_height), 0.1, 1000.0);

    float dist = 100.0f;
    float alpha = 10.0f * 3.14f / 180.0f;
    
    osg::Vec3 eye(0.0f, -dist * cosf(alpha), dist * sinf(alpha));
    osg::Vec3 center(0.0f, 0.0f, 0.0f);
    osg::Vec3 up(0.0f, 0.0f, -1.0f);
    camera->setViewMatrixAsLookAt(eye, center, up);

    float phi = 0.0f;
    float delta = -0.01f;

    while (!viewer.done())
    {
        transform1->setMatrix(osg::Matrix::rotate(static_cast<double>(phi), osg::Vec3(0.0f, 0.0f, 1.0f)));
        viewer.frame();
        phi += delta;
    }

    return 0;
}


Для создания квадрата напишем отдельную свободную функцию

osg::Geometry *createQuad(const osg::Vec3 &pos, float w, float h)
{
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    vertices->push_back( pos + osg::Vec3( w / 2, 0.0f, -h / 2) );
    vertices->push_back( pos + osg::Vec3( w / 2, 0.0f,  h / 2) );
    vertices->push_back( pos + osg::Vec3(-w / 2, 0.0f,  h / 2) );
    vertices->push_back( pos + osg::Vec3(-w / 2, 0.0f, -h / 2) );

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

    osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array;
    texcoords->push_back( osg::Vec2(1.0f, 1.0f) );
    texcoords->push_back( osg::Vec2(1.0f, 0.0f) );
    texcoords->push_back( osg::Vec2(0.0f, 0.0f) );
    texcoords->push_back( osg::Vec2(0.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->setTexCoordArray(0, texcoords.get());
    quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));

    return quad.release();
}

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

В теле основной программы загрузим модельку цессны

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

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

osg::ref_ptr<osg::MatrixTransform> transform1 = new osg::MatrixTransform;
transform1->setMatrix(osg::Matrix::rotate(0.0, osg::Vec3(0.0f, 0.0f, 1.0f)));
transform1->addChild(sub_model.get());

Теперь создадим модель для основной сцены — квадрат на который будем выполнять рендеринг

osg::ref_ptr<osg::Geode> model = new osg::Geode;
model->addChild(createQuad(osg::Vec3(0.0f, 0.0f, 0.0f), 2.0f, 2.0f));

Создаем пустую текстуру для квадрата размером 1024х1024 пикселя с форматом пикселя RGBA (32-битный трехкомпонентный цвет с альфа-каналом)

int tex_widht = 1024;
int tex_height = 1024;

osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
texture->setTextureSize(tex_widht, tex_height);
texture->setInternalFormat(GL_RGBA);
texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR);
texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR);

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

model->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get()); 

Затем создаем камеру, которая будет запекать текстуру

osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setViewport(0, 0, tex_widht, tex_height);
camera->setClearColor(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

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

camera->setRenderOrder(osg::Camera::PRE_RENDER);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
camera->attach(osg::Camera::COLOR_BUFFER, texture.get());

Порядок рендеринга PRE_RENDER указывает на то, что рендеринг этой камерой выполняется до рендеринга в основную сцену. В качестве цели рендеренга указываем FBO и прикрепляем к камере нашу текстуру. Теперь настраиваем камеру на работу в абсолютной системе координат, а в качестве сцены задаем наше поддерево, которое мы желаем рендерить в текстуру: трансформация поворота с прикрепленной к ней моделькой цессны

camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
camera->addChild(transform1.get());

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

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

Создаем и настраиваем вьювер

osgViewer::Viewer viewer;
viewer.setSceneData(root.get());
viewer.setCameraManipulator(new osgGA::TrackballManipulator);
viewer.setUpViewOnSingleScreen(0);

Настраиваем матрицу проекции для камеры — перспективная проекция через параметры пирамиды отсечения

camera->setProjectionMatrixAsPerspective(30.0, static_cast<double>(tex_widht) / static_cast<double>(tex_height), 0.1, 1000.0);

Настраиваем матрицу вида, задающую положение камеры в пространстве по отношению к началу координат подсцены с цессной

float dist = 100.0f;
float alpha = 10.0f * 3.14f / 180.0f;

osg::Vec3 eye(0.0f, -dist * cosf(alpha), dist * sinf(alpha));
osg::Vec3 center(0.0f, 0.0f, 0.0f);
osg::Vec3 up(0.0f, 0.0f, -1.0f);
camera->setViewMatrixAsLookAt(eye, center, up);

Наконец, анимируем и отображаем сцену, меняя угол поворота самолета вокруг оси Z на каждом кадре

float phi = 0.0f;
float delta = -0.01f;

while (!viewer.done())
{
    transform1->setMatrix(osg::Matrix::rotate(static_cast<double>(phi), osg::Vec3(0.0f, 0.0f, 1.0f)));
    viewer.frame();
    phi += delta;
}

В итоге мы получаем довольно интересную картинку



В данном примере мы реализовали некоторую анимацию сцены, но следует помнить о том, что разворачивание цикла run() и изменение параметров рендеринга перед или после отрисовки кадра является небезопасным занятием с точки зрения организации доступа к данным разных потоках. Поскольку OSG использует многопоточный рендеринг, то существуют и штатные механизмы встраивания собственных действий в процесс рендеринга, обеспечивающие потокобезопасный доступ к данным.

6. Сохранение результата рендеринга в файл


OSG поддерживает возможность прикрепить к камере объект osg::Image и сохранить содержимое буфера кадра в буфер данных изображения. После этого возможно сохранить эти данные на диск используя функцию osg::writeImageFile()

osg::ref_ptr<osg::Image> image = new osg::Image;
image->allocateImage( width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE );
camera->attach( osg::Camera::COLOR_BUFFER, image.get() );
...
osgDB::writeImageFile( *image, "saved_image.bmp" );

Заключение


Возможно, материал изложенный в статье покажется тривиальным. Однако, в неё излагаются самые основы работы с текстурами в OpenSceneGraph, на которых базируются более сложные приемы работы с этим движком, о которых мы обязательно поговорим в будущем.

Продолжение следует...
Tags:3d-графикаграфический движокopenscenegraph
Hubs: Programming Working with 3D-graphics Game development Development for Linux Development for Windows
+19
2.6k 40
Comments 4