使用Ogre快速渲染视频纹理

基本思路

测试时可使用和视频画面同大小的全屏四边形Rectangle2D,该rect使用动态纹理材质。

渲染时按帧率动态替换该材质的纹理单元为当前帧图像

视频读取

读取每帧视频画面我使用的是OpenCV,类似如下:

    CvCapture* mCapture = cvCreateFileCapture(mFileName.c_str());

    int totalFrames = (int)cvGetCaptureProperty(mCapture, CV_CAP_PROP_FRAME_COUNT);

    int fps = cvGetCaptureProperty(mCapture, CV_CAP_PROP_FPS);

 

   IplImage* frame = cvQueryFrame(mCapture);

 

frame即为捕捉到的画面,其中最需要的三个属性:

  width 图像宽

  height 图像高

  imageData RGB格式的数据区

 你可以使用任意数据源,最终的Ogre处理都是一样的

第一次尝试

首先想到的是直接使用已有的材质,通过卸载、加载纹理单元的方式:

//卸载原纹理

Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName("testMat");

Ogre::TexturePtr textureOld = mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->_getTexturePtr(0);

textureOld->unload();//可选

Ogre::TextureManager::getSingleton().remove(static_cast<Ogre::ResourcePtr>(textureOld)); 

 

//加载新纹理

Ogre::DataStreamPtr pDataStream(new Ogre::MemoryDataStream(frame->imageData, frame->width * frame->height * 3, false, true));

Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().loadRawData("testTexture",

    Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, pDataStream, frame->width, frame->height,

    Ogre::PF_R8G8B8, Ogre::TEX_TYPE_2D);

mat->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("testTexture");

 

因为我们有原始内存数据frame->imageData,因此可以使用内存数据流,并调用loadRawData方法加载纹理

按理说这种方法应该是很快的,因此只需担心能不能显示正确的画面

 

运行之后,画面是显示出来的,但灰突突的,没有原先的光泽度

想来大概是需要启用gamma矫正,即将纹理创建改写为:

Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().loadRawData("testTexture",

    Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, pDataStream, frame->width,

    frame->height, Ogre::PF_R8G8B8, Ogre::TEX_TYPE_2D, 0, 1.0f, true);

最后三个参数,分别设置mipmap为0, 采用硬件gamma校正(如果用软件校正,即将1.0f改为2.2,将会惊人的慢)

这一次,图像完全和原始视频相同了,但是帧率非常的低

改进

因为每次切换画面使用的纹理格式、大小都完全相同

显然可以考虑直接替换已有纹理单元的内存数据区,而不是先销毁再创建,代码如下:

    Ogre::PixelBox box(frame->width, frame->height, 1, Ogre::PF_R8G8B8, frame->imageData);

    texture->getBuffer(0, 0)->blitFromMemory(box);

这一次想来应该是足够快了,然而结果仍不理想

跟踪下去,发现blitFromMemory 的类似实现最终都调用了D3DXLoadSurfaceFromMemory,性能的瓶颈都出在这里

因为已经是D3D API,这条路也就不能继续走下去了

 

另外虽然HardwarePixelBuffer的接口声明了writeData接口,但并没有实现,也是不能使用

 

看来只剩下一种办法,通过lock复制数据,希望这一次可以成功了:

void* dst = texture->getBuffer(0, 0)->lock(Ogre::HardwareBuffer::HBL_DISCARD);

memcpy(dst, frame->imageData, frame->width * frame->height * 3);

texture->getBuffer(0, 0)->unlock();

 

这么做发现帧速确实上去了,但图像变成了重影的灰度图,显然是因为内存格式不一致

好在看显示的图像高度好像正好是原来的3/4

再考虑我们用的是RGB格式,许是因为硬件使用4字节RGBA存储像素的原因

于是修改代码为:

char* dst =(char*)( texture->getBuffer(0, 0)->lock(Ogre::HardwareBuffer::HBL_DISCARD));

char* src = frame->imageData;

for(int i=0; i<frame->width; ++i)

{

  for(int j=0; j<frame->height; ++j)

  {

    *dst++=*src++;

    *dst++=*src++;

    *dst++=*src++;

    *dst++=0; //填充

  }

}

texture->getBuffer(0, 0)->unlock();

啊哦,这一次终于成功了

进一步改进

使用的默认纹理单元时,缓冲区用途默认为TU_DEFAULT

对于需要快速刷新的场景应使用TU_DYNAMIC_WRITE_ONLY_DISCARDABLE

因此可以将纹理的创建改写为:

texture = Ogre::TextureManager::getSingleton().createManual(textureName,

  Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, frame->width, frame->height, 0,

  Ogre::PF_R8G8B8, Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE, 0, true);

这可以进一步提高性能

 

附:在测试opengl渲染时,这么处理性能不能满足预期要求,大概是Ogre的实现问题

我没有进行太多追究,如果谁正好知道,可以跟帖说一下

 

posted @ 2015-07-26 12:40  wiki3D  阅读(1784)  评论(0编辑  收藏  举报