From Wang Rui, "Attachment is an example of rendering 3D scenes to high resolution screenshots.
I uses a queue of Camera objects to do offscreen rendering with the Camera::attach() function. The entire picture is split into many tiles and it will take a few seconds while attaching and detaching cameras with tiles. You may select to output every tile as an image file, or combine them together to create a large poster, for example, a 12800 x 9600 image. Start the program like this: ./osgposter --output-poster --poster output.bmp --tilesize 800 600 --finalsize 8000 6000 cow.osg Adjust the scene camera to a suitable position and press 'p' or 'P' on the keyboard. Wait until sub-cameras dispatching is finished. And the poster file will be created while closing window. A 8000 x 6000 output.bmp will be created to show a fine-printed cow. :) The command below may also help: ./osgposter --help "
This commit is contained in:
parent
c665fdc973
commit
1402583d17
@ -80,6 +80,7 @@ IF(DYNAMIC_OPENSCENEGRAPH)
|
||||
ADD_SUBDIRECTORY(osgplanets)
|
||||
ADD_SUBDIRECTORY(osgpoints)
|
||||
ADD_SUBDIRECTORY(osgpointsprite)
|
||||
ADD_SUBDIRECTORY(osgposter)
|
||||
ADD_SUBDIRECTORY(osgprecipitation)
|
||||
ADD_SUBDIRECTORY(osgprerender)
|
||||
ADD_SUBDIRECTORY(osgprerendercubemap)
|
||||
|
2
examples/osgposter/CMakeLists.txt
Normal file
2
examples/osgposter/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
SET(TARGET_SRC osgposter.cpp PosterPrinter.cpp )
|
||||
SETUP_EXAMPLE(osgposter)
|
399
examples/osgposter/PosterPrinter.cpp
Normal file
399
examples/osgposter/PosterPrinter.cpp
Normal file
@ -0,0 +1,399 @@
|
||||
#include <osg/ClusterCullingCallback>
|
||||
#include <osgDB/ReadFile>
|
||||
#include <osgDB/WriteFile>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include "PosterPrinter.h"
|
||||
|
||||
/* PagedLoadingCallback: Callback for loading paged nodes while doing intersecting test */
|
||||
struct PagedLoadingCallback : public osgUtil::IntersectionVisitor::ReadCallback
|
||||
{
|
||||
virtual osg::Node* readNodeFile( const std::string& filename )
|
||||
{
|
||||
return osgDB::readNodeFile( filename );
|
||||
}
|
||||
};
|
||||
static osg::ref_ptr<PagedLoadingCallback> g_pagedLoadingCallback = new PagedLoadingCallback;
|
||||
|
||||
/* LodCullingCallback: Callback for culling LODs and selecting the highest level */
|
||||
class LodCullingCallback : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
virtual void operator()( osg::Node* node, osg::NodeVisitor* nv )
|
||||
{
|
||||
osg::LOD* lod = static_cast<osg::LOD*>(node);
|
||||
if ( lod && lod->getNumChildren()>0 )
|
||||
lod->getChild(lod->getNumChildren()-1)->accept(*nv);
|
||||
}
|
||||
};
|
||||
static osg::ref_ptr<LodCullingCallback> g_lodCullingCallback = new LodCullingCallback;
|
||||
|
||||
/* PagedCullingCallback: Callback for culling paged nodes and selecting the highest level */
|
||||
class PagedCullingCallback : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
virtual void operator()( osg::Node* node, osg::NodeVisitor* nv )
|
||||
{
|
||||
osg::PagedLOD* pagedLOD = static_cast<osg::PagedLOD*>(node);
|
||||
if ( pagedLOD && pagedLOD->getNumChildren()>0 )
|
||||
{
|
||||
unsigned int numChildren = pagedLOD->getNumChildren();
|
||||
bool updateTimeStamp = nv->getVisitorType()==osg::NodeVisitor::CULL_VISITOR;
|
||||
if ( nv->getFrameStamp() && updateTimeStamp )
|
||||
{
|
||||
double timeStamp = nv->getFrameStamp()?nv->getFrameStamp()->getReferenceTime():0.0;
|
||||
int frameNumber = nv->getFrameStamp()?nv->getFrameStamp()->getFrameNumber():0;
|
||||
|
||||
pagedLOD->setFrameNumberOfLastTraversal( frameNumber );
|
||||
pagedLOD->setTimeStamp( numChildren-1, timeStamp );
|
||||
pagedLOD->setFrameNumber( numChildren-1, frameNumber );
|
||||
pagedLOD->getChild(numChildren-1)->accept(*nv);
|
||||
}
|
||||
|
||||
// Request for new child
|
||||
if ( !pagedLOD->getDisableExternalChildrenPaging() &&
|
||||
nv->getDatabaseRequestHandler() &&
|
||||
numChildren<pagedLOD->getNumRanges() )
|
||||
{
|
||||
if ( pagedLOD->getDatabasePath().empty() )
|
||||
{
|
||||
nv->getDatabaseRequestHandler()->requestNodeFile(
|
||||
pagedLOD->getFileName(numChildren), pagedLOD,
|
||||
1.0, nv->getFrameStamp(),
|
||||
pagedLOD->getDatabaseRequest(numChildren), pagedLOD->getDatabaseOptions() );
|
||||
}
|
||||
else
|
||||
{
|
||||
nv->getDatabaseRequestHandler()->requestNodeFile(
|
||||
pagedLOD->getDatabasePath()+pagedLOD->getFileName(numChildren), pagedLOD,
|
||||
1.0, nv->getFrameStamp(),
|
||||
pagedLOD->getDatabaseRequest(numChildren), pagedLOD->getDatabaseOptions() );
|
||||
}
|
||||
}
|
||||
}
|
||||
//node->traverse(*nv);
|
||||
}
|
||||
};
|
||||
static osg::ref_ptr<PagedCullingCallback> g_pagedCullingCallback = new PagedCullingCallback;
|
||||
|
||||
/* PosterVisitor: A visitor for adding culling callbacks to newly allocated paged nodes */
|
||||
PosterVisitor::PosterVisitor()
|
||||
: osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
|
||||
_appliedCount(0), _needToApplyCount(0),
|
||||
_addingCallbacks(true)
|
||||
{
|
||||
}
|
||||
|
||||
void PosterVisitor::apply( osg::LOD& node )
|
||||
{
|
||||
/*if ( !hasCullCallback(node.getCullCallback(), g_lodCullingCallback.get()) )
|
||||
{
|
||||
if ( !node.getName().empty() )
|
||||
{
|
||||
PagedNodeNameSet::iterator itr = _pagedNodeNames.find( node.getName() );
|
||||
if ( itr!=_pagedNodeNames.end() )
|
||||
{
|
||||
insertCullCallback( node, g_lodCullingCallback.get() );
|
||||
_appliedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( !_addingCallbacks )
|
||||
{
|
||||
node.removeCullCallback( g_lodCullingCallback.get() );
|
||||
_appliedCount--;
|
||||
}*/
|
||||
traverse( node );
|
||||
}
|
||||
|
||||
void PosterVisitor::apply( osg::PagedLOD& node )
|
||||
{
|
||||
if ( !hasCullCallback(node.getCullCallback(), g_pagedCullingCallback.get()) )
|
||||
{
|
||||
for ( unsigned int i=0; i<node.getNumFileNames(); ++i )
|
||||
{
|
||||
if ( node.getFileName(i).empty() ) continue;
|
||||
|
||||
PagedNodeNameSet::iterator itr = _pagedNodeNames.find( node.getFileName(i) );
|
||||
if ( itr!=_pagedNodeNames.end() )
|
||||
{
|
||||
node.addCullCallback( g_pagedCullingCallback.get() );
|
||||
_appliedCount++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ( !_addingCallbacks )
|
||||
{
|
||||
node.removeCullCallback( g_pagedCullingCallback.get() );
|
||||
if ( _appliedCount>0 ) _appliedCount--;
|
||||
}
|
||||
traverse( node );
|
||||
}
|
||||
|
||||
/* PosterIntersector: A simple polytope intersector for updating pagedLODs in each image-tile */
|
||||
PosterIntersector::PosterIntersector( const osg::Polytope& polytope )
|
||||
: _intersectionVisitor(0), _parent(0), _polytope(polytope)
|
||||
{}
|
||||
|
||||
PosterIntersector::PosterIntersector( double xMin, double yMin, double xMax, double yMax )
|
||||
: Intersector(osgUtil::Intersector::PROJECTION),
|
||||
_intersectionVisitor(0), _parent(0)
|
||||
{
|
||||
_polytope.add( osg::Plane( 1.0, 0.0, 0.0,-xMin) );
|
||||
_polytope.add( osg::Plane(-1.0, 0.0, 0.0, xMax) );
|
||||
_polytope.add( osg::Plane( 0.0, 1.0, 0.0,-yMin) );
|
||||
_polytope.add( osg::Plane( 0.0,-1.0, 0.0, yMax) );
|
||||
}
|
||||
|
||||
osgUtil::Intersector* PosterIntersector::clone( osgUtil::IntersectionVisitor& iv )
|
||||
{
|
||||
osg::Matrix matrix;
|
||||
if ( iv.getProjectionMatrix() ) matrix.preMult( *iv.getProjectionMatrix() );
|
||||
if ( iv.getViewMatrix() ) matrix.preMult( *iv.getViewMatrix() );
|
||||
if ( iv.getModelMatrix() ) matrix.preMult( *iv.getModelMatrix() );
|
||||
|
||||
osg::Polytope transformedPolytope;
|
||||
transformedPolytope.setAndTransformProvidingInverse( _polytope, matrix );
|
||||
|
||||
osg::ref_ptr<PosterIntersector> pi = new PosterIntersector( transformedPolytope );
|
||||
pi->_intersectionVisitor = &iv;
|
||||
pi->_parent = this;
|
||||
return pi.release();
|
||||
}
|
||||
|
||||
bool PosterIntersector::enter( const osg::Node& node )
|
||||
{
|
||||
if ( !node.isCullingActive() ) return true;
|
||||
if ( _polytope.contains(node.getBound()) )
|
||||
{
|
||||
if ( node.getCullCallback() )
|
||||
{
|
||||
const osg::ClusterCullingCallback* cccb =
|
||||
dynamic_cast<const osg::ClusterCullingCallback*>( node.getCullCallback() );
|
||||
if ( cccb && cccb->cull(_intersectionVisitor, 0, NULL) ) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PosterIntersector::reset()
|
||||
{
|
||||
_intersectionVisitor = NULL;
|
||||
Intersector::reset();
|
||||
}
|
||||
|
||||
void PosterIntersector::intersect( osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable )
|
||||
{
|
||||
if ( !_polytope.contains(drawable->getBound()) ) return;
|
||||
if ( iv.getDoDummyTraversal() ) return;
|
||||
|
||||
// Find and collect all paged LODs in the node path
|
||||
osg::NodePath& nodePath = iv.getNodePath();
|
||||
for ( osg::NodePath::iterator itr=nodePath.begin(); itr!=nodePath.end(); ++itr )
|
||||
{
|
||||
osg::PagedLOD* pagedLOD = dynamic_cast<osg::PagedLOD*>(*itr);
|
||||
if ( pagedLOD )
|
||||
{
|
||||
// FIXME: The first non-empty getFileName() is used as the identity of this paged node.
|
||||
// This should work with VPB-generated terrains but maybe unusable with others.
|
||||
for ( unsigned int i=0; i<pagedLOD->getNumFileNames(); ++i )
|
||||
{
|
||||
if ( pagedLOD->getFileName(i).empty() ) continue;
|
||||
if ( _parent->_visitor.valid() )
|
||||
_parent->_visitor->insertName( pagedLOD->getFileName(i) );
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/*osg::LOD* lod = dynamic_cast<osg::LOD*>(*itr);
|
||||
if ( lod )
|
||||
{
|
||||
if ( !lod->getName().empty() && _parent->_visitor.valid() )
|
||||
_parent->_visitor->insertName( lod->getName() );
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
/* PosterPrinter: The implementation class of high-res rendering */
|
||||
PosterPrinter::PosterPrinter()
|
||||
: _isRunning(false), _isFinishing(false), _lastBindingFrame(0),
|
||||
_outputTiles(false), _outputTileExt("bmp"),
|
||||
_currentRow(0), _currentColumn(0),
|
||||
_camera(0), _finalPoster(0)
|
||||
{
|
||||
_intersector = new PosterIntersector(-1.0, -1.0, 1.0, 1.0);
|
||||
_visitor = new PosterVisitor;
|
||||
_intersector->setPosterVisitor( _visitor.get() );
|
||||
}
|
||||
|
||||
void PosterPrinter::init( const osg::Camera* camera )
|
||||
{
|
||||
if ( _camera.valid() )
|
||||
init( camera->getViewMatrix(), camera->getProjectionMatrix() );
|
||||
}
|
||||
|
||||
void PosterPrinter::init( const osg::Matrixd& view, const osg::Matrixd& proj )
|
||||
{
|
||||
if ( _isRunning ) return;
|
||||
_images.clear();
|
||||
_visitor->clearNames();
|
||||
_tileRows = (int)(_posterSize.y() / _tileSize.y());
|
||||
_tileColumns = (int)(_posterSize.x() / _tileSize.x());
|
||||
_currentRow = 0;
|
||||
_currentColumn = 0;
|
||||
_currentViewMatrix = view;
|
||||
_currentProjectionMatrix = proj;
|
||||
_lastBindingFrame = 0;
|
||||
_isRunning = true;
|
||||
_isFinishing = false;
|
||||
}
|
||||
|
||||
void PosterPrinter::frame( const osg::FrameStamp* fs, osg::Node* node )
|
||||
{
|
||||
// Add cull callbacks to all existing paged nodes,
|
||||
// and advance frame when all callbacks are dispatched.
|
||||
if ( addCullCallbacks(fs, node) )
|
||||
return;
|
||||
|
||||
if ( _isFinishing )
|
||||
{
|
||||
if ( (fs->getFrameNumber()-_lastBindingFrame)>2 )
|
||||
{
|
||||
// Record images and the final poster
|
||||
recordImages();
|
||||
if ( _finalPoster.valid() )
|
||||
{
|
||||
std::cout << "Writing final result to file..." << std::endl;
|
||||
osgDB::writeImageFile( *_finalPoster, _outputPosterName );
|
||||
}
|
||||
|
||||
// Release all cull callbacks to free unused paged nodes
|
||||
removeCullCallbacks( node );
|
||||
_visitor->clearNames();
|
||||
|
||||
_isFinishing = false;
|
||||
std::cout << "Recording images finished." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if ( _isRunning )
|
||||
{
|
||||
// Every "copy-to-image" process seems to be finished in 2 frames.
|
||||
// So record them and dispatch camera to next tiles.
|
||||
if ( (fs->getFrameNumber()-_lastBindingFrame)>2 )
|
||||
{
|
||||
// Record images and unref them to free memory
|
||||
recordImages();
|
||||
|
||||
// Release all cull callbacks to free unused paged nodes
|
||||
removeCullCallbacks( node );
|
||||
_visitor->clearNames();
|
||||
|
||||
if ( _camera.valid() )
|
||||
{
|
||||
std::cout << "Binding sub-camera " << _currentRow << "_" << _currentColumn
|
||||
<< " to image..." << std::endl;
|
||||
bindCameraToImage( _camera.get(), _currentRow, _currentColumn );
|
||||
if ( _currentColumn<_tileColumns-1 )
|
||||
{
|
||||
_currentColumn++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( _currentRow<_tileRows-1 )
|
||||
{
|
||||
_currentRow++;
|
||||
_currentColumn = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isRunning = false;
|
||||
_isFinishing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_lastBindingFrame = fs->getFrameNumber();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PosterPrinter::addCullCallbacks( const osg::FrameStamp* fs, osg::Node* node )
|
||||
{
|
||||
if ( !_visitor->inQueue() || done() )
|
||||
return false;
|
||||
|
||||
_visitor->setAddingCallbacks( true );
|
||||
_camera->accept( *_visitor );
|
||||
_lastBindingFrame = fs->getFrameNumber();
|
||||
|
||||
std::cout << "Dispatching callbacks to paged nodes... "
|
||||
<< _visitor->inQueue() << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PosterPrinter::removeCullCallbacks( osg::Node* node )
|
||||
{
|
||||
_visitor->setAddingCallbacks( false );
|
||||
_camera->accept( *_visitor );
|
||||
}
|
||||
|
||||
void PosterPrinter::bindCameraToImage( osg::Camera* camera, int row, int col )
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << "image_" << row << "_" << col;
|
||||
|
||||
osg::ref_ptr<osg::Image> image = new osg::Image;
|
||||
image->setName( stream.str() );
|
||||
image->allocateImage( (int)_tileSize.x(), (int)_tileSize.y(), 1, GL_RGBA, GL_UNSIGNED_BYTE );
|
||||
_images[TilePosition(row,col)] = image.get();
|
||||
|
||||
// Calculate projection matrix offset of each tile
|
||||
osg::Matrix offsetMatrix =
|
||||
osg::Matrix::scale(_tileColumns, _tileRows, 1.0) *
|
||||
osg::Matrix::translate(_tileColumns-1-2*col, _tileRows-1-2*row, 0.0);
|
||||
camera->setViewMatrix( _currentViewMatrix );
|
||||
camera->setProjectionMatrix( _currentProjectionMatrix * offsetMatrix );
|
||||
|
||||
// Check intersections between the image-tile box and the model
|
||||
osgUtil::IntersectionVisitor iv( _intersector.get() );
|
||||
iv.setReadCallback( g_pagedLoadingCallback.get() );
|
||||
_intersector->reset();
|
||||
camera->accept( iv );
|
||||
if ( _intersector->containsIntersections() )
|
||||
{
|
||||
// Apply a cull calback to every paged node obtained, to force the highest level displaying.
|
||||
// This will be done by the PosterVisitor, who already records all the paged nodes.
|
||||
}
|
||||
|
||||
// Reattach cameras and new allocated images
|
||||
camera->setRenderingCache( NULL ); // FIXME: Uses for reattaching camera with image, maybe inefficient?
|
||||
camera->detach( osg::Camera::COLOR_BUFFER );
|
||||
camera->attach( osg::Camera::COLOR_BUFFER, image.get(), 0, 0 );
|
||||
}
|
||||
|
||||
void PosterPrinter::recordImages()
|
||||
{
|
||||
for ( TileImages::iterator itr=_images.begin(); itr!=_images.end(); ++itr )
|
||||
{
|
||||
osg::Image* image = (itr->second).get();
|
||||
if ( _finalPoster.valid() )
|
||||
{
|
||||
// FIXME: A stupid way to combine tile images to final result. Any better ideas?
|
||||
unsigned int row = itr->first.first, col = itr->first.second;
|
||||
for ( int t=0; t<image->t(); ++t )
|
||||
{
|
||||
unsigned char* source = image->data( 0, t );
|
||||
unsigned char* target = _finalPoster->data( col*(int)_tileSize.x(), t + row*(int)_tileSize.y() );
|
||||
memcpy( target, source, image->s() * 4 * sizeof(unsigned char) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( _outputTiles )
|
||||
osgDB::writeImageFile( *image, image->getName()+"."+_outputTileExt );
|
||||
}
|
||||
_images.clear();
|
||||
}
|
160
examples/osgposter/PosterPrinter.h
Normal file
160
examples/osgposter/PosterPrinter.h
Normal file
@ -0,0 +1,160 @@
|
||||
#ifndef OSGPOSTER_POSTERPRINTER
|
||||
#define OSGPOSTER_POSTERPRINTER
|
||||
|
||||
#include <osg/Camera>
|
||||
#include <osg/PagedLOD>
|
||||
#include <osgUtil/IntersectionVisitor>
|
||||
|
||||
/** PosterVisitor: A visitor for adding culling callbacks to newly allocated paged nodes */
|
||||
class PosterVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
typedef std::set<std::string> PagedNodeNameSet;
|
||||
|
||||
PosterVisitor();
|
||||
META_NodeVisitor( osgPoster, PosterVisitor );
|
||||
|
||||
void insertName( const std::string& name )
|
||||
{ if ( _pagedNodeNames.insert(name).second ) _needToApplyCount++; }
|
||||
|
||||
void eraseName( const std::string& name )
|
||||
{ if ( _pagedNodeNames.erase(name)>0 ) _needToApplyCount--; }
|
||||
|
||||
void clearNames() { _pagedNodeNames.clear(); _needToApplyCount = 0; _appliedCount = 0; }
|
||||
unsigned int getNumNames() const { return _pagedNodeNames.size(); }
|
||||
|
||||
PagedNodeNameSet& getPagedNodeNames() { return _pagedNodeNames; }
|
||||
const PagedNodeNameSet& getPagedNodeNames() const { return _pagedNodeNames; }
|
||||
|
||||
unsigned int getNeedToApplyCount() const { return _needToApplyCount; }
|
||||
unsigned int getAppliedCount() const { return _appliedCount; }
|
||||
unsigned int inQueue() const { return _needToApplyCount>_appliedCount ? _needToApplyCount-_appliedCount : 0; }
|
||||
|
||||
void setAddingCallbacks( bool b ) { _addingCallbacks = b; }
|
||||
bool getAddingCallbacks() const { return _addingCallbacks; }
|
||||
|
||||
virtual void apply( osg::LOD& node );
|
||||
virtual void apply( osg::PagedLOD& node );
|
||||
|
||||
protected:
|
||||
bool hasCullCallback( osg::NodeCallback* nc, osg::NodeCallback* target )
|
||||
{
|
||||
if ( nc==target ) return true;
|
||||
else if ( !nc ) return false;
|
||||
return hasCullCallback( nc->getNestedCallback(), target );
|
||||
}
|
||||
|
||||
PagedNodeNameSet _pagedNodeNames;
|
||||
unsigned int _needToApplyCount;
|
||||
unsigned int _appliedCount;
|
||||
bool _addingCallbacks;
|
||||
};
|
||||
|
||||
/** PosterIntersector: A simple polytope intersector for updating pagedLODs in each image-tile */
|
||||
class PosterIntersector : public osgUtil::Intersector
|
||||
{
|
||||
public:
|
||||
typedef std::set<std::string> PagedNodeNameSet;
|
||||
|
||||
PosterIntersector( const osg::Polytope& polytope );
|
||||
PosterIntersector( double xMin, double yMin, double xMax, double yMax );
|
||||
|
||||
void setPosterVisitor( PosterVisitor* pcv ) { _visitor = pcv; }
|
||||
PosterVisitor* getPosterVisitor() { return _visitor.get(); }
|
||||
const PosterVisitor* getPosterVisitor() const { return _visitor.get(); }
|
||||
|
||||
virtual Intersector* clone( osgUtil::IntersectionVisitor& iv );
|
||||
|
||||
virtual bool containsIntersections()
|
||||
{ return _visitor.valid()&&_visitor->getNumNames()>0; }
|
||||
|
||||
virtual bool enter( const osg::Node& node );
|
||||
virtual void leave() {}
|
||||
virtual void reset();
|
||||
virtual void intersect( osgUtil::IntersectionVisitor& iv, osg::Drawable* drawable );
|
||||
|
||||
protected:
|
||||
osgUtil::IntersectionVisitor* _intersectionVisitor;
|
||||
osg::ref_ptr<PosterVisitor> _visitor;
|
||||
PosterIntersector* _parent;
|
||||
osg::Polytope _polytope;
|
||||
};
|
||||
|
||||
/** PosterPrinter: The implementation class of high-res rendering */
|
||||
class PosterPrinter : public osg::Referenced
|
||||
{
|
||||
public:
|
||||
typedef std::pair<unsigned int, unsigned int> TilePosition;
|
||||
typedef std::map< TilePosition, osg::ref_ptr<osg::Image> > TileImages;
|
||||
|
||||
PosterPrinter();
|
||||
|
||||
/** Set to output each sub-image-tile to disk */
|
||||
void setOutputTiles( bool b ) { _outputTiles = b; }
|
||||
bool getOutputTiles() const { return _outputTiles; }
|
||||
|
||||
/** Set the output sub-image-tile extension, e.g. bmp */
|
||||
void setOutputTileExtension( const std::string& ext ) { _outputTileExt = ext; }
|
||||
const std::string& getOutputTileExtension() const { return _outputTileExt; }
|
||||
|
||||
/** Set the output poster name, e.g. output.bmp */
|
||||
void setOutputPosterName( const std::string& name ) { _outputPosterName = name; }
|
||||
const std::string& getOutputPosterName() const { return _outputPosterName; }
|
||||
|
||||
/** Set the size of each sub-image-tile, e.g. 640x480 */
|
||||
void setTileSize( int w, int h ) { _tileSize.set(w, h); }
|
||||
const osg::Vec2& getTileSize() const { return _tileSize; }
|
||||
|
||||
/** Set the final size of the high-res poster, e.g. 6400x4800 */
|
||||
void setPosterSize( int w, int h ) { _posterSize.set(w, h); }
|
||||
const osg::Vec2& getPosterSize() const { return _posterSize; }
|
||||
|
||||
/** Set the capturing camera */
|
||||
void setCamera( osg::Camera* camera ) { _camera = camera; }
|
||||
const osg::Camera* getCamera() const { return _camera.get(); }
|
||||
|
||||
/** Set the final poster image, should be already allocated */
|
||||
void setFinalPoster( osg::Image* image ) { _finalPoster = image; }
|
||||
const osg::Image* getFinalPoster() const { return _finalPoster.get(); }
|
||||
|
||||
PosterVisitor* getPosterVisitor() { return _visitor.get(); }
|
||||
const PosterVisitor* getPosterVisitor() const { return _visitor.get(); }
|
||||
|
||||
bool done() const { return !_isRunning && !_isFinishing; }
|
||||
|
||||
void init( const osg::Camera* camera );
|
||||
void init( const osg::Matrixd& view, const osg::Matrixd& proj );
|
||||
void frame( const osg::FrameStamp* fs, osg::Node* node );
|
||||
|
||||
protected:
|
||||
virtual ~PosterPrinter() {}
|
||||
|
||||
bool addCullCallbacks( const osg::FrameStamp* fs, osg::Node* node );
|
||||
void removeCullCallbacks( osg::Node* node );
|
||||
void bindCameraToImage( osg::Camera* camera, int row, int col );
|
||||
void recordImages();
|
||||
|
||||
bool _outputTiles;
|
||||
std::string _outputTileExt;
|
||||
std::string _outputPosterName;
|
||||
osg::Vec2 _tileSize;
|
||||
osg::Vec2 _posterSize;
|
||||
|
||||
bool _isRunning;
|
||||
bool _isFinishing;
|
||||
int _lastBindingFrame;
|
||||
int _tileRows;
|
||||
int _tileColumns;
|
||||
int _currentRow;
|
||||
int _currentColumn;
|
||||
osg::ref_ptr<PosterIntersector> _intersector;
|
||||
osg::ref_ptr<PosterVisitor> _visitor;
|
||||
|
||||
osg::Matrixd _currentViewMatrix;
|
||||
osg::Matrixd _currentProjectionMatrix;
|
||||
osg::ref_ptr<osg::Camera> _camera;
|
||||
osg::ref_ptr<osg::Image> _finalPoster;
|
||||
TileImages _images;
|
||||
};
|
||||
|
||||
#endif
|
384
examples/osgposter/osgposter.cpp
Normal file
384
examples/osgposter/osgposter.cpp
Normal file
@ -0,0 +1,384 @@
|
||||
/* -*-c++-*- OpenSceneGraph example, osgposter.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <osg/ArgumentParser>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/Switch>
|
||||
#include <osgDB/ReadFile>
|
||||
#include <osgDB/WriteFile>
|
||||
#include <osgGA/TrackballManipulator>
|
||||
#include <osgGA/StateSetManipulator>
|
||||
#include <osgViewer/Renderer>
|
||||
#include <osgViewer/Viewer>
|
||||
#include <osgViewer/ViewerEventHandlers>
|
||||
#include <iostream>
|
||||
#include "PosterPrinter.h"
|
||||
|
||||
/* Computing view matrix helpers */
|
||||
template<class T>
|
||||
class FindTopMostNodeOfTypeVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
FindTopMostNodeOfTypeVisitor()
|
||||
: osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
|
||||
_foundNode(0)
|
||||
{}
|
||||
|
||||
void apply( osg::Node& node )
|
||||
{
|
||||
T* result = dynamic_cast<T*>( &node );
|
||||
if ( result ) _foundNode = result;
|
||||
else traverse( node );
|
||||
}
|
||||
|
||||
T* _foundNode;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
T* findTopMostNodeOfType( osg::Node* node )
|
||||
{
|
||||
if ( !node ) return 0;
|
||||
|
||||
FindTopMostNodeOfTypeVisitor<T> fnotv;
|
||||
node->accept( fnotv );
|
||||
return fnotv._foundNode;
|
||||
}
|
||||
|
||||
/* Computing view matrix functions */
|
||||
void computeViewMatrix( osg::Camera* camera, const osg::Vec3d& eye, const osg::Vec3d& hpr )
|
||||
{
|
||||
osg::Matrixd matrix;
|
||||
matrix.makeTranslate( eye );
|
||||
matrix.preMult( osg::Matrixd::rotate( hpr[0], 0.0, 1.0, 0.0) );
|
||||
matrix.preMult( osg::Matrixd::rotate( hpr[1], 1.0, 0.0, 0.0) );
|
||||
matrix.preMult( osg::Matrixd::rotate( hpr[2], 0.0, 0.0, 1.0) );
|
||||
camera->setViewMatrix( osg::Matrixd::inverse(matrix) );
|
||||
}
|
||||
|
||||
void computeViewMatrixOnEarth( osg::Camera* camera, osg::Node* scene,
|
||||
const osg::Vec3d& latLongHeight, const osg::Vec3d& hpr )
|
||||
{
|
||||
osg::CoordinateSystemNode* csn = findTopMostNodeOfType<osg::CoordinateSystemNode>(scene);
|
||||
if ( !csn ) return;
|
||||
|
||||
// Compute eye point in world coordiantes
|
||||
osg::Vec3d eye;
|
||||
csn->getEllipsoidModel()->convertLatLongHeightToXYZ(
|
||||
latLongHeight.x(), latLongHeight.y(), latLongHeight.z(), eye.x(), eye.y(), eye.z() );
|
||||
|
||||
// Build matrix for computing target vector
|
||||
osg::Matrixd target_matrix =
|
||||
osg::Matrixd::rotate( -hpr.x(), osg::Vec3d(1,0,0),
|
||||
-latLongHeight.x(), osg::Vec3d(0,1,0),
|
||||
latLongHeight.y(), osg::Vec3d(0,0,1) );
|
||||
|
||||
// Compute tangent vector
|
||||
osg::Vec3d tangent = target_matrix.preMult( osg::Vec3d(0,0,1) );
|
||||
|
||||
// Compute non-inclined, non-rolled up vector
|
||||
osg::Vec3d up( eye );
|
||||
up.normalize();
|
||||
|
||||
// Incline by rotating the target- and up vector around the tangent/up-vector
|
||||
// cross-product
|
||||
osg::Vec3d up_cross_tangent = up ^ tangent;
|
||||
osg::Matrixd incline_matrix = osg::Matrixd::rotate( hpr.y(), up_cross_tangent );
|
||||
osg::Vec3d target = incline_matrix.preMult( tangent );
|
||||
|
||||
// Roll by rotating the up vector around the target vector
|
||||
osg::Matrixd roll_matrix = incline_matrix * osg::Matrixd::rotate( hpr.z(), target );
|
||||
up = roll_matrix.preMult( up );
|
||||
camera->setViewMatrixAsLookAt( eye, eye+target, up );
|
||||
}
|
||||
|
||||
/* CustomRenderer: Do culling only while loading PagedLODs */
|
||||
class CustomRenderer : public osgViewer::Renderer
|
||||
{
|
||||
public:
|
||||
CustomRenderer( osg::Camera* camera )
|
||||
: osgViewer::Renderer(camera), _cullOnly(true)
|
||||
{
|
||||
}
|
||||
|
||||
void setCullOnly(bool on) { _cullOnly = on; }
|
||||
|
||||
virtual void operator ()( osg::GraphicsContext* )
|
||||
{
|
||||
if ( _graphicsThreadDoesCull )
|
||||
{
|
||||
if (_cullOnly) cull();
|
||||
else cull_draw();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void cull()
|
||||
{
|
||||
osgUtil::SceneView* sceneView = _sceneView[0].get();
|
||||
if ( !sceneView || _done || _graphicsThreadDoesCull )
|
||||
return;
|
||||
|
||||
updateSceneView( sceneView );
|
||||
|
||||
osgViewer::View* view = dynamic_cast<osgViewer::View*>( _camera->getView() );
|
||||
if ( view )
|
||||
sceneView->setFusionDistance( view->getFusionDistanceMode(), view->getFusionDistanceValue() );
|
||||
sceneView->inheritCullSettings( *(sceneView->getCamera()) );
|
||||
sceneView->cull();
|
||||
}
|
||||
|
||||
bool _cullOnly;
|
||||
};
|
||||
|
||||
/* PrintPosterHandler: A gui handler for interactive high-res capturing */
|
||||
class PrintPosterHandler : public osgGA::GUIEventHandler
|
||||
{
|
||||
public:
|
||||
PrintPosterHandler( PosterPrinter* printer )
|
||||
: _printer(printer), _started(false) {}
|
||||
|
||||
bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa )
|
||||
{
|
||||
osgViewer::View* view = dynamic_cast<osgViewer::View*>( &aa );
|
||||
if ( !view ) return false;
|
||||
|
||||
switch( ea.getEventType() )
|
||||
{
|
||||
case osgGA::GUIEventAdapter::FRAME:
|
||||
if ( view->getDatabasePager() )
|
||||
{
|
||||
// Wait until all paged nodes are processed
|
||||
if ( view->getDatabasePager()->getRequestsInProgress() )
|
||||
break;
|
||||
}
|
||||
|
||||
if ( _printer.valid() )
|
||||
{
|
||||
_printer->frame( view->getFrameStamp(), view->getSceneData() );
|
||||
if ( _started && _printer->done() )
|
||||
{
|
||||
osg::Switch* root = dynamic_cast<osg::Switch*>( view->getSceneData() );
|
||||
if ( root )
|
||||
{
|
||||
// Assume child 0 is the loaded model and 1 is the poster camera
|
||||
// Switch them in time to prevent dual traversals of subgraph
|
||||
root->setValue( 0, true );
|
||||
root->setValue( 1, false );
|
||||
}
|
||||
_started = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case osgGA::GUIEventAdapter::KEYDOWN:
|
||||
if ( ea.getKey()=='p' || ea.getKey()=='P' )
|
||||
{
|
||||
if ( _printer.valid() )
|
||||
{
|
||||
osg::Switch* root = dynamic_cast<osg::Switch*>( view->getSceneData() );
|
||||
if ( root )
|
||||
{
|
||||
// Assume child 0 is the loaded model and 1 is the poster camera
|
||||
root->setValue( 0, false );
|
||||
root->setValue( 1, true );
|
||||
}
|
||||
|
||||
_printer->init( view->getCamera() );
|
||||
_started = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
osg::ref_ptr<PosterPrinter> _printer;
|
||||
bool _started;
|
||||
};
|
||||
|
||||
/* The main entry */
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
osg::ArgumentParser arguments( &argc, argv );
|
||||
osg::ApplicationUsage* usage = arguments.getApplicationUsage();
|
||||
usage->setDescription( arguments.getApplicationName() +
|
||||
" is the example which demonstrates how to render high-resolution images (posters).");
|
||||
usage->setCommandLineUsage( arguments.getApplicationName() + " [options] scene_file" );
|
||||
usage->addCommandLineOption( "-h or --help", "Display this information." );
|
||||
usage->addCommandLineOption( "--color <r> <g> <b>", "The background color." );
|
||||
usage->addCommandLineOption( "--ext <ext>", "The output tiles' extension (Default: bmp)." );
|
||||
usage->addCommandLineOption( "--poster <filename>", "The output poster's name (Default: poster.bmp)." );
|
||||
usage->addCommandLineOption( "--tilesize <w> <h>", "Size of each image tile (Default: 640 480)." );
|
||||
usage->addCommandLineOption( "--finalsize <w> <h>", "Size of the poster (Default: 6400 4800)." );
|
||||
usage->addCommandLineOption( "--enable-output-poster", "Output the final poster file (Default)." );
|
||||
usage->addCommandLineOption( "--disable-output-poster", "Don't output the final poster file." );
|
||||
usage->addCommandLineOption( "--enable-output-tiles", "Output all tile files." );
|
||||
usage->addCommandLineOption( "--disable-output-tiles", "Don't output all tile files (Default)." );
|
||||
usage->addCommandLineOption( "--use-fb", "Use Frame Buffer for rendering tiles (Default, recommended).");
|
||||
usage->addCommandLineOption( "--use-fbo", "Use Frame Buffer Object for rendering tiles.");
|
||||
usage->addCommandLineOption( "--use-pbuffer","Use Pixel Buffer for rendering tiles.");
|
||||
usage->addCommandLineOption( "--use-pbuffer-rtt","Use Pixel Buffer RTT for rendering tiles.");
|
||||
usage->addCommandLineOption( "--inactive", "Inactive capturing mode." );
|
||||
usage->addCommandLineOption( "--camera-eye <x> <y> <z>", "Set eye position in inactive mode." );
|
||||
usage->addCommandLineOption( "--camera-latlongheight <lat> <lon> <h>", "Set eye position on earth in inactive mode." );
|
||||
usage->addCommandLineOption( "--camera-hpr <h> <p> <r>", "Set eye rotation in inactive mode." );
|
||||
|
||||
if ( arguments.read("-h") || arguments.read("--help") )
|
||||
{
|
||||
usage->write( std::cout );
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Poster arguments
|
||||
bool activeMode = true;
|
||||
bool outputPoster = true, outputTiles = false;
|
||||
int tileWidth = 640, tileHeight = 480;
|
||||
int posterWidth = 640*2, posterHeight = 480*2;
|
||||
std::string posterName = "poster.bmp", extName = "bmp";
|
||||
osg::Vec4 bgColor(0.2f, 0.2f, 0.6f, 1.0f);
|
||||
osg::Camera::RenderTargetImplementation renderImplementation = osg::Camera::FRAME_BUFFER;
|
||||
|
||||
while ( arguments.read("--inactive") ) { activeMode = false; }
|
||||
while ( arguments.read("--color", bgColor.r(), bgColor.g(), bgColor.b()) ) {}
|
||||
while ( arguments.read("--tilesize", tileWidth, tileHeight) ) {}
|
||||
while ( arguments.read("--finalsize", posterWidth, posterHeight) ) {}
|
||||
while ( arguments.read("--poster", posterName) ) {}
|
||||
while ( arguments.read("--ext", extName) ) {}
|
||||
while ( arguments.read("--enable-output-poster") ) { outputPoster = true; }
|
||||
while ( arguments.read("--disable-output-poster") ) { outputPoster = false; }
|
||||
while ( arguments.read("--enable-output-tiles") ) { outputTiles = true; }
|
||||
while ( arguments.read("--disable-output-tiles") ) { outputTiles = false; }
|
||||
while ( arguments.read("--use-fbo")) { renderImplementation = osg::Camera::FRAME_BUFFER_OBJECT; }
|
||||
while ( arguments.read("--use-pbuffer")) { renderImplementation = osg::Camera::PIXEL_BUFFER; }
|
||||
while ( arguments.read("--use-pbuffer-rtt")) { renderImplementation = osg::Camera::PIXEL_BUFFER_RTT; }
|
||||
while ( arguments.read("--use-fb")) { renderImplementation = osg::Camera::FRAME_BUFFER; }
|
||||
|
||||
// Camera settings for inactive screenshot
|
||||
bool useLatLongHeight = true;
|
||||
osg::Vec3d eye;
|
||||
osg::Vec3d latLongHeight( 50.0, 10.0, 2000.0 );
|
||||
osg::Vec3d hpr( 0.0, 0.0, 0.0 );
|
||||
if ( arguments.read("--camera-eye", eye.x(), eye.y(), eye.z()) )
|
||||
{
|
||||
useLatLongHeight = false;
|
||||
activeMode = false;
|
||||
}
|
||||
else if ( arguments.read("--camera-latlongheight", latLongHeight.x(), latLongHeight.y(), latLongHeight.z()) )
|
||||
{
|
||||
activeMode = false;
|
||||
latLongHeight.x() = osg::DegreesToRadians( latLongHeight.x() );
|
||||
latLongHeight.y() = osg::DegreesToRadians( latLongHeight.y() );
|
||||
}
|
||||
if ( arguments.read("--camera-hpr", hpr.x(), hpr.y(), hpr.z()) )
|
||||
{
|
||||
activeMode = false;
|
||||
hpr.x() = osg::DegreesToRadians( hpr.x() );
|
||||
hpr.y() = osg::DegreesToRadians( hpr.y() );
|
||||
hpr.z() = osg::DegreesToRadians( hpr.z() );
|
||||
}
|
||||
|
||||
// Construct scene graph
|
||||
osg::Node* scene = osgDB::readNodeFiles( arguments );
|
||||
if ( !scene ) scene = osgDB::readNodeFile( "cow.osg" );
|
||||
if ( !scene )
|
||||
{
|
||||
std::cout << arguments.getApplicationName() <<": No data loaded" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create camera for rendering tiles offscreen. FrameBuffer is recommended because it requires less memory.
|
||||
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
|
||||
camera->setClearColor( bgColor );
|
||||
camera->setClearMask( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
|
||||
camera->setReferenceFrame( osg::Transform::ABSOLUTE_RF );
|
||||
camera->setRenderOrder( osg::Camera::PRE_RENDER );
|
||||
camera->setRenderTargetImplementation( renderImplementation );
|
||||
camera->setViewport( 0, 0, tileWidth, tileHeight );
|
||||
camera->addChild( scene );
|
||||
|
||||
// Set the printer
|
||||
osg::ref_ptr<PosterPrinter> printer = new PosterPrinter;
|
||||
printer->setTileSize( tileWidth, tileHeight );
|
||||
printer->setPosterSize( posterWidth, posterHeight );
|
||||
printer->setCamera( camera.get() );
|
||||
|
||||
osg::ref_ptr<osg::Image> posterImage = 0;
|
||||
if ( outputPoster )
|
||||
{
|
||||
posterImage = new osg::Image;
|
||||
posterImage->allocateImage( posterWidth, posterHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE );
|
||||
printer->setFinalPoster( posterImage.get() );
|
||||
printer->setOutputPosterName( posterName );
|
||||
}
|
||||
|
||||
// Create root and start the viewer
|
||||
osg::ref_ptr<osg::Switch> root = new osg::Switch;
|
||||
root->addChild( scene, true );
|
||||
root->addChild( camera.get(), false );
|
||||
|
||||
osgViewer::Viewer viewer;
|
||||
viewer.setUpViewInWindow( 100, 100, tileWidth, tileHeight );
|
||||
viewer.setSceneData( root.get() );
|
||||
viewer.getDatabasePager()->setDoPreCompile( false );
|
||||
|
||||
if ( activeMode )
|
||||
{
|
||||
viewer.addEventHandler( new PrintPosterHandler(printer.get()) );
|
||||
viewer.addEventHandler( new osgViewer::StatsHandler );
|
||||
viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) );
|
||||
viewer.setCameraManipulator( new osgGA::TrackballManipulator );
|
||||
viewer.run();
|
||||
}
|
||||
else
|
||||
{
|
||||
osg::Camera* camera = viewer.getCamera();
|
||||
if ( !useLatLongHeight ) computeViewMatrix( camera, eye, hpr );
|
||||
else computeViewMatrixOnEarth( camera, scene, latLongHeight, hpr );
|
||||
|
||||
osg::ref_ptr<CustomRenderer> renderer = new CustomRenderer( camera );
|
||||
camera->setRenderer( renderer.get() );
|
||||
viewer.setThreadingModel( osgViewer::Viewer::SingleThreaded );
|
||||
|
||||
// Realize and initiate the first PagedLOD request
|
||||
viewer.realize();
|
||||
viewer.frame();
|
||||
|
||||
printer->init( camera );
|
||||
while ( !printer->done() )
|
||||
{
|
||||
viewer.advance();
|
||||
|
||||
// Keep updating and culling until full level of detail is reached
|
||||
renderer->setCullOnly( true );
|
||||
while ( viewer.getDatabasePager()->getRequestsInProgress() )
|
||||
{
|
||||
viewer.updateTraversal();
|
||||
viewer.renderingTraversals();
|
||||
}
|
||||
|
||||
renderer->setCullOnly( false );
|
||||
printer->frame( viewer.getFrameStamp(), viewer.getSceneData() );
|
||||
viewer.renderingTraversals();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user