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:
Ogre::TexturePtr HudTexture;
QImage ImageHudSpeed;
// get template image to use used later from the App resource area
ImageHudSpeed = QImage( ":images/Hud-Speed.png" );
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 );
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();
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