-
简介
osgFX是一个osg的附加库,是一个用于实现可重用特殊效果的架构工具,它实现的效果可以添加到OSG的节点中。它包含了一系列预定义好的特殊效果,osg3.2中主要有如下几种:
- Anisotropic Lighting 各向异性光照
- 凹凸纹理
- 卡通渲染
- 刻画线
- 立方图镜面高光
- 轮廓线
具体的类结构如下图所示:
-
使用
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();
}
编译运行: