在OSG中存在两棵树,即场景树和渲染树。渲染树是一棵以StateSet和RenderLeaf为节点的树,它可以做到StateSet相同的RenderLeaf同时渲染而不用切换OpenGL状态,并且做到尽量少但在多个不同State间切换。渲染树在CullVisitor的cull过程中创建。
OSG支持绝大部分的OpenGGL固定功能管道(fixed function pipeline)渲染,ru ALpha检验、Blending融合、剪切平面、颜色蒙版、面拣选(face culling)、深度和模板检验、雾效、点和线的光栅化(rasterization)等。OSG的渲染状态也允许应用程序指定顶点着色(vertex shader)和片段着色(fragment shader)。
osg::StateSet类
osg::StateSet类派生自osg::Referenced以实现更好的数据共享。也就是说,共享一个osg::StateSet的osg::Node或osg::Drawable类不需要额外的代码来清理其内存空间。
osg::StateSet中设置渲染状态,可以将StateSet关联到场景图像中的任意一个节点(Node)或关联到Drawable类,StateSet对象能够自动优化。用户需要尽量使用关联到场景图形的StateSet最小化。StateSet越少,内存的占用也越少,OSG在一次场景图形遍历中所耗费的工作量也越小。
OSG将渲染状态分成两个部分,分别为渲染属性(Attribute)和渲染模式(Mode)。渲染属性也就是控制渲染特性的状态变量,如雾的颜色或Blend融合函数都是OSG的状态属性。OSG中的渲染模式和OpenGL的状态特性几乎是一一对应的。如果需要设置渲染状态的值,用户程序需要执行以下两步操作:
> 为将要设置状态的Node或Drawable对象提供一个StateSet实例;
> 在StateSet实例中设置状态的渲染模式和渲染属性。
直接从某个Node或Drawable对象中获得一个StateSet实例可以使用下面的方法:
osg::StateSet *state = obj->getOrCreateStateSet();
StateSet继承自Referenced类,与StateSet关联的Node或Drawable类内部使用ref_ptr<>来一你用StateSet实例,因此,不是长时间引用StateSet的情况下,也可以使用标准C++指针来定义state。
渲染属性和渲染模式
所有的属性类均继承自osg::StateAttribute,osg::StateAttribute类是一个无法直接实例化的虚基类。OSG将所有的属性和模式分为两大部分,即纹理(texture)和肺纹理(non-texture)。OSG之所以为纹理属性的设置提供不同的接口,主要是因为纹理属性需要特别为多重纹理设置纹理单元(texture unit)。
<1> 设置渲染属性(Attribute)
如果要设置一项属性,首先需要将修改的属性类实例化,设置该类的数值,然后用osg::StateSet::setAttribute()将其关联到StateSet。
// 获取变量geom的StateSet
osg::StateSet *state = geom->getOrCreateStateSet();
// 创建并添加CullFace属性类
osg::CullFace *ef = new osg::CullFace(osg::CullFace::BACK);
state->setAttribute(ef);
<2> 设置渲染模式(Mode)
用户可以使用osg::StateSet::setMode()设置允许或禁止某种模式。
// 获取一个StateSet实例
osg::StateSet *state = geom->getOrCreateStateSet();
// 允许这个StateSet的雾效模式
state->setMode(GL_FOG, osg::StateAttribute::ON);
setMode() 的第一个输入参数可以是任何一个在glEnable()或glDisable()中合法的OpenGL枚举量Glenum;第二个输入参数可以是osg::StateAttribute::ON或osg::StateAttribute::OFF。事实上,这里用到了位屏蔽技术。
<3> 设置渲染属性和模式
OSG提供了一个简单的、可以同时设置属性和模式的单一函数接口。在很多情况下,属性和模式之间存在显著的关系。例如,CullFace属性的对应模式为GL_CULL_FACE。如果要将某个属性关联到一个StateSet,同时要求打开其对应模式的许可,可以使用osg::StateSet::setAttributeAndModes()方法。下面的代码段将关联Blend融合检验的属性,同时许可颜色融合模式。
// 创建一个BlendFace属性
osg::BlendFace *bf = new osg::BlendFace();
// 关联BlendFace并许可颜色融合模式
state->setAttributeAndModes(bf);
setAttributeAndModes()的第二个输入参数用于允许或禁止第一个参数中渲染属性对应的渲染模式,其默认值为ON。这样,用户的应用程序只需用一个函数就可以方便地指定某个渲染属性,并许可其对应的渲染模式。
状态继承
当读者设置节点的渲染状态时,这个状态将被赋予当前的节点以及子节点。如果子节点对同一个渲染状态设置了不同的属性参数,那么新的子节点状态参数将会覆盖原有的,也就是说,默认情况下子节点可以改变自身的某个状态参数或者继承父节点的同一个状态。
状态继承特性在许多情况下都非常实用,但有时渲染可能需要更多特性。假设场景图形中有一个包含了实体多边形几何体的节点,如果要以线框模式来渲染场景图形,读者的程序就需要覆盖这种多边形渲染模式状态,而不论他出现在什么位置。OSG允许用户根据场景图形中任意位置的渲染属性和模式需求单独改变原有的状态继承特性。可以选择以下几种枚举形式。
<1> osg::StateAttribute::OVERRIDE:如果将一个渲染属性和模式设置为OVERRIDE,那么所有的子节点都将继承这一属性或模式,子节点对它们的更改将会无效。
<2> osg::StateAttribute::PROTECTED:这种形式可以视为OVERRIDE的一个例外。凡是设置为PROTECTED的渲染属性或模式,均不会受到父节点的影响。
<3> osg::StateAttribute::INHERIT:这种模式强制子节点继承父节点的渲染状态,其效果是子节点的渲染状态被解除,而使用父节点的状态替代。
读者可以对这些参数进行位或叠加操作,然后再作为setAttribute()和setMode()建立一个场景图形和setAttributeAndMode() 的第二个参数输入。下面的代码段将强制使用线框模式渲染场景图形:
// 获取根节点的渲染状态StateSet
osg::StateSet *state = root->getOrCreateStateSet();
// 创建一个PolygonMode渲染属性
osg::PolygonMode *pm = new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK,osg::PolygonMode::LINE);
// 强制使用线框渲染
state->setAttributeAndModes(pm, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
使用PROTECTED参量可以保证父节点的渲染状态不会覆盖子节点的渲染状态。例如,读者可能创建了一个发光的场景,其中包含有使用亮度照明的光源几何体,如果其父节点禁用了光照,那么光源几何体的渲染将会出错。这时,对光源几何体的GL_LIGHTING渲染状态使用PROTECTED就可以保证它依然可用。
osg::ref_ptr createClipNode(osg::ref_ptr subgraph)
{
osg::ref_ptr root = new osg::Group();
osg::ret_ptr stateSet = new osg::StateSet;
// 多边形线性绘制模式,正面和反面都绘制
osg::ref_ptr polymode = new osg::PolygonMode();
polymode->setMode(osg::PolygongMode::FRONT_AND_BACK,osg::PolygonMode::LINE);
// 启用多边形线性绘制模式,并指定状态继承属性为OVERRIDE
statesSet->setAttributeAndMode(polymode,osg::StateAttribute::OVERRIDE|osg::StateAttribute::ON);
多边形线形绘制节点
osg::ref_ptrwireframe_subgraph = new osg::Group;
// 设置渲染状态
wireframe_subgraph->setStateSet(stateSet.get());
wireframe_subgraph->addChild(subgraph.get());
root->addChild(wireframe_subgraph.get());
osg::ref_ptrtransform = new osg::MatrixTransform;
// 更新回调,实现动态裁剪
osg::ref_ptr nc = new osg::AnimationPathCallback(subgraph->getBound().center(),osg::Vec3(0.0f,0.0f,1.0f),osg::inDegres(45.0f));
transform->setUpdateCallback(nc.get());
// 创建裁剪节点
osg::ref_ptr clipnode = new osg::ClipNode;
osg::BoundingSphere bs = subgraph->getBound();
bs.radius() *= 0.4f;
// 设置裁剪节点的包围盒
osg::BoundingBox bb;
bb.expandBy(bs);
// 根据前面指定的包围盒创建6个裁剪平面
clipnode->createClipBox(bb);
// 禁止拣选
clipnode->setCullingActive(false);
transform->addChild(clipnode.get());
root->addChild(transform,get());
// 创建未被裁剪的节点
osg::ref_ptr clippedNode = new osg::Group;
clippedNode->setStateSet(clipnode->getStateSet());
clippedNode->addChild(subgraph.get());
root->addChild(clippedNode.get());
return root.get();
}