Введение в OpenSceneGraph: Проигрывание видео на текстуре
Было бы круто, если бы могли проводить видеоконференции в воображаемом трехмерном мире. Нет ничего невозможного – создаем прямоугольный полигон в качестве экрана и крепим к нему динамическую текстуру. Текстура содержит серию изображений, составляющих видео. OSG использует класс osg::ImageStream, работающий с потоком изображений, получая их из видеокамеры или Интернет-трансляции. На сегодняшний день OSG имеет несколько готовых к работе плагинов, поддерживающих загрузку и воспроизведение видео в форматах AVI, MPG, MOV и так далее.
Пока мы рассмотрим этот эффект на более простом примере, используя другой класс osg::ImageSequence, позволяющий сохранять несколько объектов изображений с последовательным их воспроизведением. Этот класс имеет следующие публичные методы:
- addImage() – добавление изображения osg::Image в последовательность. Кроме того доступны методы setImage() и getImage() с доступом к последовательности по индексу, а так же getNumImages(), для получения числа изображений в последовательности.
- addImageFile() и setImageFile() – выполняют те же действия, но в качестве источника изображения указывается файл на диске.
- setLength() – задать общее время проигрывания последовательности в секундах.
- setTimeMultiplier() – задает множитель ускорения/замедления проигрывания последовательности.
- play(), pause(), rewind() и seek() – соотвественно начать проигрывание, поставить на паузу, перемотать и стать на заданную позицию (в секундах).
Покажем работу с этим классом на примере - создадим последовательность кадров, изображающих некий точечный источник света с изменяющейся яркостью и проиграем это импровизированное видео на текстуре.
main.h
#ifndef MAIN_H
#define MAIN_H
#include <osg/ImageSequence>
#include <osg/Texture2D>
#include <osg/Geometry>
#include <osg/Geode>
#include <osgViewer/Viewer>
#endif
main.cpp
#include "main.h"
//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
osg::Image *createSpotLight(const osg::Vec4 ¢erColor,
const osg::Vec4 &bgColor,
int size,
float power)
{
osg::ref_ptr<osg::Image> image = new osg::Image;
image->allocateImage(size, size, 1, GL_RGBA, GL_UNSIGNED_BYTE);
float mid = (float(size) - 1) * 0.5f;
float div = 2.0f / float(size);
for (int r = 0; r < size; ++r)
{
unsigned char *ptr = image->data(0, r);
for (int c = 0; c < size; ++c)
{
float dx = (float(c) - mid) * div;
float dy = (float(r) - mid) * div;
float r = powf(1.0f - sqrtf(dx*dx + dy*dy), power);
if (r < 0.0f)
r = 0.0f;
osg::Vec4 color = centerColor * r + bgColor * (1.0f - r);
*ptr++ = static_cast<unsigned char>((color[0]) * 255.0f);
*ptr++ = static_cast<unsigned char>((color[1]) * 255.0f);
*ptr++ = static_cast<unsigned char>((color[2]) * 255.0f);
*ptr++ = static_cast<unsigned char>((color[3]) * 255.0f);
}
}
return image.release();
}
//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
int main(int argc, char *argv[])
{
(void) argc; (void) argv;
osg::Vec4 centerColor(1.0f, 1.0f, 0.0f, 1.0f);
osg::Vec4 bgColor(0.0f, 0.0f, 0.0f, 1.0f);
osg::ref_ptr<osg::ImageSequence> sequence = new osg::ImageSequence;
sequence->addImage(createSpotLight(centerColor, bgColor, 64, 3.0f));
sequence->addImage(createSpotLight(centerColor, bgColor, 64, 3.5f));
sequence->addImage(createSpotLight(centerColor, bgColor, 64, 4.0f));
sequence->addImage(createSpotLight(centerColor, bgColor, 64, 3.5f));
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
texture->setImage(sequence.get());
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(osg::createTexturedQuadGeometry(osg::Vec3(),
osg::Vec3(1.0, 0.0, 0.0),
osg::Vec3(0.0, 0.0, 1.0)));
geode->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);
sequence->setLength(0.5);
sequence->play();
osgViewer::Viewer viewer;
viewer.setSceneData(geode.get());
return viewer.run();
}
Для реализации подобной анимации необходимо сгенерировать набор текстур с изображением светящейся точки. Для этого напишем отдельную функцию
osg::Image *createSpotLight(const osg::Vec4 ¢erColor,
const osg::Vec4 &bgColor,
int size,
float power)
{
osg::ref_ptr<osg::Image> image = new osg::Image;
image->allocateImage(size, size, 1, GL_RGBA, GL_UNSIGNED_BYTE);
float mid = (float(size) - 1) * 0.5f;
float div = 2.0f / float(size);
for (int r = 0; r < size; ++r)
{
unsigned char *ptr = image->data(0, r);
for (int c = 0; c < size; ++c)
{
float dx = (float(c) - mid) * div;
float dy = (float(r) - mid) * div;
float r = powf(1.0f - sqrtf(dx*dx + dy*dy), power);
if (r < 0.0f)
r = 0.0f;
osg::Vec4 color = centerColor * r + bgColor * (1.0f - r);
*ptr++ = static_cast<unsigned char>((color[0]) * 255.0f);
*ptr++ = static_cast<unsigned char>((color[1]) * 255.0f);
*ptr++ = static_cast<unsigned char>((color[2]) * 255.0f);
*ptr++ = static_cast<unsigned char>((color[3]) * 255.0f);
}
}
return image.release();
}
В качестве параметров функция будет принимать цвет источника, цвет фона, размер текстуры в пикселях (полагаем текстуру квадратной) и показатель мощности источника, на основании которого мы будем формировать его изображение. Чтобы не расписывать подробно алгоритм формирования изображения (оставлю это читателю для самостоятельного разбора), поясню только ключевые моменты. В функции мы создаем новое изображение и выделяем под него память в соответсвии с его размером и типом данных для отображения цветовых компонент
osg::ref_ptr<osg::Image> image = new osg::Image;
image->allocateImage(size, size, 1, GL_RGBA, GL_UNSIGNED_BYTE);
Далее вычисляем цвет каждого пикселя как суммарный цвет доли цвета источника и цвета фона
osg::Vec4 color = centerColor * r + bgColor * (1.0f - r);
где r - параметр смешения, экспоненциально зависящий от заданной мощности источника и расстояния от точки до центра текстуры (полагаем, что источник находится в центре текстуры)
float r = powf(1.0f - sqrtf(dx*dx + dy*dy), power);
Полученный цвет назначаем соотвествующему пикселю текстуры путем непосредственного обращения к буферу данных изображения по указателю ptr
*ptr++ = static_cast<unsigned char>((color[0]) * 255.0f);
*ptr++ = static_cast<unsigned char>((color[1]) * 255.0f);
*ptr++ = static_cast<unsigned char>((color[2]) * 255.0f);
*ptr++ = static_cast<unsigned char>((color[3]) * 255.0f);
который берем из объекта изображения
unsigned char *ptr = image->data(0, r);
В функции main() задаем цвет источника и цвет фона
osg::Vec4 centerColor(1.0f, 1.0f, 0.0f, 1.0f);
osg::Vec4 bgColor(0.0f, 0.0f, 0.0f, 1.0f);
Создаем объект последовательности изображений, заполняя его несколькими изображениями источника с разной яркостью
osg::ref_ptr<osg::ImageSequence> sequence = new osg::ImageSequence;
sequence->addImage(createSpotLight(centerColor, bgColor, 64, 3.0f));
sequence->addImage(createSpotLight(centerColor, bgColor, 64, 3.5f));
sequence->addImage(createSpotLight(centerColor, bgColor, 64, 4.0f));
sequence->addImage(createSpotLight(centerColor, bgColor, 64, 3.5f));
Создаем текстуру из данной последовательности
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
texture->setImage(sequence.get());
Создаем квадратный полигон и применяем к нему данную текстуру
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(osg::createTexturedQuadGeometry(osg::Vec3(),
osg::Vec3(1.0, 0.0, 0.0),
osg::Vec3(0.0, 0.0, 1.0)));
geode->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);
Задаем продолжительность проигрывания последовательности в полсекунды и запускаем проигрывание
sequence->setLength(0.5);
sequence->play();
Инициализируем и запускаем вьювер (можно я не буду больше об этом писать?)
osgViewer::Viewer viewer;
viewer.setSceneData(geode.get());
return viewer.run();
В итоге имеем интересный эффект динамической текстуры
Класс osg::ImageSequence обновляет данные рендеринга на каждом кадре на основе хранимых в нем изображений.
Интересным является применение методов addFileName() и setFileName(), загружающих файлы изображений непосредственно с диска. Здесь возможно несколько режимов работы, задаваемых методом setMode():
- PRE_LOAD_ALL_IMAGES – загрузка всех изображений сразу. Экономит время на загрузку, но раздувает используемую память процесса.
- PAGE_AND_RETAIN_IMAGES – загрузка файлов на лету с последующим кэшированием данных изображений.
- PAGE_AND_DISCARD_USED_IMAGES – удаляет все использованные изображения из памяти и перезагружает их, когда они вновь потребуются.
Для задания режима следует выполнить вызов
imageSequence->setMode( osg::ImageSequence::PAGE_AND_RETAIN_IMAGES );