+ support anti-aliasing in offscreen renderer

This commit is contained in:
wmayer 2014-11-10 14:10:38 +01:00
parent 04c1530b09
commit 730ad0fbea
5 changed files with 425 additions and 6 deletions

View File

@ -1463,7 +1463,7 @@ void StdViewScreenShot::activated(int iMsg)
QStringList formats;
SbViewportRegion vp(view->getViewer()->getSoRenderManager()->getViewportRegion());
{
SoFCOffscreenRenderer& rd = SoFCOffscreenRenderer::instance();
SoQtOffscreenRenderer rd(vp);
formats = rd.getWriteImageFiletypeInfo();
}

View File

@ -22,10 +22,17 @@
#include "PreCompiled.h"
#ifndef _PreComp_
# include <Inventor/actions/SoGLRenderAction.h>
# include <Inventor/elements/SoGLCacheContextElement.h>
# include <Inventor/fields/SoSFImage.h>
# include <Inventor/nodes/SoNode.h>
# include <QBuffer>
# include <QDateTime>
# include <QFile>
# include <QGLFormat>
# include <QGLFramebufferObject>
# include <QGLPixelBuffer>
# include <QImage>
# include <QImageWriter>
#endif
@ -365,3 +372,362 @@ void writeJPEGComment(const std::string& comment, QByteArray& ba)
}
}
}
// ---------------------------------------------------------------
#define PRIVATE(p) p
#define PUBLIC(p) p
void SoQtOffscreenRenderer::init(const SbViewportRegion & vpr,
SoGLRenderAction * glrenderaction)
{
this->backgroundcolor.setValue(0,0,0);
if (glrenderaction) {
this->renderaction = glrenderaction;
}
else {
this->renderaction = new SoGLRenderAction(vpr);
this->renderaction->setCacheContext(SoGLCacheContextElement::getUniqueCacheContext());
this->renderaction->setTransparencyType(SoGLRenderAction::SORTED_OBJECT_BLEND);
}
this->didallocation = glrenderaction ? FALSE : TRUE;
this->viewport = vpr;
this->pixelbuffer = NULL; // constructed later
this->framebuffer = NULL;
this->numSamples = -1;
this->pbuffer = QGLPixelBuffer::hasOpenGLPbuffers();
}
/*!
Constructor. Argument is the \a viewportregion we should use when
rendering. An internal SoGLRenderAction will be constructed.
*/
SoQtOffscreenRenderer::SoQtOffscreenRenderer(const SbViewportRegion & viewportregion)
{
init(viewportregion);
}
/*!
Constructor. Argument is the \a action we should apply to the
scene graph when rendering the scene. Information about the
viewport is extracted from the \a action.
*/
SoQtOffscreenRenderer::SoQtOffscreenRenderer(SoGLRenderAction * action)
{
init(action->getViewportRegion(), action);
}
/*!
Destructor.
*/
SoQtOffscreenRenderer::~SoQtOffscreenRenderer()
{
delete pixelbuffer;
delete framebuffer;
if (this->didallocation) {
delete this->renderaction;
}
}
/*!
Sets the viewport region.
This will invalidate the current buffer, if any. The buffer will not
contain valid data until another call to
SoOffscreenRendererQt::render() happens.
*/
void
SoQtOffscreenRenderer::setViewportRegion(const SbViewportRegion & region)
{
PRIVATE(this)->viewport = region;
}
/*!
Returns the viewerport region.
*/
const SbViewportRegion &
SoQtOffscreenRenderer::getViewportRegion(void) const
{
return PRIVATE(this)->viewport;
}
/*!
Sets the background color. The buffer is cleared to this color
before rendering.
*/
void
SoQtOffscreenRenderer::setBackgroundColor(const SbColor & color)
{
PRIVATE(this)->backgroundcolor = color;
}
/*!
Returns the background color.
*/
const SbColor &
SoQtOffscreenRenderer::getBackgroundColor(void) const
{
return PRIVATE(this)->backgroundcolor;
}
/*!
Sets the render action. Use this if you have special rendering needs.
*/
void
SoQtOffscreenRenderer::setGLRenderAction(SoGLRenderAction * action)
{
if (action == PRIVATE(this)->renderaction) { return; }
if (PRIVATE(this)->didallocation) { delete PRIVATE(this)->renderaction; }
PRIVATE(this)->renderaction = action;
PRIVATE(this)->didallocation = FALSE;
}
/*!
Returns the rendering action currently used.
*/
SoGLRenderAction *
SoQtOffscreenRenderer::getGLRenderAction(void) const
{
return PRIVATE(this)->renderaction;
}
void
SoQtOffscreenRenderer::setNumPasses(const int num)
{
PRIVATE(this)->numSamples = num;
}
int
SoQtOffscreenRenderer::getNumPasses(void) const
{
return PRIVATE(this)->numSamples;
}
void
SoQtOffscreenRenderer::setPbufferEnable(SbBool enable)
{
PRIVATE(this)->pbuffer = enable;
}
SbBool
SoQtOffscreenRenderer::getPbufferEnable(void) const
{
return PRIVATE(this)->pbuffer;
}
// *************************************************************************
void
SoQtOffscreenRenderer::pre_render_cb(void * userdata, SoGLRenderAction * action)
{
glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
action->setRenderingIsRemote(FALSE);
}
void
SoQtOffscreenRenderer::makePixelBuffer(int width, int height, int samples)
{
if (pixelbuffer) {
delete pixelbuffer;
pixelbuffer = NULL;
}
viewport.setWindowSize(width, height);
QGLFormat fmt;
if (samples > 0) {
fmt.setSampleBuffers(true);
fmt.setSamples(samples);
}
else {
fmt.setSampleBuffers(false);
}
pixelbuffer = new QGLPixelBuffer(width, height, fmt);
cache_context = SoGLCacheContextElement::getUniqueCacheContext(); // unique per pixel buffer object, just to be sure
}
void
SoQtOffscreenRenderer::makeFrameBuffer(int width, int height, int samples)
{
if (framebuffer) {
delete framebuffer;
framebuffer = NULL;
}
viewport.setWindowSize(width, height);
#if QT_VERSION >= 0x040600
QGLFramebufferObjectFormat fmt;
fmt.setSamples(samples);
fmt.setAttachment(QGLFramebufferObject::Depth);
#else
QGLFramebufferObject::Attachment fmt;
fmt = QGLFramebufferObject::Depth;
#endif
framebuffer = new QGLFramebufferObject(width, height, fmt);
cache_context = SoGLCacheContextElement::getUniqueCacheContext(); // unique per pixel buffer object, just to be sure
}
SbBool
SoQtOffscreenRenderer::renderFromBase(SoBase * base)
{
const SbVec2s fullsize = this->viewport.getViewportSizePixels();
if (PRIVATE(this)->pbuffer) {
if (!pixelbuffer) {
makePixelBuffer(fullsize[0], fullsize[1], PRIVATE(this)->numSamples);
}
else if (pixelbuffer->width() != fullsize[0] || pixelbuffer->height() != fullsize[1]) {
// get the size right!
makePixelBuffer(fullsize[0], fullsize[1], PRIVATE(this)->numSamples);
}
pixelbuffer->makeCurrent(); // activate us!
}
else {
if (!framebuffer) {
makeFrameBuffer(fullsize[0], fullsize[1], PRIVATE(this)->numSamples);
}
else if (framebuffer->width() != fullsize[0] || framebuffer->height() != fullsize[1]) {
// get the size right!
makeFrameBuffer(fullsize[0], fullsize[1], PRIVATE(this)->numSamples);
}
framebuffer->bind(); // activate us!
}
// oldcontext is used to restore the previous context id, in case
// the render action is not allocated by us.
const uint32_t oldcontext = this->renderaction->getCacheContext();
this->renderaction->setCacheContext(cache_context);
glEnable(GL_DEPTH_TEST);
glClearColor(this->backgroundcolor[0],
this->backgroundcolor[1],
this->backgroundcolor[2],
0.0f);
// needed to clear viewport after glViewport() is called from
// SoGLRenderAction
this->renderaction->addPreRenderCallback(pre_render_cb, NULL);
this->renderaction->setViewportRegion(this->viewport);
if (base->isOfType(SoNode::getClassTypeId()))
this->renderaction->apply((SoNode *)base);
else if (base->isOfType(SoPath::getClassTypeId()))
this->renderaction->apply((SoPath *)base);
else {
assert(FALSE && "Cannot apply to anything else than an SoNode or an SoPath");
}
this->renderaction->removePreRenderCallback(pre_render_cb, NULL);
if (PRIVATE(this)->pbuffer) {
pixelbuffer->doneCurrent();
}
else {
framebuffer->release();
}
this->renderaction->setCacheContext(oldcontext); // restore old
return TRUE;
}
/*!
Render the scenegraph rooted at \a scene into our internal pixel
buffer.
Important note: make sure you pass in a \a scene node pointer which
has both a camera and at least one lightsource below it -- otherwise
you are likely to end up with just a blank or black image buffer.
This mistake is easily made if you use an SoQtOffscreenRenderer on a
scenegraph from one of the standard viewer components, as you will
often just leave the addition of a camera and a headlight
lightsource to the viewer to set up. This camera and lightsource are
then part of the viewer's private "super-graph" outside of the scope
of the scenegraph passed in by the application programmer. To make
sure the complete scenegraph (including the viewer's "private parts"
(*snicker*)) are passed to this method, you can get the scenegraph
root from the viewer's internal SoSceneManager instance instead of
from the viewer's own getSceneGraph() method, like this:
\code
SoQtOffscreenRenderer * myRenderer = new SoQtOffscreenRenderer(vpregion);
SoNode * root = myViewer->getSceneManager()->getSceneGraph();
SbBool ok = myRenderer->render(root);
// [then use image buffer in a texture, or write it to file, or whatever]
\endcode
If you do this and still get a blank buffer, another common problem
is to have a camera which is not actually pointing at the scene
geometry you want a snapshot of. If you suspect that could be the
cause of problems on your end, take a look at SoCamera::pointAt()
and SoCamera::viewAll() to see how you can make a camera node
guaranteed to be directed at the scene geometry.
Yet another common mistake when setting up the camera is to specify
values for the SoCamera::nearDistance and SoCamera::farDistance
fields which doesn't not enclose the full scene. This will result in
either just the background color, or that parts at the front or the
back of the scene will not be visible in the rendering.
\sa writeToImage()
*/
SbBool
SoQtOffscreenRenderer::render(SoNode * scene)
{
return PRIVATE(this)->renderFromBase(scene);
}
/*!
Render the \a scene path into our internal memory buffer.
*/
SbBool
SoQtOffscreenRenderer::render(SoPath * scene)
{
return PRIVATE(this)->renderFromBase(scene);
}
/*!
Writes the rendered image buffer directly into a QImage object.
*/
void
SoQtOffscreenRenderer::writeToImage (QImage& img) const
{
if (PRIVATE(this)->pbuffer) {
if (pixelbuffer)
img = pixelbuffer->toImage();
}
else {
if (framebuffer)
img = framebuffer->toImage();
}
}
/*!
This method returns all image file formats supported by Coin3D (see getWriteFiletypeInfo()) with all QImage file formats that are
not directly supported by Coin3D, if so.
*/
QStringList SoQtOffscreenRenderer::getWriteImageFiletypeInfo() const
{
QList<QByteArray> qtformats = QImageWriter::supportedImageFormats();
QStringList formats;
for (QList<QByteArray>::Iterator it = qtformats.begin(); it != qtformats.end(); ++it) {
formats << QLatin1String(*it);
}
formats.sort();
return formats;
}
#undef PRIVATE
#undef PUBLIC

View File

@ -29,6 +29,8 @@
#include <QStringList>
class QImage;
class QGLFramebufferObject;
class QGLPixelBuffer;
namespace Gui {
@ -97,6 +99,53 @@ public:
std::string createMIBA(const SbMatrix& mat) const;
};
class GuiExport SoQtOffscreenRenderer
{
public:
SoQtOffscreenRenderer(const SbViewportRegion & viewportregion);
SoQtOffscreenRenderer(SoGLRenderAction * action);
~SoQtOffscreenRenderer();
void setViewportRegion(const SbViewportRegion & region);
const SbViewportRegion & getViewportRegion(void) const;
void setBackgroundColor(const SbColor & color);
const SbColor & getBackgroundColor(void) const;
void setGLRenderAction(SoGLRenderAction * action);
SoGLRenderAction * getGLRenderAction(void) const;
void setNumPasses(const int num);
int getNumPasses(void) const;
void setPbufferEnable(SbBool enable);
SbBool getPbufferEnable(void) const;
SbBool render(SoNode * scene);
SbBool render(SoPath * scene);
void writeToImage (QImage&) const;
QStringList getWriteImageFiletypeInfo() const;
private:
void init(const SbViewportRegion & vpr, SoGLRenderAction * glrenderaction = NULL);
static void pre_render_cb(void * userdata, SoGLRenderAction * action);
SbBool renderFromBase(SoBase * base);
void makePixelBuffer(int width, int height, int samples);
void makeFrameBuffer(int width, int height, int samples);
QGLPixelBuffer* pixelbuffer; // the offscreen rendering supported by Qt
QGLFramebufferObject* framebuffer;
uint32_t cache_context; // our unique context id
SbViewportRegion viewport;
SbColor backgroundcolor;
SoGLRenderAction * renderaction;
SbBool didallocation;
SbBool pbuffer;
int numSamples;
};
} // namespace Gui

View File

@ -809,8 +809,8 @@ void View3DInventorViewer::savePicture(int w, int h, const QColor& bg, QImage& i
//The default value is 72.0.
//If we need to support grayscale images with must either use SoOffscreenRenderer::LUMINANCE or
//SoOffscreenRenderer::LUMINANCE_TRANSPARENCY.
SoFCOffscreenRenderer& renderer = SoFCOffscreenRenderer::instance();
renderer.setViewportRegion(vp);
SoQtOffscreenRenderer renderer(vp);
renderer.setNumPasses(4);
SoCallback* cb = 0;
// for an invalid color use the viewer's current background color
@ -836,9 +836,9 @@ void View3DInventorViewer::savePicture(int w, int h, const QColor& bg, QImage& i
// The behaviour in Coin4 has changed so that when using the same instance of 'SoFCOffscreenRenderer'
// multiple times internally the biggest viewport size is stored and set to the SoGLRenderAction.
// The trick is to add a callback node and override the viewport size with wath we want.
SoCallback* cbvp = new SoCallback;
cbvp->setCallback(setViewportCB);
root->addChild(cbvp);
//SoCallback* cbvp = new SoCallback;
//cbvp->setCallback(setViewportCB);
//root->addChild(cbvp);
#endif
SoCamera* camera = getSoRenderManager()->getCamera();

View File

@ -658,10 +658,14 @@ Py::Object View3DInventorPy::isAnimationEnabled(const Py::Tuple& args)
void View3DInventorPy::createImageFromFramebuffer(int width, int height, const QColor& bgcolor, QImage& img)
{
#if QT_VERSION >= 0x040600
QGLFramebufferObjectFormat format;
format.setSamples(8);
format.setAttachment(QGLFramebufferObject::Depth);
QGLFramebufferObject fbo(width, height, format);
#else
QGLFramebufferObject fbo(width, height, QGLFramebufferObject::Depth);
#endif
const QColor col = _view->getViewer()->backgroundColor();
bool on = _view->getViewer()->hasGradientBackground();