Ogre overlays using Qt        

How to draw Ogre overlays using Qt framework 2D drawing functionality

The background:

If you'd like to display 2D graphics over the top of the Ogre rendered 3D output the overlay
functionality works nicely. If the overlays are dynamic that introduces the requirement for
a 2D drawing library to create the overlays on the fly. In my application I was already
using the Qt framework for the user interface. Qt also includes the ability to draw 2D
graphics and text using the QPainter class. I found a simple solution to use the Qt framework
with Ogre overlays.

In my project (the Landefall MMO) I need a "Heads Up Display". I decided to create a single Ogre
overlay panel the same size as the render window. I haven't tested it but I believe a single
large overlay panel will be faster to render than a number of smaller panels. The panel will
have a material with a single texture containing an image of the HUD. The majority of the texture
will be transparent so the 3D render window can actually be seen (that's always good).

The solution:

The Qt framework provides the QPainter class to render on any of a variety of backing stores.
If you use a backing store that's an in memory bitmap then you can use QPainter to draw
the HUD then paste it into the Ogre overlay panel texture.

Making it smaller and faster:

This works very well but I wanted to reduce the overhead as much as possible. The QImage
documentation reveals that it's possible to draw on an already allocated memory buffer.
I found there's an image storage format that's the same in both the Qt QImage and the Ogre
pixel buffer code. I locate the pixel buffer already create by Ogre and pass this to
QImage as its buffer to store the image. This eliminates the need to convert the image
from one format to another and copy the image from the QImage buffer to the Ogre pixel
buffer. It uses less memory, less code, and should be faster.


Example code:

Setup
Ogre::Overlay* overlay;

Ogre::TexturePtr HudTexture;

QImage ImageHudSpeed;

// get template image to use used later from the App resource area

ImageHudSpeed = QImage( ":images/Hud-Speed.png" );



Programmatically create the overlay material:
// Create the texture

HudTexture = Ogre::TextureManager::getSingleton().createManual(

"HUDTexture", // name

Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,

Ogre::TEX_TYPE_2D, // type

width(), height(), // width & height of the render window

0, // number of mipmaps

Ogre::PF_A8R8G8B8, // pixel format chosen to match a format Qt can use

Ogre::TU_DEFAULT // usage

);

// Create a material using the texture

Ogre::MaterialPtr HudMaterial = Ogre::MaterialManager::getSingleton().create( "HUDMaterial", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME );

HudMaterial->getTechnique( 0 )->getPass( 0 )->createTextureUnitState( "HUDTexture" );

HudMaterial->getTechnique( 0 )->getPass( 0 )->setSceneBlending( Ogre::SBT_TRANSPARENT_ALPHA );



Programmatically create the overlay panel:
// Create an overlay

Ogre::OverlayManager& overlayManager = Ogre::OverlayManager::getSingleton();

overlay = overlayManager.create( "OverlayName" );

// Create a panel

Ogre::OverlayContainer* panel = static_cast<Ogre::OverlayContainer*>( overlayManager.createOverlayElement( "Panel", "PanelName" ) );

panel->setPosition( 0.0, 0.0 );

panel->setDimensions( 1.0, 1.0 ); // cover the entire window

panel->setMaterialName( "HUDMaterial" );

// Add the panel to the overlay

overlay->add2D( panel );

// Show the overlay

overlay->show();


Update the HUD texture image:
// Get the pixel buffer

Ogre::HardwarePixelBufferSharedPtr pixelBuffer = HudTexture->getBuffer();

// Lock the pixel buffer and get a pixel box

pixelBuffer->lock( Ogre::HardwareBuffer::HBL_NORMAL ); // for best performance use HBL_DISCARD!

const Ogre::PixelBox& pixelBox = pixelBuffer->getCurrentLock();

Ogre::uint8* pDest = static_cast<Ogre::uint8*> ( pixelBox.data );

// construct HUD image directly in the texture buffer

{

// fill to get 100% transparent image

// the buffer content is the colors R,G,B,A. Filling with zeros gets a 100% transparent image

memset( pDest, 0, HudTexture->getWidth() * HudTexture->getHeight() );

// tell QImage to use OUR buffer and a compatible image buffer format

QImage Hud( pDest, HudTexture->getWidth(), HudTexture->getHeight(), QImage::Format_ARGB32 );

// paste in HUD speedometer. I resize the image and offset it by 8 pixels from

// the bottom left edge of the render window

QPainter painter( &Hud );

uint8_t Offset = 8;

uint16_t w = ImageHudSpeed.width() / 2;

uint16_t h = ImageHudSpeed.height() / 2;

painter.drawImage( QRect( Offset, HudTexture->getHeight() - h - Offset, w, h ), ImageHudSpeed, QRect( 0, 0, ImageHudSpeed.width(), ImageHudSpeed.height() ) );

// do any other drawing you would like here using painter

// done

painter.end();

}

// note the QImage is destroyed. Bad things will happen if you reuse the QImage!

// Unlock the pixel buffer

pixelBuffer->unlock();


That's it. Here's an example of my output

ScreenCapture.2012.5.10.12.50.21.jpg