OpenSceneGraph:OpenGLステートマシンのカプセル化

画像

はじめに


通常、レンダリングオプションを使用する場合、OpenGLはステートマシンとして機能します。 レンダリング状態は、光源、マテリアル、テクスチャ、表示モードなどの状態属性のコレクションであり、glEnable()およびglDisable()関数によってオンとオフが切り替えられます。 特定の状態が設定されると、他の機能によって変更されるまで有効のままになります。 OpenGLパイプラインは、いつでも状態を保存および復元するための状態スタックをサポートしています。 ステートマシンにより、開発者はスタック上の現在のレンダリング状態と保存されたレンダリング状態を完全に制御できます。

ただし、このアプローチはOSGで作業する場合は不便です。 このため、OpenGLステートマシンはosg :: StateSetクラスによってカプセル化されます。このクラスは、ステートスタックの操作と、シーングラフの走査中のステートスタックの設定を処理します。

osg :: StateSetクラスのインスタンスには、さまざまなレンダリング状態のサブセットが含まれており、それらをosg :: Nodeシーンとosg :: setStateSet()メソッドを使用して描画可能な幾何学的オブジェクトのノードに適用できます。

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クラスは、属性とモードをテクスチャと非テクスチャの2つのグループに分けます。 非テクスチャの属性とモードを一連の状態に追加するためのいくつかのパブリックメソッドがあります。

  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); 

識別子0のテクスチャにtextattr属性を適用します。

2.シーンノードのポリゴン表示モードを設定する


上記の理論を実用的な例で説明します-osg :: StateAttributeを継承するosg :: PolygonModeクラスを使用してOpenGLポリゴンのラスタライズモードを変更します。 このクラスはglPolygonMode()関数をカプセル化し、特定のシーンノードのポリゴン表示モードを設定するためのインターフェイスを提供します。

ポリゴンモードの例
main.h
 #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(); } 


ここで、最愛のセスナのモデルをアップロードし、それに変換を適用して、モデルの2つのインスタンスを取得します。 それらの1つに、左側に、ポリゴンのワイヤフレーム表示のモードを設定する属性を適用します

 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()を使用するときに使用できるポリゴン表示オプションを簡単に想像できます。 最初のパラメーターは、OpenGL列挙子GL_FRONT、GL_BACK、GL_FRONT_AND_BACKに対応する値osg :: PolygonMode :: FRONT、BACKおよびFRONT_AND_BACKを取ることができます。 2番目のパラメーターは、osg :: PolygonMode :: POINT、LINEおよびFILLの値を取ることができます。これらは、GL_POINT、GL_LINEおよびGL_FILLに対応します。 純粋なOpenGLで開発する場合によくある他のトリックは、ここで適用する必要はありません-OSGはほとんどの作業を処理します。 ポリゴン表示モードには関連付けられたモードがなく、glEnable()/ glDisable()のペアを呼び出す必要はありません。 この場合もsetAttributeAndModes()メソッドは正常に機能しますが、3番目のパラメーターの値は使用できません。

3.レンダリング状態の継承。 属性とモードの適用


ノードの状態のセットは、現在のノードとそのすべての子に影響します。 たとえば、前の例のtransform1に設定されたosg :: PolygonMode属性は、このノードのすべての子に適用されます。 ただし、子ノードは親属性をオーバーライドできます。つまり、子ノードが動作を変更しない場合、レンダリング状態は親ノードから継承されます。

属性の使用に関してノードの動作を再定義する必要がある場合があります。 たとえば、ほとんどの3Dエディターでは、ユーザーは以前に表示された方法に関係なく、複数のモデルをロードし、ロードされたすべてのモデルの表示モードを同時に変更できます。 言い換えれば、エディターのすべてのモデルは、モデルごとに以前に設定された方法に関係なく、単一の属性を継承する必要があります。 OSGでは、osg :: StateAttribute :: OVERRIDEフラグを使用してこれを実装できます。たとえば、

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

モードおよび属性付きモードを設定する場合、ビットごとのOR演算子が使用されます

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

さらに、属性はオーバーライドから保護することもできます。このためには、フラグosg :: StateAttribute :: PROTECTEDでマークする必要があります。

3番目のフラグosg :: StateAttribute :: INHERITがあります。これは、この属性が親ノードの状態のセットから継承されることを示すために使用されます。

以下は、OVERRIDEおよびPROTECTEDフラグを使用した短い例です。 ルートノードはOVERRIDEに設定され、すべての子ノードがその属性とモードを強制的に継承します。 この場合、子ノードはPROTECTEDフラグを使用して、または使用せずに状態を変更しようとするため、異なる結果になります。

サンプルテキストを継承
main.h
 #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ノードの照明をオフにしたにもかかわらず、シーンのルートの設定により照明をオフにしようとする試みがブロックされたため、左のハンググライダーはまだ点灯しています。 トランスフォーム2はルートノードの属性を継承しないように保護されているため、右側のハンググライダーは照明なしで表示されます(光をレンダリングせずに単純な色で塗りつぶされているために明るく見えます)。

4. OpenSceneGraphでサポートされているOpenGL属性のリスト


OSGは、osg :: StateAttributeから派生したクラスを介してOpenGLでサポートされるほぼすべての属性とレンダリングモードをサポートします。 この表は、エンジンから利用可能なOpenGLステートマシンのすべてのパラメーターを示しています。
属性タイプIDクラス名関連モード同等のOpenGL関数
アルフェフンクosg :: AlphaFuncGL_ALPHA_TESTglAlphaFunc()
ブレンドファンクosg :: BlendFuncGL_BLENDglBlendFunc()およびglBlendFuncSeparate()
クリプランosg :: ClipPlaneGL_CLIP_PLANEi(1から5まで)glClipPlane()
カラーマスクosg :: ColorMask-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()
LINEWIDTHosg :: LineWidth-glLineWidht()
LOGICOPosg :: LogicOpGL_COLOR_LOGIC_OPglLogicOp()
素材osg ::素材-glMaterial()およびglColorMaterial()
ポイントosg ::ポイントGL_POINT_SMOOTHglPointParameter()
ポイントスプライトosg :: PointSpriteGL_POINT_SPRITE_ARBOpenGLスプライトを操作するための関数
ポリゴンモードosg :: PolygonMode-glPolygonMode()
ポリゴンオフセットosg :: PolygonOffsetGL_POLYGON_OFFSET_POINTglPolygonOffset()
多角形osg :: PolygonStrippleGL_POLYGON_STRIPPLEglPolygonStripple()
はさみosg ::シザーGL_SCISSOR_TESTglScissor()
シェードモデルosg :: ShadeModel-glShadeModel()
ステンシルosg ::ステンシルGL_STENCIL_TESTglStencilFunc()、glStencilOp()およびglStencilMask()
Texenvosg :: TexEnv-glTexEnv()
Texgenosg :: TexGenGL_TEXTURE_GEN_SglTexGen()

属性タイプID列は、osg :: StateAttributeクラスの列挙子でこの属性を識別する特定のOSG識別子を示します。 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クラスでサポートされるフォグモデルを記述する1つの線形方程式と2つの指数方程式を使用します。

テキストフォグの例
main.h
 #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(); } 


最初に行うことは、フォグアトリビュートを作成することです。 線形モデルを使用し、モデルまでの距離によってフォグ表示の範囲を調整します

 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はシーンオブジェクトに直接会うために最大8つの光源をサポートします。 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 ); 

もう1つのより便利なソリューションは、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は、位置パラメーターの4番目のコンポーネントを使用して、光源のタイプを指定します。 0の場合、ライトは有向と見なされます。 値1-定位置。

照明を扱う小さな例を考えてみましょう。

ネタバレ見出し
main.h
 #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(); 

メインプログラムの本文では、3次元モデルを読み込みます(これもお気に入りのセスナです)

 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の2つの光源を作成します。最初の光源は黄色の光で輝き、2番目の光源は青緑色で輝きます

 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/J430348/


All Articles