osgFX实现方式解析

  • 简介

osgFX是一个osg的附加库,是一个用于实现可重用特殊效果的架构工具,它实现的效果可以添加到OSG的节点中。它包含了一系列预定义好的特殊效果,osg3.2中主要有如下几种:

  1. Anisotropic Lighting 各向异性光照
  2. 凹凸纹理
  3. 卡通渲染
  4. 刻画线
  5. 立方图镜面高光
  6. 轮廓线

具体的类结构如下图所示:

  • 使用

osgFX::Effect是一个Group节点,使用的时候我们只需要把需要设置效果的节点添加到osgFX::Effect节点之下。如:当需要为一个Geode节点添加线框效果,可以使用如下的方式实现:

	osg::Geode *geode = new osg::Geode;
	geode->addDrawable(new osg::ShapeDrawable(new osg::Box(osg::Vec3(), 2)));
        //声明Effect节点并把Geode添加到其中
	osgFX::Outline *outline = new osgFX::Outline;
	outline->addChild(geode);
	outline->setColor(osg::Vec4(1.0, 1.0, 0.0, 1.0));
	outline->setWidth(5.0f);

  • 原理

osgFX的实现原理实际上是构建多个渲染状态集,实现对场景中可绘制对象的多次渲染。在osgscribe示例中为实现对模型刻画线的效果,需要添加两次可绘制对象的节点,从而实现渲染两次的效果:

//rootnode需要添加两次loadedModel
//其中一次采用默认的渲染状态来渲染
//另一次采用decorator中设置的渲染状态来渲染(刻线)
osg::Node *loadedModel = osgDB::readNodeFile("cow.osgt");
osg::Group* rootnode = new osg::Group;
osg::Group* decorator = new osg::Group;
rootnode->addChild(loadedModel);
rootnode->addChild(decorator);
decorator->addChild(loadedModel);  

//设置刻线渲染状态
osg::StateSet* stateset = new osg::StateSet;
osg::PolygonOffset* polyoffset = new osg::PolygonOffset;
polyoffset->setFactor(-1.0f);
polyoffset->setUnits(-1.0f);
osg::PolygonMode* polymode = new osg::PolygonMode;
polymode->setMode(osg::PolygonMode::FRONT_AND_BACK,osg::PolygonMode::LINE);
stateset->setAttributeAndModes(polyoffset,osg::StateAttribute::OVERRIDE|osg::StateAttribute::ON);
stateset->setAttributeAndModes(polymode,osg::StateAttribute::OVERRIDE|osg::StateAttribute::ON);

osg::Material* material = new osg::Material;
stateset->setAttributeAndModes(material,osg::StateAttribute::OVERRIDE|osg::StateAttribute::ON);
stateset->setMode(GL_LIGHTING,osg::StateAttribute::OVERRIDE|osg::StateAttribute::OFF);
stateset->setTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::OVERRIDE|osg::StateAttribute::OFF);

decorator->setStateSet(stateset);
这种方式有些繁琐,但是因为在场景节点树结构中只好这样去做(因为我们的节点osg::Node只有一个_stateSet的成员,不存在多个渲染状态)。但是如果直接操作OSG渲染的状态树,就可以做到不需要再场景中添加多次(待渲染的节点),举例如下:

//为了实现同时显示实体和网格对象的OpenGL代码
//对象本身实际上被渲染了两次
glPolygonOffset(1.0, 1.0);
drawScene();
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
drawScene();
同样的代码在OSG中通过场景树结构的表达就是上面osgscribe示例中的方式。另一种方式则是通过直接操作OSG状态树的分支内容实现的,示意代码如下:

//void UserNode::traverse(osg::NodeVisitor& nv)函数中
osgUtil::CullVisitor *cv = dynamic_cast<osgUtil::CullVisitor>(&nv);
if(cv) 
{
//stateset1保存PolygonOffset状态集
//stateset2保存PolygonMode状态集
	cv->pushStateSet(stateset1);
	Node::traverse(cv);
	cv->popStateSet();
	cv->pushStateSet(stateset2);
	Node::traverse(cv);
	cv->popStateSet();
}
OSG中状态树的构建是在CullVisitor遍历和裁剪场景树的过程中完成的。以上就是osgFX库架构的基本原理了,我们详细看看osgFX中是如何做到的:

首先要实现一种自定义的Effect特效,我们需要重载define_techniques函数,一般在这个函数中我们需要定义实现自定义特效的技术(osgFX::Technique),所谓的技术实际上就是一系列的StateSet集合,并将设置好的技术添加到Effect之中(addTechnique),参考Outline的实现代码:

    bool Outline::define_techniques()
    {
        _technique = new OutlineTechnique;
        addTechnique(_technique);

        setWidth(_width);
        setColor(_color);

        return true;
    }
接下来需要做的就是实现自定义的Technique了,要实现自定义的Technique需要重载define_passes这个函数,在里面定义很多渲染的状态集StateSet(上文描述的stateset1和stateset2就在这儿进行定义并添加),同样我们参考OutlineTechnique中的实现代码:

           {
                osg::StateSet* state = new osg::StateSet;
                osg::Stencil* stencil  = new osg::Stencil;
                stencil->setFunction(osg::Stencil::ALWAYS, 1, ~0u);
                stencil->setOperation(osg::Stencil::KEEP,
                                      osg::Stencil::KEEP,
                                      osg::Stencil::REPLACE);
                state->setAttributeAndModes(stencil, Override_On);

                addPass(state);
            }
            {
                osg::StateSet* state = new osg::StateSet;
                osg::Stencil* stencil  = new osg::Stencil;
                stencil->setFunction(osg::Stencil::NOTEQUAL, 1, ~0u);
                stencil->setOperation(osg::Stencil::KEEP,
                                      osg::Stencil::KEEP,
                                      osg::Stencil::REPLACE);
                state->setAttributeAndModes(stencil, Override_On);
				......
                addPass(state);
            }
它定义了两个StateSet并添加到Technique中,这样在渲染的时候实际上渲染了两次,分别用各自定义的渲染状态来渲染。

我们再来看看渲染的代码:按我们上文所说应该是在CullVisitor中进行的,查看源码也正好验证了我们所想,代码正好在Technique的traverse()之中,它的实现直接调用了traverse_implement:

void Technique::traverse_implementation(osg::NodeVisitor& nv, Effect* fx)
{
    if (_passes.empty()) {
        define_passes();
    }

    // special actions must be taken if the node visitor is actually a CullVisitor
    osgUtil::CullVisitor *cv = dynamic_cast<osgUtil::CullVisitor *>(&nv);

    for (int i=0; i<getNumPasses(); ++i) 
	{
        if (cv) {
            cv->pushStateSet(_passes[i].get());
        }
        osg::Node *override = getOverrideChild(i);
        if (override) {
            override->accept(nv);
        } else {
            fx->inherited_traverse(nv);
        }
        if (cv) {
            cv->popStateSet();
        }
    }
}
是不是很熟悉呢,正好和我们上面的演示伪代码一样。另外我们还需要知道这个Technique的traverse函数是在什么地方调用的呢?正是在Effect的traverse函数中调用的,在场景开始遍历的时候就会调用Technique的traverse函数完成渲染树的构建了。osgFX::Effect的traverse函数代码如下所示:

void Effect::traverse(osg::NodeVisitor& nv)
{ 
   ......
   
   if (tech) {
        tech->traverse(nv, this);
    } else {
        if (nv.getTraversalMode() == osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {
            inherited_traverse(nv);
        }
    }
}

  • 示例

下面是一个简单实用osgFX::Outline的例子,其他特效类Effect的使用方式类同:

#include <osgViewer/Viewer>
#include <osg/Group>
#include <osg/Geode>
#include <osgGA/GUIEventHandler>
#include <osgFX/Outline>
#include <osg/ShapeDrawable>


void int main(int argc, char **argv)
{
	osg::Geode *geode = new osg::Geode;
	geode->addDrawable(new osg::ShapeDrawable(new osg::Box(osg::Vec3(), 2)));

	osgFX::Outline *outline = new osgFX::Outline;
	outline->addChild(geode);
	outline->setColor(osg::Vec4(1.0, 1.0, 0.0, 1.0));
	outline->setWidth(5.0f);

	osgViewer::Viewer viewer;

	osg::Group *root = new osg::Group;
	root->addChild(outline);
	viewer.setUpViewInWindow(100, 100, 800, 600);
	viewer.setSceneData(root);
	//Outline类的使用需要先用0清空StencilBuffer
	unsigned int clearMask = viewer.getCamera()->getClearMask();
	viewer.getCamera()->setClearMask(clearMask | GL_STENCIL_BUFFER_BIT);
	viewer.getCamera()->setClearStencil(0);
	viewer.run();
}

编译运行:






评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值