OpenSceneGraph:封装OpenGL状态机

图片

引言


通常,在使用渲染参数时,OpenGL充当状态机。 渲染状态是状态属性的集合,例如通过glEnable()和glDisable()函数打开和关闭的光源,材质,纹理和显示模式。 设置特定状态后,该状态将一直有效,直到其他功能对其进行更改为止。 OpenGL管道支持状态堆栈,用于在任何给定时间保存和还原状态。 状态机使开发人员可以完全控制堆栈上的当前和保存的渲染状态。

但是,这种方法在使用OSG时很不方便。 因此,OpenGL状态机由osg :: StateSet类封装,该类负责处理状态堆栈并在遍历场景图时对其进行设置。

osg :: StateSet类的实例包含各种渲染状态的子集,并可以使用setStateSet()方法将其应用于osg :: Node场景节点和osg :: Drawable几何对象

osg::StateSet *stateset = new osg::StateSet; node->setStateSet(stateset); 

一种更安全的方法是使用getOrCreateStateSet()方法,该方法保证返回正确的状态及其对节点或可绘制对象的附加

 osg::StateSet *stateset = node->getOrCreateStateSet(); 

osg :: Node和osg :: Drawable类通过智能指针osg :: ref_ptr <>控制成员变量osg :: StateSet。 这意味着可以在场景中的多个对象之间划分一组状态,并且只有在所有这些对象都被销毁时才会被销毁。

1.属性和模式


OSG定义了osg :: StateAttribute类,用于存储渲染属性。 这是一个虚拟基类,它由各种渲染属性(例如灯光,材质和雾)继承。

渲染模式就像可以打开和关闭的开关一样工作。 此外,它们与枚举器关联,该枚举器用于指示OpenGL模式的类型。 有时,渲染模式与属性相关联,例如,GL_LIGHTING模式包含用于光源的变量,这些变量在打开时将发送到OpenGL管道,否则将关闭照明。

osg :: StateSet类将属性和模式分为两组:纹理和非纹理。 它具有几种用于将非纹理属性和模式添加到一组状态的公共方法:

  1. setAttribute()-将类型为osg :: StateAttribute的对象添加到状态集中。 相同类型的属性不能在同一状态集中共存。 先前的设定点将被新的设定点覆盖。
  2. setMode()-将模式枚举器附加到一组状态,并将其值设置为osg :: StateAttribute :: :: ON或osg :: StateAttribute :: OFF,这意味着启用或禁用该模式。
  3. setAttributeAndModes()-附加渲染属性及其关联模式,并设置开关的值(默认为ON)。 应该记住的是,并非每个属性都有对应的模式,但是在任何情况下都可以使用此方法。

要设置属性及其关联模式,可以使用以下代码

 stateset->setAttributeAndModes(attr, osg::StateAttribute::ON); 

要设置纹理属性,必须传递一个附加参数来指示应将其应用到的纹理。 Osg :: StateSet为此提供了其他一些公共方法,例如setTextureAttribute(),setTextureMode()和setTextureAttributeAndModes()

 stateset->setTextureAttributeAndModes(0, textattr, osg::StateAttribute::ON); 

将textattr属性应用于标识符为0的纹理。

2.设置场景节点的多边形显示模式


我们以一个实际示例说明上述理论-使用osg :: PolygonMode类更改OpenGL多边形的栅格化模式,该类继承自osg :: StateAttribute。 此类封装了glPolygonMode()函数,并提供了用于设置特定场景节点的多边形显示模式的接口。

多边形模式示例
主文件
 #ifndef MAIN_H #define MAIN_H #include <osg/PolygonMode> #include <osg/MatrixTransform> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif 

main.cpp
 #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/cessna.osg"); osg::ref_ptr<osg::MatrixTransform> transform1 = new osg::MatrixTransform; transform1->setMatrix(osg::Matrix::translate(-25.0f, 0.0f, 0.0f)); transform1->addChild(model.get()); osg::ref_ptr<osg::MatrixTransform> transform2 = new osg::MatrixTransform; transform2->setMatrix(osg::Matrix::translate(25.0f, 0.0f, 0.0f)); transform2->addChild(model.get()); osg::ref_ptr<osg::PolygonMode> pm = new osg::PolygonMode; pm->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); transform1->getOrCreateStateSet()->setAttribute(pm.get()); osg::ref_ptr<osg::Group> root = new osg::Group; root->addChild(transform1.get()); root->addChild(transform2.get()); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


在这里,我们上传了我们最爱的塞斯纳的模型,并对其进行了转换,我们得到了该模型的两个实例。 对于其中一个,在左侧,我们应用一个属性,该属性设置多边形的线框显示模式

 osg::ref_ptr<osg::PolygonMode> pm = new osg::PolygonMode; pm->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); transform1->getOrCreateStateSet()->setAttribute(pm.get()); 



如果我们转向OpenGL规范,我们可以轻松地想象在这种特殊情况下使用setMode()时可以使用哪些多边形显示选项。 第一个参数可以使用值osg :: PolygonMode :: FRONT,BACK和FRONT_AND_BACK,与OpenGL枚举器GL_FRONT,GL_BACK,GL_FRONT_AND_BACK对应。 第二个参数可以使用值osg :: PolygonMode :: POINT,LINE和FILL,它们对应于GL_POINT,GL_LINE和GL_FILL。 这里不需要其他技巧,而在纯OpenGL上开发时通常不需要这种技巧-OSG负责大部分工作。 多边形显示模式没有关联的模式,不需要调用glEnable()/ glDisable()对。 在这种情况下,setAttributeAndModes()方法也可以正常工作,但是其第三个参数的值将无用。

3.渲染状态的继承。 应用属性和模式


节点状态集会影响当前节点及其所有子节点。 例如,上一个示例中为transform1设置的osg :: PolygonMode属性将应用于该节点的所有子级。 但是,子节点可以覆盖父属性,即,如果子节点不更改行为,则渲染状态将从父节点继承。

有时您需要根据属性使用情况重新定义节点的行为。 例如,在大多数3D编辑器中,用户可以同时加载多个模型并同时更改所有已加载模型的显示模式,而不管它们之前的显示方式如何。 换句话说,编辑器中的所有模型都必须继承一个属性,而不管先前为每个模型如何设置它们。 在OSG中,这可以使用osg :: StateAttribute :: :: OVERRIDE标志实现。

 stateset->StateAttribute(attr, osg::StateAttribute::OVERRIDE); 

在设置模式和具有属性的模式时,使用按位或运算符

 stateset->StateAttributeAndModes(attr, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); 

此外,还可以防止覆盖属性-为此,必须使用osg :: StateAttribute :: PROTECTED标志对其进行标记。

第三个标志osg :: StateAttribute :: INHERIT,用于指示该属性应从父节点的状态集继承。

这是使用OVERRIDE和PROTECTED标志的简短示例。 根节点将设置为OVERRIDE,以强制所有子节点继承其属性和模式。 在这种情况下,子节点将尝试在有或没有PROTECTED标志的帮助下更改其状态,这将导致不同的结果。

继承示例文本
主文件
 #ifndef MAIN_H #define MAIN_H #include <osg/PolygonMode> #include <osg/MatrixTransform> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif 

main.cpp
 #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/glider.osg"); osg::ref_ptr<osg::MatrixTransform> transform1 = new osg::MatrixTransform; transform1->setMatrix(osg::Matrix::translate(-0.5f, 0.0f, 0.0f)); transform1->addChild(model.get()); osg::ref_ptr<osg::MatrixTransform> transform2 = new osg::MatrixTransform; transform2->setMatrix(osg::Matrix::translate(0.5f, 0.0f, 0.0f)); transform2->addChild(model.get()); osg::ref_ptr<osg::Group> root = new osg::Group; root->addChild(transform1.get()); root->addChild(transform2.get()); transform1->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); transform2->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); root->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 




要了解发生了什么,您需要下载全时OSG osgviewer,以查看常亮的悬挂式滑翔机的外观

 $ osgviewer glider.osg 

在示例中,我们试图通过完全关闭照明来更改transform1和transform2节点的照明模式。

 transform1->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); transform2->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); 

在这种情况下,我们为根节点打开照明模式,并为其所有子节点使用OVERRIDE标志,以便它们继承根节点的状态。 但是,trnsform2使用PROTECTED标志来防止影响根节点设置。

 transform2->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); 

结果,尽管事实上我们已关闭了transform1节点处的照明,但由于场景根的设置阻止了我们为其尝试关闭照明的尝试,因此左侧悬挂式滑翔机仍然点亮。 右侧悬挂式滑翔机在没有照明的情况下显示(它看起来更亮,仅是因为它被简单的颜色淹没而没有渲染照明),这是因为保护了transform2不会继承根节点的属性。

4. OpenSceneGraph支持的OpenGL属性列表


OSG通过从osg :: StateAttribute派生的类支持OpenGL支持的几乎所有属性和渲染模式。 该表显示了可从引擎获得的OpenGL状态机的所有参数。
属性类型ID类名关联模式等效的OpenGL功能
阿尔法芬osg :: AlphaFuncGL_ALPHA_TESTglAlphaFunc()
混合功能osg :: BlendFuncGL_BLENDglBlendFunc()和glBlendFuncSeparate()
剪辑平面osg :: ClipPlaneGL_CLIP_PLANEi(i从1到5)glClipPlane()
色板osg ::色彩遮罩--glColorMask()
鬼脸osg :: CullFaceGL_CULLFACEglCullFace()
深度osg ::深度GL_DEPTH_TESTglDepthFunc(),glDepthRange()和glDepthMask()
雾气osg ::雾GL_FOGglFog()
前脸osg :: FrontFace--glFrontFace()
轻便osg ::轻GL_LIGHTi(i从1到7)glLight()
灯光模型osg :: LightModel--glLightModel()
线带osg :: LineStrippleGL_LINE_STRIPPLEglLineStripple()
线宽osg ::线宽--glLineWidht()
罗吉osg ::逻辑运算GL_COLOR_LOGIC_OPglLogicOp()
材质osg ::材料--glMaterial()和glColorMaterial()
要点osg ::点GL_POINT_SMOOTHglPointParameter()
点赞osg :: PointSpriteGL_POINT_SPRITE_ARB用于OpenGL精灵的函数
多边形模式osg :: PolygonMode--glPolygonMode()
多边形偏移osg :: PolygonOffsetGL_POLYGON_OFFSET_POINTglPolygonOffset()
多带osg ::多边形条纹GL_POLYGON_STRIPPLEglPolygonStripple()
剪刀osg ::剪刀GL_SCISSOR_TESTglScissor()
阴影模型osg :: ShadeModel--glShadeModel()
斯蒂尼尔osg ::模具GL_STENCIL_TESTglStencilFunc(),glStencilOp()和glStencilMask()
特森夫osg :: TexEnv--glTexEnv()
特克斯根osg :: TexGenGL_TEXTURE_GEN_SglTexGen()

属性类型ID列指示特定的OSG标识符,该标识符在osg :: StateAttribute类的枚举数中标识此属性。 可以在getAttribute方法中使用它来获取特定属性的值。

 osg::PolygonMode *pm = dynamic_cast<osg::PolygonMode *>(stateset->getAttribute(osg::StateAttribute::POLYGONMODE)); 

有效的指针表示该属性先前已设置。 否则,该方法将为NULL。 您还可以使用调用获取当前模式的值

 osg::StateAttribute::GLModeValue value = stateset->getMode(GL_LIGHTING); 

此处GL_LIGHTING枚举器用于启用/禁用整个场景中的照明。

5.将雾应用于场景中的模型


让我们以雾化效果作为展示如何使用各种属性和渲染模式的理想方法。 OpenGL使用一个描述雾模型的线性方程和两个指数方程,由osg :: Fog类支持。

文字雾示例
主文件
 #ifndef MAIN_H #define MAIN_H #include <osg/Fog> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif 


main.cpp

 #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Fog> fog = new osg::Fog; fog->setMode(osg::Fog::LINEAR); fog->setStart(500.0f); fog->setEnd(2500.0f); fog->setColor(osg::Vec4(1.0f, 1.0f, 0.0f, 1.0f)); osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/lz.osg"); model->getOrCreateStateSet()->setAttributeAndModes(fog.get()); osgViewer::Viewer viewer; viewer.setSceneData(model.get()); return viewer.run(); } 


首先,创建fog属性。 我们使用线性模型,通过与模型的距离来调整雾显示的范围

 osg::ref_ptr<osg::Fog> fog = new osg::Fog; fog->setMode(osg::Fog::LINEAR); fog->setStart(500.0f); fog->setEnd(2500.0f); fog->setColor(osg::Vec4(1.0f, 1.0f, 0.0f, 1.0f)); 

我们加载景观样本lz.osg并将其应用于此属性

 osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/lz.osg"); model->getOrCreateStateSet()->setAttributeAndModes(fog.get()); 

在查看器窗口中,我们看到了模糊的风景,并且可以看到雾的密度如何根据与模型的距离而变化







6.使用光源和照明


与OpenGL一样,OSG最多支持八个光源以直接遇到场景对象。 与OpenGL类似,OSG不会自动计算阴影。 光线来自直线源,被物体反射并被它们散射,然后被观看者的眼睛察觉。 为了进行高质量的照明处理,必须设置材料属性,对象的法线几何形状等。

osg :: Light类提供了控制光源的方法,包括:setLightNum()和getLightNum()-用于处理多个光源; setAmbient()和getAmbient()控制周围的组件; setDiffuse()和getDiffuse()-用于处理分散的组件等。

OSG还描述了osg :: LightSource类,用于将光源添加到场景中。 它提供setLight()方法,并且是具有单个属性的场景图的叶节点。 如果设置了GL_LIGHTi的相应模式,则场景图的所有其他节点都会受到光源的影响。 例如:

 //   1 osg::ref_ptr<osg::Light> light = new osg::Light; light->setLightNum( 1 ); ... //       osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource; lightSource->setLight( light.get() ); ... //             root->addChild( lightSource.get() ); root->getOrCreateStateSet()->setMode( GL_LIGHT1, osg::StateAttribute::ON ); 

另一个更方便的解决方案是setStateSetModes()方法,通过该方法,具有所需编号的光源会自动附加到根节点

 root->addChild( lightSource.get() ); lightSource->setStateSetModes( root->getOrCreateStateSet(), osg::StateAttribute::ON ); 

您可以将子节点添加到光源,但这一点也不意味着,您将以某种特殊方式照亮与之关联的子图。 它将被处理为几何体,由光源的物理形式表示。

osg :: LightSource节点可以附加到转换节点,例如,点光源可以在空间中移动。 可以通过设置光源的绝对坐标系来禁用此功能。

 lightSource->setReferenceFrame( osg::LightSource::ABSOLUTE_RF ); 

7.在场景中创建光源


默认情况下,OSG会自动将光源设置为数字0,从而向场景发射均匀的定向光。 但是,您可以随时添加几个其他光源,甚至可以使用坐标变换节点进行控制。 只能移动位置源(点源)。 定向光仅具有方向(来自无限远的平行光线流),并且不绑定到舞台上的特定位置。 OpenGL和OSG使用position参数的第四部分来指定光源的类型。 如果为0,则认为灯光是定向的;否则为0。 值为1-位置。

考虑一个使用照明的小例子。

扰流板方向
主文件
 #ifndef MAIN_H #define MAIN_H #include <osg/MatrixTransform> #include <osg/LightSource> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif 

main.cpp

 #include "main.h" //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osg::Node *createLightSource(int num, const osg::Vec3 &trans, const osg::Vec4 &color) { osg::ref_ptr<osg::Light> light = new osg::Light; light->setLightNum(num); light->setDiffuse(color); light->setPosition(osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource; lightSource->setLight(light); osg::ref_ptr<osg::MatrixTransform> sourceTrans = new osg::MatrixTransform; sourceTrans->setMatrix(osg::Matrix::translate(trans)); sourceTrans->addChild(lightSource.get()); return sourceTrans.release(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/cessna.osg"); osg::ref_ptr<osg::Group> root = new osg::Group; root->addChild(model.get()); osg::Node *light0 = createLightSource(0, osg::Vec3(-20.0f, 0.0f, 0.0f), osg::Vec4(1.0f, 1.0f, 0.0f, 1.0f)); osg::Node *light1 = createLightSource(1, osg::Vec3(0.0f, -20.0f, 0.0f), osg::Vec4(0.0f, 1.0f, 1.0f, 1.0f)); root->getOrCreateStateSet()->setMode(GL_LIGHT0, osg::StateAttribute::ON); root->getOrCreateStateSet()->setMode(GL_LIGHT1, osg::StateAttribute::ON); root->addChild(light0); root->addChild(light1); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


要创建光源,我们有一个单独的功能

 osg::Node *createLightSource(int num, const osg::Vec3 &trans, const osg::Vec4 &color) { osg::ref_ptr<osg::Light> light = new osg::Light; light->setLightNum(num); light->setDiffuse(color); light->setPosition(osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource; lightSource->setLight(light); osg::ref_ptr<osg::MatrixTransform> sourceTrans = new osg::MatrixTransform; sourceTrans->setMatrix(osg::Matrix::translate(trans)); sourceTrans->addChild(lightSource.get()); return sourceTrans.release(); } 

在此功能中,我们首先确定光源提供的照明参数,从而创建GL_LIGHTi属性

 osg::ref_ptr<osg::Light> light = new osg::Light; //    light->setLightNum(num); //   light->setDiffuse(color); //  .     ,    light->setPosition(osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); 

之后,创建一个为其分配了此属性的光源。

 osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource; lightSource->setLight(light); 

我们创建并配置转换节点,将我们的光源作为子节点传递给它

 osg::ref_ptr<osg::MatrixTransform> sourceTrans = new osg::MatrixTransform; sourceTrans->setMatrix(osg::Matrix::translate(trans)); sourceTrans->addChild(lightSource.get()); 

返回指向转换节点的指针

 return sourceTrans.release(); 

在主程序的主体中,我们加载一个三维模型(同样,我们最喜欢的塞斯纳)

 osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/cessna.osg"); osg::ref_ptr<osg::Group> root = new osg::Group; root->addChild(model.get()); 

我们创建了两个数字分别为0和1的光源。第一个将发出黄色,第二个将发出蓝绿色。

 osg::Node *light0 = createLightSource(0, osg::Vec3(-20.0f, 0.0f, 0.0f), osg::Vec4(1.0f, 1.0f, 0.0f, 1.0f)); osg::Node *light1 = createLightSource(1, osg::Vec3(0.0f, -20.0f, 0.0f), osg::Vec4(0.0f, 1.0f, 1.0f, 1.0f)); 

我们通知OpenGL状态机,有必要打开0和1光源并将我们创建的光源添加到场景中

 root->getOrCreateStateSet()->setMode(GL_LIGHT0, osg::StateAttribute::ON); root->getOrCreateStateSet()->setMode(GL_LIGHT1, osg::StateAttribute::ON); root->addChild(light0); root->addChild(light1); 

初始化并启动查看器后,我们得到一张图片



结论


感兴趣的人们对这个周期的关注非常感动。 这个动作起步不是很充分,但是我觉得社区需要文章。 感谢您的各种积极反馈。

今天,我再次尝试考虑OSG引擎的基本知识。 不知道有什么好玩的。 但是到目前为止,我只是按照我自己理解它们的方式来列出原始事物。 我亲自检查了所有示例,可在此处找到我的存储库。 谢谢亲爱的同事们,我将继续保持这个故事...

Source: https://habr.com/ru/post/zh-CN430348/


All Articles